Agent: coding
Takes a task from Slack, scopes fast, implements the change in its sandbox, pushes the branch, and submits a typed PR description — OpenSwitchboard renders the body and opens the PR itself (pr-description.md item 5). Its unit of delivery is a pushed branch plus a validated PrDescription, not prose; the PR URL comes from code.
- Code:
src/agents/registry.ts(CODING_SYSTEM; resident-path variantCODING_SYSTEM_RESIDENT) - Docs: The agents and their toolsets
- Budgets: 60 turns / 45 min / 64k tokens · no built-in effort — set it per deployment/channel/user/thread/message through the config layers (routing-and-config.md item 2; e.g.
config set channel efforts.coding=medium— the wall clock is the real budget, and a 31-min live run at the model's default effort spent 97 % of it thinking between one-line greps) · toolsetfull
Behavior
- Scope first, hard rule (≤5 tool calls): identify the target repo and surface; genuinely ambiguous → ask ONE question and stop the turn. Never clones multiple repos or maps the org to avoid asking.
- Workflow: get the repo in place (cold path: clone, ≤1 repo/task; resident path: the warm worktree is already attached — see 7) → branch → implement matching surrounding style → run quick tests/linters if present → commit → push the branch → submit the typed description (see 3). OpenSwitchboard renders the PR body at the pushed head and opens (or updates) the PR itself; the agent never opens a PR — with
ghor any API call — and both prompts carry an explicit never-merge/never-approve instruction mirroring the review prompt's (a write-scoped token puts a merge one command away). - Typed PR description by default: every PR ships from a
PrDescriptionobject submitted throughsubmit_pr_description(pr-description.md) — the user never has to ask for review guidance. Right after implementing, the agent understands the change better than anyone, so it brings that context forward. The prompt's content contract (PR_DESCRIPTION_TEMPLATEinregistry.ts) maps every rendered section to its object field — the renderer owns headings and layout, the agent never authors PR-body markdown:title(the PR title's single source),tldr(two sentences for a naive reader),whatWhy(linked to the triggering issue/request), the Tour (tour+remaining, below),decisions(alternatives/trade-offs),risks(blast radius, migration/rollout/compat, or "none" and why),validation(what was tested + actual results). Rules baked into the prompt: default for EVERY PR (not on request) — including a PR that already exists when the run pushes, whoever opened it (a person, dependabot, an earlier run): after every push the agent reads the PR's current title and body (github_issue_gettakes a PR number;gh pr viewwhere gh exists), judges them against the change at the pushed head, and resubmits the object describing the PR as it now is, carrying forward what is still true and anchoring the Tour at the new head — an out-of-date description is a bug and "someone else's PR" is never a reason to leave it (enforced, not only stated: a run that pushed onto an open PR without resubmitting gets one bounded description turn asking for it, and a push still without one is flagged by the post-step — pr-description.md item 5); prose unwrapped (no hard line breaks); the triggering issue/request always hyperlinked; validation states exactly what was run, never fabricated; concise, not padded. The resident variant's diff digest informs the object's content (which files the Tour must walk, what belongs in risks) rather than pasted markdown.- The Tour (supersedes the prose Changes and How to review sections): a guided, hunk-anchored walkthrough of the change, reader-first — ordered steps of
{ title, description, lookFor?, anchor }, each anchor a(path, from, to)line range at the pushed head, with every uncovered file inremaining. The sha is a render-time input the SYSTEM supplies, so the regenerate-on-every-repush rule is code: after a later push the agent resubmits with corrected anchors and the body is re-rendered at the new head. The craft — reading order, the reader-first step shape, the anchor rules (≲25 lines each, never written from memory — each range located withgrep -nand verified so the embed shows exactly the code the prose describes), theRemaining changescatch-all — lives in the first-partypr-tourskill (skills.md item 11,skills/pr-tour/SKILL.md); the prompt keeps the field and instructs loading that skill withuse_skillBEFORE authoring the steps, so the rules live in one place and every coding run's Tour load is a visibleskill_useevent. The author writes it right after implementing, so it costs no extra model call and carries the most context anyone has. Modeled on Ramp Inspect's hunk-anchored Tour; the rendering is GitHub's own.
- The Tour (supersedes the prose Changes and How to review sections): a guided, hunk-anchored walkthrough of the change, reader-first — ordered steps of
- Reads task context itself (
gh issue view,gh pr view) — requires the GitHub App's Issues permission (see execution.md §5). - Maintains the status-card checklist (outcomes, never commands; nothing pre-marked).
- Reports faithfully: failing tests and skipped steps are stated plainly; the final message leads with the outcome, and OpenSwitchboard appends the PR link when it opens (or updates) the PR. A run that pushed but submitted no description is reported honestly by the dispatcher instead of with a claimed PR: when the pushed branch already heads an open PR (a follow-up that repushed an existing PR's own branch) the reply names that PR as updated by the push, otherwise it carries the branch compare URL (pr-description.md item 5).
- Resident-path variant (resident-repos.md §31): when the run lands in a resident repo environment, the dispatcher swaps in
CODING_SYSTEM_RESIDENTviaRunOptions.system(the AgentDef is never mutated). It replaces scope-first/clone with: the workspace is a READY git worktree on the thread's bound branch, deps installed, build warm — no cloning, no installing, no repo discovery.ghis not in the resident image: push with git (the repo-scoped token in.git/github-credentials; an auth-refused push is said plainly, never retried blind), calldiff_digestto shape the description, then submit it — the bot opens the PR, so the prompt carries no PR-creation API call at all.
Roadmap (gaps)
[gap](pr-description.md) The panel renderer. The description-as-data pipeline is live (items 2/3 above: the agent submitssubmit_pr_description, the system renders at the pushed head and opens/edits the PR, the object rides the run stream as apr_descriptionevent); what remains of "rendered per surface" is the run page's review panel rendering the same object — tracked as the panel-renderer gap in pr-description.md.
Validation criteria
| Criterion | Proof |
|---|---|
| Budgets and toolset as specified; no built-in effort | [unit] src/agents/registry.test.ts::coding: full toolset, 60 turns, 45 min, no built-in effort (config layers decide); budget mechanics in src/runner.test.ts. |
| Resident variant: ready worktree, no clone/install/gh instructions; push-then-submit, PR creation not its job; fallback prompt unchanged | [unit] src/agents/registry.test.ts::resident prompt variants (incl. ::coding variant pushes the branch and submits the description; PR creation is not its job); selection wiring in src/core/dispatcher.test.ts::repo/ref resolution + resident prompt selection …. |
Push then submit, never open/merge/approve (both prompts): the branch is pushed, the description submitted through submit_pr_description, the PR is never opened by the agent, the curl POST /pulls step is gone, and never-merge/never-approve is explicit | [unit] src/agents/registry.test.ts::coding prompts: push then submit_pr_description (opening the PR is the system's job) (4: ::both coding prompts instruct pushing the branch, then submitting the typed description, ::neither coding prompt tells the agent to open the PR itself, ::the resident prompt no longer carries the curl POST /pulls instruction, ::both coding prompts forbid merging and approving (mirrors the review prompts' wording)). |
| Typed description by default (both prompts): every rendered section mapped to its object field, no markdown-body authoring instructions, honesty rules stated | [unit] src/agents/registry.test.ts::coding prompts: the PR-description content contract (submitted object) (::both coding prompts map every rendered section to its object field, ::no markdown-body authoring instructions remain (the renderer owns headings and layout), ::both prompts state the rules that keep the description honest, ::both prompts require re-reading and resubmitting the description after every push to an existing PR, whoever opened it). [agent] agent:coding fix <issue URL> with NO description ask → the bot-opened PR's body has every section, the issue hyperlinked, and validation matching what actually ran. |
Tour supersedes prose Changes / How to review; the prompt mandates loading pr-tour before authoring the steps and does not duplicate the craft; the skill pins the step shape, anchor rules and catch-all; the repush rule is code (resubmit → re-render at the new head) | [unit] src/agents/registry.test.ts::…::the Tour supersedes the prose Changes / How-to-review sections, ::the Tour field requires loading the pr-tour skill before authoring its steps; src/skills/prTourSkill.test.ts (7, incl. anchor verification + embed conditions); render-at-head in src/core/dispatcher.test.ts::description submitted + head observed → the PR opens from typed values…. [agent] open a PR via agent:coding; the run page shows a 📚 skill pr-tour row before the description is submitted; the body arrives Tour-shaped with every link's sha equal to gh pr view --json headRefOid, each rendering as an embedded code block. Then a follow-up in the same thread that changes code → after the repush every Tour sha equals the NEW head. |
| End-to-end: issue → PR | [agent] @switchboard agent:coding investigate and fix <issue URL> on a small real issue. Expect: it reads the issue itself (no 403, no asking you to paste it), one repo cloned (cold path) / attached to the warm resident worktree (resident path), a bot-opened PR whose diff addresses the issue, final message = outcome with the PR link appended by OpenSwitchboard. |
| Ambiguity → one question, then stop | [agent] Give a task naming no repo with several plausible candidates; expect exactly one clarifying question and a stopped turn — not a survey of the org. |
| Honest failure reporting | [agent] Ask for a change in a repo whose tests are broken at HEAD; the final message must state the failure plainly rather than claim success. |
| No placeholder narration | [agent] Replies must never contain bracketed placeholders (e.g. "[title and details from the issue]") in place of real command output. A follow-up mis-routed to general produces exactly this; guarded by thread stickiness (routing-and-config.md). |