Skip to content

fix(agent): bound recovery requests and harden the structured-output example#17

Merged
ThomasK33 merged 3 commits into
mainfrom
agent-structured-output-hardening
Jul 9, 2026
Merged

fix(agent): bound recovery requests and harden the structured-output example#17
ThomasK33 merged 3 commits into
mainfrom
agent-structured-output-hardening

Conversation

@ThomasK33

Copy link
Copy Markdown
Member

Follow-up to #15, carrying the final max-effort review findings that were still local when #15 merged. All changes are in the example/docs surface (no library code):

  • Every recovery request is bounded and truly cancelled. The settle's interrupt fallback goes through client.interruptChat(chatId, AbortSignal.timeout(8s)) (was an unbounded agent.interrupt() — the one path reached precisely when the server is stalled could hang ~5 min). archiveQuietly now calls archiveChat/interruptChat with real signals, so a stalled attempt aborts the socket instead of stacking duplicate in-flight requests and pinning the event loop; the bespoke bounded()/TIMED_OUT race is gone, and the 15s deadline is checked before each attempt so it actually bounds the function.
  • READ: last valid call wins. A schema-invalid re-file no longer shadows a valid answer filed earlier in the same turn.
  • structuredOutput(schema, { tools }) merges extra client tools into the ToolSet, closing the ...agentOpts spread-clobber trap; documented at the call site.
  • Tool errors submit SDK-style messages (string / Error.message / JSON-stringify — never "[object Object]"); the toModelOutput boundary is documented explicitly.
  • README rule 4 synced with the example: outcomes read off the step's content parts, tool-error parts submitted with is_error: true.
  • CODER_MODEL / CODER_TOOL_MODEL fallbacks hoisted into loadEnv() with || semantics, shared by examples 03 and 06.

Deliberately deferred (candidate follow-up issue): package-level ownership of the stranded-turn settle and a wind-down-aware archive()/asyncDispose — both flagged by review as library-grade recovery currently living in example code.

Verification

  • pnpm check (format, lint, typecheck) and unit tests green (50/50).
  • Example verified live against dev.coder.com on the pre-rework logic; a fresh live smoke is pending deployment health (its chat event stream was degraded yesterday evening — turns completed server-side but clients never received events).

🤖 Generated with Claude Code

…inal review

Follow-up to #15 — final max-effort review findings on the example/README:

- All settle/interrupt/archive attempts now carry AbortSignal.timeout: the
  interrupt fallback goes through client.interruptChat (was unbounded
  agent.interrupt()), and archiveQuietly calls archiveChat/interruptChat
  with real signals — a stalled request is cancelled, not abandoned (no
  duplicate in-flight requests, no ref'd socket pinning the event loop).
  The bespoke bounded()/TIMED_OUT race and its stale module-scope stack
  are gone; the deadline is checked BEFORE each attempt so 15s means 15s.
- READ: last VALID call wins — a schema-invalid re-file no longer shadows
  a valid answer already filed earlier in the turn.
- structuredOutput(schema, { tools }) merges extra client tools into the
  ToolSet so the spread cannot silently clobber user tools; documented.
- Tool errors submit the SDK-style message (string/Error/JSON-stringify),
  never "[object Object]"; the toModelOutput boundary is documented.
- README rule 4 now reads outcomes off content parts with is_error, in
  sync with the example.
- CODER_TOOL_MODEL/CODER_MODEL fallbacks hoisted into loadEnv with ||
  semantics shared by examples 03 and 06.

Change-Id: I921c77b539f9376260e51d332777a917a15872fb
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

Live smoke completed: the deployment's event stream recovered overnight, and the reworked example runs clean against dev.coder.com — turn finished "stop" after 2 step(s), typed triage returned, archive succeeded, process exited promptly (also exercising the new signal-carrying cleanup path end-to-end).

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: bd1edcec5b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Final-gate review of #17's diff:

- README rule 4 and the inline snippet taught what the example just
  replaced: unbounded agent.interrupt()/archive() recovery and
  last-call-wins reading. Both now match the example — bounded
  client.interruptChat/archiveChat with AbortSignal, last-VALID-call-wins
  scan — and the structuredOutput(schema, { tools }) composition path
  (and its constructor-clobber hazard) is documented.
- structuredOutput throws on an opts.tools entry named structured_output
  instead of silently replacing the caller's tool.
- errorText is total: returns a string for symbols/functions (JSON.stringify
  yields undefined) and circular values (String() can throw), never throws.
- archiveQuietly logs the last failure when the deadline expires and skips
  the doomed interrupt+sleep once the budget is spent.
- loadEnv's printed setup help documents CODER_TOOL_MODEL; dead array copy
  in the READ scan dropped.

Deliberate tradeoffs kept (documented in-file): last-valid-wins returns an
earlier valid answer instead of burning the nudge on an invalid re-file;
the archive deadline is strict rather than overtime-forgiving.

Change-Id: Ia7a326c11af9e3b015021dee32eecf8c89d481c6
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🚀

Reviewed commit: 15fa2d9219

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…lly total

Convergence review of the previous commit:

- README rule 4: the settle fragment now carries the AbortSignal third arg
  (its own bold sentence mandates bounding every recovery request), and
  the prose spells out the agent.chatId guard (undefined before the first
  turn) instead of prescribing a fragment that neither typechecks nor
  no-ops safely.
- errorText: an Error subclass with a throwing `message` getter could
  escape the try blocks — the instanceof branch is now wrapped too, so
  the totality contract holds.

Change-Id: I8580e2de0d4326f29fc40cccbbd4625df21803f5
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

Reviewed commit: 4ffada7d2e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33 ThomasK33 added this pull request to the merge queue Jul 9, 2026
Merged via the queue into main with commit 5577db9 Jul 9, 2026
6 checks passed
@ThomasK33 ThomasK33 deleted the agent-structured-output-hardening branch July 9, 2026 08:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant