Commit graph

209 commits

Author SHA1 Message Date
Bryan Helmkamp
73b00f047b Add per-run retrospectives with LLM-powered retro agent
After each pipeline run, auto-derive stats from the checkpoint (stages,
retries, cost, files touched) then run an Opus agent session that
explores progress.ndjson to produce qualitative analysis: smoothness
rating, intent, outcome, learnings, friction points, and open items.

Backend:
- retro.rs: data model, save/load, derive_retro(), extract_stage_durations()
- retro_agent.rs: post-pipeline agent session with submit_retro tool
- cli/run.rs: hook retro generation after final.json, before engine_result?
- server.rs: GET /pipelines/{id}/retro endpoint, auto-derive on completion

Frontend:
- data/retros.ts: TS types + mock data + smoothness color config
- routes/retros.tsx: list page with smoothness badges
- routes/run-retro.tsx: detail view (stats, intent, stages, learnings)
- routes.ts + run-detail.tsx: wire up retro route and tab

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 15:46:02 -05:00
Bryan Helmkamp
4f085b1f12 Add pricing data for all non-Anthropic models in catalog
Populated input/output cost per million tokens for OpenAI, Gemini,
Kimi, ZAI, MiniMax, and Inception models based on current public
API pricing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 15:43:51 -05:00
Bryan Helmkamp
4dd2980a56 Add parallel git branching with per-branch worktree isolation
Parallel branches now get isolated git worktrees so concurrent file
writes don't collide. Works across Local, Docker (bind-mount), and
Daytona (remote exec_command) environments.

Key changes:
- git.rs: add create_branch_at() and merge_ff_only() helpers
- engine.rs: add GitState struct, remote worktree helpers
  (git_create_branch_at_remote, git_add_worktree_remote, etc.)
- handler/mod.rs: add git_state field to EngineServices (RwLock)
- handler/parallel.rs: WorktreeEnv wrapper, per-branch worktree
  setup/teardown, checkpoint commits per branch, ff-merge winner
  before returning to engine
- handler/fan_in.rs: ff-merge to winner's HEAD, set best_head_sha
- E2E tests for Host (local) and Daytona (remote) modes

When git_state is None, behavior is unchanged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-01 14:24:00 -05:00
Bryan Helmkamp
c01dae2077 Bump Anthropic max_tokens default from 16384 to 65536
Prevents truncation of large tool calls (e.g. write_file with big
content) when neither agent config nor model catalog provides a value.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 13:53:45 -05:00
Bryan Helmkamp
517f5faed3 Add alias normalization for FailureClass parsing
LLM-authored output can set failure_class to non-canonical strings like
"retryable", "transient", or "permanent". Expand FromStr to accept 30+
aliases with case-insensitive trimmed matching, matching Kilroy's
normalizedFailureClass(). Unknown values fail-closed to Deterministic
instead of returning Err.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 13:46:48 -05:00
Bryan Helmkamp
4c75f2b67f Add script_absolute_cd lint rule to warn on absolute cd paths in scripts
Absolute `cd` paths in shell commands (script/tool_command attributes) silently
override the engine's worktree CWD, breaking portability across machines,
containers, and worktrees. Ported from kilroy (danshapiro/kilroy d9c1fec).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 13:20:13 -05:00
Bryan Helmkamp
24cff30518 Fix clippy warnings, test failures, and apply cargo fmt
- Fix 4 test failures: add unconditional fallback edges to branching.dot
  and conditions.dot to satisfy all_conditional_edges validation rule
- Fix clippy await_holding_lock: scope MutexGuard before await in
  daytona_integration.rs
- Fix clippy unnecessary_get_then_check: use contains_key in script.rs
- Fix clippy expect_fun_call: use unwrap_or_else in integration.rs
- Run cargo fmt across entire workspace

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 13:02:16 -05:00
Bryan Helmkamp
5dfc72316f Align failure classification with Kilroy reference implementation
Add 8 missing transient_infra patterns (crates.io registry, toolchain,
cross-device link errors), 2 structural hints (write_scope_violation
variants), and reorder heuristic priority to check transient_infra
before budget_exhausted to match Kilroy's classification behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 12:56:33 -05:00
Bryan Helmkamp
1e07fc0b09 Bump Anthropic max_tokens default and add per-node max_tokens override
Raise the Anthropic adapter fallback from 4096 to 16384 to prevent
truncation of large tool call JSON when the model isn't in the catalog.

Add max_tokens as a configurable DOT node attribute that flows through
SessionConfig to LLM requests, following the same pattern as
reasoning_effort. Priority: node attribute > catalog > provider default.

Ported from kilroy (danshapiro/kilroy) commits 99a5cd7 and 78fadad.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 12:54:23 -05:00
Bryan Helmkamp
ebc1d5ea6a Expand failure classification patterns and add regression tests
Extract hint patterns into const arrays (TRANSIENT_INFRA_HINTS,
BUDGET_EXHAUSTED_HINTS, STRUCTURAL_HINTS) and add 28 new patterns
from Kilroy to prevent transient/budget failures from misclassifying
as deterministic. Add comprehensive regression test per pattern plus
count-guard tests to catch accidental additions/removals.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 12:29:38 -05:00
Bryan Helmkamp
8aa56285f4 Rename remaining attractor references to arc
- AttractorError → ArcError across 30 source files
- .attractor/ → .arc/ for artifacts and skills paths
- ATTRACTOR_NODE_ID → ARC_NODE_ID env var
- attractor-rust → arc in Cargo.toml repository URLs
- attractor-spec.md → arc-spec.md with content updates
- Update server banner, test comments, README examples, and docs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 12:11:24 -05:00
Bryan Helmkamp
99ad7ffe29 Extract server code into arc-api crate
Move HTTP server (Axum routes, JWT auth, serve CLI command, server
config) from arc-workflows into a dedicated arc-api crate. This
improves separation — arc-workflows is a pipeline engine library,
not a web server.

- Create crates/arc-api with server.rs, jwt_auth.rs, serve.rs,
  server_config.rs and their integration tests
- Remove server feature flag and optional deps from arc-workflows
- Update arc-cli to depend on arc-api for ServeArgs and serve_command

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 11:51:21 -05:00
Bryan Helmkamp
771b70889b Add ServerConfig for arc.toml server configuration
Introduces server_config.rs with a TOML-based ServerConfig struct
(version + url) following the same patterns as TaskConfig.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 11:20:33 -05:00
Bryan Helmkamp
6d22a6c622 Rename arc-attractor crate to arc-workflows
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 03:05:43 -05:00
Bryan Helmkamp
90dc528c99 Add shadow commits for Docker and Daytona (feature parity)
- Change GitCheckpointMode::Remote to Remote(PathBuf) so both variants
  carry a repo path for MetadataStore shadow commits
- Unify init_run and shadow write logic to work with either Host or
  Remote mode, eliminating Host-only gates
- Add trailers (Arc-Run, Arc-Completed, Arc-Checkpoint) to remote
  checkpoint commits via write_file + git commit -F to avoid shell
  escaping issues with multi-line messages
- Wire up meta_branch for Daytona in run.rs (was only set for worktree)
- Fix sandbox name collisions by adding random hex suffix
- Fix pre-existing build_router() test compilation errors from JWT auth
- Add E2E tests for Host shadow branch and Daytona shadow branch

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 21:45:48 -05:00
Bryan Helmkamp
77b7bf7481 Support unquoted bare string values in DOT parser (e.g., gpt-5.2)
Add a bare_string parser that accepts values containing hyphens and
dots like gpt-5.2-codex-spark and gemini-3-flash-preview. These are
common in kilroy DOT files for model names but were previously rejected
by arc's strict identifier parser.

All 14 kilroy DOT files now parse successfully.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 18:09:38 -05:00
Bryan Helmkamp
6d869aa8da Add asymmetric JWT service-to-service auth between arc-web and arc-attractor
Ed25519 asymmetric JWT: arc-web signs with private key, arc-attractor verifies
with public key. Adds AuthenticatedService axum extractor to all routes, jose
dependency for TypeScript signing, and key generation script.

Startup behavior: ARC_JWT_PUBLIC_KEY set → enforce JWT auth; not set +
ARC_INSECURE_DISABLE_AUTHENTICATION=true → allow unauthenticated; neither →
refuse to start with clear error.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 18:05:49 -05:00
Bryan Helmkamp
88f2fa245b Add kilroy DOT files and parsing compatibility tests
Copy 14 DOT workflow files from the kilroy project and add tests proving
arc can parse them. 11 files parse successfully, exercising features
including subgraphs, fan-out/fan-in, conditional routing, goal gates,
model stylesheets, and large 40+ node workflows.

3 batch test files (batch_*.dot) document a parser gap: arc requires
quoted values for strings with hyphens/dots (e.g., "gpt-5.2") while
kilroy's parser accepts them unquoted.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 18:05:34 -05:00
Bryan Helmkamp
2a82a4e147 Change default_max_retry from 50 to 3
Aligns with the kilroy implementation of the Attractor spec. A default
of 50 retries is far too aggressive for most workflows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 18:02:30 -05:00
Bryan Helmkamp
1b26702f95 Add guardrail tests and fix config hygiene across provider system
- Add Provider::ALL constant for iterating all variants in tests
- Add catalog guardrail tests: every provider has models, provider strings
  round-trip, as_str round-trips through from_str
- Add arc-agent guardrail tests: every default_model exists in catalog,
  profile context_window matches catalog for default models
- Fix context window drift: profiles now look up catalog instead of
  hardcoding sizes, with conservative fallbacks for unknown models
- Add #[serde(deny_unknown_fields)] to config structs so typos like
  [lmm] instead of [llm] produce parse errors
- Extract DEFAULT_BASE_URL constant in OpenAI adapter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 17:51:31 -05:00
Bryan Helmkamp
8adbf79b12 Add 3 lint rules: all_conditional_edges, orphan_custom_outcome, condition_eval
- all_conditional_edges (ERROR): fires when a node has outgoing edges but
  all are conditional with no unconditional fallback, preventing silent
  fall-through to arbitrary edge selection.
- orphan_custom_outcome (WARNING): fires when outcome-based routing lacks
  an unconditional fallback edge, catching typos in outcome values.
- Enhanced condition_syntax rule to also validate via parse_condition(),
  catching malformed expressions that pass static checks (e.g. empty key).
- Updated integration test graph to use unconditional fallback edge.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 17:51:15 -05:00
Bryan Helmkamp
631971d211 Return infrastructure errors as Err from ScriptHandler
Spawn failures (binary not found) and timeouts are infrastructure issues,
not domain failures. Return them as Err(AttractorError::Handler(...)) so
the engine can distinguish them from script exit-code failures and
potentially retry.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 17:34:01 -05:00
Bryan Helmkamp
aa0be8f379 Remove brittle built_in_rules count test
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 17:29:17 -05:00
Bryan Helmkamp
6f512c665d Fix built_in_rules count test after adding reserved keyword rule
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 17:28:32 -05:00
Bryan Helmkamp
cbb94ae9b2 Add Inception Labs (Mercury) provider
Register Inception Labs' Mercury diffusion LLM as a new provider using
the OpenAI-compatible adapter at api.inceptionlabs.ai. Adds mercury and
mercury-coder to the model catalog and wires up all exhaustive match
arms across arc-llm, arc-agent, and arc-attractor.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-28 17:25:37 -05:00
Bryan Helmkamp
1245106cea Add reserved keyword node ID lint rule
Warn when DOT reserved keywords (graph, digraph, subgraph, node, edge,
strict, if) are used as node IDs since they cause silent routing failures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-28 17:16:24 -05:00
Bryan Helmkamp
2d79362750 Unify ullm, arc-agent, arc-attractor into single arc binary
Three separate binaries are replaced by a single `arc` CLI with subcommands:
  arc llm prompt/models, arc agent, arc run, arc validate, arc serve

Extract public CLI modules (arc_llm::cli, arc_agent::cli::AgentArgs/run_with_args)
so the new arc-cli crate can dispatch to each library. Integration tests migrate
to crates/arc-cli/tests/cli.rs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-28 17:09:30 -05:00
Bryan Helmkamp
0f9606e9e3 Write final.patch (base_sha → HEAD) to logs_root after pipeline completes
Per-stage diff.patch files only capture incremental changes between
checkpoints. This adds a comprehensive final.patch covering all changes
from the run's base SHA to the final HEAD, written to logs_root for all
execution environments. Especially important for Daytona where the
sandbox is destroyed after the run.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 15:15:40 -05:00
Bryan Helmkamp
9ef45204f6 Add git checkpointing for Docker and Daytona execution environments
Git checkpoint commits (stage-level snapshots, diff.patch, GitCheckpoint
events) previously only ran for Local execution. This extends support to
Docker (bind-mount uses host git, same as Local) and Daytona (runs git
commands remotely via exec_command).

- Add GitCheckpointMode enum (Host/Remote) replacing RunConfig.work_dir
- Extract git_checkpoint_host/git_diff_host helpers from inline code
- Add git_checkpoint_remote/git_diff_remote using exec_command
- Enable git_clean check for Docker alongside Local
- Add setup_daytona_git to create run branch in remote sandbox
- Switch Daytona wrap_bash_command from quote-escaping to base64 encoding
  (matches TypeScript/Python/Ruby Daytona SDKs, avoids nested quote issues)
- Add e2e tests for both Host mode and Remote mode (Daytona, live-tested)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 15:08:22 -05:00
Bryan Helmkamp
4572012bc3 Use ULID instead of UUID for run IDs
ULIDs are lexicographically sortable by creation time, making log
directories and run lists naturally ordered without extra metadata.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 14:16:52 -05:00
Bryan Helmkamp
53ae30dfd4 Collapse pipeline_id and run_id into single run_id
Make RunConfig.run_id required (String instead of Option<String>) so the
caller always provides the ID. This eliminates the duplicate UUID that
was generated: one in the HTTP server / CLI and a second inside the
engine fallback.

Also fixes a bug in preamble.rs where context key "run_id" was read but
the engine stores it as "internal.run_id", so the run ID always showed
as "unknown" in preambles.

Renames PipelineStarted.id to PipelineStarted.run_id for consistency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 14:15:25 -05:00
Bryan Helmkamp
91f5fc938a Surface git metadata in pipeline events, manifest, and final.json
Add git observability to match Kilroy parity: base_sha, run_branch,
worktree_dir on PipelineStarted; GitCheckpoint events with commit SHAs;
final_git_commit_sha on PipelineCompleted/PipelineFailed; run_branch in
manifest.json; final_git_commit_sha in final.json; diff.patch per node;
and git::diff_against helper.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 11:09:36 -05:00
Bryan Helmkamp
e97c16f03f Git worktree isolation and per-node checkpoint commits
Create a dedicated git branch + worktree per pipeline run (Local env only)
and commit after every node checkpoint. This gives each run an isolated
working directory and a full git trail of changes per stage.

New module: git.rs with ensure_clean, head_sha, create_branch,
add/remove_worktree, and checkpoint_commit (using arc identity).

Engine changes: RunConfig gains run_id and work_dir fields; after each
checkpoint save, a git commit is created in the worktree and the SHA
is stored in checkpoint.git_commit_sha.

CLI changes: for Local execution, the repo cleanliness is verified
before any log files are written, then a worktree is created on branch
arc/run/{uuid}, cwd is switched into it, and cleanup runs after the
engine completes.

Handler changes: run_hook() accepts work_dir so hooks execute in the
worktree.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 10:51:37 -05:00
Bryan Helmkamp
32e4f5eb96 Rename all crates from unprefixed to arc-* prefix
Rename crate directories, package names, binary names, path
dependencies, use statements, qualified paths, clap command names,
and string literals across the workspace.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-28 04:20:47 -05:00
Bryan Helmkamp
5ba3535a1c Add Kimi, Z.AI, and Minimax provider support
All three providers use the OpenAI Chat Completions protocol via
OpenAiCompatibleAdapter:
- Kimi (KIMI_API_KEY) → api.moonshot.ai/v1
- Z.AI (ZAI_API_KEY) → api.z.ai/api/coding/paas/v4
- Minimax (MINIMAX_API_KEY) → api.minimax.io/v1

Key changes:
- Extend Provider enum with Kimi, Zai, Minimax variants
- Add with_name() to AnthropicAdapter for non-Anthropic providers
  using the Messages protocol (conditional Bearer vs x-api-key auth)
- Add complete_via_stream() for providers requiring stream=true
- Add with_provider() to AnthropicProfile and OpenAiProfile so the
  session routes requests to the correct adapter
- Add kimi-k2.5, glm-4.7, minimax-m2.5 to model catalog
- Handle reasoning_content in OpenAI compatible adapter (capture in
  stream, store as ContentPart::Thinking, echo back in assistant
  messages) — required by Kimi for multi-turn tool use
- Handle missing [DONE] sentinel in SSE streams (Minimax omits it)
- Wire up all exhaustive Provider matches across agent and attractor

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-28 04:06:20 -05:00
Bryan Helmkamp
950a2d06a7 Introduce Provider enum and ModelId type for compile-time provider safety
Replace scattered provider string literals ("anthropic", "openai", "gemini")
with a Provider enum and ModelId struct in the llm crate. This prevents
bugs like routing an OpenAI model to Anthropic's API (the bug fixed in
3263d0c) by making the provider identity a compile-time checked value.

Key changes:
- Add Provider enum (Anthropic, OpenAi, Gemini) with as_str/Display/FromStr
- Add ModelId struct bundling Provider + model name
- Replace WebFetchSummarizer's separate model+provider fields with ModelId
- Replace BaseProfile.id: &'static str with BaseProfile.provider: Provider
- Replace ProviderProfile::id() -> &str with provider() -> Provider
- Parse --provider CLI strings to Provider early via FromStr
- Update AgentBackend and CliBackend to use Provider instead of String/Option

Serialization boundaries (Request.provider, Response.provider, Client HashMap
keys) remain as strings, converted via provider.as_str().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-28 03:29:30 -05:00
Bryan Helmkamp
3263d0c21e Fix web_fetch summarizer routing to wrong LLM provider
The WebFetchSummarizer was sending requests without specifying a
provider, so they always routed to the default (Anthropic). When using
the OpenAI or Gemini profile, the summarizer model (e.g. gpt-4o-mini)
was rejected by Anthropic with a 404.

Add a `provider` field to WebFetchSummarizer so the summarization
request routes to the correct provider. Also improve the error message
to include the model name, and relax the parity test assertion to
accept summarized content.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-28 01:14:04 -05:00
Bryan Helmkamp
876e487821 Suppress too_many_arguments lint on compact_context
The 8 params are all distinct concerns freshly extracted from Session;
bundling them into a struct would add indirection without clarity.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-27 23:49:36 -05:00
Bryan Helmkamp
275c0aa5a9 Extract tool execution from session.rs into tool_execution.rs
Move the tool execution subsystem (~250 lines) into a dedicated module:
- execute_tool_calls: dispatches to parallel or sequential execution
- execute_and_emit_one_tool: wraps execution with event emission
- execute_one_tool: registry lookup, validation, and execution
- validate_tool_args: JSON schema validation for tool arguments
- truncate_tool_result: output truncation for history storage

These functions have zero dependency on Session's history, state machine,
or LLM interaction. Session now calls the extracted free functions,
passing needed context as parameters.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-27 23:47:46 -05:00
Bryan Helmkamp
ee831c2db0 Introduce ToolContext struct to simplify ToolExecutor type
Bundle the execution parameters (env, cancel) into a single
ToolContext struct, reducing the ToolExecutor closure signature
from 3 parameters to 2. This makes the type alias simpler and
means future parameters won't change the signature.

Key changes:
- Add ToolContext { env, cancel } in tool_registry.rs
- Update ToolExecutor type alias: Fn(Value, ToolContext) -> ...
- Update all tool factories in tools.rs, v4a_patch.rs,
  subagent.rs, mcp_integration.rs, and skills.rs
- Update all call sites in session.rs and tool_execution.rs
- Update test helpers and test call sites

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-27 23:46:23 -05:00
Bryan Helmkamp
b391af7968 Register compaction module in lib.rs
The compaction.rs module was created and session.rs was updated to use
it in the previous commit, but the module was not registered in lib.rs.
Add `pub mod compaction;` to complete the extraction.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-27 23:45:20 -05:00
Bryan Helmkamp
e8694f08bb Add delegate_execution_env macro to reduce decorator boilerplate
ReadBeforeWriteEnvironment had 9 pass-through methods that just forwarded
to self.inner. The new delegate_execution_env! macro generates these
automatically, so only the 4 customized methods (read_file, write_file,
delete_file, grep) need to be written explicitly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-27 23:44:28 -05:00
Bryan Helmkamp
b80f9cb100 Unify reasoning storage in Turn::Assistant
Remove the standalone `reasoning: Option<String>` field from
Turn::Assistant. Reasoning/thinking text is now stored exclusively
in `provider_parts` as `ContentPart::Thinking` blocks, eliminating
the dual-storage reconciliation logic in `convert_to_messages`.

Add `Turn::reasoning_text() -> Option<&str>` accessor that extracts
the first non-redacted thinking text from provider_parts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-27 23:43:38 -05:00
Bryan Helmkamp
5c74f88814 Consolidate tool registration into register_core_tools
All three provider profiles (Anthropic, OpenAI, Gemini) independently
registered the same 7 core tools (read_file, write_file, shell, grep,
glob, web_search, web_fetch). Extract a shared register_core_tools()
function in tools.rs that accepts a ToolRegistry, SessionConfig (for
shell timeout customization), and optional WebFetchSummarizer.

Each profile now calls register_core_tools() then adds its
profile-specific tools:
- Anthropic: edit_file (with 120s shell timeout via config)
- OpenAI: apply_patch (default 10s shell timeout)
- Gemini: edit_file, read_many_files, list_dir (default 10s shell timeout)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-27 23:40:49 -05:00
Bryan Helmkamp
96443e2508 Extract v4a patch parser from OpenAI profile into standalone module
The parser, applicator, types, and tool factory had no dependency on the
OpenAI profile. Moving them to `v4a_patch.rs` makes them independently
testable, reusable by other profiles, and cuts `openai.rs` nearly in half.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-27 23:36:45 -05:00
Bryan Helmkamp
06dcd3c4f5 Unify tool execution event emission into execute_and_emit_one_tool
Extract duplicated ToolCallStarted/ToolCallOutputDelta/ToolCallCompleted
emission and output truncation from both sequential and parallel paths
into a single shared function.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-27 23:32:32 -05:00
Bryan Helmkamp
819911fd3d Add MCP client support for connecting to external tool servers
New `mcp` crate using rmcp v0.15.0 with stdio and HTTP transports.
MCP tools are registered as regular `RegisteredTool` instances in the
agent's `ToolRegistry`, sharing the same `execute_one_tool` path as
built-in tools. Tools are namespaced as `mcp__{server}__{tool}`.

Includes end-to-end test: real MCP server subprocess (Python echo
server) → mock LLM issues tool call → MCP bridge executes → result
flows back through the session loop.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-27 23:28:33 -05:00
Bryan Helmkamp
6bafc012c3 Add structured compaction with file tracking
After compaction, the agent previously lost awareness of which files it
touched. This adds a FileTracker that records file operations from tool
calls (read_file, write_file, edit_file, apply_patch) and injects a
## File Operations section into the structured compaction prompt so file
context survives across compaction cycles.

- FileTracker: BTreeMap-based tracker with record_from_tool_calls()
- Structured compaction prompt with Goal/Progress/Key Decisions/
  Failed Approaches/Open Issues/Next Steps sections
- tracked_file_count field on CompactionCompleted event
- File tracker accumulates monotonically (never reset)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:46:29 -05:00
Bryan Helmkamp
61ed9a6bdb Add HTML-to-markdown conversion and prompt summarization to web_fetch
web_fetch now converts HTML responses to clean markdown using the htmd
crate (stripping script/style tags), and supports an optional prompt
parameter that makes a secondary LLM call to answer questions about the
fetched content. Each provider profile picks a cheap/fast summarizer
model (Haiku, gpt-4o-mini, gemini-2.0-flash).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:26:32 -05:00
Bryan Helmkamp
35005ca0fe Add use_skill tool for agent-initiated skill loading
Let the agent autonomously load skill templates when it recognizes a
matching task, instead of requiring users to type /skill-name. The
system prompt now instructs the agent to call `use_skill` and skill
names use backtick formatting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:03:16 -05:00