Operational knowledge for agents (and humans) working on coder/ai-sdk. For
contributor setup and command details, see CONTRIBUTING.md;
this file covers what you need to operate the repo without tripping over it.
- pnpm workspace (
packages/*). Toolchain (Node, pnpm, actionlint, zizmor) is pinned inmise.tomland locked inmise.lock— usemise install. - Three independently versioned npm packages, all targeting Vercel AI SDK v7:
@coder/ai-sdk-agent,@coder/ai-sdk-provider,@coder/ai-sdk-sandbox. - Two private packages:
packages/release-please-ai— release-please run as a library with AI-generated changelog notes; it drives the release workflow — andpackages/effect(@coder/ai-sdk-effect) — an experimental Effect bridge spike (issue #144), unpublished and outside release-please until it graduates.
Run before every push:
pnpm check— oxfmt format check + oxlint + typecheck (the CI lint gate).pnpm -r buildandpnpm -r test.- Live e2e (optional; needs a real deployment):
cd packages/agent && npx vitest run test/e2e— self-skips unlessCODER_URLandCODER_SESSION_TOKENare set.
CI additionally runs pnpm publint and pnpm attw (publish hygiene for
sandbox + provider) and lints workflows with actionlint + zizmor.
Gotchas:
- oxfmt formats Markdown too — including TypeScript inside fenced code
blocks. After editing any
.md, runpnpm formatorpnpm checkfails. - The vendored anti-slop oxlint rules reject
low-evidence "clever" patterns — e.g.
no-conditional-empty-object-spreadbans...(cond ? { x } : {}). Write code imperatively: build the object, then conditionally assign. Ratchet policy is in CONTRIBUTING.md.
- Conventional Commit PR titles are load-bearing. PRs are squash-merged,
the title becomes the commit on
main, and release-please derives versions and changelogs from those commits.pr-title.ymlvalidates titles (scopesandbox/agent/provider/effector none; subject starts lowercase, no trailing period). - Merge only through the merge queue (squash). Auto-merge is disabled; if
gh pr mergelacks queue support, use GraphQLenqueuePullRequest. - The single required status check is the aggregate
Requiredjob inci.yml; it also runs in the merge queue (merge_group). - Branch names must not collide with issue-tracker IDs. When a branch
references a GitHub issue number, write it as
issue-<number>(e.g.issue-1234-retry-timeout), not as a bare<word>-<number>(docs-1234,api-1234) or a leading number. Connected trackers (e.g. Linear) auto-link any branch containing a<key>-<number>token to the same-numbered issue on the team that owns<key>, which silently attaches the PR to an unrelated ticket and moves it through that ticket's workflow. Theissue-prefix keeps the reference readable and avoids the match.
Maintainer agents run BOTH review loops on every PR until each is clean on the current head:
- Comment exactly
@codex review. - Comment exactly
@codex security review(a separate loop).
- Re-trigger both after every push — a clean verdict only counts for the head it names.
- A connector comment saying "Something went wrong" / "Unknown error" is an errored round: it never produces a verdict. Re-trigger that loop immediately.
- When scripting a watcher: the clean-verdict phrase and the
Reviewed commit: <sha>line live on different lines of one comment — match per-comment, not per-line. Paginate comment/review fetches, and derivecreated_atcutoffs from the trigger comment's actual timestamp.
Merging a release-please PR (chore(<component>): release X.Y.Z) tags
<component>-vX.Y.Z and publishes to npm via OIDC trusted publishing
(.github/workflows/release-please.yml). No manual publishing; release-please
owns CHANGELOG.md (excluded from oxfmt) — never hand-edit it.
- Release-please ignores
packages/*/examplesbecause examples are unpublished, so commits that only touch a package's examples do not cut a release for it.
Known quirks when several release PRs are open:
- Merging one release PR makes release-please force-refresh the sibling release PR branches, which silently dequeues them from the merge queue (no error; the PR still shows clean/open). Enqueue release PRs serially and re-verify/re-enqueue after each merge.
- A refreshed branch resets CI, and its runs can stick at
action_required(workflow-approval gate) indefinitely. Checkgh run list --branch <branch>and approve viagh api -X POST repos/{owner}/{repo}/actions/runs/<id>/approve. gh pr checks --watcherrors with "no checks reported" until CI starts on the refreshed head — poll with a grace loop until checks appear.- A release tagged on GitHub but never published to npm is recovered via
workflow_dispatchonrelease-please.ymlwith the release tag.
Deliberate design — do not "fix" these. The authoritative protocol docs are
the doc comments in packages/agent/src/coder/ws.ts (stream, replay, redial)
and packages/agent/src/model/translate.ts (dedup, revision reconciliation);
unit tests under packages/agent/test/unit/ pin the behavior.
- The
after_idcursor refers only to committedmessageids.message_partdeltas carry no message id and replay fromseq1 on every (re)connection, so a cursor can never advance past a delta — and redials deliberately reuse the turn's original cursor (an advanced one would drop same-id revision snapshots). - On the per-chat
/stream(streamChatEvents), an unparseable frame is terminal by design: a redial replays from the original cursor and would deliver the same frame forever. The non-replaying global/watchredials past malformed frames instead. TurnTranslatorkeeps a per-message emitted-content ledger to reconcile replays and revisions; consumers must tolerate repeatedmessagesnapshots.
- Lean on Diátaxis (tutorial / how-to / reference / explanation) where it fits.
- README code snippets must typecheck. Package tsconfigs include
examples/: assemble the snippets into a scratch file there, runpnpm --filter <pkg> typecheck, then delete the scratch file. - Platform-behavior claims in docs state their verification tier in the PR body: verified against source, verified live against a deployment, or upstream-docs-only.