kew delegate — Assign-to-kew delegation watcher¶
kew delegate lets a human hand off a Linear issue to kew by assigning it to the kew
seat (or adding a configured label). kew delegate watch runs a foreground loop that
detects the assignment, claims the issue, and dispatches it as a one-node ephemeral epic
— reusing the same claim → dispatch → close path as kew epic run.
Status — supervised use is safe; unattended use is close. As of #966 the delegation reservation lifecycle is sound (terminal outcomes release their reservation, so the fleet no longer deadlocks after
[parallel] max_workerscompletions), and #971 hardens the refusal path for long unattended runs: kew comments once per refusal episode (not on every cooldown expiry), a failed unclaim leaves a reconcile retry surface instead of stranding the item, and an expired/revokedLINEAR_API_KEYterminates the watcher loudly (exit2) rather than retrying forever. Runningkew delegate watchunder supervision is safe today; fully unattended operation is pending the remainder of thedelegation-followupsepic (driver-side reservation reconcile and epic-driver lock liveness).--dry-runis always safe.
Refusal-episode comment semantics (#971)¶
When a budget/preflight pre-check refuses a delegated issue, kew writes a terminal
refusal row and posts one Linear comment naming the cap that bound and the knob
to raise. While the issue stays over budget the watcher keeps re-surfacing it (its
trigger label survives the unclaim), but kew comments only once per episode: a
repeat refusal updates state silently until the episode ends (a successful dispatch
or any non-refusal outcome resets it). Raise the named budget.*_usd limit and the
next re-claim proceeds. See [delegation].refusal_cooldown_s.
Assign means go¶
There is no confirm step. The moment an issue is assigned to the kew seat (or carries the trigger label) and the watcher sees it, kew claims it and spends money on a contained agent run. Nothing asks you first — that is the point of the feature.
The guards are the budget caps, not a prompt:
[budget].per_issue_usd,daily_usd, andweekly_usdare checked in the claim preflight through the shared resolver (env > DB override >kew.toml> default), exactly as for an epic node. Over cap, kew refuses, comments once naming the cap that bound, and leaves the issue alone.[parallel].max_workersis the shared fleet ceiling — delegated work andkew epic run --alldraw from the same pool, so delegation cannot outrun it.- The breaker and sandbox containment apply unchanged.
Set those caps to numbers you are willing to lose before you assign anything to
kew, and use --dry-run first if you want to see what would be picked up.
Requirements¶
[tracker].type = "linear"— delegation is a Linear-only feature.[delegation].assigneeset to the kew Linear member name — the watcher resolves this to a member ID at startup and refuses to start if it is blank.LINEAR_API_KEYenvironment variable set to a Linear personal API key.
Setting up the kew member seat¶
kew is a real Linear member seat, not an integration or an OAuth app — its identity is a personal API key belonging to that seat. Single-tenant and firm-operated: one seat, one key.
- Invite a seat with a plus-addressed email. Most mail providers (Gmail among
them) deliver
you+kew@gmail.comtoyou@gmail.com, so you can create a second Linear member without a second mailbox. In Linear: Settings → Members → Invite and send the invite toyou+kew@gmail.com. - Accept the invite in an incognito/private window. Your normal browser is already signed in as you; accepting there would just re-authenticate your own account. Open the invite link in an incognito window and complete signup there.
- Set the member's name to
kew(or whatever you put in[delegation].assignee— the watcher resolves that display name to a member ID at startup and refuses to start if it cannot). Keep the two in sync. - Create the API key as kew. Still in the incognito session:
Settings → Security & access → Personal API keys → New key. Export it as
LINEAR_API_KEYwhere you run the watcher. Everything kew does — comments, state transitions, the post-merge close — is attributed to that seat, so the audit trail in Linear reads as "kew did this", not "you did this". - Create the trigger label (
kew:readyby default) in the workspace, if you intend to delegate by label as well as by assignment.
Anyone who can assign an issue to the kew seat — or apply the trigger label — can spend your budget. Treat seat membership and the ability to apply that label as spend permissions — see Assign means go above.
kew delegate watch [--dry-run]¶
Usage: kew delegate watch [OPTIONS]
Options:
--dry-run Log 'would claim / would dispatch' without reserving or spending.
kew delegate watch # foreground watcher — Ctrl-C to stop
kew delegate watch --dry-run # preview mode: no claims, no spend
Starts a foreground polling loop (like kew ingest serve — no daemon, no autostart).
On each tick it reads the local event cursor from the run store (every
[delegation].fast_interval_s seconds) and polls Linear (at most once per
[delegation].poll_interval_s seconds) to find issues that are either assigned to
the kew member or carry the configured delegation label — either trigger alone
surfaces a candidate. For each candidate it:
- Re-verifies against the live Linear API (not a snapshot): the issue must still be assigned to the kew member and still be in a claimable state. A label-only candidate is surfaced by the poll but released here until the issue is also assigned to the kew seat.
- Claims the issue — transitions it to In Progress and reserves a fleet slot.
- Dispatches it as a one-node ephemeral epic under the
"delegation"pseudo-epic, sharing the[parallel] max_workersfleet cap withkew epic run --all.
Ctrl-C shuts down cleanly.
Configuration¶
The [delegation] block in kew.toml:
[tracker]
type = "linear" # delegation is Linear-only
[delegation]
assignee = "kew" # the kew Linear member name (required at watch start)
label = "kew:ready" # Linear label that signals a delegated issue is ready
poll_interval_s = 20 # throttled Linear API poll floor (seconds, >= 1)
fast_interval_s = 2 # local tracker-events read cadence (seconds, >= 1)
refusal_cooldown_s = 300 # skip re-claiming a budget-refused item this long (0 = off)
| Field | Default | Description |
|---|---|---|
assignee |
"" |
The kew Linear member name. Required at watch start. |
label |
"kew:ready" |
Linear label that signals a delegated issue is ready. |
poll_interval_s |
20 |
Throttled Linear API poll floor (seconds). |
fast_interval_s |
2 |
Local tracker-events read cadence (seconds). |
refusal_cooldown_s |
300 |
Skip re-claiming a budget-refused item for this long, damping the refuse→comment loop (0 disables). |
Field-by-field notes live in [delegation] in the
configuration reference. Precedence is the shared resolver's: env > DB override >
kew.toml > default — so a budget override set in the kew serve Budget panel
governs a delegation run exactly as it governs an epic-driver run.
Optional: the webhook tunnel¶
Without a webhook, the watcher's local tracker_events read has nothing new to find
and pickup latency is bounded by [delegation].poll_interval_s (20s by default) —
which is fine, and needs no inbound network at all.
Wiring the Linear webhook receiver makes assignment events land locally within
fast_interval_s instead. It needs a public URL pointing at kew ingest serve, so
in development that means a tunnel. The full recipe — creating the webhook in Linear,
the signing secret, and the cloudflared tunnel — is in
Linear webhooks — dev-tunnel recipe.
Delegation is correct either way: the throttled poll is the floor, the webhook is only an accelerator, and the claim reservation (not the event stream) is what prevents a double dispatch.
Exit codes¶
| Code | Meaning |
|---|---|
0 |
Clean shutdown (Ctrl-C). |
1 |
Misconfiguration (tracker.type not linear, delegation.assignee not set). |
2 |
LINEAR_API_KEY not set at startup, or Linear authentication failed at runtime (expired/revoked key) — the watcher terminates loudly rather than retrying forever (#971). |
See also¶
Configuration — [delegation] — full field reference.
Configuration — override precedence — how the [budget] caps that guard an assign=go dispatch are resolved.
Linear webhooks — the optional receiver + dev-tunnel recipe that shortens pickup latency.
kew epic — the epic driver that delegated work shares its fleet cap and claim/dispatch/close path with.