Commit Graph
12 Commits
Author SHA1 Message Date
darman a930537207 WIP 2026-07-09 07:25:14 +02:00
darmanandClaude Opus 4.8 54f91b7e4c harness-tools + app: subagent context-file reporting to the job board (M4)
A ContextReporter wired into subagent sessions lets the read tool advertise the
files it reads (≥10 lines) on its job board entry, so a completed specialist
shows what it already looked at. Root sessions report nothing (no parent board).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 06:34:04 +02:00
darmanandClaude Opus 4.8 ecb3267a50 harness-tools + app: task tool, subagent spawner, board wiring (M4)
The `task` tool delegates to a SubagentSpawner (owned by the composition root):
resolves the agent, enforces the depth limit, applies permission intersection,
filters tools per-agent, and runs the child session foreground or background.
Foreground returns the child's final text; background registers on the job board
and detaches under the parent run token. The run loop injects the board into
primary-agent requests and reconciles terminal jobs each step.

Integration tests cover foreground run + alias reuse continuing the same child
session, depth-limit and unknown-agent rejection, and board prompt injection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 06:29:12 +02:00
darmanandClaude Opus 4.8 77873ccb69 harness-core: agent registry + bundled markdown agents (M4)
Layered agent definitions (bundled -> global -> project -> config patch),
opencode-compatible markdown frontmatter, generated {{SUBAGENTS}} routing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 06:05:53 +02:00
darmanandClaude Opus 4.8 9301d387fa harness-providers: models.dev catalog + cost display wiring (M3)
- modelsdev.rs: ModelCatalog parses models.dev api.json → ModelInfo keyed by
  (provider, model); 24h file cache at ~/.cache/ai-harness/models.json with a
  baked assets/models-snapshot.json fallback so cost/limits work offline.
  load_cached_or_baked (no network) + refresh/refresh_default_cache (background).
- App: loads the catalog at init (cached-or-baked, never blocks), warms the cache
  in a background task, and passes the session model's pricing into RunConfig.cost
  so session cost accrues for real.
- TUI: AppState tracks session_cost/session_tokens from Session events; status bar
  shows "<tokens> · $<cost>". Snapshots updated.
- 7 modelsdev tests (parse, defaults, unknown-model, baked snapshot, cache TTL,
  cached-vs-baked load).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 23:51:23 +02:00
darmanandClaude Opus 4.8 39d62348d8 harness-providers: auth.json credential storage (M3)
- auth.rs: AuthRecord (OAuth {access, refresh, expires} | Api {key}) with
  is_expired(now, skew) honoring expires==0 = never; AuthStorage keyed by
  provider id over ~/.local/share/ai-harness/auth.json.
- Read-modify-write on every op so concurrent refresh/login don't clobber;
  writes go through a temp-file rename set to 0600 (unix) to avoid truncated
  auth files.
- 6 tempdir tests: missing→empty, set/get/overwrite, multi-provider coexist,
  scoped remove (+ no-op on absent), expiry skew/never, 0600 perms.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 23:37:34 +02:00
darman 0812f6d94c M2 TUI: EngineHandle, ratatui app, markdown renderer, permission modal, session picker, snapshot tests
harness-app:
- EngineHandle: non-blocking multi-turn API (prompt/abort/permission_reply/
  list_sessions/session_messages/message_parts), persistent SQLite store,
  no auto-approve — TUI handles permission asks via real oneshot path
- App refactored to wrap EngineHandle; headless run -p keeps auto-approve

harness-tui:
- Terminal guard (raw mode, alternate screen, panic hook, Drop restore)
- Event loop: tokio::select! over crossterm events, bus events, 33ms render tick
- AppState with MessageView/PartView (cached Vec<Line> field), ModalState
- pulldown-cmark → ratatui markdown renderer (headings, bold, italic, code
  blocks, lists, blockquotes, links, manual word-wrap)
- Layout: header, chat viewport, input (tui-textarea), status bar
- Permission modal (y/a/n) wired to EngineHandle::permission_reply
- Session picker (Ctrl+S) with resume
- Abort (Esc), Ctrl+C×2 quit, slash commands (/new /model /agent /sessions)
- 7 TestBackend snapshot tests (empty, messages, tool cards, modals)
2026-07-08 23:35:40 +02:00
Erik Simon b6e94c67c7 harness-app + harness-tui: composition root and harness run -p debug command
App::init wires config loading, an in-memory Store, EventBus, PermissionService
with the M1 auto-approve stub frontend, the full built-in ToolRegistry, and a
ProviderRegistry populated with AnthropicProvider when an API key is available
(config or ANTHROPIC_API_KEY). App::run_prompt creates a root session, appends
the prompt as a user message, and drives engine::run_session to completion;
final_text reads back the concatenated text parts of the last message.

harness-tui's `harness` binary gains a `run -p "<prompt>" [-m provider/model]`
subcommand built on this. Manually verified end-to-end against the real
Anthropic API: an empty API key produced a genuine 401 that our SSE error
path correctly classified as ProviderError::Auth and surfaced as a clean
CLI error message (exit 1) -- confirming the full request/header/error-
handling pipeline works against the live service, not just fixtures.

This closes M1 (docs/10-milestones.md): headless core loop + Anthropic
provider, config loading, all six built-in tools, and the debug CLI.
131 tests passing across the workspace, clippy clean, fmt clean.
2026-07-08 17:28:37 +02:00
Erik Simon bbac60d744 harness-providers: Anthropic codec + provider
/v1/messages request builder (cache_control breakpoints on the first 2
system blocks + last 2 messages, tool schema -> input_schema, extended
thinking budget) and an SSE decoder built on eventsource-stream, mapping
content_block_start/delta/stop and message_delta into our normalized
LlmEvent stream (text, thinking+signature, streamed tool-call JSON
accumulated and parsed at content_block_stop, usage merged from
message_start + message_delta). AnthropicProvider wires this to reqwest
with x-api-key/anthropic-version/anthropic-beta headers and classifies
HTTP errors into RateLimited/Auth/Overloaded/Http. ProviderRegistry
resolves "provider/model" strings.

Also tightened processor::process_step's cancellation: the event loop now
selects the stream poll against ctx.cancel instead of only checking at the
top of the loop, so a blocked provider stream is actually interrupted by
abort (matches docs/02-engine.md's cancellation semantics).

127 tests passing, clippy clean.
2026-07-08 17:25:35 +02:00
Erik Simon 27ab6de4f5 harness-tools: read, write, bash, glob, grep
Five of the six M1 built-ins (edit's replacer chain is a separate port).
bash uses process_group(0) + SIGKILL-the-group on timeout/cancel; glob/grep
are gitignore-aware via ignore::WalkBuilder and don't ask permission
(read-only); read/write/bash ask through ctx.ask with opencode's coarser
"always" pattern (first word + wildcard for bash, path for edit/read).
Wired tool output through the central 30k-char truncate() in the processor
so every tool gets spill-to-disk behavior for free. 70 tests passing,
clippy clean.
2026-07-08 17:12:48 +02:00
Erik Simon a68ca02894 M1 core: tool trait, permission service, config, Provider trait, engine loop
harness-core now has everything the headless agent loop needs:
- Tool trait/ToolCtx/ToolRegistry + 30k-char head+tail output truncation
- PermissionService: async ask over a oneshot + AppEvent::PermissionAsked,
  Once/Always/Reject replies, an auto-approve stub for tests/headless runs
- Config: JSONC loading, bundled/global/project-chain/env precedence,
  {env:VAR} and {file:path} interpolation
- llm.rs: LlmEvent/LlmRequest/Provider trait, wire message/content types
- engine/: outer loop (run_session), inner stream processor (persists
  parts/messages as events arrive, executes tool calls inline), retry
  policy (retries only the pre-first-event window), doom-loop guard,
  system prompt assembly

Verified end-to-end against a scripted MockProvider: text -> tool call
(read) -> final text, with messages/parts persisted in the right shape,
plus a provider-error-before-any-event case surfacing as Errored (no
partial message left behind). 49 tests passing, clippy clean.
2026-07-08 17:05:37 +02:00
Erik Simon 8b16348b4c M0: scaffold Cargo workspace, core types, event bus, permission engine, storage actor
Seven-crate workspace per docs/01-architecture.md. harness-core gets the
domain types (Session/Message/Part/ToolState), a broadcast EventBus, the
last-match-wins wildcard permission evaluator, and a SQLite storage actor
(dedicated thread + mpsc, JSON-blob rows) with roundtrip tests. All other
crates are compiling stubs. CI runs fmt/clippy -D warnings/test.
2026-07-08 16:28:42 +02:00