Summary
Symphony's Pi runtime currently hard-disables Pi auto-compaction on every worker turn via RPC, and because Pi persists that RPC toggle to settings.json, the worker can mutate the operator's interactive Pi config.
This was introduced in the initial Pi RPC spike as a conservative simplification, but it now looks stale and actively harmful.
What I found
Original rationale
This behavior was introduced in the initial Pi worker spike:
3c4f677 — Add Pi RPC worker spike runner
635e5ac — Document the Pi RPC worker contract
68404eb — Add Pi worker adapter modules
The contract doc explicitly says:
Disable Pi-managed compaction for the initial spike
... compaction can be reintroduced later once multi-turn Pi worker behavior is stable
Relevant files:
docs/PI_RPC_CONTRACT.md
tools/pi-rpc-spike/client.ts
orchestrator/elixir/lib/symphony_elixir/pi/rpc_client.ex
Current behavior
On each worker turn, Symphony sends:
{"type":"set_auto_retry","enabled":false}
{"type":"set_auto_compaction","enabled":false}
Today, Pi implements those RPC commands by mutating and saving global settings, rather than treating them as session-local worker toggles.
So Symphony workers can end up rewriting the operator's interactive config.
Why this is a problem
1) Workers should not mutate shared interactive Pi settings
This is the immediate bug.
If the operator's ~/.pi/agent/settings.json is shared with their interactive Pi setup (or symlinked into a tracked repo, as on my machine), Symphony worker turns can silently flip:
compaction.enabled
retry.enabled
That leaks worker policy into the human operator environment.
2) The original "disable auto-compaction" rationale is likely stale
Symphony now runs multi-turn Pi workers (agent.max_turns > 1 in real workflows), which is exactly the kind of unattended / long-running process that benefits from auto-compaction.
I did not find evidence of a current concrete compaction bug that still justifies hard-disabling it.
3) Re-enabling auto-compaction is not just a one-line flip
There is one real lifecycle nuance to handle correctly:
Pi can do compaction after agent_end.
- threshold compaction:
agent_end -> compaction -> no auto-retry
- overflow compaction:
agent_end -> compaction -> auto-retry same prompt
Current Symphony WorkerRunner treats the first agent_end as final turn completion, so simply removing set_auto_compaction false may cause turn-boundary races / desync unless the worker runner learns to tolerate post-agent_end compaction/retry.
Relevant file:
orchestrator/elixir/lib/symphony_elixir/pi/worker_runner.ex
Proposed direction
Immediate safety fix
Do not let Pi workers share the operator's normal config dir.
When launching Pi workers, set a dedicated PI_CODING_AGENT_DIR for Symphony worker processes so any worker-scoped RPC settings cannot mutate the operator's interactive setup.
That isolates:
- settings
- auth/model metadata
- package/config side effects
from the operator's main ~/.pi/agent.
Runtime fix
Stop hardcoding:
set_auto_compaction { enabled: false }
at least as an unconditional default.
Options:
- remove it entirely once worker lifecycle is safe
- or make it an explicit Symphony config option instead of a hardcoded spike artifact
Turn lifecycle fix
Before re-enabling compaction by default, make Pi worker turn completion robust to post-agent_end compaction.
Likely options:
- after
agent_end, poll get_state until isStreaming == false and isCompacting == false
- and/or explicitly handle
compaction_start / compaction_end
- and treat
compaction_end.willRetry == true as "turn still in progress"
Acceptance criteria
- Symphony Pi workers no longer mutate the operator's interactive
~/.pi/agent/settings.json
- Worker retry/compaction policy is isolated from the operator's normal Pi environment
- The hardcoded
set_auto_compaction false spike behavior is removed or made explicit/configurable
- Worker turn completion correctly handles post-
agent_end compaction / overflow-retry semantics
- We can safely enable auto-compaction for unattended multi-turn Pi workers
Notes
Pi supports PI_CODING_AGENT_DIR explicitly, so Symphony should be able to isolate worker config without upstream Pi changes.
There is also a likely related leak for set_auto_retry, since Symphony sends that the same way and Pi persists it similarly.
Summary
Symphony's Pi runtime currently hard-disables Pi auto-compaction on every worker turn via RPC, and because Pi persists that RPC toggle to
settings.json, the worker can mutate the operator's interactive Pi config.This was introduced in the initial Pi RPC spike as a conservative simplification, but it now looks stale and actively harmful.
What I found
Original rationale
This behavior was introduced in the initial Pi worker spike:
3c4f677—Add Pi RPC worker spike runner635e5ac—Document the Pi RPC worker contract68404eb—Add Pi worker adapter modulesThe contract doc explicitly says:
Relevant files:
docs/PI_RPC_CONTRACT.mdtools/pi-rpc-spike/client.tsorchestrator/elixir/lib/symphony_elixir/pi/rpc_client.exCurrent behavior
On each worker turn, Symphony sends:
{"type":"set_auto_retry","enabled":false} {"type":"set_auto_compaction","enabled":false}Today, Pi implements those RPC commands by mutating and saving global settings, rather than treating them as session-local worker toggles.
So Symphony workers can end up rewriting the operator's interactive config.
Why this is a problem
1) Workers should not mutate shared interactive Pi settings
This is the immediate bug.
If the operator's
~/.pi/agent/settings.jsonis shared with their interactive Pi setup (or symlinked into a tracked repo, as on my machine), Symphony worker turns can silently flip:compaction.enabledretry.enabledThat leaks worker policy into the human operator environment.
2) The original "disable auto-compaction" rationale is likely stale
Symphony now runs multi-turn Pi workers (
agent.max_turns> 1 in real workflows), which is exactly the kind of unattended / long-running process that benefits from auto-compaction.I did not find evidence of a current concrete compaction bug that still justifies hard-disabling it.
3) Re-enabling auto-compaction is not just a one-line flip
There is one real lifecycle nuance to handle correctly:
Pi can do compaction after
agent_end.agent_end-> compaction -> no auto-retryagent_end-> compaction -> auto-retry same promptCurrent Symphony
WorkerRunnertreats the firstagent_endas final turn completion, so simply removingset_auto_compaction falsemay cause turn-boundary races / desync unless the worker runner learns to tolerate post-agent_endcompaction/retry.Relevant file:
orchestrator/elixir/lib/symphony_elixir/pi/worker_runner.exProposed direction
Immediate safety fix
Do not let Pi workers share the operator's normal config dir.
When launching Pi workers, set a dedicated
PI_CODING_AGENT_DIRfor Symphony worker processes so any worker-scoped RPC settings cannot mutate the operator's interactive setup.That isolates:
from the operator's main
~/.pi/agent.Runtime fix
Stop hardcoding:
set_auto_compaction { enabled: false }at least as an unconditional default.
Options:
Turn lifecycle fix
Before re-enabling compaction by default, make Pi worker turn completion robust to post-
agent_endcompaction.Likely options:
agent_end, pollget_stateuntilisStreaming == falseandisCompacting == falsecompaction_start/compaction_endcompaction_end.willRetry == trueas "turn still in progress"Acceptance criteria
~/.pi/agent/settings.jsonset_auto_compaction falsespike behavior is removed or made explicit/configurableagent_endcompaction / overflow-retry semanticsNotes
Pi supports
PI_CODING_AGENT_DIRexplicitly, so Symphony should be able to isolate worker config without upstream Pi changes.There is also a likely related leak for
set_auto_retry, since Symphony sends that the same way and Pi persists it similarly.