Goal
A working headless engine loop against a real provider (Anthropic), with the tool trait,
config loading, and permission service it depends on.
Scope
Tooltrait + config loading/schema- Engine loop: text → tool call → final text, with a doom-loop guard and retry scaffolding
- Permission service (auto-approve stub frontend for headless use)
- read, write, bash, glob, grep tools
- edit tool: opencode's replacer chain ported verbatim, including its test table
- Anthropic SSE codec + provider, registry wiring
harness-appcomposition rootharness run -p "<prompt>"debug command
Acceptance criteria
- Integration test: a scripted
MockProviderdoes text → tool call (read) → final text with
correct parts persisted. harness run -p "<prompt>"works against real Anthropic.
M2 — TUI
Goal
A ratatui-based terminal UI driving the M1 engine loop interactively.
Scope
EngineHandlebridging the async engine to the render loop- Event loop + chat viewport, with a markdown renderer and a render cache
- Input handling
- Permission modal wired to the real oneshot ask path
- Session picker / resume
- Abort (cancel an in-flight turn)
TestBackendsnapshot tests: empty session, tool cards (running/completed/error),
permission modal, session picker, user/assistant messages
Acceptance criteria
- Manually drive a multi-turn session with permission-gated edits.
TestBackendsnapshot tests pass.
Goal
Round out provider support (OpenAI, Copilot) alongside Anthropic, with auth, cost tracking,
and retry/doom-loop hardening.
Scope
- OpenAI chat + responses codecs, provider, registry wiring
auth.jsoncredential storage- Copilot: device-flow login modal, token exchange with direct-Bearer fallback,
/modelsrouting across the three codecs - Model pricing + per-step cost accumulation
- models.dev metadata catalog + cost display wiring in the TUI
- Retry policy + doom-loop guard (landed in M1's engine loop, exercised fully once all
three providers exist)
Acceptance criteria
- The same session works on all three providers.
- Device-flow login completes inside the TUI.
- Costs shown.
Top risk carried into this milestone
Copilot token exchange variance — opencode ships direct-Bearer, but classic Copilot API wants
the copilot_internal/v2/token exchange. Mitigated with a dual-path token.rs; verify against
the live API early.
Goal
Let a session spawn and track subagents, foreground or background, with their own
permission scope.
Scope
- Agent registry: bundled markdown agent definitions (designer, explorer, fixer,
librarian, oracle, orchestrator) with override support - Permission intersection: a spawned subagent's effective rules are the parent's
narrowed by its own - JobBoard: persistence + context injection into the parent's next turn
tasktool + subagent spawner: aliases/reuse, depth limits- Subagent context-file reporting to the job board
- Jobs pane, subtask drill-in, reminders for long-running background jobs
- Dedicated opencode (Zen) provider
Acceptance criteria
- An orchestrator session spawns a background explorer; the board appears in next-turn
context (verify via request logging). - Reuse by alias continues the same child session.
Goal
Extend tool access beyond the built-ins: external MCP servers, language-server diagnostics,
and user-authored commands/skills.
Scope
- LSP pool + diagnostics:
harness-core::lspseam trait,harness-lsppool (lazily
spawns one server per language, only when its binary is onPATH), surfaced in
edit/write/bash/glob/grep output viaharness-tools::diagnostics - rmcp stdio client + namespaced tool adapters, wired through
harness-app - Skills:
SKILL.mdfrontmatter/body parsing + skill tool, advertised in the system prompt - Slash commands: markdown command files under
command/*.md, TUI input handling for
/mycommand
Acceptance criteria
- A config-declared MCP server's tool is callable with ask-permission.
- Editing a broken
.rsfile surfaces rust-analyzer errors in the tool result. /mycommandworks.
M6 — Polish
Goal
Make long sessions durable and the TUI pleasant to actually live in.
Scope — done
- Auto-compaction:
Compactor+ overflow trigger, threaded through the engine loop - Session-title generation via a configured
small_model - Crash-resume hardening: an unfinished assistant message left by a killed process is
marked aborted on load instead of replayed - Theming —
theme: Option<String>exists inconfig::schemabut isn't read anywhere in
harness-tui - Syntax-highlighted code blocks in the markdown renderer
- Streaming-render perf pass (incremental layout / tail-only re-render beyond the
existing markdown render cache from M2)
Acceptance criteria
- A session driven past the context limit compacts via
Compactorand continues correctly,
with prior turns summarized rather than dropped or truncated mid-message. - After a new session's first turn, its title reflects a
small_model-generated summary
instead of the default placeholder. - Resuming a session whose process was killed mid-turn shows the last assistant message as
aborted, and does not replay or re-send it to the provider. - Setting
themein config produces a visibly different TUI color scheme; switching between
at least two configured themes changes the rendered output. - Fenced code blocks in assistant messages render with syntax highlighting matching the
fence's declared language. - Streaming a long assistant response renders incrementally without visible input lag or
full-viewport re-layout on each token.