Commit graph

480 commits

Author SHA1 Message Date
Bryan Helmkamp
9a823447fa Pass [sandbox.env] through API backend tool execution
Previously sandbox env vars only reached CLI backend agents but not
API backend tool calls. Thread tool_env through ToolContext so shell
and web_fetch tools pass env vars to exec_command for all backends.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 23:13:51 -05:00
Bryan Helmkamp
13ee1735c0 Consolidate duplicate test backends in prompt handler tests
Hoist OneShotCapturingBackend to module scope and remove two identical
CapturingBackend definitions that duplicated its functionality.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-07 23:01:15 -05:00
Bryan Helmkamp
eb2ff9dd5c Add project_memory attribute for prompt nodes
Prompt nodes now discover project docs (AGENTS.md, CLAUDE.md, etc.)
and pass them as a system prompt to one_shot LLM calls. The
project_memory attribute defaults to true and can be set to false
to disable this behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-07 22:56:26 -05:00
Bryan Helmkamp
5d6691df22 Add [sandbox.env] support to workflow configs
Allow passing environment variables into sandbox command execution via
`[sandbox.env]` in TOML configs. Supports literal values and host env
passthrough via `${env.VARNAME}` syntax (whole-value only, missing vars
are hard errors). Env vars are injected into command nodes via
`cmd.envs()` and into CLI backend agents via the sandbox env file.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 22:43:16 -05:00
Bryan Helmkamp
006f198014 Rename stale agent variable to exec in cli_config tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 22:42:15 -05:00
Bryan Helmkamp
1825b61b0e Rename arc agent CLI subcommand to arc exec
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 22:38:03 -05:00
Bryan Helmkamp
6cee3e1926 DRY up telemetry ID loading and Telemetry construction
Extract read_existing_id() helper to deduplicate file-read-trim-check
pattern, and extract private Telemetry::new() to consolidate for_server/for_cli.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 22:03:58 -05:00
Bryan Helmkamp
f4b530c691 Split anonymous ID into server (UUID-on-disk) and CLI (MAC-based hash) strategies
Server is long-lived on a fixed host, so a persisted UUID at ~/.arc/.id is
appropriate. CLI runs ephemerally, so an MD5 of the MAC address avoids file
I/O and is stable per-machine. CLI falls back to ~/.arc/.id if it exists
for migration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 22:00:44 -05:00
Bryan Helmkamp
2b8d6a40a1 Add Segment-based telemetry module to arc-util
Adds a telemetry library for product analytics with Segment integration.
Includes Track/User wire types, persistent anonymous ID (~/.arc/anonymous_id),
OS/arch/locale context, fire-and-forget sender via tokio::spawn, and
ARC_TELEMETRY env var control (off/errors/all). No CLI or server integration yet.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 21:47:03 -05:00
Bryan Helmkamp
d56ed52490 Inline untracked @file references in DOT prompts at prepare time
Cloud sandboxes clone the repo, so untracked local files referenced via
prompt="@path/to/file.md" won't exist. This inlines those file contents
at prepare time while leaving git-tracked @references for the agent to
read from the sandbox.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 21:32:53 -05:00
Bryan Helmkamp
c62d9b68d9 Simplify opaque OpenAI item handling and DockerfileSource serde
- Extract ContentPart::is_opaque_openai() to deduplicate matches! patterns
- Use std::mem::take to avoid cloning reasoning/message items
- Replace hand-rolled DockerfileSource serde with derive + untagged enum
- Add --fail-with-body to imagegen curl for better error reporting
- Add tmp to .gitignore

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 21:16:27 -05:00
Bryan Helmkamp
22a1bba192 Add --goal CLI arg to arc run start
Allows overriding the workflow goal from the command line, which is
exposed as $goal in node prompts via VariableExpansionTransform.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 21:11:34 -05:00
Bryan Helmkamp
6f1bd9cff6 Support dockerfile = { path = "..." } in snapshot config
Add DockerfileSource enum (Inline/Path) with custom serde to allow
referencing a Dockerfile by path instead of embedding content inline.
Paths are resolved relative to the config file directory during loading.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 20:54:55 -05:00
Bryan Helmkamp
e73803039c Extract ContentPart::OPENAI_REASONING and OPENAI_MESSAGE constants
Replace 16 raw string literal usages of "openai_reasoning" and
"openai_message" across openai.rs and history.rs with constants
defined on ContentPart, eliminating typo risk and centralizing
the kind identifiers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 18:43:39 -05:00
Bryan Helmkamp
ca71b2895d Preserve OpenAI message output items for Responses API round-tripping
When an assistant turn has reasoning + text + tool calls, the message
output item was reconstructed without its original `id` and `status`
fields. The Responses API requires reasoning items to be followed by
a valid output item identified by `id`, so the reconstructed message
was not recognized, causing "Item 'rs_...' was provided without its
required following item" errors.

Preserve the full message output item as an opaque `openai_message`
provider part (like we already do for `openai_reasoning`), and use it
in translate_input instead of constructing a new message from text.
Also strip `openai_message` items during compaction alongside reasoning.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 18:35:06 -05:00
Bryan Helmkamp
f0c97a2739 Add server-mode support for arc llm chat via session endpoints
Implement the server-side session handlers (create, retrieve, send message,
stream events, list) with in-memory storage and LLM generation, wire them
into the router replacing not_implemented stubs, add run_chat_via_server
CLI function with SSE streaming, and add mode dispatch so `arc llm chat
--mode server` delegates to the API server.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 18:34:15 -05:00
Bryan Helmkamp
21f94f48de Add POST /completions endpoint with Anthropic-style SSE streaming
Adds a completions API endpoint that supports both streaming (SSE) and
non-streaming (JSON) modes, with structured output via JSON Schema.
Wires up the CLI `arc llm prompt` command to use the server when
`--mode server` is specified.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 18:06:58 -05:00
Bryan Helmkamp
0c26bf060c Strip OpenAI reasoning items from provider_parts after compaction
After compaction replaces old turns with a summary, preserved Assistant
turns may contain opaque openai_reasoning items that reference the now-
removed context. These orphaned items violate the OpenAI Responses API
constraint that reasoning items must be followed by their paired output,
causing "Item 'rs_...' was provided without its required following item"
errors. Strip them in a new strip_opaque_reasoning() method called at
the end of compact(). Anthropic thinking blocks are left untouched.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 18:06:58 -05:00
Bryan Helmkamp
c3ec637f09 Add arc-sprites crate for Sprites (Fly.io) VM sandbox
Implements the Sandbox trait backed by the `sprite` CLI binary.
Includes 29 unit tests with mock runner and an e2e integration test
against the live Sprites service.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 18:06:58 -05:00
Bryan Helmkamp
ac535b925b Remove Start Workflow Run endpoint (duplicates Start Run)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 18:06:58 -05:00
Bryan Helmkamp
add14c63ee Remove List Projects and List Branches API endpoints
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 18:06:58 -05:00
Bryan Helmkamp
1dcb4acb2a Accept bare @@ hunk headers in v4a apply_patch parser
GPT-5.4 frequently emits bare @@ instead of @@ context @@, causing all
Update File patches to fail. The parser now accepts bare @@ and locates
the hunk position from the first remove/context change line. Also
improves the system prompt with an explicit @@ context @@ example.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 18:06:57 -05:00
Bryan Helmkamp
f36ec7aab4 Replace git_author_name/email parameter pairs with GitAuthor struct
Consolidates the two separate String parameters (git_author_name,
git_author_email) threaded through ~10 function signatures into a
single GitAuthor struct with Default providing "arc"/"arc@local".
Also quotes git config values in parallel.rs shell commands.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 18:06:57 -05:00
Bryan Helmkamp
c9265749eb Handle OpenAI reasoning SSE events to prevent stall watchdog kills
The OpenAI Responses API sends reasoning_summary_text.delta and
reasoning_text.delta SSE events during extended reasoning, but the
OpenAI provider silently swallowed them. This caused the tight-loop
in process_next_sse_events to consume events without yielding any
StreamEvent, so emitter.touch() was never called and the 600s stall
watchdog fired during long reasoning phases (e.g. GPT-5.4-pro).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 18:06:57 -05:00
Bryan Helmkamp
99ee007572 Add configurable git author identity for checkpoint commits
Users can now configure the git author name/email used for checkpoint
commits via [git.author] in server.toml (default) and cli.toml (override).
Defaults to "arc" / "arc@local" preserving current behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 18:06:57 -05:00
Bryan Helmkamp
02a10b2f8d Fix stall watchdog killing agent during long LLM reasoning turns
Move emitter.touch() before the event filter in spawn_event_forwarder
so streaming events (TextDelta, AssistantTextStart, etc.) also reset
the watchdog timer. Previously these were filtered out, causing the
600s stall watchdog to fire during long generation turns with no
tool calls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 18:06:57 -05:00
Bryan Helmkamp
501ff3df40 Push metadata branch to origin after checkpoint in Remote (Daytona) mode
After each checkpoint commit, the metadata branch (checkpoint.json, manifest,
graph DOT, artifacts) is now pushed from the host process to the GitHub remote
using a GitHub App installation token. The local custom ref (refs/arc/{run_id})
is mapped to refs/heads/arc/meta/{run_id} on the remote since GitHub rejects
branch names starting with "refs/".

Changes:
- Move ssh_url_to_https to github_app.rs as pub fn for reuse
- Add push_ref() to git.rs for pushing a ref to an explicit URL
- Add github_app field to RunConfig to thread credentials into the engine
- Add git_push_meta_host() async wrapper in engine.rs
- Call git_push_meta_host after each remote checkpoint

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 13:08:49 -05:00
Bryan Helmkamp
95d95e81b1 Rename arc models CLI subcommand to arc model
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 12:47:07 -05:00
Bryan Helmkamp
9b842c49d5 Simplify git add pathspec building by hoisting common arg
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 12:29:10 -05:00
Bryan Helmkamp
314ee9be99 Add checkpoint exclude globs to skip bulky artifacts from git checkpoint commits
Introduces a [checkpoint] config table with exclude_globs in both run.toml
(per-run) and server.toml (defaults). Globs are merged (union + dedup) when
both are present. Non-empty excludes use git pathspec :(glob,exclude) syntax
to prevent staging matching files during checkpoint commits.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 12:25:27 -05:00
Bryan Helmkamp
7836eb4eaa Separate serve logs from CLI logs
`arc serve` writes to `serve-YYYY-MM-DD.log` and all other commands
write to `cli-YYYY-MM-DD.log` so the two are easy to tail independently.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 11:31:12 -05:00
Bryan Helmkamp
d2c6b50c90 Add POST /models/{id}/test endpoint for server-mode model testing
Enables `arc models test` to work in server mode by adding an API
endpoint that sends "Say OK" (max_tokens=16, 30s timeout) to a model
and reports pass/fail. Dry-run mode returns synthetic "ok" status.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 11:17:10 -05:00
Bryan Helmkamp
73d2d7918c Add --mode standalone|server CLI support for arc models list
Commands can now delegate to a running Arc API server instead of
executing in-process. Adds ExecutionMode, ServerDefaults, and
ClientTlsConfig to cli.toml parsing with CLI flag > config > default
precedence. The models list command fetches from GET /models when in
server mode, with mTLS client certificate auth when [server.tls] is
configured.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 11:03:55 -05:00
Bryan Helmkamp
c1630b5a21 Restructure ModelInfo into nested shape with new fields
Replaces flat ModelInfo fields with nested sub-structs (ModelLimits,
ModelFeatures, ModelCosts) and adds family, training, and
cache_input_cost_per_mtok fields to enrich the model catalog.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 10:44:25 -05:00
Bryan Helmkamp
4a95fac48f Remove unnecessary Vec allocation in debug log
HashMap::keys() already implements Debug, no need to collect into a Vec.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 10:38:01 -05:00
Bryan Helmkamp
4cefad86eb Fix sub-workflow context diff leaking child internals into parent
Filter engine-internal keys (internal.*, graph.*, thread.*, current*)
from the context diff returned by SubWorkflowHandler, preventing child
run state from overwriting parent values. Pass the parent's preamble
into the child context so child workflows have awareness of what the
parent already accomplished.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 10:31:50 -05:00
Bryan Helmkamp
b6c549e1fc Simplify list_models: pass ModelInfo directly to paginated_response
ModelInfo already derives Serialize with matching field names, so the
manual json!({...}) mapping was redundant. Matches the pattern used by
every other paginated handler in demo/mod.rs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 10:16:36 -05:00
Bryan Helmkamp
116046502c Add GET /models endpoint returning paginated LLM catalog
Exposes the embedded model catalog (same data as `arc models list`) via
a new authenticated API endpoint so the web UI can display available models.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 10:12:21 -05:00
Bryan Helmkamp
72c1e0759f Rename verification API: categories → criteria, separate controls endpoints
Restructure the verification API from a flat `/verifications` namespace to
`/verification/criteria` and `/verification/controls` as distinct resources.
Singularize the run sub-resource path to `/runs/{id}/verification`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-07 09:49:18 -05:00
Bryan Helmkamp
ce80cb6101 Extract outcome and failure_reason from LLM routing directives
The extract_status_fields function recognized "outcome" as a field for
detecting status JSON objects but never read its value — LLM responses
like {"outcome": "fail", "failure_reason": "tests failed"} were silently
ignored and the outcome was always Success.

Now extract_status_fields reads the outcome field to set the node status
and failure_reason to populate the failure detail. Also adds a fallback:
if no routing directives are found in the response text, the handler
reads status.json from the sandbox CWD (written by agents that prefer
file output over inline JSON). Response text always takes priority.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-07 09:38:16 -05:00
Bryan Helmkamp
5df7e178ac Add SandboxProvider::Exe for exe.dev VM sandboxes
New `arc-exe` crate that runs agent tool operations inside ephemeral
exe.dev VMs via SSH. Uses two SSH connections: a management plane
(`ssh exe.dev`) for VM lifecycle and a data plane (`ssh vmname.exe.xyz`)
for command execution and file I/O.

Includes SshRunner trait with MockSshRunner for unit tests and
OpensshRunner for real SSH, with raw_mode for the exe.dev management
plane's custom command handler.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-07 09:18:00 -05:00
Bryan Helmkamp
c82fdf81d6 Add gpt-5.3-codex-spark to model catalog
OpenAI's Codex Spark is a smaller, faster codex derivative on Cerebras
hardware (1000 tok/s, 128K context, text-only, no pricing yet).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-07 03:46:58 -05:00
Bryan Helmkamp
98066669b3 Fix turn/tool-call counts always showing 0 in non-TTY mode
The active_stages map was only populated inside a TTY renderer guard,
so Plain mode never tracked counts. Move counters to a separate
stage_counts map that is always populated regardless of renderer.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-07 03:33:19 -05:00
Bryan Helmkamp
b2f8a55b8f Fix push credentials for public repos in Daytona sandbox
resolve_clone_credentials was short-circuiting for public repos,
returning no token. This broke git push from the sandbox since push
requires authentication regardless of repo visibility. Always generate
an installation access token.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-07 03:20:53 -05:00
Bryan Helmkamp
4d9b0828c1 Fix ensure_cli for Daytona: rootless Node.js install, better errors
- Replace apt-get/NodeSource install (requires root) with direct Node.js
  binary download to ~/.local (works as non-root daytona user)
- Run node install + npm install in single shell so PATH persists
- Add ~/.local/bin to PATH in env file and version check
- Fall back to stdout for error details when stderr is empty (Daytona
  always returns empty stderr)
- Add e2e assertion that cli_stdout.log is written during poll
- Verified on Daytona with haiku: ensure_cli installs in 2s, full
  workflow succeeds

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-07 02:01:47 -05:00
Bryan Helmkamp
50aaf22f8c Auto-install agent CLIs in sandboxes when missing
AgentCliBackend now detects missing CLIs at runtime and installs them
on-demand (including Node.js via NodeSource if needed), removing the
need for custom Dockerfiles that pre-install CLI tools.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-07 01:44:14 -05:00
Bryan Helmkamp
bb48b0d33b Centralize model defaults in catalog, upgrade OpenAI default to gpt-5.4
Remove hardcoded default_model_for_provider() from arc-workflows and
default_model() from arc-agent, delegating both to the catalog via
arc_llm::catalog::default_model_for_provider(). Add claude-sonnet-4-6
to catalog and move "sonnet"/"claude-sonnet" aliases to it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-07 00:59:28 -05:00
Bryan Helmkamp
378c3eb3e3 Stream CLI stdout/stderr to stage logs during poll
Sync cli_stdout.log and cli_stderr.log to stage_dir each poll iteration
so there is visibility into what the CLI agent is doing before it finishes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-07 00:58:40 -05:00
Bryan Helmkamp
552e7d7636 Use setsid to detach CLI process, touch emitter during poll
Daytona's POST /process/execute blocks until all descendant processes
exit. The backgrounded claude process kept the API hanging, causing a
60-second HTTP timeout. Using setsid creates a new session so the child
is fully detached — the API now returns in ~200ms.

Also touch the event emitter during the poll loop to prevent the stall
watchdog from killing the stage while waiting for claude to finish.

Falls back gracefully on macOS where setsid isn't available (not needed
since the local exec implementation doesn't wait for grandchildren).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-06 23:36:00 -05:00
Bryan Helmkamp
e532842a3b CLI backend: background+poll to avoid HTTP proxy timeouts on Daytona
Daytona's POST /process/execute is synchronous and blocks until the
command finishes. Long-running CLI agent sessions (claude, codex, gemini)
cause HTTP proxy timeouts. Replace the single blocking exec_command with
a background launch + poll pattern:

- Generate UUID-based temp file paths to avoid collisions between
  concurrent CLI nodes
- Disable sandbox auto-stop before launching (new Sandbox trait method)
- Launch command in background, capture PID
- Poll every 5s for exit code file
- Read stdout/stderr from temp files after completion
- Cleanup temp files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-06 19:52:13 -05:00