Update changelog and docs for installer, devcontainers, arc pr, and preflight improvements

Regenerate 2026-03-10 changelog with full commit set (installer, devcontainer
support, arc pr subcommands, --no-dotenv removal). Add 2026-03-11 changelog
for shadow branch run records and arc diff improvements. Update docs: CLI
reference (arc pr list/view/merge/close, --shortstat), devcontainers page
(replace placeholder with full content), run-configuration (devcontainer
field), checkpoints (per-node files on metadata branch), models catalog
(gemini-3.1-pro-preview-customtools). Improve preflight to show per-model
LLM checks instead of a single merged provider line.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-03-11 08:33:04 -04:00
parent 35b6282e6d
commit cfd7ea8c97
11 changed files with 220 additions and 58 deletions

View file

@ -1 +1 @@
485f2a5718b6d983520aa5cccd39128d99bad7ae
15a56263f52992592d97b904914626a366eee0d8

View file

@ -1 +1 @@
485f2a5718b6d983520aa5cccd39128d99bad7ae
15a56263f52992592d97b904914626a366eee0d8

View file

@ -1,8 +1,19 @@
---
title: "MCP servers in workflows, arc init, and arc diff"
title: "One-line installer, MCP servers, and devcontainer support"
date: "2026-03-10"
---
## One-line installer and guided setup wizard
Getting Arc installed and configured previously required multiple manual steps — downloading binaries, setting API keys, and configuring GitHub integration individually. Now, a single command installs Arc and walks you through everything:
```bash
curl -fsSL https://raw.githubusercontent.com/brynary/arc/main/install.sh | bash
arc install
```
The installer detects your platform, installs to `~/.arc/bin`, and auto-configures your shell PATH. The `arc install` wizard then guides you through LLM provider setup with immediate API key validation, OpenAI OAuth for Codex compatibility, and GitHub App creation via manifest flow — all with styled terminal output showing progress at each step.
## MCP servers in workflows
Agents running inside sandboxes can now access MCP tools during workflow runs. A new `sandbox` transport type starts an MCP server inside the Daytona sandbox, waits for it to listen, and connects automatically — no host-side setup required. This means agents can use tools like Playwright for browser automation, or any other MCP server, directly from within their sandbox environment.
@ -14,39 +25,50 @@ command = ["npx", "@playwright/mcp@latest", "--port", "3100", "--headless"]
port = 3100
```
## `arc init` — initialize a new project
## Devcontainer support in sandboxes
Getting started with Arc previously meant creating `arc.toml` and workflow files by hand. Now, `arc init` scaffolds a project with a generated `arc.toml` that includes comments and a link to the docs.
Workflows can now use your project's `devcontainer.json` to configure sandbox environments. When `devcontainer = true` is set in the sandbox config, Arc resolves the devcontainer from the repo, uses its Dockerfile for the Daytona snapshot, runs lifecycle hooks (`onCreateCommand`, `postCreateCommand`, `postStartCommand`), and merges devcontainer environment variables into the sandbox. Unsupported `COPY`/`ADD` instructions in base Dockerfiles are detected and reported.
```bash
arc init
```toml title="workflow.toml"
[sandbox]
devcontainer = true
```
## `arc diff` — view workflow run diffs
You can now inspect the code changes from any workflow run without digging through log files. `arc diff` surfaces the final patch and per-node diffs from completed runs. For in-progress runs, it reconnects to the sandbox to show a live diff.
```bash
arc diff <run-id>
```
<Warning>
**`--no-dotenv` flag removed.** Arc now always loads `~/.arc/.env` and never loads a local `./.env` file. If you were relying on project-local `.env` files, move those values to `~/.arc/.env`.
</Warning>
## More
<Accordion title="API">
- New signoff resource in the verification system — stamps of approval for (control, repository, commit SHA) tuples via `POST /verification/signoffs`
</Accordion>
<Accordion title="CLI">
- Added `arc pr list/view/merge/close` subcommands for managing pull requests created by workflow runs
- Added `arc init` command to scaffold new projects with a generated `arc.toml`
- Added `arc diff` command to view code changes from workflow runs, with live diffs for in-progress runs
- `arc setup` renamed to `arc install` for clarity
- Added `arc ssh` command for direct SSH access to Daytona sandboxes
- Added `arc preview` command to get preview URLs for ports exposed by Daytona sandboxes
- `arc doctor` now runs live service probes by default; use `--dry-run` to skip
- `arc doctor` now validates GitHub App configuration and private key
- `arc doctor` now hides unconfigured LLM providers for a cleaner output
- `arc doctor` sections reordered: Config, LLM, GitHub App, Cloud sandbox, Brave Search
- `arc run --preflight` now shows a Repository section, merged LLM check, and truncated output
- Added `[pull_request]` support to project config (`arc.toml`) for project-level PR defaults
- Per-run data moved from `~/.arc/logs/` to `~/.arc/runs/`; daily CLI logs stay in `~/.arc/logs/`
- Per-run tracing logs now written to `cli.log` inside each run directory
- Asset paths now display full `~/...` paths after workflow run output
- Hidden `arc ps`, `llm`, `exec`, and `parse` from CLI help output
- PR body now includes Arc Details section even when retro is skipped
- Bumped version to 0.2.0
</Accordion>
<Accordion title="Fixes">
- Fixed Daytona sandbox clone failing when the worktree branch hadn't been pushed to the remote
- Fixed "Git clean: false" showing incorrectly for remote sandboxes by checking both clean and pushed status
- Fixed LLM provider resolution to check the model catalog before falling back to the default provider
- Fixed verbose mode breaking terminal rendering after many tool calls
- Fixed `install.sh` prompt not showing when piped via stdin
- Fixed race condition in git checkpoint push operations
</Accordion>

View file

@ -0,0 +1,27 @@
---
title: "Shadow branch run records and arc diff improvements"
date: "2026-03-11"
---
## Shadow branch as a complete run record
The shadow branch now stores the full execution trace for each workflow run — prompts, responses, status files, diffs, and retro data per node — alongside the existing checkpoint and manifest data. This makes the shadow branch a self-contained, git-native record of everything that happened during a run, browsable with standard git tools.
## More
<Accordion title="API">
- Added `devcontainer` field to `SandboxConfiguration` OpenAPI schema
</Accordion>
<Accordion title="CLI">
- Added `--shortstat` flag to `arc diff` for compact summary output
- `arc diff` now includes uncommitted changes for in-progress runs, not just committed ones
</Accordion>
<Accordion title="Improvements">
- Added `gemini-3.1-pro-preview-customtools` to model catalog
- Parallel fan-out branches now render like subagent info lines in the progress UI
- Tool call paths now display relative to the sandbox working directory
- Remote sandbox runs skip unnecessary local worktree creation
- Added `last_file_touched` as a third fallback for status extraction
</Accordion>

View file

@ -24,6 +24,7 @@ No single model is best at everything. Arc lets you assign the right model to ea
| `gpt-5.4` | openai | `gpt54` | 1M | $2.50 / $15.00 | 70 tok/s |
| `gpt-5.4-pro` | openai | `gpt54-pro` | 1M | $30.00 / $180.00 | 20 tok/s |
| `gemini-3.1-pro-preview` | gemini | `gemini-pro` | 1M | $2.00 / $12.00 | 85 tok/s |
| `gemini-3.1-pro-preview-customtools` | gemini | `gemini-customtools` | 1M | $2.00 / $12.00 | 85 tok/s |
| `gemini-3-flash-preview` | gemini | `gemini-flash` | 1M | $0.50 / $3.00 | 150 tok/s |
| `gemini-3.1-flash-lite-preview` | gemini | `gemini-flash-lite` | 1M | $0.20 / $1.50 | 200 tok/s |
| `kimi-k2.5` | kimi | `kimi` | 262K | $0.60 / $3.00 | 50 tok/s |

View file

@ -268,6 +268,7 @@
"group": "March 2026",
"icon": "clock-rotate-left",
"pages": [
"changelog/2026-03-11",
"changelog/2026-03-10",
"changelog/2026-03-09",
"changelog/2026-03-08",

View file

@ -50,6 +50,7 @@ After each node, the metadata branch is updated with:
- **`checkpoint.json`** — Full execution state (see below)
- **`artifacts/*.json`** — Any offloaded artifact data (large context values over 100KB)
- **`nodes/{node_id}/`** — Per-node execution trace files (prompts, responses, status, diffs — files under 512KB from an allowlist)
## What's in a checkpoint

View file

@ -3,6 +3,41 @@ title: "Devcontainers"
description: "Run Arc workflows inside development containers"
---
<Warning>
Devcontainer support is **under development** and not yet available for use. This page will be updated when the feature ships.
</Warning>
Arc can use your project's [devcontainer](https://containers.dev/) configuration to set up sandbox environments. When enabled, Arc resolves `devcontainer.json` from the repository, uses its Dockerfile to build the Daytona sandbox snapshot, runs lifecycle hooks inside the sandbox, and merges devcontainer environment variables into the sandbox environment.
## Enabling devcontainer support
Set `devcontainer = true` in the `[sandbox]` section of your run config:
```toml title="run.toml"
version = 1
graph = "workflow.dot"
[sandbox]
provider = "daytona"
devcontainer = true
```
Arc looks for `.devcontainer/devcontainer.json` in the repository root. If found, it extracts the Dockerfile, lifecycle commands, and environment variables from the configuration.
## What Arc uses from devcontainer.json
| Field | How Arc uses it |
|---|---|
| `build.dockerfile` | Used as the Dockerfile for the Daytona sandbox snapshot. A deterministic snapshot name is generated from a hash of the Dockerfile content. |
| `onCreateCommand` | Runs inside the sandbox after it's created |
| `postCreateCommand` | Runs after `onCreateCommand` completes |
| `postStartCommand` | Runs after the sandbox starts |
| `containerEnv` | Merged into sandbox environment variables (TOML `[sandbox.env]` values take precedence on key collisions) |
Lifecycle commands (`onCreateCommand`, `postCreateCommand`, `postStartCommand`) execute sequentially inside the sandbox. If any command fails, the run aborts before the workflow starts.
## Dockerfile limitations
Arc detects and reports unsupported `COPY` and `ADD` instructions in devcontainer base Dockerfiles. These instructions reference files from the build context, which isn't available when building Daytona snapshots. If your Dockerfile uses `COPY` or `ADD`, you'll need to restructure it to use `RUN` commands that fetch files at build time (e.g., via `curl` or `wget`).
## Interaction with other sandbox settings
When `devcontainer = true`, the devcontainer Dockerfile overrides any `snapshot.dockerfile` setting in `[sandbox.daytona]`. Other Daytona settings (`cpu`, `memory`, `disk`, `auto_stop_interval`, `labels`) still apply.
Environment variables from `containerEnv` in the devcontainer config are merged with `[sandbox.env]` from the TOML config. On key collisions, the TOML config wins.

View file

@ -157,6 +157,7 @@ preserve = true
|---|---|
| `provider` | Sandbox mode: `local` (default), `docker`, `daytona`, or `exe`. |
| `preserve` | When `true`, keep the sandbox alive after the run finishes. Useful for debugging. |
| `devcontainer` | When `true`, use the repo's `devcontainer.json` to configure the sandbox. See [Devcontainers](/execution/devcontainers). |
#### `[sandbox.daytona]`

View file

@ -252,9 +252,13 @@ arc cp -r <run-id>:/output ./results # recursive download
Run IDs support prefix matching — you can use the first few characters instead of the full ID.
## `arc pr create`
## `arc pr`
Create a GitHub pull request from a completed workflow run. Uses the run's persisted manifest, conclusion, and diff. Requires a [GitHub App](/integrations/github) to be configured.
Manage GitHub pull requests created by workflow runs. Requires a [GitHub App](/integrations/github) to be configured.
### `arc pr create`
Create a GitHub pull request from a completed workflow run. Uses the run's persisted manifest, conclusion, and diff.
```bash
arc pr create <run-id>
@ -268,6 +272,57 @@ arc pr create <run-id> --model claude-opus-4-6
The run must have completed successfully (or with partial success) and have a `final.patch` with changes.
### `arc pr list`
List all pull requests created by workflow runs. Shows PR number, title, status, and the associated run ID.
```bash
arc pr list
arc pr list --all
```
| Flag | Description |
|---|---|
| `--all` | Include closed and merged PRs (default: open only) |
### `arc pr view`
Show details of a pull request from a specific run.
```bash
arc pr view <run-id>
```
| Argument | Description |
|---|---|
| `<RUN_ID>` | Run ID or prefix (required) |
### `arc pr merge`
Merge a pull request from a specific run.
```bash
arc pr merge <run-id>
arc pr merge <run-id> --method rebase
```
| Argument / Flag | Description |
|---|---|
| `<RUN_ID>` | Run ID or prefix (required) |
| `--method <METHOD>` | Merge method: `squash` (default), `merge`, or `rebase` |
### `arc pr close`
Close a pull request from a specific run without merging.
```bash
arc pr close <run-id>
```
| Argument | Description |
|---|---|
| `<RUN_ID>` | Run ID or prefix (required) |
---
## `arc system df`
@ -338,6 +393,7 @@ arc diff <run-id> --stat
| `<RUN>` | Run ID or prefix (required) |
| `--node <NODE>` | Show diff for a specific node instead of the full run |
| `--stat` | Show diffstat instead of full patch (live diffs only) |
| `--shortstat` | Show only files-changed/insertions/deletions summary (live diffs only) |
Output is colorized when writing to a terminal.

View file

@ -1911,11 +1911,6 @@ async fn run_preflight(
CheckDetail::new(format!("Nodes: {}", graph.nodes.len())),
CheckDetail::new(format!("Edges: {}", graph.edges.len())),
CheckDetail::new(format!("Goal: {}", graph.goal())),
CheckDetail::new(format!("Model: {model}")),
CheckDetail::new(format!(
"Provider: {}",
provider.as_deref().unwrap_or("anthropic")
)),
],
remediation: None,
});
@ -2006,62 +2001,85 @@ async fn run_preflight(
});
}
// 4. LLM check (merged providers + provider parse)
let resolved_provider = provider.as_deref().unwrap_or("anthropic");
// 4. Per-model LLM checks
let default_provider = provider.as_deref().unwrap_or("anthropic");
let llm_ok = match arc_llm::client::Client::from_env().await {
Ok(c) => {
let names: Vec<String> = c.provider_names().iter().map(|s| s.to_string()).collect();
if names.is_empty() {
checks.push(CheckResult {
name: "LLM".into(),
status: CheckStatus::Error,
summary: "no API keys".into(),
details: vec![],
remediation: Some("Set at least one LLM provider API key".into()),
});
false
} else {
match resolved_provider.parse::<Provider>() {
let configured: Vec<String> =
c.provider_names().iter().map(|s| s.to_string()).collect();
// Collect all distinct (model, provider) pairs from LLM nodes
let mut model_providers = std::collections::BTreeSet::new();
for node in graph.nodes.values() {
if !crate::graph::types::is_llm_handler_type(node.handler_type()) {
continue;
}
let node_model = node.llm_model().unwrap_or(&model);
let node_provider = node.llm_provider().unwrap_or(default_provider);
// Resolve through catalog to get canonical model ID and provider
let (resolved_model, resolved_provider) =
if let Some(info) = arc_llm::catalog::get_model_info(node_model) {
(info.id, info.provider)
} else {
(node_model.to_string(), node_provider.to_string())
};
// Use node-level provider override if explicitly set, otherwise catalog provider
let final_provider = if node.llm_provider().is_some() {
node_provider.to_string()
} else {
resolved_provider
};
model_providers.insert((resolved_model, final_provider));
}
// If no LLM nodes found, fall back to the default model/provider
if model_providers.is_empty() {
let (resolved_model, resolved_provider) =
if let Some(info) = arc_llm::catalog::get_model_info(&model) {
(info.id, info.provider)
} else {
(model.clone(), default_provider.to_string())
};
model_providers.insert((resolved_model, resolved_provider));
}
let mut all_ok = true;
for (model_id, provider_name) in &model_providers {
match provider_name.parse::<Provider>() {
Ok(_) => {
let mut status = CheckStatus::Pass;
if !names.iter().any(|n| n == resolved_provider) {
if !configured.iter().any(|n| n == provider_name) {
status = CheckStatus::Warning;
all_ok = false;
}
checks.push(CheckResult {
name: "LLM".into(),
status,
summary: resolved_provider.to_string(),
details: vec![CheckDetail::new(format!(
"Configured: {}",
names.join(", ")
))],
summary: model_id.clone(),
details: vec![CheckDetail::new(format!("Provider: {provider_name}"))],
remediation: if status == CheckStatus::Warning {
Some(format!(
"Provider \"{resolved_provider}\" not in configured providers"
))
Some(format!("Provider \"{provider_name}\" is not configured"))
} else {
None
},
});
status == CheckStatus::Pass
}
Err(e) => {
checks.push(CheckResult {
name: "LLM".into(),
status: CheckStatus::Error,
summary: resolved_provider.to_string(),
details: vec![CheckDetail::new(format!(
"Configured: {}",
names.join(", ")
))],
remediation: Some(format!(
"Invalid provider \"{resolved_provider}\": {e}"
)),
summary: model_id.clone(),
details: vec![CheckDetail::new(format!("Provider: {provider_name}"))],
remediation: Some(format!("Invalid provider \"{provider_name}\": {e}")),
});
false
all_ok = false;
}
}
}
all_ok
}
Err(e) => {
checks.push(CheckResult {