Session Resume & Forking
Managed resume requires a resolved native execution context, including automatic start and restart with a recorded conversation ID. If that context cannot be attested, AoE refuses the launch with recovery guidance instead of delegating the ID to unconstrained native lookup. Ordinary pane replacement checks this before retiring the outgoing pane, preserving its recorded identity and transcripts on refusal. This protection does not roll back an earlier explicitly requested container discard or container reconstruction for a tool change. Qualified conversations also validate their agent, store, working directory, and filesystem. A pin or fork that migration left unqualified uses configuration-derived context, subject to Supported managed contexts; a status label or raw ID does not qualify it.
Runtime conversation changes such as /clear, /new, fork, continue, or a fresh pane generation rotate the recorded identity when the native agent publishes the change. The old identity and any artifact predating the launch boundary cannot be recaptured after an AoE process restart.
Physical launch and pane adoption decisions probe the exact tmux name live. A cached tmux session snapshot cannot suppress recreation of a missing pane.
Automatic capture matrix
| Agent | Host terminal | Sandboxed terminal | Authoritative source |
|---|---|---|---|
| Claude Code | Yes | Yes | Pane-scoped native hook |
| Codex | Yes | Yes | Pane-scoped native hook (host), isolated managed store (sandbox). On a macOS host the capture is unbound: no launch binding and no final flush at teardown; see Codex. |
| Cursor Agent | Yes | Yes | beforeSubmitPrompt hook conversation_id |
| Pi | Yes | Yes | Pane-scoped AoE extension |
| OMP | Yes | Yes | Pane-scoped routed terminal store |
| OpenCode | Opt-in | No | AoE-preassigned native id |
| Hermes | Yes | Yes | pre_llm_call hook of the CLI or TUI foreground agent (host; subagents and background tasks are never adopted), isolated managed store (sandbox) |
| Gemini CLI, Kimi CLI | No | Yes | Isolated managed store |
| Prime Agent | No | Yes | Native daemon root observation and isolated managed store |
| Vibe, Droid, Copilot CLI, Settl, Qwen Code, Kiro CLI, Antigravity | No | No | None verified |
No means automatic discovery is unsupported there, not that resume is: a user-provided exact id stays authoritative for any agent with a verified resume contract, and agents with no such contract reject automatic resume entirely. OpenCode host capture also needs session.opencode_preassign_session_id = true. AoE never scans a shared store or infers an identity from recency. Importing is different: you pick the conversation, and its recency only drives a warning.
Hermes host identity hooks validate the complete JSON payload while retaining only the foreground identity, not the conversation history. Payloads up to 64 MiB are accepted. Larger or malformed payloads do not update the stored identity; the previous identity remains unchanged. This transport limit bounds input processing and temporary token storage, not exact process memory usage.
Sandbox config and conversation stores are staged per AoE instance, including custom agent_config_dir roots, and a cross-process lease guards each managed store, so two sessions in the same directory cannot claim each other’s conversation.
agent_detect_as controls status detection and ACP adapter inheritance, not terminal execution identity. Direct built-in commands identify their native agent independently. Opaque wrappers need the explicit contract below for managed resume, fork, and capture from shared stores.
Execution identity and wrappers
A conflicting built-in tool and command, such as tool claude with command codex, is rejected for managed resume and fork. An opaque wrapper requires both session.agent_execution_as and session.agent_config_dir in trusted global or profile configuration. This asserts that the wrapper invokes that native agent, forwards native arguments unchanged, and uses only the declared store and working-directory/filesystem context. Repository overrides are refused because a repository must not grant itself access to another native conversation store.
A bare, non-path wrapper mapped by agent_detect_as can still start fresh and capture its pane-scoped published ID without the execution contract. AoE does not preassign a native ID from a status alias alone, including after an explicit fresh choice or disabled auto_resume_on_restart. Resuming a published ID still requires the contract. A native selector supplied by the user remains user-owned only when the row has no AoE-managed conversation state; AoE does not inject another selector. A known conversation can still be tried after an external working-directory or store move on Default when native execution resolves and its target checks pass; its old binding remains authoritative until a qualified observation updates it.
Shell pipelines, remote launchers, redirections, expansion, and unrecognized context-changing arguments are not supported managed invocations. The program, routing environment, and native namespace arguments are fixed from one validated launch snapshot and restored after the login shell.
Literal leading NAME=value assignments and a shell-quoted executable are supported for direct commands. The assignments are included when resolving the executable and its native store.
Supported managed contexts
- Claude: on a host launch, the store its conversation recorded, a declared
agent_config_dir, the resolvedCLAUDE_CONFIG_DIR, or the default Claude store; a sandboxed session takes a per-session child of the entry instead. A recorded conversation store outranks a conflicting configuration-directory setting; see Swapping the engine on a restart. Managed arguments allow namespace-neutral--mcp-configwith one or more files or JSON strings, and zero-arity--strict-mcp-config, per the Claude CLI reference. Extra native session selectors and unsupported configuration-routing options remain refused. - Codex: a local host-readable
CODEX_HOME, file-backed API-key authentication, and local SQLite/thread storage. Cloud, keyring, profile, project-routing, managed-policy, and host macOS contexts are not currently proven. On a host whose only gap is macOS managed preferences or its credentials (ChatGPT login, keyring, noauth.json), a Default restart still runscodex resume <uuid>when every rollout of that exact ID found under the store’ssessions/YYYY/MM/DDdirectories is a root thread (not a subagent) that records the session’s working directory. A sandbox with those credentials gets the same check against its private store and container workdir; legacy shared stores and wrappers refuse. A store with more than 1,048,576 entries undersessions/cannot be checked and refuses. Aconfig_toml_base64orrequirements_toml_base64value in thecom.openai.codexpreference domain, forced or not, can route the store, so it refuses, as do a profile, project routing, a remote thread store, and, on a host,session.agent_config_dir. - OpenCode: an explicit
OPENCODE_DB, or the common database whenOPENCODE_DISABLE_CHANNEL_DBis already enabled. Sandboxed explicit resume requires a host-readable database mount; the target working directory is still validated inside the container. Target routing is read only from the session table selected by the prepared executable’s verified release/build identity. An inactive table cannot supply a missing target or override its working directory or workspace. A workspace-routed target or different stored working directory is refused. Managed current-generation launches and resumes use--standaloneso an existing background service cannot redirect them to another database. - Pi and OMP: the verified transcript and exact store. Recovery uses
--storewith the transcript file. A Pi row that has an id but no recorded transcript path looks for<timestamp>_<id>.jsonlin the store, then resumes it with--session <path>. OMP also verifies its stored working directory and pins the resolved profile. - Gemini, Cursor, Kimi, and Copilot: their native config/share/home store inputs. AoE does not combine unrelated environment roots.
- Prime: the resolved agent root and session directory, including a verified
--cwd. A declared wrapper can resume an explicitly bound conversation but does not gain capture or fork capability. - Hermes: an explicitly declared configuration root and local
state.db. Stored lineage and working directory must match the launch context. - Vibe: an explicitly declared configuration root and default session store. Remote and alternate-store selectors are refused.
Target validation supports official OpenCode 1.16.0, 1.16.2, 1.17.0 through 1.17.20, and 1.18.0 through 1.18.35 with session; official 2.0.0 through 2.0.26 and the verified 2.0.24+31bdd8e build use session_v2. Unlisted versions, previews and local builds refuse managed resume and fork, including Default restarts, until their schema is verified. This relies on the trusted executable’s truthful --version identity, not cryptographic verification of its source. CLI generation and table presence alone do not identify the active schema. Fresh launches without a target do not perform this row validation.
OpenCode legacy-generation builds approve permissions through OPENCODE_PERMISSION and fork with a root --fork; current-generation builds use --auto and POST /api/session/{id}/fork. Root --fork is the legacy generation marker. A launch without an attested store or an AoE-owned conversation keeps the login shell’s environment, including credentials, SSH sockets, proxies and version-manager paths. Its resolved executable stays pinned. For YOLO, AoE probes that executable after login under the same working directory and effective environment it will execute, including explicit profile and before-session overrides, so a shim’s downstream engine receives the matching approval mechanism.
Store-bound launches, managed resume/fork and successful preassignment restore the selected environment after login startup so the native store, generation and API-created ID agree. Configure credentials through profile environment entries or enable session.inherit_host_environment; login-only exports are not imported. TERM, TMUX and TMUX_PANE come from the new pane. If help cannot establish a generation, managed operations are refused and no approval mechanism is guessed. Legacy preassignment remains disabled; sandbox preparation probes inside the actual selected container, not on the host or from an image-tag assumption.
Captured managed host environment values travel through a protected descriptor, not process arguments.
The transport preserves non-UTF-8 keys and values, arbitrary environment names and multiline values without replacing the pane’s standard input.
The helper executes the pinned program directly and receives literal argv values. Only valid initial KEY=value assignments override the environment; = in an executable path or a later argument is preserved. A missing or empty program is refused before execution.
Successful host generation and first-launch naming answers are reprobed without retaining their launch environments.
A failed help probe cools down only the identical program, working directory and complete environment for 30 seconds; a different context, including a new launch-source ID, probes immediately.
After a failure, later probes of that executable use the longer retry timeout.
On Linux, if a running AoE’s executable was replaced in place, the managed launch resolves the installed replacement.
AoE and its installed helper must implement the same payload contract; restart AoE if an upgrade changes that contract.
Disabling agent_status_hooks removes status writers only; identity hooks declared for native resume stay installed.
Prime Agent 0.10.x uses the native daemon API, not a JavaScript extension. Managed launches accept native model, provider, thinking, autonomous, and resource options while refusing selectors that replace AoE’s session or daemon namespace. Interactive sandbox capture requires one attached depth-zero root and corroborates its UUID, working directory, and transcript header; RLM children, sibling roots, and a recent file alone are not evidence. A confirmed empty root starts fresh rather than resuming an older conversation. Print capture accepts only a single eligible root transcript in the bound store. Keep --session-dir inside the writable managed agent store, with bounded regular settings files; symlinked settings are refused. Automatic identity capture remains sandbox-only; host terminal sessions receive native activity detection.
Common managed resume and fork refusals
Automatic resume also refuses when native execution cannot be resolved in these contexts. The table describes explicit pin and fork operations, not interface status labels. set-session-id uses the recorded active execution when available; otherwise it resolves the current launch configuration. For agents identified by their store alone, restoring the original configuration can resolve the refusal.
When native execution resolution fails for a recorded managed conversation under Default, including Prime Agent, the API returns HTTP 409 with aoe-error-code: managed_resume_refused and a fixed recovery reason. Missing wrapper contracts identify the trusted settings to restore; their declarations must match the actual native agent and original store. The API receipt contract describes the finite cause metadata.
Transcript-specific refusals distinguish the agents’ requirements:
- OMP: resume requires a binding to the exact transcript file; the native ID alone is insufficient.
- Pi: a matching binding must supply the required transcript path, or permitted discovery must find it. Nonexplicit preallocated bindings are exempt from this check.
Neither refusal deletes the conversation or proves that its transcript is missing or its configuration changed. For host sessions, use exact-file recovery, not a recent conversation. Manual transcript rebinding is unsupported for sandboxed Pi/OMP terminal sessions. They can explicitly choose fresh as described in that section, replacing the AoE conversation association on a subsequent launch without deleting the original transcript.
Local diagnostics include the native refusal chain and any scope-retirement error; public recovery messages exclude both. If scope retirement remains unproven, inspect its diagnostic before retrying. A refused launch does not prove that every process in the outgoing scope has stopped.
| Context | set-session-id |
Fork | Remedy |
|---|---|---|---|
| Unsandboxed Codex on a macOS host | Refused | Refused | Default restarts resume a verified rollout; otherwise clear the ID, restart, and run /resume <id> in Codex. A managed container with a local OpenAI API key supports all three |
Codex with a ChatGPT login, keyring, or no auth.json |
Refused | Refused | Default restarts on a host or in a private sandbox store resume a verified rollout; a local OpenAI API key supports all three |
| OpenCode with no provable durable database | Refused | Refused | Point OPENCODE_DB at a durable database (opencode debug paths prints the current one); otherwise OPENCODE_DISABLE_CHANNEL_DB must be exactly 1 or true, and :memory: never qualifies, per OpenCode |
| OpenCode with an unlisted release/build identity | May be recorded; resume refused | Refused | Use a listed identity; Default restarts also refuse without guessing the active schema |
| Sandboxed OpenCode fork, or a current-generation wrapper-mediated store fork | n/a | Refused | Disable the sandbox; for a current-generation store fork, launch opencode directly |
Legacy-generation OpenCode with session.opencode_preassign_session_id |
n/a | n/a | Preassignment stays disabled: generation alone cannot establish whether the build exposes the /api session-create endpoint |
| Prime Agent fork on a host terminal | n/a | Refused | Run Prime in a sandbox: it mints the child id, and only the sandbox captures it. The fork launches prime-agent --fork <parent-id> after AoE copies the parent’s persisted transcript into the child’s own sandbox store; a parent whose transcript cannot be found there refuses the fork |
| Any agent in an Apple Container sandbox | Refused | Refused | Set sandbox.container_runtime to Docker or Podman, per Configuration |
An opaque wrapper missing session.agent_execution_as or session.agent_config_dir |
Refused | Refused | Declare the wrapper contract, per Execution identity and wrappers |
Pinning or resetting a conversation
Pin a terminal session to a specific native conversation:
aoe session set-session-id <session-name-or-id> <native-session-id>
Pi/OMP transcript rebinding is host-only. For other agents that support sandboxed pinning, keep the container running so AoE can resolve its environment and canonical paths.
This records an assertion about the intended native target, separately from any observed conversation. The pin is sticky. If the execution context changes, restore it or explicitly rebind the intended conversation before retrying. Legacy IDs and IDs from old unqualified publishers remain unknown after migration; current configuration does not relabel them.
When a launch requests resume, AoE preserves an unknown stored ID and attempts it only when native execution resolves. A qualified capture can establish its binding. If the native resume probe fails, AoE preserves the ID, records a durable failure marker, and returns resume_failed without starting fresh in that call.
Same-ID observations, including binding qualification and OMP launch confirmation, preserve the durable failure marker. Capturing a different ID clears it; an explicit set-session-id assertion or a successful launch can also clear it.
On the next orchestrated restart or send-driven revival under Default, a resume-capable conversation with that marker starts fresh instead of repeating the probe. This applies to both restart policies, including when auto_resume_on_restart = false. The fresh launch abandons the AoE association with the old conversation, not its native transcript. CLI restart reports the fresh result; sends and restart wake messages may then reach the new conversation.
AoE reports this fresh result only after the new association, or the cleared association for an agent that publishes its own ID later, is durably committed. A publication failure refuses the launch instead of reporting fresh success with the old association still stored.
To retry the original conversation through the restart cascade, first restore its original configuration and store, then run aoe session set-session-id <session> <native-id>, using --store with the exact store or transcript where required. That assertion clears the failure marker. An intentional configured-fresh restart remains available; use the empty-ID command below for a one-shot fresh conversation.
Direct aoe session start keeps its separate policy: it may retry a previously failed ID when native execution and target validation succeed. It performs that shared preflight before replacing an ordinary dead pane, but does not run the cascade’s final resume probe. A successful start therefore does not prove that the native conversation loaded.
An explicit pin or fork that migration left unqualified resumes against the store current configuration resolves. Agents that resolve their store or transcript before that check still refuse, for the reasons listed in Supported managed contexts. An armed pin is the exception to the rule above: no capture qualifies the binding it names.
An agent that publishes its own conversation spends the pin at launch; its recorded binding remains unqualified until a capture qualifies it. A spent pin follows the Default failed-probe policy above. An agent without such a publisher keeps its persistent Use pin armed, including after a failed probe; that pin remains retryable without another assertion. Each retry still validates the resolved execution context and any qualified binding. A dispatched fork is always spent, whether or not it resumes. set-session-id is the only way to assert a store without launching:
For Claude, --store selects its configuration directory:
aoe session set-session-id <session> <native-id> --store /absolute/native/store
For Pi and OMP (host sessions only), use the exact existing transcript file, not its directory. The file’s header must name the requested ID:
aoe session set-session-id <session> <native-id> --store /absolute/path/to/transcript.jsonl
The file must belong to the configured store. If store routing changed, restore the original store configuration before rebinding.
Then start or restart the session. Other agents resolve their store from
configuration and reject explicit --store routing.
To start fresh once, including for sandboxed terminal sessions:
aoe session set-session-id <session-name-or-id> ""
This records fresh intent without launching. Start or restart separately to replace the current AoE conversation association. The command neither deletes nor recovers the original transcript; the recorded observed ID can remain visible until that launch.
Automatic capture then takes over where supported. The abandoned conversation stays excluded only in its recorded agent, store, and filesystem namespace. Legacy exclusions without a known namespace remain ID-wide.
Structured-view conversations remain managed by ACP. For a Claude terminal handoff, AoE records the native execution resolved for the current ACP ID. A handoff whose store cannot be resolved, or which the structured worker does not share, is refused before worker teardown with recovery guidance. Other structured resume-target changes are rejected.
Forking a session
A fork starts a new, independent session from an existing session’s conversation, so you can take the same history in a different direction. Only the fork is new: the original session and its transcript are untouched.
- TUI: the command palette’s Fork session (resume context, diverge), or Fork session on a session row’s right-click menu. The dialog inherits the source working directory, group, and title. A rejected launch preserves the pending fork instead of silently starting fresh.
- Web: Fork session on the sidebar context menu of a forkable session.
- CLI:
aoe add --fork-from <session-id-or-title>.
The fork inherits the parent tool, group, working directory, and conversation binding. A qualified binding has already established the native agent and store through a qualified publication, import, or explicit recovery assertion. A raw or preallocated ID is insufficient; status detection and matching tool labels grant no authority. A raw ID that migration left unqualified is not a reason to refuse: a fork of it launches against the store current configuration resolves, under the same agent the parent row resolves to, because nothing recorded the store or the tool its parent used. A child asked for under another agent is refused before dispatch, whatever it is asked for by name. A Claude child whose tool resolves to a different store than the parent’s (for example a custom agent with its own agent_config_dir) gets the parent’s transcript copied into that store, and its binding records that store; the fork is refused if the transcript cannot be copied. A conflicting native command or user-supplied resume/fork selector is refused before dispatch whatever the binding, and for a qualified binding so is a different tool, without clearing the pending fork. --fork-from cannot be combined with --worktree / --new-branch or --sandbox / --sandbox-image.
The child gets its own AoE ID and native conversation. The parent row and transcript remain unchanged.
After a Claude terminal fork copies a transcript to a different physical store, the child’s prepared launch determines credential routing. The parent’s default-store export provenance does not carry over.
Current-generation OpenCode store forks validate the returned child in the prepared active store after the short-lived server is stopped, before adoption. Local control requests bypass ambient proxies; provider traffic keeps the selected environment. Shutdown preserves the process group’s grace period even if a shim exits first, then escalates and bounds direct-child reaping. Launch success requires confirmation that the adopted child is durably published. A failed confirmation refuses the launch. If adoption was already saved, retry opens that child instead of forking again. A returned parent, missing child, or unsupported route preserves the pending fork. Automatic recovery still depends on the agent’s capture capability and supported execution context; dispatching a native fork adds no new child-ID discovery path.
Forking needs an agent that can branch a conversation: claude, codex, opencode, pi, and prime-agent in the supported managed contexts, and the Claude adapter for structured sessions. Resume-only agents (gemini, vibe, copilot) and agents without resume in AoE (cursor, droid, kiro, qwen) hide or refuse the action.
Pi terminal forks require the same working directory and an attested host pi on PATH that supports both --fork and --session-id. AoE assigns the child id before launch. A sandboxed Pi, an unattestable wrapper, or a Pi without either flag refuses the fork rather than creating an untracked conversation. The child copies the parent’s saved transcript, starting from the last persisted message. Stopping the child saves its launch-scoped transcript before clearing publication evidence; later starts resume that exact child rather than forking the parent again. Forking into a new worktree is not supported for Pi.
Swapping the engine on a restart
The restart dialog can change the tool a session runs. Session IDs live in per-agent namespaces, so swapping to a different agent parks the outgoing agent’s conversation under its own tool name and starts a new one; swapping back restores what was parked.
Two tool names can also point at the same agent on different accounts, through [session.agent_config_dir]. That swap changes the agent’s config root, so the conversation is still on disk but under the account you swapped away from. AoE carries it across: it copies the transcript into the incoming account’s config root so the agent resumes where it left off, and the row keeps its conversation id, model, and effort setting, since none of those changed agent.
The carry applies only when the new tool resolves to the same built-in agent, the session has a conversation to carry, and AoE knows the agent’s transcript layout (Claude Code today). Anything else takes the parking swap above.
The CLI reaches this swap with aoe session restart <session> --tool <tool>, and for a structured session aoe acp switch-account <session> <tool> does the same but refuses instead of parking when the carry does not apply. A structured session parked on a rate limit continues with the interrupted prompt instead of the restart wake message; see Switching accounts manually.
The outgoing account keeps its own copy, so swapping accounts back and forth stays continuous: the account you swap away from is the one that was just running, so on the way back its transcript replaces the older copy the earlier swap left behind. A copy the incoming account wrote more recently than the outgoing one is left alone.
Editing one tool’s agent_config_dir entry in place is not that swap. A Claude conversation on the host whose binding recorded a store resumes there, and that store outranks the entry, so repointing or removing it leaves the session on the account it recorded and only new sessions follow the entry. A structured session resuming the same conversation pins that store without logging, and a sandboxed Claude session keeps the store its container was created with, because nothing recreates a container for an agent-config mount change; AoE still stages a fresh store at the new entry’s per-session path on the next start, and the session reads that store only once the container is recreated for some other reason (Per-session agent stores).
A host launch whose conversation recorded a store records a session.store warning when that store differs from the store a new session would take, naming the store the launch used as launch_store, the store a new session would take as new_session_store, and where that one comes from as new_session_store_source: agent_config_dir, environment, or default. Both are the resolved host locations the route already selects, so an entry spelled differently but naming the same directory as the recorded store produces no line at all. It is the only line on that target carrying new_session_store=, so match on that field: the same target also carries unrelated configuration warnings about entries that will be ignored. The line goes to the log the TUI and the daemon write, which aoe logs opens, and the default level passes it. A one-shot aoe session restart has no log of its own, so the line appears there only with AOE_LOG_LEVEL or AGENT_OF_EMPIRES_DEBUG set, and a level of error filters it away. See Environment variables for both.
Rebinding the record with aoe session set-session-id --store copies nothing, so it only resumes if the target account already holds the conversation; swapping tool names as above is what has AoE copy the transcript.
Picking up an upgraded agent CLI
Upgrading the agent binary from inside a session does not replace the process in the pane. Restart the session instead of creating a new one: press e (E with strict hotkeys) or F5, or run aoe session restart <session> (--all to restart every eligible session in the profile; from an agent session, the caller is skipped so the command can finish). A restart runs only on_launch, whose failures are warnings, and resumes the conversation while session.auto_resume_on_restart is on (the default). A new session runs on_create, whose failure aborts creation.
AoE also notices when the agent binary was replaced after a session launched. While the session is idle, the web sidebar menu offers Restart to update (it keeps the conversation) and the TUI row shows an [update] badge, with the same restart under the context menu.
Importing an existing conversation
Conversations started outside AoE can be pulled into a structured-view session from the web wizard’s Import session tab. The tab appears when at least one built-in agent has both its CLI and its ACP adapter installed, since the import resumes through that adapter. Pick the agent, then a conversation: AoE sorts the returned conversations by activity, showing their title, working directory, and last activity. It preselects Claude when available and remembers your last pick.
Picking one creates a structured-view session in that conversation’s recorded working directory and loads it with ACP session/load, so the prior transcript is there and you can keep going. It always uses the recorded directory and never creates a worktree or a sandbox, because the conversation only resolves on the host where it started. The original is read in place, not copied.
The list hides conversations not worth importing: ones AoE already owns, scratch sessions, and anything inside an AoE worktree directory. It returns at most 200 conversations. Resource limits can make the returned list partial even below 200; unread pages may contain newer conversations. An empty partial result does not establish that no importable conversations exist. Sessions whose directory no longer exists are hidden until you tick “show missing directories”, and then shown disabled. A conversation active in the last 10 minutes, or with no recorded activity, asks for confirmation first: if it is still open in another terminal, two processes would append to the same transcript.
Claude
Claude conversations come from the Claude Code store on disk (under $CLAUDE_CONFIG_DIR or ~/.claude/projects). The tab reads CLAUDE_CONFIG_DIR from AoE’s own process environment and consults neither agent_config_dir nor a recorded store, so a conversation held in the store the entry points at is not listed. Imports bind the selected store for both terminal and structured resumes; later profile routing and hook output do not replace it.
Other agents
Every other built-in agent is asked through ACP session/list. AoE starts the adapter each time the picker opens and again to check the pick on create, both under the profile selected in the wizard. The adapter gets that profile’s host [environment] and before_session output, and stops after listing. It never creates or loads a session in that process. An agent that does not advertise both session/list and session/load shows “This agent can’t list sessions”. agent_config_dir is not consulted.
The list spawn runs its hooks in a temporary directory with AOE_SESSION_ID=__list__<agent>, while the later load runs them in the project with the real session id. Route the agent’s store with values that do not depend on that context, such as static [environment] entries. A hook that picks the store from the project path or session id can list from one store and load from another. For pi, set a custom store with PI_CODING_AGENT_DIR and a wrapper with PI_ACP_PI_COMMAND in [environment].