Commit graph

367 commits

Author SHA1 Message Date
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
Bryan Helmkamp
cfd1d5ccea Simplify codebase: deduplicate providers, remove redundancy, clean up types
Extract shared provider helpers (parse_error_body, send_and_read_body,
extract_system_prompt, ApiMessage) into providers/common.rs, eliminating
duplicated logic across all three providers. Simplify StepResult and
GenerateResult to derive fields from the embedded Response rather than
storing redundant copies. Remove stringly-typed ToolCall.r#type and
ResponseFormat.r#type in favor of proper enums. Remove unused dependencies
(hyper, clap, rayon, base64, etc.), use LazyLock for the model catalog,
and fix miscellaneous idiom issues (unnecessary Vec collects, inconsistent
error Display, module_name_repetitions).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 18:38:02 -04:00
Bryan Helmkamp
be7f78ba9f Replace stringly-typed structs with proper Rust enums and deduplicate types
- ContentPart: struct with kind discriminant + 7 Option fields → enum with 8 variants
- StreamEvent: struct with type discriminant + 8 Option fields → enum with 13 variants
- FinishReason: struct wrapping String → enum (Stop, Length, ToolCalls, etc.)
- ToolChoice: struct with mode String → enum (Auto, None, Required, Named)
- SdkError: 9 provider variants with identical fields → Provider { kind, detail }
- Merge ToolCallData into ToolCall, remove ToolResultData in favor of ToolResult
- Add provider implementations (anthropic, openai, gemini) and integration tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 16:20:45 -04:00
Bryan Helmkamp
38a2cb0a0a Enable all clippy lints and fix all warnings
Enable clippy::all, pedantic, nursery, and cargo lint groups at the
workspace level. Fix all resulting warnings: merge identical match arms,
add #[must_use] and doc sections, derive Eq, use clone_from, add type
alias for complex types, extract helpers to reduce function length, and
add crate metadata.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 15:41:43 -04:00