Document one option change at a time per agent

The agent manager runs option, model, thinking and mode changes in the agent's
lifecycle lane; providers do no sequencing. The hook deadlock warning now covers
agent.create.before and option writes.
This commit is contained in:
Mohamed Boudra
2026-11-08 12:59:25 +01:00
parent 99f270962b
commit c6f2a782bb
3 changed files with 17 additions and 10 deletions
+4 -3
View File
@@ -474,9 +474,10 @@ path shape: a plugin can place a worktree several directories below the default
Emit from the operation owner, not a client subscription. Provider history replay must not trigger
live hooks. Notifications must not be awaited inside agent mutations: a handler can send a prompt
or answer a permission through its own daemon session. Awaiting it there deadlocks that command.
The same holds for a hook that runs inside an agent's lifecycle lane: an `agent.open.before` hook
that awaits a write to the agent being opened (`setMetadata`, `setLabels`) waits on the open, which
waits on the hook. The hooks reference tells plugin authors to start such a write without awaiting it.
The same holds for a hook that runs inside an agent's lifecycle lane: an `agent.open.before` or
`agent.create.before` hook that awaits a write to the agent being opened (`setMetadata`, `setLabels`,
an option, model, thinking or mode change) waits on the open, which waits on the hook. The hooks
reference tells plugin authors to start such a write without awaiting it.
## Contribute a provider
+12 -6
View File
@@ -53,12 +53,18 @@ or without a control.
first prompt only Fast can change. Codex fixes its approval policy and sandbox when the thread
starts, so it applies only `service_tier` and `plan_mode` live. A session with no
`applyOptions` rejects every patch; the daemon enforces this. The provider returns the options
it ended up with when they differ from the request. The daemon
stores the patch and that result in one step after the call resolves, onto the agent's options
as they are at that moment, so the last write wins. Throwing rejects the whole patch and
none of it is stored. Emit nothing from inside the call. If the provider's own state moved
before it refused, it reports the moved values once as `options_changed` after the call has
ended, because the stored options must match what runs.
it ended up with when they differ from the request. The daemon stores the patch and that
result in one step after the call resolves. Throwing rejects the whole patch and none of it is
stored. Emit nothing from inside the call. If the provider's own state moved before it refused,
it reports the moved values once as `options_changed` after the call has ended, because the
stored options must match what runs.
- **One change at a time per agent.** `AgentManager` runs every option, model, thinking and mode
change inside the agent's lifecycle lane, the same queue that opens, reopens, closes and
archives it. `applyOptions`, `setModel`, `setThinkingOption` and `setMode` are never in flight
together on one session, and none of them overlaps an open or reopen. Providers do no
sequencing of their own: no queues, no "apply over the options as they are now" handling. A
change that arrives while another runs waits for it and then reads the agent's stored options
as that one left them.
- **Old data converts in the agent manager.** Codex used to take `fast_mode`; it takes
`service_tier` now. `AgentManager` runs `convertLegacyOptions` (`option-controls.ts`) on an
agent's own options when it creates, resumes, imports, reloads or applies to it, and on draft
+1 -1
View File
@@ -283,7 +283,7 @@ server.addHook("agent.create.after", async ({ agent }) => {
Receives `{ request }`; only `env`, `tools`, and `mcpServers` may change, for this launch. All other fields are read-only. A throw, invalid result, or timeout fails the open.
Do not await a write to the agent being opened, such as `setMetadata`. The write waits for the open to finish, the open waits for your hook, so the hook times out and the open fails. Start the write without awaiting it; it runs right after the open:
Do not await a write to the agent being opened: `setMetadata`, `setLabels`, or an option, model, thinking or mode change. The write waits for the open to finish, the open waits for your hook, so the hook times out and the open fails. Start the write without awaiting it; it runs right after the open:
```ts
server.addHook("agent.open.before", async ({ request }, { paseo }) => {