Linear agent integration: delegate issues to h2#28
Open
vihaanshahh wants to merge 10 commits into
Open
Conversation
Add an outbound, opt-in integration that mirrors an agent's live state to a Linear attachment on a linked issue. Fully side-channel: it observes the existing monitor state machine off the agent's hot path and swallows all Linear errors, so it never affects the agent or the daemon. - internal/linear: self-contained GraphQL client (resolve issue, upsert/ update attachment) + Watcher that maps agent state to a coarse, deduped, rate-capped status. No dependency on the session/monitor packages. - config: top-level `linear.api_token`; per-session `LinearIssue` on RuntimeConfig set via `h2 run --linear LIN-123` (rejected with --pod/--resume). - daemon: one guarded call (startLinearWatcher) wires it in; inert unless an issue is linked and a token is configured. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pivot the Linear integration from outbound attachments to native agent delegation: delegate/@mention an issue to h2 → Linear webhooks h2 → h2 acks, spawns an agent to work it, and reports the result to the agent session. This is Phase 1 (webhook-only, for dev). The transport is abstracted behind an inbound.Source so a hosted outbound-dialed relay can replace it later without touching the service. - internal/linear: OAuth (Bearer) client variant + agent-session mutations (Viewer, CreateAgentActivity for thought/action/response/elicitation/error). - internal/linearagent: AgentSessionEvent types, webhook Source with HMAC signature verification, and the Service (ack within ~10s → spawn via an injected AgentRunner → response on completion). Fully unit-tested with fakes. - cmd: `h2 linear serve` wires config → webhook source → OAuth reporter → a real AgentRunner that spawns an h2 agent, delivers the issue prompt, and detects completion by polling agent state. - config: LinearConfig extended with oauth_token, app_id, inbound (webhook mode/address/path/secret), and agent.role. - docs: plan-linear-agent-delegation.md; attachment plan marked superseded. Known MVP limit: Result() points back to the agent rather than capturing its verbatim final message (deferred to the live-activity/interactivity phases). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ence
Build out the agent-delegation integration through Phases 2-4:
- Phase 2 (live activity): AgentHandle.Activities() streams thoughts/actions
derived from the agent's state transitions; the service forwards each to the
Linear agent session during the run.
- Phase 3 (interactivity): a session map routes follow-up ("prompted") events
to the running agent via Deliver; permission-blocks surface as elicitation
activities so the human is prompted to approve.
- Phase 4 (persistence/cleanup): FileStore persists sessionID->agent so
follow-ups resume after a linear-service restart (DeliverTo by name); removed
the superseded attachment watcher (internal/session/linear.go,
internal/linear/watcher.go) and the now-dead attachment client methods.
The inbound transport stays behind Source so the future relay drops in without
touching the service. All layers unit-tested with fakes; make check passes.
Live smoke test confirms signature verification, ack, spawn, and fail-safe
error handling end to end.
Known limits (documented in the plan): verbatim response capture (currently a
summary pointing back to the agent), per-team role mapping, and the relay
transport remain for a later pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d debug log Two changes to de-risk the first live test: - Prefer Linear's pre-formatted `promptContext` when seeding the spawned agent, falling back to the triggering comment, then issue title/description. - Add `h2 linear serve --debug` which logs each raw inbound webhook payload, so the exact AgentSessionEvent shape can be confirmed against a real delegation and any parsing mismatch fixed immediately. Off by default (payloads may carry issue content). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the relay so other users plug into Linear with a single "Authorize h2" click — no OAuth app creation, no webhook config, no tunnel, and no Linear token on their machine. Keeps the full native agent experience. - internal/linearrelay: the hosted relay server (run via `h2 linear relay`). Single public endpoint for a published OAuth app: handles the OAuth install, holds each workspace's actor=app token, verifies + receives all agent-session webhooks, and routes each event to the paired user's daemon over an HTTP long-poll hub keyed by organizationId. Activity posts are proxied to Linear with the stored token (the token never leaves the relay). - internal/linearagent: RelaySource (outbound long-poll with reconnect/backoff, implements Source) and RelayReporter (posts activities via the relay, implements Reporter). Extracted VerifySignature so the relay edge and the direct webhook share one impl. - cmd: `h2 linear serve` now defaults to relay mode (pairing_token only); webhook mode retained for dev/self-host. New `h2 linear relay` runs the server. - config: inbound.relay_url/pairing_token; linear.relay server block. - linear.Client: Organization() lookup (relay routes webhooks by org id). Transport is HTTP long-poll (zero new deps, firewall-friendly). Full install->webhook->poll->activity flow is unit-tested with fakes; make check passes. Live verification still needs a published Linear app + deployed relay. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make the relay deployable and durable: - Persistence: relay now persists workspace tokens + pairings to StatePath (atomic write, 0600) and reloads them on start, so a restart no longer drops connected workspaces. Tested across a simulated restart. - Env config: `h2 linear relay` reads H2_RELAY_* env vars (BASE_URL, CLIENT_ID, CLIENT_SECRET, WEBHOOK_SECRET, ADDR, STATE_PATH), so a container deploy needs no h2 directory; falls back to the linear.relay config block otherwise. - Deploy scaffolding under deploy/linear-relay/: distroless Dockerfile, docker-compose with a persisted volume, .env.example, and an ops README. Verified live: env-configured relay starts with no h2 dir, /healthz 200, /oauth/authorize redirects to Linear with actor=app + scopes, bad webhook signature 401. make check passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two fixes found while testing the delegation flow end-to-end: - Set Priority "normal" on the delivered message. Without it the message had an empty priority and was never delivered to the agent (manual `h2 send`, which defaults to "normal", worked — the auto-delivery path did not). - Wait for the agent to reach idle (TUI booted, ready for input) before delivering, instead of sending as soon as the socket opens, which raced the harness's startup screens and dropped the task. Verified: a simulated delegation now spawns an agent and the task is delivered and completed with no manual intervention. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tuned from the live delegation run, where the agent finishing its turn took ~9s+ to be detected and reported back: - completion detection: 3s poll x 3 idle confirmations (~9s) -> 2s poll x 2 (~4s), still long enough to ride out brief idle between tool calls. - spawn-time task delivery: poll for the agent's idle/ready state every 500ms instead of 1s, so the task lands as soon as the TUI is ready. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a native Linear agent integration: delegate (or @mention) an issue to
h2in Linear, and h2 spawns an agent to work it in your repo, streaming progress back to the issue's agent-session activity feed. Same UX model as Cursor/Devin/Charlie.What it does
actor=app); delegate an issue →AgentSessionEventwebhook → h2 acks (thought), spawns an agent, streamsaction/thoughtactivities, and posts a finalresponse.elicitation. Sessions persist so follow-ups survive a restart.Architecture
internal/linear— GraphQL client (OAuth + agent-session activity mutations).internal/linearagent—Source/Reporterinterfaces, the agent service (ack → spawn → stream → respond), webhook source (HMAC-verified), and a persistent session map.internal/linearrelay— optional hosted relay for one-click plug-and-play install: single public endpoint that does the OAuth install, holds each workspace's token, and routes webhooks to each user's local daemon over an outbound long-poll (no inbound port/tunnel; token never leaves the relay).deploy/linear-relay/has a Dockerfile + compose.h2 linear serve(relay or webhook mode) andh2 linear relay.Two transports
pairing_token.Testing
make checkpasses.Deferred (documented in the plan)
🤖 Generated with Claude Code