Files
byre/docs/plan.md
T

507 lines
30 KiB
Markdown

# byre — Plan
A self-hosted Go server on a home Mac Studio that manages third-party coding agents (Claude Code, Codex) across projects and tasks, exposed to a SPA over Tailscale. Built for its author first; multi-user and multi-device from the start.
## Working agreement
This plan reflects decisions already agreed. Do not deviate from it without asking first. If an approach turns out not to work, a decision needs revisiting, or something requires reverting work or a destructive action, stop and ask rather than choosing an alternative. Items under "Open items" are not yet decided; raise them when they become blocking.
`AGENTS.md` holds the standing rules for working on byre (deploying before handoff, UI layout and look, web code). `docs/operations.md` describes how the built system works and runs. "Build sequence" below says what is built.
## Goals
- Manage many concurrent agent sessions across projects (repos) and tasks (worktrees).
- Spend human attention efficiently: an inbox of structured questions and reports, reviews that explain changes visually, and diffs as the least-encouraged way to understand work.
- Support customizable workflows (for example plan → build → eval) with per-stage vendors, models, and review types.
- Snapshot everything so work can be scrubbed through, forked, and compared.
- Keep the history of why work was done attached to the merges it produced.
- API-first so a future native macOS app is a second client, not a rewrite.
## Non-goals (for now)
- Mobile UI.
- Pull requests. Merges are local, then pushed.
- Our own agent. We orchestrate third-party agents only.
- Public internet exposure (no Tailscale Funnel, no cloud relay).
- Generic agent-to-agent wiring. Agent collaboration happens through workflow stages and the specific patterns below.
- General DAG workflow engine.
## Future features
- Humans editing code directly in task worktrees (open-in-editor, rules for hand edits while agents run).
- Mobile UI.
- Claim/assign ownership of inbox items.
---
## Architecture
### Server
- Go, single binary, run under launchd on the Mac Studio.
- Binds to `127.0.0.1` only. Fronted by `tailscale serve` on its own HTTPS port of the machine's `*.ts.net` name (not a path prefix, which breaks the SPA's absolute paths), with a valid certificate. Clients get HTTP/2 and SSE passes through unbuffered.
- SPA served from `embed.FS` in release builds and `os.DirFS` behind a dev flag. Same handler either way.
- Storage: SQLite via `modernc.org/sqlite` (pure Go, no CGO), WAL mode.
### Auth and identity
- Identity comes from Tailscale's injected headers (`Tailscale-User-Login`, `Tailscale-User-Name`). Trust them only on requests arriving from loopback via the serve proxy.
- Multi-user is a real requirement. Every mutating action records the acting user.
- Users are created on first request. Each sets the git name and email used for commit authorship and co-author trailers through the API (`PUT /api/me`); agents can't start until it is set.
### API
- REST, OpenAPI spec-first (`api/openapi.yaml`). Go server types and routing are generated with `oapi-codegen`; the SPA's TypeScript types are generated with `openapi-typescript` and used through a small typed client. A Swift client can be generated later for the macOS app.
- Real-time state: one multiplexed SSE stream per client. Every event carries a monotonic sequence number; clients resume with `Last-Event-ID`.
- Terminals are the only WebSocket use: one WebSocket per open terminal.
### Notifications
- Web push via a service worker (possible because of the real `ts.net` certificate).
- Notify on blocked inbox items first; other kinds are opt-in.
- Per-user preferences; collapse bursts into one notification.
### Server observability
- Agent process liveness; detection of stuck and zombie processes.
- Adapter and protocol-parse errors.
- Snapshot counts and disk usage per task.
- Concurrency queue state and vendor rate-limit status.
- Exposed in an admin view in the SPA.
### Concurrency and usage
- Global and per-vendor caps on concurrently running agent turns (idle sessions don't count); excess turns queue.
- Usage (cost, duration, turns) is recorded per turn in the event log and available through the API per task, workflow stage, and vendor. The UI never shows it.
- Vendor rate-limit errors surface as their own inbox state rather than as generic failures.
---
## Agent integration
### Adapters and the event model
Vendor specifics live behind adapters. The event model is a **shared core plus vendor-specific extensions**:
- Core event kinds: message, tool call, tool result, file change, question, turn end, error.
- Vendor-specific event types for features that don't map cleanly, rendered by vendor-specific UI components.
**Interface coverage.** The SPA covers the vendor capabilities this workflow actually uses, each added as a specific feature when it is needed, rather than mirroring the native interface wholesale: interrupts, model switching, compaction, and image input. Compaction is byre's own and vendor-agnostic, not the vendor's: the agent writes a handoff (the same contents as an `export_context` bundle) and the work continues in a new session that starts from it. Subagent views and Mermaid rendering come later, once agents use them. Not planned: generic slash-command passthrough (a command worth having gets its own UI) and plan mode (planning is a workflow stage).
**Claude.** Implement the Claude Code CLI's stream-json control protocol natively in Go, reverse-engineered from the open-source Agent SDKs (TypeScript and Python). These SDKs spawn the `claude` CLI and exchange stream-json over stdio with a bidirectional control channel. Required capabilities: start session, stream events, send messages, interrupt, resume by session ID, fork session, register hooks (at minimum Stop and PostToolUse), attach MCP servers.
**Codex.** Implement a JSON-RPC client for `codex app-server`. Generate Go types from the protocol schema the CLI can emit. Required capabilities: start thread, stream events, send turns, interrupt, resume. Fork support needs verification.
**Protocol stability.** Neither protocol is a guaranteed public contract.
- Disable CLI auto-updates, so the version changes only when the owner updates the CLI. The version is not pinned.
- Maintain a contract test suite built from recorded transcripts; run it after updating the CLI.
- Fallback if Claude protocol drift becomes painful: a thin Node sidecar wrapping the official SDK, speaking our protocol to Go. Do not adopt the fallback without asking.
### Session configuration
- Permission posture: fully permissive ("yolo"). Agents act freely inside their worktree; humans review afterwards.
- Agents don't commit. byre commits everything in the worktree to the task branch at the end of every turn that changed files, authored as the session's creator, with the first sentence of the turn's summary as the message. Turns are the unit the human reviews, and the merge cycle squashes the branch anyway, so these commits only mark turn boundaries for diffing and restore. For Claude, also disable co-author attribution via Claude Code's settings for the session, since attribution is a default behavior.
- Instruct agents not to push, not to touch other branches, and not to run git maintenance commands (see "Known hazards").
- Every session is launched with our MCP server attached.
### Lifecycle
- Agent processes are children of the server. On server restart they are killed.
- On restart, sessions that were active are resumed by vendor session ID. The server then injects a message: the agent was interrupted mid-turn and must re-check the working tree before continuing.
### MCP tools
The Go server hosts an MCP server attached to every agent session.
- `ask_human(context, question, why_blocked, options[], recommended_option, attachments[], blocking, thread_id?)`
- `context` carries the background the human needs to answer without reading the transcript.
- Non-blocking at the protocol level: returns immediately with "question filed."
- `blocking: true` means the agent should end its turn; `false` means it continues with unblocked work.
- The human's answer arrives as a structured next turn.
- `thread_id` continues an existing inbox thread.
- `request_review(scope, concerns[], profile?)`
- `export_context(purpose)` → a handoff bundle (see "Handoff bundles").
- `spawn_stages(stages[])` and `collect(stage_ids[])` for just-in-time fan-out and fan-in (see "Workflows").
`attachments` share one schema across tools, inbox replies, and reviews: file and line-range references, diff references, images, diagrams (Mermaid source), links to tasks or snapshots, and URLs.
Validation: reject malformed calls with a corrective message (for example, an `ask_human` with no options or no `why_blocked`).
**Turn summaries.** There is no report tool. Each turn's final message is its summary: session guidance asks agents to end every turn with one covering what they did, every departure from the plan, risks, and what's next, and byre files it to the inbox as a report. A dedicated report tool was tried and dropped: models wrote the closing recap anyway, even when the tool's result, the system prompt and a per-turn reminder all said not to, so the human read everything twice.
Questions asked in plain prose (a final message that asks something without an `ask_human` call) are bounced by a Stop hook that sends the agent back to ask properly, once per turn. Detection is a sentence ending in `?` outside code, quotes and blockquotes; expect to tune it, since overly strict detection produces nudge loops and overly lenient lets prose questions through.
---
## Data model
```
User (tailscale_login, display_name, git_name, git_email,
notification_prefs)
Project (id, name, repo_path, default_branch, remote, setup_config,
issue_tracker_config)
Workflow (id, name, version, definition) -- server-side, exportable
Task (id, project_id, title, branch, worktree_path, base_ref,
status, workflow_id, workflow_snapshot, created_by,
created_at, closed_at, merge_commit)
TaskIssueLink (task_id, tracker, issue_ref)
Stage (id, task_id, parent_stage_id, def_id, role, vendor, model,
state, origin) -- origin: workflow | jit
AgentSession (id, task_id, stage_id, vendor, model, vendor_session_id,
parent_session_id, forked_from_snapshot_id, state, role)
Artifact (id, task_id, stage_id, kind, version, payload, created_by)
-- plan, eval-report, handoff, ...
Event (seq, agent_session_id, task_id, project_id, kind,
payload, created_at) -- append-only
Snapshot (id, task_id, agent_session_id, n, commit_sha, tree_sha,
event_seq, trigger, created_at)
InboxItem (id, project_id, task_id, agent_session_id, thread_id, kind,
priority, blocking, payload, snapshot_id, review_id, state,
snoozed_until)
InboxResponse (id, inbox_item_id, user_id, type, text, attachments,
created_at) -- type: option | other | snooze | dismiss
Review (id, task_id, snapshot_id, tree_sha, profile, reviewer_vendor,
trigger, state, summary, highlights, attachments, stale)
ReviewProfile (id, name, prompt, vendor, model, output_schema, capture_config)
Merge (id, task_id, cycle, commit_sha, merged_by, co_authors,
note_ref, pushed_at)
Blob (id, content_type, path) -- screenshots, recordings, etc.
Terminal (id, task_id, cwd, created_by, state)
Preview (id, task_id, port, url, process_state)
Participation (task_id, user_id, kind, since_merge_cycle) -- for co-author trailers
```
Notes:
- `AgentSession.role` distinguishes implementers from planners, reviewers, evaluators, merge resolvers, judges, and triage agents.
- `Event` is the source of truth for the SSE stream and for scrubbing; `seq` is global and monotonic.
- `Task.workflow_snapshot` freezes the workflow definition at intake, so later edits to a workflow don't change running tasks.
- Browser connections are ephemeral and are not part of the persistent model.
---
## Workflows
### Model
A workflow is a sequence of **stages**. Each stage has:
- a role prompt template,
- a vendor and model,
- typed artifact inputs and outputs,
- an optional gate: human approval (via the inbox), a review pass, or an eval pass,
- optional review profiles to run during the stage.
Engine scope: linear stages, gates, bounded loops (`on_fail: goto … max … then escalate`), and fan-out with fan-in. No general DAGs until a real workflow needs one.
Example:
```yaml
name: plan-build-eval
stages:
- id: plan
agent: { vendor: claude, model: opus }
prompt: templates/plan.md
outputs: [plan]
gate: human # approve / edit / redirect via inbox
- id: build
agent: { vendor: codex }
inputs: [plan]
reviews: [plan-conformance, security]
- id: eval
agent: { vendor: claude }
inputs: [plan, diff]
outputs: [eval-report]
on_fail: { goto: build, max: 3, then: escalate }
```
### Storage
- Workflow definitions, prompt templates, and review profiles live on the server, not in repos.
- They are portable: export and import as files.
### Everything is swappable
Templates and roles are starting points. At intake and at any point mid-task, a human can swap a stage's vendor, model, prompt, or review profiles, and edit the task's frozen workflow (add, remove, or reorder stages).
### Just-in-time fan-out
Agents can construct workflows on the fly via `spawn_stages` and `collect`:
- Spawned stages use the same stage definition format.
- Limits: maximum depth, maximum concurrent spawned stages, and per-task usage budget, all configurable.
- An optional human gate on spawning, configured per workflow.
- Spawned stages are visible in the task's UI tree with `origin: jit`.
- A just-in-time structure can be saved as a reusable workflow template.
### Stage patterns
The earlier "agent features" are expressed as stage patterns:
1. **Cross-vendor review:** a review profile whose vendor differs from the implementer's.
2. **Merge resolution:** a stage triggered when merging the default branch into a task branch conflicts; its result goes to review.
3. **Fork-and-compare:** a fan-out stage (N forks with different vendors or prompts from one snapshot) followed by a judge stage that produces a comparison with visuals.
4. **Inbox triage:** an agent answers questions resolvable from the repo or docs and escalates the rest with its reasoning attached. Tune on real inbox data.
Build them in that order.
### Plan artifact
Standard schema:
- goal
- approach
- steps
- acceptance criteria
- risks
- open questions
Rules:
- Versioned. Humans edit through the inbox; agents propose edits, which go to the inbox for approval.
- Stored on the server and attached to merges via git notes. Not committed to the repo.
- Build agents state every departure from the plan in their turn summaries.
- Eval stages report pass or fail per acceptance criterion.
- The plan-conformance review profile compares the diff against the plan.
### Task intake
1. Pick a workflow.
2. Fill its inputs: a brief, attachments, linked GitHub issues, handoff bundles from other tasks.
3. Optionally override vendors, models, prompts, or review profiles per stage.
---
## Handoff bundles
Produced by `export_context(purpose)`:
- goal
- decisions made and why
- current state
- dead ends tried
- open questions
- relevant files
Used for:
- cross-vendor forks (the forked agent receives exactly what it needs),
- stage-to-stage handoff in workflows,
- the durable record of a task.
---
## Git mechanics
### Worktrees
- One worktree and one branch per task, created from the project's default branch.
- Per-project setup config: setup script (dependency install, env file copying) and port allocation so parallel dev servers don't collide.
- The repo's original checkout is used only by the server, as the integration checkout for merges.
### Diffs and stats (never touching the real index)
Compute "as if everything were staged" without forcing staging:
```
GIT_INDEX_FILE=$tmp git read-tree HEAD
GIT_INDEX_FILE=$tmp git add -A # respects .gitignore, includes untracked
GIT_INDEX_FILE=$tmp git write-tree # → tree sha
```
Diff and stats are computed between the resulting tree and the merge-base with the default branch.
### Snapshots
- Built with the same temp-index technique, then `git commit-tree` on the resulting tree.
- Stored under hidden refs: `refs/byre/tasks/<task-id>/snap/<n>`. Git deduplicates blobs, so frequent snapshots are cheap.
- A snapshot pairs the code commit with the conversation position (`event_seq`).
- Triggers: after every tool call (PostToolUse hook where available; shell commands can change files, so file-editing tools alone are not enough), at turn end, on `ask_human`, and on a timer as a backstop. Skip if the tree sha is unchanged.
- Retention: keep everything until task completion.
### Scrubbing and forking
- Timeline UI over a task's snapshots, showing the diff at each point alongside the transcript up to that event.
- Fork: a new worktree from a snapshot commit plus a new AgentSession.
- Same vendor: native session fork/resume.
- Different vendor: a handoff bundle from `export_context`. The UI labels it as a handoff, not a true fork.
### Merge cycle
A task can go through many merge cycles before completion. Merges happen only at turn boundaries; partially done work is not merged. Each cycle:
1. Wait for the task's implementer to reach a turn boundary, then pause it.
2. In the task worktree, merge the default branch into the task branch. The merge-resolution stage resolves conflicts; review runs on the result.
3. Human confirms. In the integration checkout, run `git merge --squash <task-branch>` and commit:
- Author: the human who confirmed the merge.
- Co-author trailers: other humans who participated in the task since the last merge cycle (resolved inbox items, messaged agents, triggered forks).
- No agent attribution.
- Message: drafted by an agent from the task's turn summaries since the last merge; the human edits before confirming.
4. Attach a git note to the squash commit (see "History").
5. Push the default branch and the notes ref. If rejected because the remote moved, fetch and repeat from step 2.
6. Reset the task branch to the new default-branch head. Required because squash commits make agent commits non-ancestors of the default branch; without the reset, later cycles accumulate spurious conflicts. Pre-squash history (the per-turn commits) remains available via snapshot refs.
7. Tell the agent explicitly that its branch history was squashed and reset, so it doesn't try to repair history. Then resume it. Files on disk are unchanged by the reset.
### Task completion
An explicit human action, as one of two buttons: **Merge** squashes the task onto the default branch and then removes it; **Discard** removes it without merging, after a confirm that states the commits and changed files that will be lost. Either way the worktree, the task branch, and the task's snapshot refs are removed. Transcripts, artifacts, reviews, and inbox history remain archived in SQLite. Nothing deletes a task branch without one of these two buttons.
---
## History
- On each squash merge, attach a git note under `refs/notes/byre` containing: the plan, the handoff bundle, review summaries, eval results, and resolved inbox decisions. Full transcripts are referenced by task ID.
- Notes don't push or fetch by default; the merge cycle pushes the notes ref explicitly, and setup configures fetching it.
- Archived tasks are searchable across transcripts, artifacts, and decisions.
### Issues
- Issue tracker integration is pluggable; GitHub Issues first.
- Tasks link to issues at intake.
- On merge, post or update a summary comment on linked issues.
---
## Reviews
- Reviews are performed by a separate agent session, never the implementer grading itself. By default the reviewer is a different vendor from the implementer.
- Every review targets a specific snapshot and is cached by tree sha. When the task's tree moves on, existing reviews are marked stale.
- Review types are named **review profiles** (security, performance, privacy, visual UX, plan conformance, and so on), each with a prompt, vendor, model, output schema, and capture config. Stages reference profiles by name.
- Triggers:
- On `ask_human`: the inbox item appears immediately; the review attaches when it completes. If the item is resolved first, the review still attaches to its history.
- Before merge.
- On demand.
- Periodic: on whichever comes first of elapsed time or diff size, with per-project overrides. Debounced to turn boundaries or a quiet period so half-applied edits are not reviewed.
### Review output
Structured, leaning visual rather than quoting code:
- Summary.
- Highlights, each with a category, severity, and anchors to files or lines so the UI can jump to them.
- Visual attachments:
- Mermaid diagrams for changed data flow, call graphs, state machines, and schemas.
- Screenshots or recordings of the running result. Per-project config declares how to run and capture: Playwright MCP for web projects; `xcrun simctl io` screenshot/recordVideo for iOS simulator projects.
- Before/after pairs from the base ref and the current snapshot where possible.
### Change view
In increasing order of how much the UI encourages their use:
1. File diffs
2. Diff stats
3. Agent-generated summary
4. Agent highlights with visual explanations
---
## Inbox
### Views and priority
- One underlying query with global, per-project, and per-task views.
- Sources: `ask_human` calls, reports, completed reviews, gate approvals, rate-limit states, sessions that went idle or errored.
- Grouped by task, tasks with a blocked item (an agent is idle waiting on it) first, then by project; within a task, items are in time order.
- Everyone sees every item. Resolutions record who resolved them and are broadcast to all clients over SSE.
### Response types
- **Pick an option**.
- **Other**: the only text field, for an answer outside the options, a question back, or new instructions. If it asks the agent something, the agent answers through a follow-up `ask_human` with the same `thread_id`.
- **Snooze**: hidden until a set time; the agent stays blocked.
- Reports are FYI and take only an OK.
Responses can carry attachments (pasted images and sketches, file and line references, links to tasks or snapshots, URLs). When an agent asks several questions at once, they are answered as one form with one submit.
---
## Other features
### Previews
- Web: the server supervises a per-task dev server and exposes it over Tailscale, preferably as per-task ports through `tailscale serve` (path-prefix proxying breaks apps that use absolute paths).
- iOS: on-demand simulator screenshots and recordings attached to the task; Screen Sharing over Tailscale as the manual fallback.
### Terminals
PTYs via `github.com/creack/pty`, rendered with xterm.js, one WebSocket each. Scoped to a task's worktree by default.
### File browser
Read-only, scoped to project and worktree roots.
### UI
- Two panes, always side by side at exactly half the width each, under one top navbar. Each pane has tabs and can show any view; new views (diff, files, terminal, preview) become tabs, never columns. `AGENTS.md` holds the binding layout and look rules.
- Fast task switching: a ⌘K switcher in the navbar, plus a Tasks view.
- Frameworkless: TypeScript compiled file-by-file to ES modules, no bundler.
- Atkinson Hyperlegible Mono throughout, self-hosted; high-contrast text on a quiet palette.
---
## Known hazards
Issues identified in review that implementation must account for:
- **Shared `.git` across worktrees.** Permissive agents can delete snapshot refs, run `git gc` or `git worktree prune`, check out other branches, or push. Mitigate with instructions, a pre-push hook on the shared repo that blocks pushes not made by the server, and periodic verification that snapshot refs survive.
- **Snapshot cost.** `git add -A` on every tool call is slow in large repos, and non-ignored build output bloats the object store. Nothing is garbage-collectable until task completion.
- **Shared machine resources.** Parallel Xcode builds compete for CPU; per-worktree DerivedData costs disk and cold builds; iOS simulators are shared between tasks and review agents and need allocation or locking.
- **Visual review reliability.** Capturing meaningful screenshots requires building, seeding data, and navigating to the affected screen. Expect flakiness; noisy reviews train users to ignore them.
- **Squash-and-reset under a running agent.** Agents may notice their commits disappearing and try to repair history; step 7 of the merge cycle addresses this and needs testing per vendor.
- **Event log growth.** Stream-json is verbose and tool outputs are large. SSE resume and scrubbing need efficient queries; consider separating large payloads into blobs.
- **Migration numbering across branches.** Migrations are tracked by position (`PRAGMA user_version`). Each task branch appends its own, and byre is deployed from task branches, so two branches can claim the same number: the database records it as applied and the other branch's migration silently never runs. Fix: track applied migrations by name in a table. Before switching, make every existing data migration safe to run twice, since one recorded only by number would run again under its name.
- **Status: fixed.** Migrations are named and tracked in a `migrations` table (see `docs/operations.md`, "Data").
---
## Build sequence
### Spike (first)
**Status: done.** `internal/claude` is the client; `docs/claude-protocol.md` records the protocol; `internal/claude/session_test.go` exercises each capability against the real CLI and records transcripts into `internal/claude/testdata/transcripts/`, with the account email scrubbed. Fork is wired in but untested.
A Go client for the Claude Code CLI's stream-json control protocol that can:
- Start a session, stream events, send a message.
- Interrupt and resume by session ID.
- Register a Stop hook and a PostToolUse hook.
- Launch with co-author attribution disabled and `GIT_AUTHOR_*` environment variables set.
- Attach an MCP server.
Record transcripts from the spike as the seed of the contract test suite.
### Milestones
- **M0 — Skeleton.** launchd service, tailscale serve auth, SQLite schema, OpenAPI spec, projects, tasks, worktree create/delete, event log, SSE stream with resume, embedded SPA shell, basic server observability.
- **Status: done**, and running as a service on the Mac Studio. Observability is exposed through `GET /api/admin/status` only; the SPA has no admin view yet.
- **M1 — One agent end to end (Claude).** Sessions, streaming, messaging, the interface coverage for Claude, the `ask_human` MCP tool, turn summaries, inbox with all views and response types, web push notifications, restart-and-resume, concurrency caps.
- **Done:** sessions, streaming, messaging, interrupt; `ask_human` with validation; each turn's final message filed to the inbox as its summary; inbox with global, project, and task views and every response type, answered as one batch and delivered to the agent as one message; restart-and-resume; global and per-vendor caps with queueing; rate limits as their own inbox state; the web UI (two panes, task switcher, transcript, inbox) with Markdown rendering; the session instructions from "Session configuration", given as an appended system prompt and a per-turn reminder; per-turn commits by byre; bouncing questions asked in plain prose.
- **Not done:** image input (see "Interface coverage"); web push notifications. Compaction is done, as a handoff into a fresh session; model switching is done, per message.
- **Later:** subagent views and Mermaid rendering.
- **M2 — Seeing the work.** Temp-index diffs and stats, snapshots, timeline scrubbing, terminals, file browser, previews.
- **Done:** temp-index diffs and stats, with a Changes view.
- **Not done:** snapshots, timeline scrubbing, terminals, file browser, previews.
- **M3 — Second vendor and review.** Codex adapter with the same interface coverage, review profiles, review triggers, visual review output, cross-vendor review.
- **M4 — Merging and history.** Full merge cycle with squash, authorship and co-author trailers, drafted commit messages, merge-resolution stage, git notes, task completion and archive, archive search, GitHub Issues integration.
- **Done:** Merge and Discard replace "Complete task". Merge squashes the worktree, uncommitted files included, into one commit on the default branch, authored by the merging user, and fast-forwards the project checkout; it doesn't push, and it refuses on conflicts or while a turn is running.
- **Next:** the squash commit message, which is the task title for now. Decided: a one-shot `claude -p` run in the task's worktree writes a one-line summary from the branch's log and diff stat, so Merge becomes a background job that reports back with a `task.merged` event or an error.
- **Not done:** co-author trailers, merge-resolution stage, git notes, push, repeated merge cycles, archive search, GitHub Issues.
- **M5 — Workflows.** Workflow definitions and storage, stages, gates, bounded loops, plan artifact, eval stages, task intake, mid-task swapping.
- **M6 — Fan-out.** `export_context` and handoff bundles, same-vendor forks, cross-vendor handoffs, fork-and-compare, just-in-time fan-out with limits.
- **M7 — Inbox triage.**
---
## Open items
- Verify Codex app-server support for forking threads.
- Confirm `tailscale serve` behavior for exposing per-task preview ports.
- Terminal persistence across server restarts: accept loss, or back terminals with tmux?
- Format of per-project setup config (setup script, port allocation, run/capture config for reviews).
- Simulator allocation strategy for parallel tasks and review agents.
- Periodic review thresholds (defaults for time and diff size).
- Default limits for just-in-time fan-out (depth, concurrency, budget).
- Task navigation: keep both the ⌘K switcher and the Tasks view, or only one.
- Interface coverage still undecided: todo-list rendering and `@`-file mentions.