Commit graph

53 commits

Author SHA1 Message Date
Bryan Helmkamp
cab25a3e7e Add failure signatures and circuit breakers for deterministic failure cycle detection
Introduces reason normalization (strip variable data like line numbers,
hex hashes), composite failure signatures (node_id|class|normalized_reason),
and circuit breakers that track signature counts to abort when the same
deterministic failure repeats beyond a configurable limit (default 3).

Key additions:
- normalize_failure_reason() strips hex, digits, whitespace for stable grouping
- FailureSignature type with handler-provided hint priority
- FailureClass::is_signature_tracked() (deterministic + structural only)
- Graph-level loop_restart_signature_limit attribute
- LoopState struct bundling node_visits + signature maps through run_internal
- Loop failure circuit breaker (same node repeating)
- Restart failure circuit breaker (across loop_restart edges)
- Checkpoint persistence for both signature maps with backward compat
- 18 e2e integration tests covering all circuit breaker scenarios

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 18:03:09 -05:00
Bryan Helmkamp
c3d46c3885 Add SQLite persistence via sqlx with arc-db crate
Introduces durable storage so pipeline run data survives restarts.
The new arc-db crate provides SQLite connection helpers, a
PRAGMA user_version migration system, and a WorkflowRun model.
AppConfig (arc.toml) controls data_dir; the server initializes
the DB at startup and threads the pool through AppState.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 15:56:32 -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
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
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
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
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
434c9e61f1 Update Cargo.lock for ulid dependency
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 14:22:24 -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
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
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
6338f3ec57 Implement web_fetch tool via exec_command curl
Replace the placeholder web_fetch tool with a working implementation
that executes curl within the execution environment, respecting
Docker/Daytona network sandboxing.

- Build curl command with shell-escaped URL, follow redirects, custom
  user agent, and configurable timeout (default 30s, max 60s)
- Validate URL scheme (http/https only) to prevent misuse
- Truncate responses exceeding 100KB to protect context window
- Register web_fetch in Anthropic and OpenAI profiles (was Gemini-only)
- Add web_fetch guidance to all three profile system prompts
- Add captured_command to MockExecutionEnvironment for test assertions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 20:07:28 -05:00
Bryan Helmkamp
29b08fc6c5 Implement web_search tool using Brave Search API
Replace the placeholder web_search tool with a real implementation backed
by the Brave Search API. Register it in all three profiles (Anthropic,
OpenAI, Gemini) so every provider has web search capability.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 19:36:38 -05:00
Bryan Helmkamp
0756a42ce8 Consolidate redact and terminal crates into util
Merges two small utility crates into a single `util` crate to reduce
workspace clutter. No behavioral changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 18:44:19 -05:00
Bryan Helmkamp
7e4fda2a87 Add Daytona E2E test for artifact sync and update SDK
Update daytona-sdk-rust to 5d370099 which fixes multipart file upload
(upload_file was sending an empty form). Add E2E test that verifies
artifact pointers are rewritten and files uploaded to a live Daytona
sandbox.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 16:53:59 -05:00
Bryan Helmkamp
5504660379 Add more tests for daytona 2026-02-26 22:10:43 -05:00
Bryan Helmkamp
8d593ff68e Add Daytona execution environment and replace --docker with --execution-env
Replace the `--docker` boolean flag with `--execution-env <local|docker|daytona>`
to support three execution environments. The new `DaytonaExecutionEnvironment`
uses the Daytona cloud sandbox SDK to run agent tools remotely, auto-cloning the
current git repo into the sandbox via `gh auth token` credentials.

Setup commands from TOML task configs now run inside the execution environment
(via `exec_command` after `initialize()`) rather than locally, so they work
correctly for both Docker and Daytona sandboxes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 306da8ff6de1
2026-02-26 18:35:33 -05:00
Bryan Helmkamp
0f9e31204f Add TOML-based task config for attractor run
Support `attractor run task.toml` as an alternative to `.dot` files.
Auto-detects format by file extension. TOML bundles pipeline config
(graph path, model, setup commands, working directory) into a single
file with precedence: CLI flag > TOML > DOT graph attrs > defaults.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 8744d57d6053
2026-02-26 13:37:08 -05:00
Bryan Helmkamp
d47c8e9048 Redact secrets from pipeline event output (NDJSON, live.json, SSE)
Apply redact::redact_jsonl_line at all three serialization sites so
secrets (AWS keys, GitHub PATs, private keys, etc.) are scrubbed
before reaching disk or the network.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: e23c92750cd9
2026-02-25 16:02:06 -05:00
Bryan Helmkamp
90e7bf229a Add redact crate for secret detection and redaction in NDJSON logs
Two-layer detection: Shannon entropy on high-entropy alphanumeric tokens
(threshold 4.5) and gitleaks v8.22.1 pattern matching (202 rules) with
Aho-Corasick keyword pre-filtering. JSONL-aware redaction skips exempt
fields (IDs, paths) and image objects. 43 tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 9e8476d16412
2026-02-25 15:25:52 -05:00
Bryan Helmkamp
3d39e06c29 Add skills system for reusable prompt templates
Skills are markdown files with YAML frontmatter that define reusable
prompt templates (e.g., /commit, /review-pr). When a user references
/skill-name in their input, the skill template expands in place with
{{user_input}} receiving the remaining text.

- Add skills.rs with parse, expand, discover, and formatting functions
- Discover skills from ~/.attractor/skills/, <git-root>/.attractor/skills/,
  and <git-root>/skills/ (overridable with --skills-dir CLI flag)
- Inject available skills into system prompt between project docs and
  user instructions
- Expand skill references in session input before recording user turn

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 2a8ed77f26c5
2026-02-25 11:32:07 -05:00
Bryan Helmkamp
8e4b8a6e05 Add git-storage crate for storing data in git object database
Layered library for blob/tree/commit/ref operations without touching
the working directory. Four modules: gitobj (primitives), branchstore
(key-value on a branch), snapshot (working dir captures), trailerlink
(commit message trailers). 56 tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 1e40b3267885
2026-02-24 23:17:44 -05:00
Bryan Helmkamp
b74d837b95 Add ullm models sync command to download OpenRouter model metadata
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 6908e4dfbe5e
2026-02-24 14:18:43 -05:00
Bryan Helmkamp
0431fdb242 Revert "Replace text-scanning with report_outcome tool for routing"
This reverts commit f166bb4959.
2026-02-24 12:10:39 -05:00
Bryan Helmkamp
c9b7bee2da Change default logs directory to ~/.attractor/logs
Logs from `attractor run` were cluttering project directories. Now defaults
to ~/.attractor/logs/attractor-run-TIMESTAMP instead of ./attractor-run-TIMESTAMP.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 09:58:20 -05:00
Bryan Helmkamp
f166bb4959 Replace text-scanning with report_outcome tool for routing
The previous approach scanned LLM response text for JSON containing
routing fields (extract_status_fields), which was fragile and violated
the spec's abstraction boundary. Replace with two clean mechanisms:

- Backend registers a report_outcome tool that the LLM calls to declare
  routing decisions (status, preferred_next_label, context_updates, etc.)
- Handler auto-generates a routing preamble listing available edge labels,
  appended to the prompt only when 2+ labeled unconditional edges exist

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 09:29:25 -05:00
Bryan Helmkamp
b4068b6364 Add missing integration tests for multi-turn caching, cross-provider parity, and attractor E2E
Test 1 (llm crate): Multi-turn cache verification runs 6 conversation turns
with a large system prompt (~5460 tokens) and verifies cache_read_tokens on
the final turn. Anthropic threshold 0.5, OpenAI/Gemini 0.0 (automatic
caching not guaranteed).

Test 2 (agent crate): Cross-provider parity matrix with 15 scenarios
(file CRUD, shell, grep/glob, editing, steering, reasoning effort, loop
detection, error recovery, etc.) across Anthropic, OpenAI, and Gemini.
41 total tests. Some scenarios excluded for OpenAI due to gpt-4o-mini
limitations (no reasoning.effort, is_error rejection, weak editing).

Test 3 (attractor crate): E2E pipeline with real LLM using AgentBackend,
AutoApproveInterviewer, and default_registry. Verifies pipeline success,
artifact files, goal gate outcomes, and checkpoint state.

All tests are #[ignore] and require API keys to run.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 18:24:34 -05:00
Bryan Helmkamp
2a2a610654 Replace abort_flag with CancellationToken for abort-aware process cancellation
Thread CancellationToken into tool executors and exec_command so that
running processes are killed (SIGTERM -> 2s -> SIGKILL) when abort fires,
rather than only checking the flag between LLM calls. Key changes:

- ToolExecutor type gains CancellationToken parameter
- ExecutionEnvironment::exec_command gains cancel_token param
- LocalExecutionEnvironment uses tokio::select! (completion vs timeout
  vs cancellation) with extracted sigterm_then_kill helper
- DockerExecutionEnvironment uses same select! pattern
- Session replaces Arc<AtomicBool> with CancellationToken, passes
  child_token() per tool call
- Shell tool forwards cancel token to exec_command
- CLI SIGINT handler calls cancel_token.cancel()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 18:10:10 -05:00
Bryan Helmkamp
028353c82e Replace manual CLI prompts with dialoguer for arrow-key navigation
Interactive TTY sessions now use dialoguer widgets (Select, MultiSelect,
Confirm, Input) instead of raw eprintln/read_line. Non-TTY input falls
back to the existing line-based reader. Suppresses redundant "Stage
started" inform message for wait.human nodes since the prompt itself
serves as notification.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 17:58:18 -05:00
Bryan Helmkamp
a3605ec0d4 Speed up slow tests: drop SSE broadcast on completion, switch to rustls-tls, replace hardcoded sleeps with poll loops, and prevent real API calls in ullm tests
- Drop event_tx from ManagedPipeline when pipeline completes/cancels/fails so
  SSE streams end promptly instead of blocking until timeout (3.5s → 0.02s)
- Switch reqwest from native-tls to rustls-tls to avoid 500ms macOS cert store
  load per process (0.67s → 0.005s per OpenAI adapter test)
- Replace hardcoded sleep(500ms)/sleep(200ms)/sleep(100ms) in server and
  integration tests with 10ms poll loops (0.2-0.5s → 0.02-0.03s each)
- Add env_clear() to ullm prompt tests to prevent .env from triggering real
  Anthropic API calls (0.45s → 0.15s)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 17:19:09 -05:00
Bryan Helmkamp
60dad3c1cd Add DockerExecutionEnvironment for sandboxed agent tool execution
Implements ExecutionEnvironment trait backed by Docker containers via
bollard. Host working directory is bind-mounted; all file ops, commands,
grep, and glob execute inside the container via docker exec. Extracts
shared format_lines_numbered() helper from LocalExecutionEnvironment.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 13:29:38 -05:00
Bryan Helmkamp
dddc6df8c8 Extract terminal crate and prettify attractor CLI output
Move ANSI Styles struct from agent/cli.rs into a shared terminal crate
so both binaries can use it. Add green and yellow color codes. Prettify
all attractor CLI output: bold headers, colored diagnostics by severity,
green/red status, yellow warnings, dimmed event details, and styled
interviewer prompts. Move pipeline status output from stdout to stderr.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 12:06:24 -05:00
Bryan Helmkamp
df8502d83b Merge agent-cli crate into agent as cli module
The agent-cli binary was a thin wrapper over the agent library with
nothing else depending on it. Moving it into the agent crate as a
`pub mod cli` with a `[[bin]]` entry reduces workspace complexity
and follows the attractor crate pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:14:25 -05:00
Bryan Helmkamp
0edc93e1c3 Add CLI tests for validate and dry-run on all test workflows
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 10:58:54 -05:00
Bryan Helmkamp
b7883d6bef Add agent CLI with tool approval callback
Introduce `ToolApprovalFn` callback in `SessionConfig` to gate tool
execution by permission level. Create `agent-cli` crate as a thin CLI
binary wrapping `Session` with provider/model resolution, permission
model (read-only/read-write/full), interactive approval prompts,
real-time event rendering, debug middleware, and SIGINT handling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 10:50:40 -05:00
Bryan Helmkamp
4bb2de5a49 Rename unified-llm crate to llm
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 09:22:14 -05:00
Bryan Helmkamp
30cf3a6787 Rename coding-agent-loop crate to agent
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 09:17:48 -05:00
Bryan Helmkamp
433a7c6247 Implement attractor CLI binary with run and validate subcommands
Add a [[bin]] target to the attractor crate with two subcommands:
- `attractor validate <pipeline.dot>` -- parse and validate only
- `attractor run <pipeline.dot>` -- full pipeline execution with LLM backend

The CLI supports --dry-run, --auto-approve, --resume, --model, --provider,
and two-level verbosity (-v one-line summaries, -vv full event details).

Extracts a shared `default_registry()` function in handler/mod.rs so both
the CLI and server can build a fully-wired HandlerRegistry without
duplicating handler registration boilerplate.

The AgentBackend in cli/backend.rs implements CodergenBackend by creating
a coding-agent-loop Session per node invocation, giving LLM nodes access
to file and shell tools.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 09:11:42 -05:00
Bryan Helmkamp
08d76b93de Add 8 integration tests: HTTP lifecycle, SSE events, sub-pipeline, manager loop, graph merge, real LLM
- Change server registry_factory to accept Arc<dyn Interviewer> so WaitHumanHandler
  shares the same WebInterviewer as the REST API endpoints
- Add full HTTP lifecycle tests: approve-and-complete flow + cancel flow
- Add SSE event stream content parsing test with frame-level verification
- Add sub-pipeline E2E test through the engine with context propagation
- Add manager loop E2E test with SimulatingChildObserver
- Add graph merge E2E test verifying module prefixing and execution ordering
- Add 3 real LLM tests (#[ignore]) using claude-haiku via AnthropicAdapter
- Add dotenvy and http-body-util dev dependencies

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 13:07:26 -04:00
Bryan Helmkamp
c15b532bcf Implement three missing spec features: SubPipelineHandler, GraphMergeTransform, HTTP Server
Add SubPipelineHandler (handler/sub_pipeline.rs) that inline-executes a parsed
sub-graph within the same engine, reading DOT source from node attributes and
propagating context diffs back to the parent pipeline.

Add GraphMergeTransform (transform.rs) that merges nodes and edges from secondary
graphs into a primary graph with namespace-prefixed IDs to avoid collisions.

Add WebInterviewer (interviewer/web.rs) backed by oneshot channels for async
question/answer flow, and HTTP server (server.rs) with 8 axum endpoints behind
a "server" feature flag for pipeline management and human-in-the-loop via web.

Fix tempfile dev-dependency usage in server production code by using std::env::temp_dir.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:07:44 -04:00
Bryan Helmkamp
34836200e5 Implement attractor crate: DOT-based pipeline runner with full spec compliance
Adds the attractor crate implementing all 11 sections of the attractor spec:
- DOT parser (lexer, grammar, semantic analysis) for strict DOT subset
- Pipeline execution engine with edge selection, goal gates, retry logic,
  failure routing, checkpoint save/resume, and loop_restart
- 9 node handlers: start, exit, codergen, wait_human, conditional, parallel
  (concurrent with join/error policies), fan_in (with LLM eval), tool, manager_loop
- State management: PipelineContext, Outcome, Artifact store, fidelity resolution
- Human-in-the-loop: Interviewer trait with auto_approve, callback, queue,
  recording, and console implementations, plus timeout enforcement
- Validation: 14 built-in lint rules with custom rule registration API
- Model stylesheet with universal/shape/class/ID selectors and specificity
- Transforms: variable expansion, stylesheet application, preamble; plus
  PipelineBuilder with register_transform and prepare_pipeline
- Condition expression language with =, !=, bare-key truthiness, && combinator
- Event system with all 16 event types emitted by engine and handlers
- Tool call hooks (pre/post) for CodergenHandler
- Run directory with manifest.json and per-node status.json

370 tests (354 unit + 16 integration) covering all spec sections.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 12:17:32 -04:00
Bryan Helmkamp
db32f00cd0 Simplify coding-agent-loop: deduplicate mocks, narrow traits, type events
- Extract shared test infrastructure (MockExecutionEnvironment, TestProfile,
  MockLlmProvider) replacing 11 duplicate mock implementations across tests
- Deduplicate tool execution logic between sequential and parallel paths
- Narrow ProviderProfile trait from 14 to 7 required methods via
  ProfileCapabilities struct and default implementations
- Replace stringly-typed HashMap event data with typed EventData enum
- Extract shared assemble_system_prompt helper and register_subagent_tools
  default method, eliminating copy-paste across all 3 profiles
- Replace fragile shell-based glob with glob crate, fix rg detection
- Add delete_file to ExecutionEnvironment, wire git context into env block
- Remove dead code (AgentError::Io, count_turns, trivial derived-trait tests)
- Use match-based lookups in truncation instead of per-call HashMap allocation

Net reduction: -1,401 lines across 20 files. All 180 tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 16:34:29 -04:00
Bryan Helmkamp
99db2753ca Add user instructions, env context, expanded profiles to coding-agent-loop
Close spec compliance gaps:
- Add user_instructions to SessionConfig and ProviderProfile trait (spec Section 6.1 layer 5)
- Populate EnvContext with git branch, date, model name during Session::initialize()
- Expand all three profile system prompts with identity, tool guidance, coding practices
- Implement provider_options: Anthropic beta headers, OpenAI reasoning effort, Gemini safety settings
- Add register_subagent_tools() to all profiles for spawn_agent/send_input/wait/close_agent
- Add list_dir tool to Gemini profile (spec Section 3.6)
- Add chrono dependency for date formatting

20 new tests (170 → 190), all passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 14:51:13 -04:00
Bryan Helmkamp
010d09a03a Close remaining spec compliance gaps in coding-agent-loop
- Abort now transitions to CLOSED state and returns Err(Aborted)
- Closed sessions no longer emit SessionStart before rejecting input
- Add set_reasoning_effort() for mid-session reasoning effort changes
- Fix spawn_agent truncation limit from 30k to spec-required 20k
- Add JSON Schema validation of tool arguments before execution

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 14:05:16 -04:00
Bryan Helmkamp
a8c576deba Close spec compliance gaps in coding-agent-loop
Replace all stub tools in profiles with real make_*_tool() factories,
wire up project docs discovery in session, add missing events
(SessionStart, SteeringInjected, Error), enrich environment context
block, fix per-tool truncation modes, improve loop detection to check
all groups, add SIGTERM-before-SIGKILL on timeout, and update subagent
with SubAgentResult struct.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 13:54:50 -04:00
Bryan Helmkamp
890a6e9ccb Simplify unified-llm-cli: remove boilerplate, deduplicate, modernize idioms
- Remove unused tokio-stream dependency
- Replace manual Runtime::new().block_on() with #[tokio::main]
- Extract print_usage helper to deduplicate token display format
- Flatten Models match (single-variant enum destructure)
- Extract PromptArgs struct to reduce run_prompt parameter count
- Defer trim().to_string() allocation in read_stdin_prompt
- Fix #[allow(deprecated)] comment accuracy
- Replace ref q with &query (Rust 2018+ idiom)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 13:48:27 -04:00
Bryan Helmkamp
fa542605d7 Add ullm CLI for unified-llm library
New crate `unified-llm-cli` with binary `ullm` providing:
- `prompt` command: generate text via streaming/non-streaming, with
  system prompts, options (-o temperature=0.5), stdin piping, and
  token usage display
- `models list` command: browse catalog models with --provider and
  --query filters
- Auto-detects provider from catalog for correct API routing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 12:48:19 -04:00
Bryan Helmkamp
5f2385b87b Implement spec gaps: abort signal, StreamResult, provider_options, max_tool_rounds fix
- Add abort signal support using CancellationToken for cooperative cancellation
  of generate() and stream() calls
- Fix max_tool_rounds=0 to skip tool execution entirely (was executing first round)
- Add StreamResult wrapper with response(), text_stream(), partial_response()
  and multi-step tool loop support in high-level stream()
- Add OpenAI metadata and provider_options.openai pass-through to Responses API
- Add Gemini provider_options.gemini pass-through (safety settings, cached content)
- Add OpenAI-compatible provider_options.<name> pass-through using adapter name

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 11:23:22 -04:00
Bryan Helmkamp
ed07d43335 Implement spec gaps: rate limit headers, error classification, total timeout, metadata, stream_object
- Parse x-ratelimit-* headers into RateLimitInfo for Anthropic, OpenAI, and
  OpenAI-compatible providers (previously hardcoded to None)
- Add "not found"/"does not exist" and "unauthorized"/"invalid key" error
  message classification patterns for ambiguous HTTP status codes
- Apply TimeoutConfig.total to wrap the entire multi-step generate() loop
  (previously only per_step was used)
- Add metadata field to GenerateParams with builder method, pass through to
  Request instead of hardcoding None
- Implement stream_object() for streaming structured output with incremental
  JSON parsing via new ObjectStreamEvent type (Partial/Delta/Complete variants)
- Add OpenAI-compatible Chat Completions adapter for third-party endpoints

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 11:05:10 -04:00