SOW — Linear as Core Shared Workspace
Authored 2026-07-28 · Owner: Akash · Executor: any Claude session (this doc is self-contained — a fresh session can complete it cold) · Architecture per Nick Saraev's agent-workflow videos (brain video_notes 2547 + 2552)
The decision (Akash, 2026-07-28): Linear becomes the front-of-house shared human+AI workspace — capture, board, agent dispatch visibility, QA, comments. The Postgres brain stays the system of record; Linear is the operating surface. Two-way sync, never a data fork. Linear workspace exists: team "Stpierre", business trial active.
1 · Where the system stands today (all LIVE unless noted)
| Component | State | Where |
Board columns on public.tasks | LIVE flow_status (inbox/next/doing/waiting/done), agent_ready, waiting_on, executor (agent/akash/collins), verdict (keep/park/kill), triage_reason/locked | Postgres brain |
| Dispatch pipe | LIVE trigger → task_dispatch_outbox → n8n reader (5-min cron) → taskos_events | n8n wf ut9OwshB5eSbeCgI |
| Agent runner | LIVE every 30 min, ≤3 events/run, kill-gate first, consumes task_activity context as binding brief, model-tiered subagents, parks at waiting — never self-marks done, never runs gated writes | scheduled task agent-board-runner; spec pair: 01-Projects/St_Pierre_Digital/agent-board-rail/runner.md = ~/.claude/scheduled-tasks/agent-board-runner/SKILL.md (edit BOTH together) |
| Kanban v2 board | LIVE = the board Collins sees | team.stpierre.ai/builderpipeline + preview stpierre-builder-kanban-v2.pages.dev; renderer 01-Projects/St_Pierre_Digital/builder-kanban-v2/render_board.py; 06:45 chain re-renders daily |
| Morning QA surface | LIVE QA queue + decisions + human lanes + aging; three moves per card | stpierre-morning-qa.pages.dev; 01-Projects/St_Pierre_Digital/morning-qa/; CLIs ~/bin/verdict (approve/redo/kill) + capture context <id> "..." |
| Capture | LIVE host CLI ~/bin/capture; iOS n8n webhook wkkBq03HfcscTD4n ACTIVE (capture + context routes, X-Capture-Secret header, secret in vault) | .codex/references/capture/README.md (has iPhone Shortcut recipe + Mac Shortcut click-path — Akash's 2-min setup still pending) |
| Card activity trail | LIVE public.task_activity (plan/progress/context/verdict rows) | brain; rendered on both boards |
| Evals | LIVE canon retry loop (check_and_iterate.py + gap checks v2 at warn severity) · akash-scorecard (8×0–2, any-zero fails) · 80/20 kill gate · Build Contract gate | .codex/references/creative-canon/ · .codex/references/evals/ |
| Backlog state | APPLIED 1,226 keep / 605 park / 9 soft-kills / 1,777 legacy executor-labeled (labels only, NOT queued) / 190 stale parked as archive candidates / 15 PROPOSED-KILL cards in QA lane | undo files: 01-Projects/St_Pierre_Digital/builder-kanban-v2/undo/ |
| Cost routing + learned-rules + contract gates | LIVE in AGENTS.md + .codex/references/cost-routing.md | ledgers 91839–44 |
| Slack waiting-on-Akash ping | GATED n8n wf VRx1w1HUvcfjMj2X inactive — needs Akash to vault a working Slack token | ops_alerts → 09:00 briefing covers the gap |
| Linear scaffolding | BUILDING NOW (background agent): inactive n8n Linear-webhook receiver + linear_sync.py (bootstrap/webhook/push/comment/import functions) + activation runbook | 01-Projects/St_Pierre_Digital/linear-workspace/ — check its README for final ids |
Ledger trail for the whole build: change_ledger 91805–91854 (+ scaffolding rows after). Every write has reverse_how or an undo file.
2 · Target architecture — Linear front-of-house, brain as record
Flow: Capture (Linear hotkey C / mobile app / our CLIs) → issue in team Stpierre → status Next + label agent-ready → Linear webhook → n8n receiver → upsert public.tasks → existing outbox/dispatch/runner (unchanged) → runner mirrors back: plan/progress as Linear comments, state moves (Doing → Waiting), label waiting-on-akash → Akash QAs in Linear (or Morning QA page) → approve/redo/kill flows back through the sync.
Mapping contract:
| Linear | Brain (public.tasks) |
| Workflow states Inbox / Next / Doing / Waiting / Done | flow_status inbox / next / doing / waiting / done |
Label agent-ready | agent_ready=true |
Label waiting-on-akash | flow_status='waiting' + waiting_on='akash' |
Labels human-only / collins | executor='akash' / 'collins' |
| Issue identifier (e.g. STP-42) | source='linear', source_ref=identifier |
| Comments | task_activity rows (context/plan/progress mirror both ways) |
| Issue description | description (DoD) + triage_reason |
Anti-loop rule: the sync skips webhook events authored by the API-key user that match its own last write. Brain remains authoritative on verdicts and ledger; Linear is authoritative on nothing — it is a view + input surface.
3 · AKASH — your manual steps (the only human-gated part)
On the settings page you screenshotted (Settings → Administration → API):
- Leave "API key creation: All members" as-is. Nothing to change on this page itself.
- Do NOT create an OAuth application — that's Phase 2 (a named agent identity like "Nick OS"); we start with your profile like Nick does ("I'm cheap" mode).
- Do NOT click New webhook manually — the sync creates it via API with a signing secret already vaulted. (Manual fallback exists in the linear-workspace README if API creation hits a permission wall.)
- The key itself is NOT made on this page: click "security & access settings" (linked in the Member API keys paragraph, or sidebar Personal → Security & access) → Personal API keys → New API key.
- Label it
stpierre-agent-rail. If asked for scopes/teams: full read/write, team Stpierre (or all).
- Copy the key (starts
lin_api_) and paste it into any Claude session. Tokenmaster auto-vaults it as service='linear', scope='stpierre', label='api-key'. That paste is the trigger for the whole activation runbook below.
Also still pending from before (unrelated to Linear): the 2-minute Mac Shortcut for capture (recipe in
.codex/references/capture/README.md) — though once Linear is live, Linear's own
C hotkey + mobile app may make it redundant. Decide after Phase 3.
4 · Execution phases (any session can run these in order)
| # | Phase | What | Gate |
| P0 | Key in vault | Akash pastes the Linear API key (step 3 above) → verify with brain.get_credential(service='linear', scope='stpierre', label='api-key') | Akash |
| P1 | Bootstrap workspace | python3 linear_sync.py bootstrap — creates the 5 states + 4 labels on team Stpierre, idempotent. Verify by listing states via API. | auto once P0 |
| P2 | Webhook live | Activate the inactive n8n Linear receiver, then linear_sync.py create-webhook. Test: create a throwaway Linear issue, move it to Next + agent-ready, confirm the tasks row appears, delete both. | auto once P1 |
| P3 | Import curated cards | linear_sync.py import-curated — ONLY board cards (source='builder-board-v1' OR flow_status set, verdict≠kill, ≈90). NEVER the 1,857 backlog — Linear stays curated; the lake stays in Postgres. | auto once P2 |
| P4 | Runner mirror | Runner posts plan/progress/waiting as Linear comments + state changes (spec: "Linear mirror" section of agent-board-rail.md). Edit runner.md AND the scheduled-task SKILL.md together — they are a pair. | 1 approval |
| P5 | QA in Linear | Akash's approve/redo/kill via moving cards / labels in Linear flows back through the webhook (verdict semantics preserved: approve→Next+agent-ready, kill→cancel, redo→Next + comment as context). Morning QA page stays as the batched daily view until Linear views fully replace it — retire/merge is Akash's call per the reporting-surfaces rule. | Akash tries it |
| P6 | Capture cutover decision | Compare: Linear C-hotkey/mobile/Action-button vs our CLI+webhook. Keep both or retire ours. Also decide: point the 06:45 kanban render at Linear data or retire the HTML board (Collins's view!). | Akash |
| P7 | Agent identity (optional) | OAuth app w/ agent capability → a named agent actor ("SP Agent") instead of acting as Akash. Nick-parity vanity; do only if attribution matters. | Akash |
5 · Session learnings a fresh session must know (hard-won today)
- Runner spec is a PAIR:
runner.md and ~/.claude/scheduled-tasks/agent-board-runner/SKILL.md must be edited together; two agents editing concurrently nearly forked them today.
- taskos_events.notify_status has a CHECK (pending/sent/suppressed only) — runner claim state lives in
agent_runner_claims, don't try to add states.
- curl against our CF Pages domains can 403 (bot challenge) — a "failed" readback may be a false negative; verify browser-grade before declaring a deploy dead.
- CF Pages deploys need
--branch=main — bare deploys hit a preview branch and custom domains never update.
- Scheduled tasks are capped at daily without Akash's explicit override — the runner's */30 IS his override (ledger 91819); don't "normalize" it back.
- Purge classifier lesson: remediation work mentioning a dead system (e.g. "migrate off Pipeboard") pattern-matches as killable — the remediation-verb guard in purge_proposal.py exists for this; keep it when extending.
- 15 PROPOSED-KILL cards + 190 archive candidates + TIMEOUT-POLICY.md draft are sitting in Morning QA awaiting Akash verdicts — a Linear migration must not orphan them.
- Slack pings need a token only Akash can vault; ops_alerts→09:00 briefing is the working alert path meanwhile.
- Sandbox python has no
requests; use curl subprocess (house pattern). Host python for DB shims = /opt/homebrew/bin/python3.12, never system 3.9.
- Nothing client-facing activates without Akash — the whole rail runs draft-first: agents park, humans approve. Preserve this in every Linear-side automation.
6 · Link registry
- Rail contract:
.codex/references/agent-board-rail.md · runner dir 01-Projects/St_Pierre_Digital/agent-board-rail/
- Kanban v2:
01-Projects/St_Pierre_Digital/builder-kanban-v2/ · live team.stpierre.ai/builderpipeline
- Morning QA:
01-Projects/St_Pierre_Digital/morning-qa/ · stpierre-morning-qa.pages.dev
- Linear scaffolding:
01-Projects/St_Pierre_Digital/linear-workspace/ (README = activation runbook)
- Capture:
.codex/references/capture/ · context trail: 01-Projects/St_Pierre_Digital/card-context-trail/
- Evals:
.codex/references/evals/ (akash-scorecard, eighty-twenty-gate + Build Contract) · cost routing: .codex/references/cost-routing.md
- Canon:
.codex/references/creative-canon/ (check_and_iterate + gap checks + GAP-CHECKS-AUDIT.md)
- n8n: dispatch
ut9OwshB5eSbeCgI · iOS capture/context wkkBq03HfcscTD4n (active) · Slack ping VRx1w1HUvcfjMj2X (inactive) · Linear receiver: see linear-workspace README
- Source videos in brain: video_notes 2547 (workflow) + 2552 (2-hr course, incl. 62-item prescriptions checklist)
- Memory:
agent-board-dispatch-rail.md in Claude auto-memory — the running state-of-the-rail note
Definition of done for this SOW: P0–P5 complete and verified (a Linear card moved to Next by Akash produces agent work that returns as a Linear comment + Waiting state, and his approve in Linear re-queues it) · the curated ~90 cards live in Linear · no orphaned QA items · capture + board retirement decisions (P6) explicitly made by Akash, not defaulted.