Adr 018 linear agent session interaction
ADR-018: Linear agent-session as a future interaction channel
Section titled “ADR-018: Linear agent-session as a future interaction channel”Status: proposed Date: 2026-06-17
Context
Section titled “Context”ABCA’s Linear integration today triggers and reports work through a hand-rolled comment protocol layered on Linear’s generic Issue/Comment webhooks:
- Trigger — a string match on
@bgagentin aCommentwebhook body (parseCommentTrigger), plus a label-add on an issue to seed a sub-issue orchestration. - Acknowledgement — emoji reactions managed by hand (👀 on receipt →
✅/❌ on settle via
swapCommentReaction/swapIssueReaction), threaded replies (replyToComment), and a single maturing “epic panel” comment edited in place (upsertEpicPanel).
This protocol works and is now well-tested, but the comment seam has been
the single richest source of edge-case bugs:
reply issueId vs parentId rules, “parent comment must be top-level”
threading, webhook-redelivery reply spam, self-trigger loops from our own
@bgagent example text, and reaction/state flapping. Each was a
consequence of bolting an agent protocol onto a human-comment surface.
Linear now ships a first-class Agents API (agent-session model):
delegate or @mention an installed agent app → a typed AgentSessionEvent
webhook (created/prompted) → the agent emits typed activities
(thought / action / response / elicitation / error) and Linear
derives a native session state (pending/active/awaitingInput/
error/complete/stale) with a built-in “thinking”/activity UI.
Two facts establish the starting point:
- The auth migration is already done. ABCA’s OAuth flow
(
cli/src/linear-oauth.ts) requestsread write app:assignable app:mentionablewithactor=app. Verified against a deployed dev stack: every deployed workspace token carries exactly that scope. bgagent is already installed as an app actor — it is assignable, mentionable, and delegatable today. No auth work is needed to adopt agent sessions. - Linear is an interaction layer, not compute. Adopting agent sessions changes how we are triggered and how status is shown. All compute (clone, run the coding agent, build/test, open the PR) still runs on ABCA’s own AgentCore Runtime + ECS. The switch offloads nothing to Linear and does not change the AWS architecture or cost model.
Decision
Section titled “Decision”Adopt the Linear agent-session model as an ADDITIONAL, flag-gated trigger/ack channel once Linear marks the Agents API GA — not now, and not as a replacement for the comment path.
The orchestration engine is channel-agnostic by design (its trigger-agnostic seams): graph discovery, the reconciler, the epic panel/rollup, base-branch stacking, and the cascade do not care how a task was triggered. Agent sessions slot in as a new front end to that engine, mapping cleanly onto what we already built:
| ABCA today (hand-rolled) | Linear agent-session (native) |
|---|---|
@bgagent string match in comment | created AgentSessionEvent (mention/delegate) |
| 👀 reaction “on it” | thought activity |
| 🤖 Starting / 🔗 PR opened | action activity (+ result) |
| ✅ Updated / completion | response activity |
| ❌ failure reply | error activity |
| ”reply with guidance” retry | elicitation + prompted webhook + conversation history |
| panel header state (🔄/✅/⚠️) | session state (active/complete/error) |
Preview-API spike
Section titled “Preview-API spike”A time-boxed, no-infra spike validated the API surface against the deployed
app-actor token (the bgagent app in a real workspace) — read-only schema
probes + mutation input validation, no migration code:
- API reachable by our token. Introspection confirms
agentActivityCreate,agentSessionCreateOnIssue/OnComment/Create,AgentSession(fields incl.status,issue,comment,appUser), andAgentActivityType=thought, action, response, elicitation, error, prompt— exactly the docs. - Activity input shape verified callable.
agentActivityCreate(input: {agentSessionId, content: JSONObject, signal, ephemeral})accepts our{type:'thought', body}content — a call failed only on session-id lookup, not schema/enablement, so the ack-emission half of the loop is proven. - BLOCKER (config, not code):
agentSessionCreateOnIssuereturns"Agent sessions are not enabled for this application."The bgagent OAuth app has the scopes +actor=appbut has not been enabled as an agent in its Linear Application settings. Per docs, enabling = edit the app at Settings → API → Applications, enable webhooks, and select the “Agent session events” category. App-owner action; no waitlist mentioned. - The 10s-ack-vs-long-compute risk is therefore NOT yet proven end-to-end —
it needs a real
agentSessionId, which is gated on the enablement toggle above. The pieces it depends on (immediatethoughtack, then lateraction/responseactivities) are individually confirmed callable; the remaining unknown is purely whether Linear marks the session unresponsive if our spawn exceeds 10s after the initialthought(docs say thethoughtack within 10s is sufficient, which our processor can emit synchronously before the async spawn — same shape as today’s 👀).
Net (first pass): the spike de-risked reachability + the activity model and pinpointed the single enablement step, without committing to migration.
Spike re-run, after the app owner enabled “Agent session events” — the core risk is RESOLVED end-to-end:
agentSessionCreateOnIssuenow succeeds → sessionstatus: active.- The 10s-vs-long-compute question is answered: emit a
thoughtat t+0 (statusactive), then wait 14s with no further activity → session staysactive(not stale/unresponsive). The 10s rule governs only the initial ack; once athoughtlands, an arbitrarily long gap before the next activity is fine. ABCA’s webhook can emit thethoughtsynchronously (exactly like today’s 👀) and let the >10s async spawn proceed — no architectural conflict. - Full lifecycle derives correctly, matching the mapping table below:
thought→active,action→active,action+result→active,response→complete; on a second sessionelicitation→awaitingInput,error→error. All five emittable types accepted; states auto-derive from the last activity. (AgentActivityContentis a union —AgentActivityActionContent/…ElicitationContent/…ErrorContent/etc. — so each type persists as a distinct typed record.)
Conclusion: the trigger/ack half is fully validated against the live Preview API. The remaining gate for an actual additive channel is unchanged — it’s the per-issue-session vs. cross-issue-epic-rollup gap (engine stays ours) plus the Preview→GA stability wait, NOT any technical blocker we found. The spike issues were created + deleted; no migration code written.
⚠️ The enablement toggle is NOT a side-effect-free no-op. Leaving “Agent session events” ON after the spike means every
@bgagentmention now also spawns a native agent session that Linear expects answered viaagentActivityCreatewithin 10s. Our deployed code answers on the comment path (👀 + reply) and emits no session activity, so the session gets zero activities, goesstale, and Linear surfaces a misleading “bgagent did not respond” banner — even though the comment reply posted fine (observed in live use: reply at t+2s, sessionstale, activities[]). Consequence for phasing: adoption is not “additive alongside the comment path for free” — once the toggle is on, mentions route to sessions and the adapter MUST emit activities or every mention looks dead. So the toggle stays OFF until the flag-gated adapter (Phase 2 below) ships in the same change that flips it. Interim action after the spike: turn the toggle off (app owner, Settings → API → Applications).
Why a channel, not a rewrite
Section titled “Why a channel, not a rewrite”- The win is real but partial: agent sessions retire the brittle trigger + per-comment ack seam (the bug class above), but Linear agent sessions are per-issue delegations with no native cross-issue epic rollup. The parent-epic panel, fan-out integration node, dependency cascade, and base-branch stacking stay ABCA’s responsibility either way — so roughly half of the recent bug classes (panel settle, cross-issue concurrency) are unaffected by the migration.
- The Agents API is a Developer Preview (confirmed against
developers.linear.app): “in active development… may change before GA.” Ripping out a working, now-hardened comment path to depend on an unstable API is the wrong trade today. - Treating it as an additive channel behind a flag (per ADR-006) lets us reuse the channel-agnostic engine, run both paths side by side during evaluation, and revert via the flag if the Preview API shifts.
Consequences
Section titled “Consequences”- Positive: removes the highest-friction seam (string-match trigger + hand-rolled threading/reactions); native progress UI; conversation-history retry replaces our bespoke loop; no auth work (already app-actor).
- Negative / risk: Preview API churn; hard runtime constraints (webhook
receiver must return within ~5s; an activity or external URL must be
emitted within ~10s of
createdor the session is marked unresponsive) — ABCA’s task spawn is async and slower than 10s, so thecreatedhandler must emit an immediatethoughtack and hand off, exactly as the current processor 👀s then spawns. - No-op surfaces: the orchestration engine, panel/rollup renderer, reconciler, cascade, and base-branch logic are untouched by this decision.
Phasing
Section titled “Phasing”- Now (this ADR): record the decision; auth verified; do not build. Keep the hardened comment path as the sole Linear interaction channel.
- When Linear GAs the Agents API: spike a flag-gated
agent-sessiontrigger/ack adapter behind the existing channel-agnostic engine —created→seed/iterate, activities↔our ack states — running in parallel with the comment path on a dev stack. - After evaluation: if the native path is strictly better, default the
flag on and deprecate the
@bgagentstring-match trigger; keep the panel/rollup engine.
Out of scope (this ADR)
Section titled “Out of scope (this ADR)”- Any implementation. This is a direction + go/no-go record only.
- Changes to the orchestration engine, OAuth/token storage (done, ADR-016 governs pluggable identity), or the Slack/Jira channels.
References
Section titled “References”cli/src/linear-oauth.ts—actor=app,app:assignable/app:mentionablecdk/src/handlers/linear-webhook-processor.ts— current comment trigger + acks- ADR-006 (feature flags), ADR-015 (Jira integration), ADR-016 (pluggable identity and auth)
- Linear Agents API —
https://linear.app/developers/agents,https://linear.app/developers/agent-interaction(Developer Preview)