AgentEvent variants were duplicated in PipelineEvent with `stage: String`
added, requiring a 130-line mechanical bridge to translate between them.
Collapse into a single `PipelineEvent::Agent { stage, event }` variant.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 9e9f00828e4b
The run and serve commands now resolve model aliases (e.g. "claude-haiku"
→ "claude-haiku-4-5-20251001") through the catalog before passing to the
backend, matching the behavior of the ullm CLI.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 2b1f7b77b1ab
- LlmRetry: wrap agent session stream() with llm::retry, emit
AgentEvent::LlmRetry and forward to PipelineEvent::LlmRetry
- failure_class on StageFailed/StageCompleted: "transient" for
retry-eligible failures, "terminal" for final failures, None for success
- ParallelEarlyTermination: emit on fail_fast break with reason,
completed_count, and pending_count
- SubgraphStarted/SubgraphCompleted: boundary events with timing and
step count for sub-pipeline execution
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 82c1bbcbe2f2
The parser used separated_list1 for attr_block which required at least
one attribute inside brackets. DOT files from kilroy use empty brackets
(e.g., `consolidate_dod []`) which caused parse failures. Changed to
separated_list0 to accept empty attribute blocks.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: bccbc8222bde
Emit missing progress NDJSON data identified in gap analysis:
- Add attempt/max_attempts fields to StageStarted, StageCompleted, StageRetrying
- Add EdgeSelected event emitted after edge selection with label/condition
- Add LoopRestart event emitted before recursive loop-restart execution
- Simplify backend.rs usage aggregation to use Usage::Add impl
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: f47850c1422d
Embed the full Usage struct (with cache_read_tokens, cache_write_tokens,
reasoning_tokens) in AssistantMessage events instead of bare input/output
token fields. Add skip_serializing_if annotations to keep NDJSON clean.
Extend StageUsage with cache/reasoning aggregation. Track files touched
via write_file/edit_file tool call correlation in the backend bridge.
Update format functions, cost accumulator, and TypeScript types.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 2c15cd247556
Thread existing data through to pipeline events to match kilroy's
progress.ndjson schema: handler_type on StageStarted, failure_reason on
StageFailed/StageCompleted, notes on StageCompleted, join_policy and
error_policy on ParallelStarted, status (replacing success bool) on
ParallelBranchCompleted, and question_type on InterviewStarted. Adds
Display impls for QuestionType, JoinPolicy, and ErrorPolicy.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 2d4a98c3b5f0
When context usage exceeds the configurable threshold (default 80%), older
turns are summarized via a non-streaming LLM call and replaced with a single
System turn. Compaction is non-fatal — errors are emitted as AgentEvent::Error
and the session continues.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 18435f31db91
Restructures agent events from misaligned EventKind+EventData pair into flat
AgentEvent enum, enriches AssistantMessage with model/token/tool_call data,
adds 8 new PipelineEvent variants (Prompt, AssistantMessage, ToolCallStarted,
ToolCallCompleted, SessionError, ContextWindowWarning, LoopDetected,
TurnLimitReached), and forwards agent session events to the pipeline emitter
in AgentBackend so they appear in progress.ndjson.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: d59f9e69008a
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>
Every PipelineEvent is now logged as a JSON envelope with timestamp,
run_id, and event fields — matching the Kilroy reference implementation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Removed the workspace-level clippy lint config that enabled all, pedantic, nursery,
and cargo lint groups. Removed all #[allow(clippy::...)] annotations that were only
needed to suppress those extra lints, and fixed the few default clippy warnings that
were uncovered.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
Downstream nodes in multi-model pipelines reference response.<node_id>
context keys in their prompts. The codergen handler was only storing
last_response (truncated) and last_stage, so these references resolved
to nothing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Surface LLM token consumption and dollar cost at every verbosity level:
default mode appends per-stage tokens/cost, verbose modes include it in
event summary/detail, and the Pipeline Result section shows a total.
Cost is computed from the catalog pricing for Anthropic models; providers
without pricing (OpenAI, Gemini) show token counts only. Dry runs with
zero tokens omit the cost line entirely.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
Show per-stage completion/failure timing and total pipeline duration
in the result block, even without -v flag. Uses a new
format_duration_human helper for human-readable durations (ms/s/m s).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
- Stylesheet: add bare-word Shape selector (specificity between Universal and Class)
- LLM: apply per_step timeout to connection and total timeout to stream (Section 4.7)
- Interviewer: add MultiSelect question type alongside MultipleChoice
- Session: move SessionStart/SessionEnd to initialize()/close(), deduplicate close logic
- Engine: return Ok(fail outcome) instead of error when goal gate unsatisfied with no retry_target
- Docker: mark Docker-dependent tests with #[ignore]
- Validation: add extensive unit test coverage for all rule types
- Integration: update tests to match engine/fidelity changes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>
Add status, preferred_label, and suggested_next_ids fields to the
StageCompleted event so pipeline flow decisions are visible in CLI
output and the web UI, making it easier to debug why a pipeline
took a particular path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ParallelHandler and SubPipelineHandler needed Arc<HandlerRegistry> at
construction time but also lived inside the registry, creating a circular
dependency. The previous fix special-cased ParallelHandler as a separate
field on PipelineEngine with a resolve_handler() override.
Instead, add an EngineServices struct (registry + emitter) passed through
Handler::execute(). ParallelHandler and SubPipelineHandler become unit
structs that get what they need at execution time. No special-casing,
both register normally in default_registry() like every other handler.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The spec says "exactly one" exit node in the shape table (line 184),
exit handler docs (line 648), and test criteria (line 1834). The lint
rule table (line 1437) says "at least one" but is the minority. The
previous commit incorrectly reverted this — the terminal node change
was not causing the test failures (all three were from retry-on-Fail).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Revert two incorrect behavioral changes introduced in ceaa9bf:
1. StageStatus::Fail must return immediately, not retry. Fail is a
deliberate routing outcome (e.g. to take a "fail" edge). Retrying it
caused conditional_branching and manager_loop tests to hang.
2. Pipelines can legitimately have multiple terminal nodes. The
"exactly one" constraint broke branching_loop_back_on_failure.
Restore "at least one" validation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The parallel handler was never registered in default_registry() due to a
circular dependency (ParallelHandler::new needs Arc<HandlerRegistry>).
Break the cycle by storing ParallelHandler as a separate field on
PipelineEngine, created after Arc-wrapping the registry and emitter.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Break out of streaming loop on abort and drop the stream before emitting
SessionEnd to properly cancel the HTTP connection
- Emit ToolCallOutputDelta events for tool call results in both sequential
and parallel execution paths
- Retry on StageStatus::Fail in addition to Retry in pipeline engine
- Set preferred_label on WaitHumanHandler choice outcomes
- Enforce exactly one terminal node in pipeline validation
- Downgrade unreachable node diagnostic from Error to Warning
- Update context window test to match 1M token limit
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When CodergenHandler receives CodergenResult::Text, it now extracts
preferred_next_label, suggested_next_ids, and context_updates from
the last JSON object in the response. This enables edge selection
via condition matching and preferred label instead of always falling
through to unconditional edges.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch default model from claude-sonnet-4-5 to claude-opus-4-6 across
run and serve commands. Enable the 1M token context window via the
context-1m-2025-08-07 beta header for opus-4-6 requests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Updates the default Gemini model from gemini-3-pro-preview to
gemini-3.1-pro-preview across agent, attractor, and ullm CLIs.
Adds "Using model:" output to stderr in both agent and ullm CLIs
so users can see which model is being used.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When --docker is passed, agent tools execute inside a Docker container
via DockerExecutionEnvironment instead of running on the host. The host
working directory is bind-mounted into the container at /workspace.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a list endpoint that returns all in-memory pipelines with their
status, and a "Recent Pipelines" section in the start form that polls
every 3s so users can navigate to any pipeline started since server launch.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ApiQuestion now includes options and allow_freeform fields so API clients
can render multiple-choice questions. submit_answer accepts an optional
selected_option_key to produce AnswerValue::Selected instead of always
creating AnswerValue::Text.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The handler was converting all backend errors into Ok(Outcome::fail(...)),
which the engine treats as a terminal result. Retryable errors (timeouts,
network failures) are now propagated as Err so the engine retry loop kicks in.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
Stores DOT source in ManagedPipeline and pipes it through `dot -Tsvg`
on request. Returns image/svg+xml on success, 502 if graphviz is
unavailable, 404 if pipeline not found. Resolves spec gap #1.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
Closes the subgraph class derivation test gap by adding a parse() pipeline
test that verifies DOT subgraph labels produce correct CSS-like classes on
contained nodes. Updates gap analysis to remove resolved items, add spec
contradictions section, and narrow remaining gaps to SVG rendering and
retry predicate customization.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Gap 1: Real preamble synthesis per fidelity mode (truncate, compact,
summary:low/medium/high) in new preamble.rs module, replacing placeholder
- Gap 2: Pass thread_id to CodergenBackend.run() so backends can reuse
LLM sessions across nodes sharing the same thread
- Gap 7: Engine cancellation via AtomicBool token checked between nodes,
wired to server cancel endpoint, new Cancelled error variant
- Gap 8: RecordingInterviewer serialization (to_json/from_json, file I/O)
and new ReplayInterviewer for replaying recorded Q&A sessions
- Gap 12: Preset retry policies selectable from DOT via retry_policy attr
(none, standard, aggressive, linear, patient)
- Gap 15: Engine calls Interviewer.inform() at pipeline start, stage
start, and stage complete lifecycle points
37 new tests (450 unit + 49 integration, all passing).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>
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>
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>