How it works
Two mechanics carry the example:
- The issue binding. Both triage triggers route
bindon thegithub.issuetarget withonUnmatched: spawn: the first event on an issue spawns a session already bound to it, and a repeat event on an issue whose triage session is still live delivers into that session instead of spawning a duplicate. - Agent-to-agent handoff. Triage calls the
auto.sessions.spawntool withagent: shepherdand a message carrying the issue number, URL, triage summary, acceptance criteria, and constraints. Shepherd receives the full brief in the spawn message, so this dispatch path needs no duplicate issue trigger.
auto-triage label is a one-shot request token, not a standing subscription: triage removes it once it has acted, so re-applying the label is how humans request another pass.
Install from the managed template
Issue Triage and Shepherd are separate retained roles in@auto/agents:
.auto/agents/issue-triage.yaml
.auto/agents/shepherd.yaml
@auto/agents@latest/issue-triage-linear.yaml. The catalog installer automatically adds this optional Linear reporting overlay to the separately installed Shepherd; add the same overlay to a hand-authored facade:
.auto/agents/shepherd.yaml (Linear overlay)
auto.sessions.spawn, so Shepherd needs the Linear chat tool but no duplicate Linear event trigger. Merge to your production branch for GitHub Sync to apply.
The full configuration
.auto/fragments/environments/agent-runtime.yaml
.auto/agents/issue-triage.yaml
.auto/agents/shepherd.yaml
issues: write and nothing else (pullRequests: none), while Shepherd gets contents: write and pullRequests: write but is only ever started with a brief that triage already validated. Filtering both GitHub triggers on $.github.auto.authored: false keeps issues opened by auto’s own GitHub App from re-triggering triage in a loop.
Plain-issue comment events use the
github.issue.comment.* keys;
github.issue_comment.* is GitHub’s PR-comment family. And on GitHub events
the authored flag lives at $.github.auto.authored — $.auto.authored is the
chat-event path. See GitHub events.Walkthrough
1
An issue opens
github.issue.opened arrives; issue-opened matches. No session is bound
to this issue, so onUnmatched: spawn starts a triage session bound to the
github.issue target.2
Triage acts on the issue
The session inspects the issue with
issue_read, checks for duplicates,
sets the most specific existing labels and a priority with issue_write (it
never creates labels), and posts one comment explaining what it did — or
asking for reproduction steps and acceptance criteria when the report is too
thin.3
A human requests a re-pass
Later, someone adds the
auto-triage label. github.issue.labeled with
$.github.label.name: auto-triage matches; the bind route delivers into
the still-live triage session, or spawns a fresh one bound to the issue if
the original is gone. Triage acts, then removes the label — consuming the
token.4
Handoff to Shepherd
When the issue is implementation-ready, triage comments handoff context on
the issue, removes the label, and calls
auto.sessions.spawn with agent: shepherd and the full brief. The tool returns the new session (with its
URL), so the handoff is traceable from either session.5
Shepherd ships a PR
The Shepherd session boots with the repo mounted at
main, creates
auto/issue-123-…, works test-first, pushes, opens a PR with a Review Map
via create_pull_request, and closes the loop with add_issue_comment: PR
link, tests run, residual risks. If a code review
agent is installed, it picks the PR up from
github.pull_request.opened.Linear variant
Teams that track work in Linear import the-linear entrypoints. The shape changes in three ways: the trigger source becomes linear.issue.* events, the label is the only request token (there is no every-new-issue trigger), and issue metadata is read and mutated through the unified chat tool’s chat.issue.get / chat.issue.update instead of the GitHub tool.
.auto/agents/issue-triage.yaml
linear.updatedFrom.labelNames.added carries exactly the labels the update added:
{{linear.issue.identifier}}, {{linear.issue.title}}, {{linear.issue.url}}. Shepherd still needs the GitHub App mount to open PRs; it reports back on the Linear issue via chat.send with target provider linear. See Linear events.
Variations
- Slack visibility. The managed template’s base entrypoints already carry
optional: trueSlack wiring — an ad-hoc@mentiontrigger on each agent plus triage’s ready-work notes in#dev— which apply skips while noslackconnection exists and activates on the next apply once one does. The handwritten configuration above keeps triage’s chat tool but trims the mention triggers; copy thementiontrigger from the code review example to restore them. - Direct coding handoffs. For “just fix it” requests on issues (a human mentions the handoff agent in an issue comment), the handoff example is purpose-built: it keeps the issue binding, opens a PR, binds it as
github.pull_request, and reports back on the issue. - Raise or lower the handoff bar. The definition of “implementation-ready” lives entirely in the triage
systemPrompt. Tighten it (require acceptance criteria and a reproduction) for riskier codebases; loosen it for internal tools. - Guard the spend. Add
spendCapstoshepherd(for examplespendCaps: { maxPerSessionUsd: "10" }) so a runaway implementation session self-limits. - Smoke test. After apply, open a test issue and confirm a triage session spawns and comments. Then label an implementation-ready issue
auto-triageand watch Shepherd open a PR. You can also start either agent directly from the web app with a message like “Triage issue #42”.