Repository navigation
feat(server): MCP errors that say what to send, and idempotent archive (TEC-1819) - #356
Conversation
…ycle tool for agents - t3_dispatch_command: a wrong command names its type, required fields and a minimal example; an unknown type gets the closest valid one; lifecycle commands point to t3_session_action. - Control errors bring the orchestrator's own reason through (for example "Thread x is already archived.") instead of the generic invariant text. - t3_session_action archive/unarchive are idempotent: already in the target state returns success with changed: false and dispatches nothing. - New agent tool t3_session_lifecycle (archive, unarchive, stop, snooze, unsnooze, delete; destructive) on the shared runSessionLifecycle, so OAuth agent-list clients can archive sessions. - t3_ui_list_tools: one-line descriptions, requiredFields, description search, and tools/tool to return input schemas without t3_ui_get_tool. The call, batch and get schemas no longer embed the ~250-name enum; unknown names get the closest matches. - t3_start_session: advise timeoutSeconds <= 50 and retry with the same clientRequestId; the result starts with the full session id. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bk-agent-01
left a comment
There was a problem hiding this comment.
Cross-review round 1: three findings on the requested head. No tests, builds, or linters ran on this host.
… race - t3_session_lifecycle authorizes its target through resolveMcpSessionTarget (t3.control), the same policy as t3_session_action: a provider credential without user-wide scope acts only on its own session. - A no-op archive/unarchive with a caller's command id writes an accepted command receipt with sequence 0; a retry of that id replays the no-op, and a later dispatch of that id replays instead of acting. - An archive/unarchive that loses a race and is refused as already in the target state returns success with changed: false; other refusals still fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bk-agent-01
left a comment
There was a problem hiding this comment.
[cross-review r2 · gpt-6.1-sol] P2 Two retry defects remain. The authorization fix is accepted.
- The lifecycle paths now require CommandReceiptStoreV2. The control and agent toolkit registrations provide CommandReceiptStore .layerFromApplicationReceipts over the receipt repository the server already exposes, so no-op receipts persist in production. - When this command loses a race to the state it asked for, the rejected receipt the orchestrator wrote for its command id is replaced by an accepted no-op (upsert), so a retry after an inverse action replays the no-op instead of failing as previously rejected. - Tests: rejected-receipt race path, and a replay through the production agent registration layer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…le-errors-idempotent-archive
Cross-review record — gpt-6.1-sol on 9ce0f56Rounds: 3. Verdict: clean.
Deferred: none. Fallback: none. The lifecycle tools require the receipt service. Both registrations provide the adapter over the same application receipt repository used by the orchestrator. The adapter keeps no separate state. The command id includes the authenticated caller namespace, target session, lifecycle action, and request id. The replacement uses that same command id. Only the specific archive or unarchive state refusal permits replacement; unrelated errors remain failures. I read every non-merge commit after 6c6785a and the relevant production paths. I also read the regression tests and CI results. I ran no tests, builds, or linters on this host. |
|
Merge decision: tushar chose "AI fixes it" (test fix, then merge B1 and B2 to stage) in the T3 session on 2026-10-29. Head 9ce0f56, all checks green, gpt-6.1-sol cross-review clean after 3 rounds, no open threads. bkmain promotion stays tushar's call. |
Closes TEC-1819
Summary
Agents that send a wrong command now get an error that says what to send. Archive retries are safe. Agents on the OAuth tool list (Toolyard) can now archive a session and read an action's input schema.
Archive error, before:
After: a second
t3_session_action action=archivereturns success withchanged: falseand dispatches nothing. When a race still causes a refusal, the agent sees the real reason:t3_dispatch_commandwith{"type":"thread.archive","commandId":"mcp:1"}, after:An unknown type gets the closest valid type and the list of valid types one time.
Other changes:
t3_session_lifecycle(archive, unarchive, stop, snooze, unsnooze, delete). It uses the same code ast3_session_action. It is marked destructive because of delete. It is in the OAuth agent tool list automatically.t3_ui_list_toolsentries now have a one-line description andrequiredFields.tools: [name](up to 5) ortool: namereturns the fullinputSchema, so an agent does not needt3_ui_get_tool.queryalso searches descriptions.t3_ui_call,t3_ui_batchandt3_ui_get_toolinput schemas no longer contain the enum of about 250 names (about 17k characters). The server still checks the name and returns the closest names.t3_start_session: the result starts with the full session id. ThetimeoutSecondstext says to keep it at 50 or less and to retry with the sameclientRequestId.t3_dispatch_commanddescription points tot3_session_actionandt3_ui_get_tool.t3_session_lifecycleuses the same session policy ast3_session_action(an in-session credential acts only on its own session). A no-op archive with aclientRequestIdwrites a command receipt, so a retry replays the no-op. Two archive calls that race both succeed.Decisions
Orchestrator.ts/OrchestrationEngine.tsgetThreadShellByIdfirst; catch the refusal and turn it into successcauseas the control error messagerejectionReasont3_session_lifecycle, sharedrunSessionLifecyclet3_session_actionto the agent list; new agent-shaped toolt3_ui_list_toolscompact entries: name, description, requiredFields, readOnly, authorized (streamMode for streams)t3_ui_get_toolt3_session_lifecycletargets withresolveMcpSessionTargett3_session_actionMcpHttpServer.ts, marked)ManagerService.expbkt3.ts)Acceptance check
thread.archivewithoutthreadIddispatchCommandHelp.test.tsdispatchCommandHelp.test.tshandlers.sessionLifecycle.expbkt3.test.ts,sessionLifecycle.expbkt3.test.tshandlers.sessionLifecycle.expbkt3.test.tst3_ui_list_toolsentries haverequiredFieldsand descriptionsregistration.listTools.expbkt3.test.tst3_ui_list_tools tools:[name]returnsinputSchemaregistration.listTools.expbkt3.test.tsregistration.listTools.expbkt3.test.tst3_session_lifecycleis in the agent list, destructive, idempotent archive, sameclientRequestIdgives same command idsessionLifecycle.expbkt3.test.tst3_session_lifecyclesessionLifecycle.expbkt3.test.tshandlers.sessionLifecycle.expbkt3.test.tssessionLifecycle.registration.expbkt3.test.tshandlers.sessionLifecycle.expbkt3.test.tshandlers.sessionLifecycle.expbkt3.test.tst3_start_sessiontext: full id first, timeout adviceagent/handlers.tsandagent/tools.tsHow I tested
No local tests on the shared host; CI runs the suite.
b34d819c8(test type fix in6c6785abf); threads answered and resolved.9ee59f7b5; threads answered and resolved.9ce0f56b5(withorigin/stagemerged, which has the storageCleanup fix from fix(server): storage cleanup handles newer git's broken-gitfile error #357): every named check passes, including typecheck, lint, format, fork markers and all 8 server test shards. The new and changed test files pass (27 tests).🤖 Generated with Claude Code