specs/018-orchestrator/plan.md
Implementation plan: task orchestration
Branch: orch/tasks | Date: 2026-10-04 | Spec: spec.md
Input: spec.md + research-codebase.md + research-external.md +
research-practices.md (these three ARE the Phase 0 research; no unknowns
remain open).
Summary
Add a persisted Task entity with an A2A-aligned lifecycle to the server, plus
the four orchestrator primitives around it: async start (sync cap → base →
worktree → spawn returning pending, server-owned background ready + gated
submit), gated prompt submit (herdr-style), cursor-based rendered reads (fixing
tail garbling at the root), turn-end-without-report → input_required,
structured result reports, and explicit merge/discard finish against the recorded
target branch (workmux rules). CLI parity
for all of it; minimal GUI row surfacing. MCP server, parent-inbox injection,
cost budgets, and kanban are deferred with in-spec reasons.
Technical Context
Language/Version: Rust, edition 2021, minimum 1.92 (dev toolchain pinned in
rust-toolchain.toml)
Primary Dependencies: tokio, serde/serde_json, gtk4/libadwaita/vte (GUI
only), vt100, portable-pty, git CLI subprocesses
Storage: snapshot.json (+ tasks with serde defaults), audit.jsonl
journal, per-pane runtime scrollback ring (not persisted)
Testing: cargo test --workspace; server integration tests via
signaltty-testkit (ephemeral socket + state dir); GUI headless unit tests +
ignored display tests under xvfb
Target Platform: Linux (Ubuntu 26.04 reference)
Project Type: Native desktop app (server + GTK GUI + CLI)
Performance Goals: Submit gate + stall detection within documented budgets
(300 ms paste delay, 5 s activity gate); reads bounded (≤ 5000 lines/reply);
scrollback ring 5000 lines/pane
Constraints: One writer per state dir; per-path git lock; zero new clippy
warnings; snapshot backward-compatible (serde defaults)
Scale/Scope: One PR; default max 4 parallel tasks; attention listing
default limit 50
Constitution Check
- I. Spec-driven: spec → plan → tasks before code; no
[NEEDS CLARIFICATION]. - II. Product bar: attention-first listing (directive 1), inline approvals
reused not rebuilt (2), hooks-first + screen fallback (3), server owns tasks
- versioned socket/CLI/
--json+ replay (4), agents-as-API-clients viatask.*/report/wait(5), no new GUI views so no rendering-language risk (6), row chip via libadwaita patterns (7). No deviations.
- versioned socket/CLI/
- III. Skills: verify-signaltty skill governs delivery; 016 is the format/quality reference.
- IV. Test-first: E2E acceptance test written first (fails), then per-story red-green; integration tests for every new IPC method; GUI headless tests.
- V. Deep modules:
Task/transitions pure insignaltty-core; typedparams.rsdecode;Storetransition methods pair mutate+emit; GUI reconcile untouched structurally (row text only). - VI. Decisions on record: ADR-0020 + rows in docs
02/08for model/methods. - VII. Simplicity: no inbox machine, no MCP server, no evaluator, no profiles, no kanban in this PR (all deferred in-spec); passive reads (no alt-screen refusal); branch kept by default.
GATE: PASS. Re-check after design: no new violations introduced below.
Round-1 re-check (async start, input_required-on-turn-end, data-dir worktrees, recorded merge target, split test files, skill-embedded orchestrator loop): still PASS — no new views, no inbox machine, no MCP server; the background step is server-owned plumbing inside the existing task primitive, and the recorded target only constrains the existing finish path.
Project Structure
Documentation (this feature)
| 1 | specs/018-orchestrator/ |
| 2 | ├── plan.md # This file |
| 3 | ├── research-codebase.md # Phase 0: what signaltty has (kept as-is) |
| 4 | ├── research-external.md # Phase 0: how others do it (kept as-is) |
| 5 | ├── research-practices.md# Phase 0: 2026 best practices (kept as-is) |
| 6 | ├── data-model.md # Phase 1: Task/Result/Contract/Pane additions |
| 7 | ├── quickstart.md # Phase 1: E2E validation guide |
| 8 | ├── contracts/ipc.md # Phase 1: methods/events/codes/CLI/MCP map |
| 9 | ├── tasks.md # Phase 2: dependency-ordered TDD tasks |
| 10 | └── checklists/requirements.md |
Source Code (repository root)
| 1 | crates/signaltty-core/src/ # model.rs (Task/Result/Contract), state.rs (TaskState), |
| 2 | # paths.rs (data_dir for worktree root) |
| 3 | crates/signaltty-proto/src/ # lib.rs (method/event/code constants) |
| 4 | crates/signaltty-term/src/ # headless.rs (scrollback + rendered ring + content_seq) |
| 5 | crates/signaltty-server/src/ # store.rs, persist.rs, router.rs, params.rs, |
| 6 | # pty.rs (env), git.rs, file_diff.rs, worktrees.rs, config.rs |
| 7 | crates/signaltty-server/tests/ # orchestrator_{e2e,submit,read,tasks,finish}.rs (NEW) |
| 8 | crates/signaltty-testkit/src/ # lib.rs (temp-repo + hook-driver fixtures) |
| 9 | crates/signaltty-cli/src/ # main.rs (task/*, report, submit, attention) |
| 10 | crates/signaltty-cli/assets/ # SKILL.md (orchestrator loop worked example) |
| 11 | crates/signaltty-gui/src/ # sidebar.rs (row chip), actor.rs (task.* events) |
| 12 | docs/adr/0020-task-orchestration.md |
| 13 | docs/{02-data-model,08-ipc}.md # rows for Task + methods (implementers update) |
Structure Decision: No new crates. Task logic lives in existing layers (core pure → server owns → CLI/GUI are clients), per ADR-0004 and the harmony rule that only the GUI uses gtk/vte.
Interfaces and ownership
- Core owns
Task,TaskState, transition validation (pure, unit-tested); serverStoreowns task storage + transition methods that pair mutation, emit, and persistence marking. Snapshot.tasksserde-defaulted;persist::applyrestores tasks, fails non-terminal tasks with dead panes (evidence, once), and failspendingtasks whose background ready-wait died with the server (stage: "restart").- Router owns
task.*/pane.submit/attention.pendinghandlers with typed params; thetask.startbackground ready + submit step is server-owned (survives client disconnect; cancelled by cancel/discard/shutdown) and mutates tasks only viaStoretransition methods; git helpers own all subprocess calls (porcelain only, bounded deadlines,LC_ALL=C; never English-stderr matching). - Term crate owns the rendered-line ring +
content_seq;pane.readmodes all derive from it. - CLI is a thin wrapper (
--jsonpassthrough); GUI subscribestask.*and renders label + state on existing rows. - E2E + failure-path tests own a temp-repo fixture and a synthetic-hook driver in testkit; the native-permission fixture pattern is reused for the block-and-answer leg.
Complexity Tracking
No constitution violations to justify. The largest surface (finish safety rules) is specified behavior copied from workmux/t3code, not new complexity.
Validation
E2E scenario green (spec §Deterministic…); six failure-path tests green;
scripts/verify.sh full; light + dark GUI screenshots; independent diff
review before PR.