Create comprehensive READMEs for unified-llm, unified-llm-cli, and
coding-agent-loop crates, and expand the attractor README from a
one-liner into full documentation covering key concepts, API usage,
and code examples.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
- 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>
- Add knowledge_cutoff() method to ProviderProfile trait so session can
populate EnvContext from the profile instead of leaving it empty
- Set subagent default max_turns to 50 per spec (was using session
factory default which could be 0/unlimited)
- Add corrected spec compliance review after manual verification found
the initial 5-agent review was largely false positives
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix all gaps identified by spec review against docs/specs/coding-agent-loop-spec.md:
- Replace placeholder system prompts with substantial provider-aligned prompts
for OpenAI (codex-rs style), Anthropic (Claude Code style), and Gemini
(gemini-cli style) covering identity, tool usage, and coding best practices
- Fix ExecutionEnvironment trait: add offset/limit to read_file, depth to
list_directory, path to glob, remove separate args from exec_command
- LocalEnv: use /bin/bash -c, spawn process groups with setsid, SIGTERM to
-pid, use ripgrep with grep fallback, add env var safelist
- Add environment context block with <environment> XML tags including git
branch, date, model, and knowledge cutoff fields
- Capture git context snapshot (branch, status, recent commits) on init
- Add user_instructions to SessionConfig, appended as final prompt layer
- Emit AssistantTextStart before LLM calls, SESSION_END on abort path
- Fix truncation messages to match spec wording exactly
- Implement provider_options() for all profiles (reasoning, beta headers,
safety settings)
- Add Gemini-specific tools: read_many_files, list_dir, web_search, web_fetch
- Wire spawn_agent max_turns parameter
- Fall back to working_dir for project doc discovery outside git repos
All 188 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
- 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>
resolve_model() was passing raw alias strings (e.g. "gpt5") directly to
APIs instead of resolving them to actual model IDs (e.g. "gpt-5.2").
Also rename gpt-5.2-mini to gpt-5-mini, which is the correct OpenAI
model ID.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
Extend the Middleware trait with process_stream_event() for event-level
observation/transformation of streaming responses. Add
wrap_stream_with_middleware() helper. Re-export set_default_client at the
crate root per spec Section 2.5.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
- Enforce stream_read timeout (30s default) in all 4 providers' streaming code
- Add with_timeout() builder method to all adapter constructors
- Fix ResponseFormatType::JsonObject to serialize as "json" per spec
- Add STEP_FINISH to StreamEventType enum in spec doc
- Add UnsupportedToolChoice error and enforce in all adapters via validate_tool_choice()
- Fix error classification to check status code before message content
- Add stop_sequences support to OpenAI Responses API adapter
- Handle Gemini thought parts (thought: true) in both complete and streaming paths
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- OpenAI adapter: include is_error flag on function_call_output items
- Anthropic adapter: extract retry_after from headers in streaming error path
- Error retryability: unknown errors now default to retryable per spec
- stream_object: add ObjectStreamResult wrapper with object() accessor
- TimeoutConfig: add From<f64> for total-only timeout shorthand
- Message::tool_result: accept serde_json::Value to preserve structured content
- GenerateParams: expose repair_tool_call field wired to execute_all_tools_with_repair
- Both generate() and stream() tool loops use repair-aware execution
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Anthropic adapter estimates reasoning_tokens from thinking block text lengths
- Add tool call validation against JSON schema + repair_tool_call callback
- Call adapter.initialize() on provider registration
- Extract model catalog to catalog.json data file loaded via include_str!
- Add ContentPart::Other variant for unknown/extensible content kinds
- Change StreamEvent::Error field from String to SdkError
- Add ToolContext (tool_call_id, messages, abort_signal) to execute handlers
- Gemini adapter uses gRPC status codes from error bodies for classification
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Audio/Document content part handling across all providers:
Anthropic supports documents natively, Gemini supports both audio
and documents, OpenAI and OpenAI-compatible produce text fallbacks
- Add stop_when support to streaming tool loops (was only in generate())
- Add retry on initial stream connection (matching generate() behavior)
- Add total and per_step timeout support to streaming tool loops
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add StepFinish stream event variant emitted between tool execution steps
during streaming, matching spec section 5.9. Add OPENAI_ORG_ID and
OPENAI_PROJECT_ID env var support with corresponding HTTP headers. Add
default_headers builder method to all four provider adapters for custom
header injection in programmatic setup.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Anthropic: Add response_format support via tool-based extraction (JsonSchema
injects synthetic tool, JsonObject appends system prompt instruction)
- Anthropic: Pass Request.metadata through to Messages API
- Anthropic: Refactor complete()/stream() to share build_api_request()
- Gemini: Parse rate limit headers from HTTP responses in complete() and stream()
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>
- 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>
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>
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>