From c6f2a782bb5119c0d502af9e4301fb6a3ceb89e4 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sun, 8 Nov 2026 12:59:25 +0100 Subject: [PATCH] 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. --- docs/plugins.md | 7 ++++--- docs/providers.md | 18 ++++++++++++------ public-docs/plugins/hooks.md | 2 +- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/docs/plugins.md b/docs/plugins.md index 583e39da28..3fa61d1457 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -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 diff --git a/docs/providers.md b/docs/providers.md index 10dc7b4656..fe3d58446a 100644 --- a/docs/providers.md +++ b/docs/providers.md @@ -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 diff --git a/public-docs/plugins/hooks.md b/public-docs/plugins/hooks.md index 1d6691c4ef..a41a209018 100644 --- a/public-docs/plugins/hooks.md +++ b/public-docs/plugins/hooks.md @@ -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 }) => {