Commit graph

504 commits

Author SHA1 Message Date
Bryan Helmkamp
fd70d41fca Simplify hook system after code review
- Replace JSON-based HookEvent::Display with simple match arms
- Use floor_char_boundary for Unicode-safe truncation in effective_name
- Cache compiled regexes in HookRunner instead of recompiling per check
- Simplify run_non_blocking (was run_parallel) to plain sequential loop
- Propagate hook_runner through parallel handler branch services
- Use unique temp file paths for sandbox hook context (avoid collisions)
- Add hook imports to engine.rs, reducing verbose crate:🪝: paths
- Extract duplicate RunFailed hook code into run_failed_hook helper

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 01:49:53 -05:00
Bryan Helmkamp
55d44f7488 Add lifecycle hook system replacing legacy tool_hooks
Introduce a configurable hook system that triggers user-defined actions
at workflow lifecycle points (RunStart, StageStart, StageComplete,
StageFailed, EdgeSelected, CheckpointSaved, etc). Hooks can block
execution, skip nodes, or override edge routing via JSON decisions.

- New `hook/` module: types, config, executor (command), runner
- Engine instrumented at 8 lifecycle points with HookRunner calls
- TOML config: `[[hooks]]` in server.toml and run config files
- Config cascade: server hooks + run hooks merge, name collisions
  resolved by run config winning
- Remove legacy tool_hooks.pre/post from codergen handler (breaking)
- 30 e2e integration tests covering all hook events and behaviors

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 01:42:19 -05:00
Bryan Helmkamp
b57e30efad Rename run-files-changed.tsx to run-compare.tsx to match route
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 00:57:52 -05:00
Bryan Helmkamp
26f08a904e Rename GET /runs/{id}/files to GET /runs/{id}/compare
Rename the endpoint, schema (RunFiles -> RunCompare), operation ID,
handlers, and frontend route across the full stack.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 00:53:45 -05:00
Bryan Helmkamp
a40c6bdd29 Add QuestionType enum to OpenAPI spec replacing stringly-typed field
The question_type field on ApiQuestion was a bare string serialized via
Debug formatting. Define a proper enum in the spec so typify generates a
typed QuestionType, then map from the workflow enum in the handler.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 00:49:03 -05:00
Bryan Helmkamp
4f1297572d Extract typed ListResponse struct to eliminate double serialization
Replace inline json!() wrappers with a shared ListResponse<T> struct
that serializes directly, avoiding the intermediate serde_json::Value.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 00:46:11 -05:00
Bryan Helmkamp
14322f40c2 Wrap non-paginated list endpoints in paginated response shape
Wrap 4 list endpoints that returned bare arrays in the standard
paginated `{ data, meta: { has_more } }` shape so adding real
pagination later is additive rather than a breaking change.

Endpoints: GET /runs/{id}/questions, /runs/{id}/stages,
/runs/{id}/verifications, and /verifications.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 00:41:32 -05:00
Bryan Helmkamp
40b707b077 Use typed enums in cli.toml config and simplify defaults
- Deserialize permissions/output_format as typed enums instead of strings
  so invalid values in cli.toml fail at parse time
- Use Option::or/or_else combinators instead of if-is_none pattern
- Load cli.toml only for agent/llm commands, not all CLI invocations
- Standalone arc-agent binary calls apply_cli_defaults for single source
  of hardcoded defaults
- Remove redundant #[serde(default)] on Option fields

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 00:37:43 -05:00
Bryan Helmkamp
d0c7ac342e Remove unused per-service Dockerfiles
These are superseded by the unified root Dockerfile with entrypoint router.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 00:34:03 -05:00
Bryan Helmkamp
129b8b98c4 Add Docker Compose demo setup for local development
Multi-stage Dockerfile builds Rust API binary and bundles the web app
and docs into a single image. docker-compose.demo.yaml orchestrates
the three services (api, web, docs) with a shared entrypoint router.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 00:32:48 -05:00
Bryan Helmkamp
7580be75ea Add ~/.arc/cli.toml config for persistent CLI defaults
Users who always use the same provider/model/permissions no longer need
to pass flags every time. Precedence: CLI flag > cli.toml > hardcoded default.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 00:31:31 -05:00
Bryan Helmkamp
a9a59bfc7f Simplify failover implementation after code review
- Define failover_eligible() in terms of retryable() to prevent drift
  (only difference: QuotaExceeded is failover-eligible but not retryable)
- Extract spawn_event_forwarder() to eliminate duplicated event-forwarding
  spawn blocks and fix missing file-change tracking in failover path
- Replace hardcoded "anthropic" default with self.provider.as_str()
- Refactor create_session into create_session_for(model, provider) to
  avoid constructing throwaway AgentApiBackend during failover
- Use &[FallbackTarget] slice instead of cloning Vec on every one_shot
- Remove redundant run_defaults fallback in resolve_fallback_chain
  (apply_defaults already merges fallbacks before it's called)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 00:23:02 -05:00
Bryan Helmkamp
da2ea8ebc3 Add provider-level model failover for LLM resilience
When an LLM provider returns a transient error (rate limit, server error,
quota exceeded, timeout, network, or stream failure), Arc now automatically
retries on fallback providers using the closest matching model from the
catalog based on capability filters and cost proximity.

Key changes:
- closest_model() and build_fallback_chain() in arc-llm catalog
- failover_eligible() on SdkError to classify transient vs deterministic errors
- fallbacks config field on LlmConfig with task-wins-over-defaults merging
- WorkflowRunEvent::Failover variant for observability
- Failover logic in both one_shot and agent session (run) code paths

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 00:17:04 -05:00
Bryan Helmkamp
38be58028d Clean up expand_variables: add HashMap import, use from() constructor
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 00:08:06 -05:00
Bryan Helmkamp
7a83cb11ed Error on unknown $variable placeholders in DOT prompts
Reuse expand_vars() from run_config to scan for $identifier patterns
in codergen prompt expansion. Unknown variables like $gaol now produce
an ArcError::Validation at runtime instead of silently passing through.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 00:05:11 -05:00
Bryan Helmkamp
3470044ece Extract paginated_response helper to deduplicate demo handlers
Replaces 10 identical copies of the pagination block with a single
shared function. Also eliminates a redundant second collect() by
using truncate() instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 00:00:52 -05:00
Bryan Helmkamp
af12b0b697 Add offset-based pagination to 9 list endpoints
Apply the same page[limit]/page[offset] pagination pattern from
GET /runs to: workflows, workflow runs, retros, sessions, projects,
branches, saved queries, query history, and stage turns.

Each endpoint now returns { data, meta: { has_more } } instead of
a bare array. Includes OpenAPI spec updates, demo handler changes,
regenerated TS client, updated frontend consumers, and a new
pagination conformance test.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 23:56:32 -05:00
Bryan Helmkamp
ba90d1e90e Add --config CLI arg to override server config path
load_server_config() now accepts an optional explicit path. When
provided, it reads from that path (erroring if missing) instead of
the default ~/.arc/server.toml. The --config flag is wired through
ServeArgs and the hot-reload polling loop.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 23:36:32 -05:00
Bryan Helmkamp
15da1c04b9 Remove unused server_config from AppState, use read lock for config comparison
- AppState.server_config was stored but never read; remove it and revert
  create_app_state_with_options back to 5 parameters
- Config polling now compares under a read lock first, only acquiring
  the write lock when a change is detected

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 23:23:34 -05:00
Bryan Helmkamp
3df2961d22 Add hot config reload for server.toml with catalog-driven model defaults
Poll ~/.arc/server.toml every 5s and swap run_defaults/git config for new
runs without restarting. CLI overrides (--model, --provider) always win.
On parse error, log a warning and keep the previous config.

Also add a `default` field to the model catalog so default model resolution
uses catalog data instead of hardcoded model names in Rust code.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 23:11:35 -05:00
Bryan Helmkamp
9274208c24 Change default max_concurrent_runs from 4 to 5
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 23:01:55 -05:00
Bryan Helmkamp
b268efd85f Add global concurrency limiter with queued/starting run states
Runs now go through a state machine (queued → starting → running →
completed/failed/cancelled) instead of spawning immediately. A background
scheduler promotes queued runs when capacity is available, defaulting to
4 concurrent runs. Configurable via --max-concurrent-runs CLI flag or
max_concurrent_runs in server.toml.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:56:19 -05:00
Bryan Helmkamp
1d0e45ffdf Add offset-based pagination to GET /runs
Introduce page[limit]/page[offset] query params and { data, meta: { hasMore } }
response wrapper for the /runs endpoint, establishing the pagination pattern
for all future list endpoints.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:32:57 -05:00
Bryan Helmkamp
ae6dd66384 Simplify AggregateUsageTotals: named struct, derive totals from by_model
Replace opaque (i64, i64, i64, f64) tuple with ModelUsageTotals struct
for readable field access. Remove redundant top-level token/cost fields
that duplicated by_model sums — derive them at read time instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:04:28 -05:00
Bryan Helmkamp
43a0c18161 Add GET /usage endpoint for aggregate token/cost accounting across runs
Tracks total runs, input/output tokens, cost, and runtime in-memory
(resets on server restart). Accumulates from checkpoint node_outcomes
when runs complete. Includes per-model breakdown.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 21:56:50 -05:00
Bryan Helmkamp
3e68c41f4e Adopt uniform error response format across Arc API
All API errors now return a consistent JSON shape:
{"errors": [{"status": "4xx", "title": "...", "detail": "..."}]}

Introduces ApiError type with IntoResponse impl, replaces bare
StatusCode returns and ad-hoc {"error": "..."} responses in all
handlers and auth extractors. Updates OpenAPI spec and regenerates
TypeScript client.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 21:32:24 -05:00
Bryan Helmkamp
af7a2e52eb Add wait.timer node type for sleeping a configured duration
Maps the Graphviz "insulator" shape to a new wait.timer handler that
reads a duration attribute and sleeps before proceeding with success.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 21:24:10 -05:00
Bryan Helmkamp
02e233a0b5 Add Gemini 3.1 Flash Lite to model catalog
Google's most cost-efficient model ($0.25/M input, $1.50/M output) with
1M context, 65K output, and full tool/vision/reasoning support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 21:17:28 -05:00
Bryan Helmkamp
bb0fdfe972 Add /, /health, /openapi.json, /user endpoints to Arc API
Add discovery, health check, OpenAPI spec, and current user endpoints.
The first three are public; /user requires authentication and returns
the login extracted from JWT sub claim, mTLS CN, or "demo" in demo mode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 21:10:59 -05:00
Bryan Helmkamp
8a2595ad82 spec-dod-multimodel.png 2026-03-04 17:38:48 -05:00
Bryan Helmkamp
bbd71dfbc0 Fix pre-existing clippy lints
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 13:00:02 -05:00
Bryan Helmkamp
289451e78a Extract CheckReport into arc-util and use it for preflight output
Move CheckReport, CheckResult, CheckDetail, and CheckStatus types from
arc-cli doctor into arc-util so they can be reused. Refactor the workflow
run_preflight to render a styled check report instead of ad-hoc key=value
output and separate stderr errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 11:52:58 -05:00
Bryan Helmkamp
d283ece683 Fix demo mode: install rustls CryptoProvider, skip TLS, add ARC_DEMO env var
- Install ring CryptoProvider at CLI startup to prevent rustls panic
- Skip TLS in demo mode so the server uses plain HTTP
- Add ARC_DEMO=1 env var to web app config to bypass GitHub OAuth

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 11:51:57 -05:00
Bryan Helmkamp
93afb4bf6d Fix HTTP 529 (Overloaded) misclassified as non-retryable InvalidRequest
The Anthropic API returns HTTP 529 for overload conditions, but this
status code was falling through to the catch-all which classified it as
InvalidRequest (non-retryable). This meant neither LLM-level nor
node-level retries would trigger, causing workflow nodes to fail
immediately on transient overload errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 11:34:18 -05:00
Bryan Helmkamp
9556dcdcb1 Add stylesheet_model_known lint rule to validate model/provider names
Checks llm_model and llm_provider values in model_stylesheet against the
built-in catalog during `arc validate`. Unknown models or providers emit
warnings (not errors) since the catalog is advisory, not restrictive.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 11:34:04 -05:00
Bryan Helmkamp
5fd4a5ea2e Collapse whitespace in ProgressUI tool call display to prevent line breaks
Multi-line arguments (e.g. spawn_agent task descriptions) were breaking
the single-line progress output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 10:25:43 -05:00
Bryan Helmkamp
28b5d485c1 Render Markdown output in terminal using termimad
LLM responses were printing raw Markdown (bold markers, table pipes,
heading hashes). Use termimad to render with proper ANSI formatting
when color is enabled, falling back to plain text when NO_COLOR is set.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 10:25:36 -05:00
Bryan Helmkamp
e6bbac771d Show failure error message when a stage fails in ProgressUI
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 10:17:02 -05:00
Bryan Helmkamp
c959368452 Redesign --verbose mode to use ProgressUI instead of raw event logs
Both verbose and normal modes now use the same indicatif-based ProgressUI
renderer with the same visual hierarchy (indentation, glyphs, colors).
In verbose mode: tool calls persist after stage completion, no 5-call cap,
stage completion shows stats (turns, tool calls, tokens), and additional
events are rendered (edge transitions, loop restarts, setup commands,
retries, context warnings, compaction, subagents).

Remove the old format_event_summary function and its ~70 tests, the
verbose stderr printing from AgentApiBackend, and the verbose/styles
fields that are no longer needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 09:48:10 -05:00
Bryan Helmkamp
45165c1fa3 Add multi-provider ensemble demo and generate PNG/SVG for all demo workflows
New demo 11-ensemble.dot fans out to Opus (Anthropic), Gemini (Google),
Codex (OpenAI), and Mercury (Inception) in parallel, then synthesizes
results. Also generates PNG and SVG renderings for all existing demo
dot files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 09:47:54 -05:00
Bryan Helmkamp
13ad7d948a Add --preserve-sandbox flag to keep sandboxes alive for debugging
Adds a `--preserve-sandbox` CLI flag and `sandbox.preserve` TOML config
option that skips sandbox cleanup after a run. Cascade order:
CLI flag > run TOML > server.toml defaults > false.

When preserving, prints sandbox identity (container ID or Daytona
sandbox name) so users can reconnect for debugging.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 09:32:17 -05:00
Bryan Helmkamp
49ac81624b Simplify SSH access API and add parallel fan-in jump_to_node
- Simplify create_ssh_access to return String directly
- Add jump_to_node field to Outcome for parallel handler fan-in
- Find convergence node from branch outgoing edges
- Use ..Outcome::success() spread in tests and error paths

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 09:23:01 -05:00
Bryan Helmkamp
b6bc58073b Simplify create_ssh_access return type and signature
Return just the SSH command string instead of the full SshAccessDto,
and hardcode the 60-minute expiration internally.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 09:22:39 -05:00
Bryan Helmkamp
e64f1e3c47 Add === Retro === section header before retro output
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 09:15:12 -05:00
Bryan Helmkamp
932524dd98 Print retro summary inline in CLI output
Show smoothness rating, outcome, friction/open item counts, and file
path after retro completes instead of just the file path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 09:13:01 -05:00
Bryan Helmkamp
97d370b727 Add --ssh flag to arc run for Daytona SSH access
Creates SSH access after sandbox init and displays the connection
command in the progress output, aligned under the sandbox detail line.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 09:11:39 -05:00
Bryan Helmkamp
6dc359f867 Show parallel branches in progress UI
Handle ParallelBranchStarted/Completed events as nested entries under
the fork stage, using the same indented spinner styling as tool calls.
Branch bars persist after the stage completes so they remain visible
in the final output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 08:58:44 -05:00
Bryan Helmkamp
5ab740b9d3 Fix sandbox detail line positioning with insert_after
Use insert_after instead of add so the detail bar renders directly
below the sandbox bar, not at the bottom of the multi-progress.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 08:49:13 -05:00
Bryan Helmkamp
990b2e9124 Add sandbox details and hyperlink to progress output
Show sandbox name, CPU, and memory on a second indented detail line
beneath the "Sandbox: provider" line. Hyperlink the provider name to
its dashboard URL using OSC 8 terminal escape sequences (TTY only).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 08:45:42 -05:00
Bryan Helmkamp
bde59168c9 Fix sandbox cleanup race and improve warning formatting
Sandbox cleanup was spawned fire-and-forget in a scopeguard, causing DNS
resolution to be cancelled when the tokio runtime shut down before the
HTTP request completed. Now cleanup is awaited explicitly before
returning, with the scopeguard kept only as a safety net for panics.

Also: add blank line before the warning and color-code it yellow to
match other warnings. Update daytona-sdk to include full error source
chain in error messages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 08:29:05 -05:00