Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,40 @@ All notable changes to Trail are documented in this file. Trail follows

## [Unreleased]

### Fixed

- Trail's managed Colima runtime now keeps Lima VM state at the private,
socket-safe `~/.trail-lima/` path on macOS, avoiding startup failures when a
workspace-scoped profile exceeds the platform's Unix-domain socket limit.

### Added

- Lane-private OCI services can now use a first-class Colima provider through
`trail env runtime setup colima`. Trail creates or reuses a workspace-specific
profile without host mounts or global Docker-context activation, targets its
explicit Docker context on every operation, reports typed provider status,
and leaves VM lifecycle outside implicit lane cleanup. Existing workspaces
retain ambient Docker/Podman auto-detection. Colima file-secret mounts fail
closed until a VM-safe secret broker is available. On macOS 13+ arm64/x86_64,
explicit setup requires no prior installation: Trail downloads pinned Colima
0.10.3, Lima 2.2.0, and Docker CLI 29.7.2 artifacts, verifies compiled
SHA-256 identities, retains their licenses, and atomically publishes them in
a Trail-owned cache. All non-setup provider operations remain network-free.

- Managed lane commands and test/eval gates can now select a no-host-mount
Colima/Lima execution backend. Trail deterministically projects bounded lane
state into an execution-scoped guest namespace, runs direct argv with a
bounded environment and timeout, validates and imports only source changes,
checkpoints non-zero and timed-out results, and cleans up without stopping
the profile. CLI, HTTP, MCP, OpenAPI, lifecycle receipts, turn provenance, and
doctor recovery diagnostics share the same contract; `host` remains the
compatibility default. CLI, HTTP, and MCP now also share an execution
cancellation operation that terminates only Trail's recorded guest process
group, skips candidate import, preserves unrelated profile processes, and
returns the distinct `EXECUTION_CANCELLED` category (CLI exit 17 / HTTP 409).
Candidate-validation and guest-infrastructure failures are likewise distinct
machine categories (CLI exits 18/19 and HTTP 422/503).

## [0.4.0] - 2026-08-12

### Added
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,10 @@ For example:
trail lane spawn fix-login --from main
trail env sync all fix-login # optional prewarm; first managed execution also converges

# Optional on macOS: provision tools and execute managed commands in the VM.
trail env runtime setup colima --execution-backend colima
trail lane exec fix-login --timeout-secs 900 -- cargo test

# Work is isolated in the lane until it is reviewed and validated.
trail lane record fix-login -m "Fix login validation"
trail lane readiness fix-login
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ These docs are written from the current Rust code, CLI definitions, exported mod

- [Lane overview](lanes/overview.md)
- [Lane work model](lanes/work-model.md)
- [Colima-sandboxed lane execution](lanes/colima-sandboxed-execution.md)
- [Spawn and materialize workdirs](lanes/spawn-and-materialize-workdirs.md)
- [Structured patches](lanes/structured-patches.md)
- [Sessions, turns, messages, and runs](lanes/sessions-turns-messages-and-runs.md)
Expand Down
11 changes: 11 additions & 0 deletions docs/design/guardrails-security-and-redaction.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,17 @@ CLI daemon routing can read the token from `--daemon-token`, `TRAIL_DAEMON_TOKEN
- Patch rejected: `PATCH_REJECTED`, exit 7.
- Missing capability enforcement rejects the action; it does not silently run
an untrusted resolver or constructor unsandboxed.
- Colima lane execution accepts only a verified no-host-mount profile, passes
direct argv through explicit `limactl`, clears ambient credentials and Docker
endpoints, bounds projection/archive/output/time/concurrency, validates every
returned path and symlink before mutation, and rejects concurrent host source
edits. `.trail`, real Git state, host home, and the Docker socket never enter
the guest projection.
- Guest cancellation is an authenticated execution-scoped request. Trail
launches direct argv in a new guest process group, records its numeric owner
beneath the execution namespace, validates that receipt, sends TERM/KILL only
to the negative group ID, and refuses candidate import after cancellation.
It never uses a profile-wide stop as execution cleanup.
- Secret-tainted output remains private and non-promotable even when its content
would otherwise match a shared desired key.
- Divergent ready content for one desired key and trust scope quarantines both
Expand Down
9 changes: 9 additions & 0 deletions docs/design/layered-lane-workspaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,7 @@ trail lane spawn task-a --from main --workdir-mode auto
trail lane mount task-a [--foreground]
trail lane unmount task-a
trail lane exec task-a -- <command>
trail lane exec task-a --turn <TURN_ID> --timeout-secs 900 -- <command>
trail lane checkpoint task-a -m "checkpoint"
trail lane update task-a --from main
trail lane space task-a
Expand All @@ -1026,6 +1027,14 @@ dispose execution-owned runtime resources, and unmount. Every phase produces a
durable receipt. Generated and scratch changes are accounted for but excluded
from the source checkpoint.

When `runtime.execution_backend=colima`, the mount remains a host-side input to
Trail only. Trail streams a deterministic bounded projection through verified
`limactl`, executes in a workspace/execution-derived guest namespace, exports a
bounded candidate, validates it in host staging, rejects concurrent source
edits, and applies only its source delta before the normal checkpoint. This is
a data-plane backend beneath the shared lifecycle; agents, Trail storage, and
checkpoint authority remain on the host.

Shared report types include:

- `LaneWorkspaceViewReport`
Expand Down
27 changes: 25 additions & 2 deletions docs/design/universal-lane-environments.md
Original file line number Diff line number Diff line change
Expand Up @@ -576,12 +576,35 @@ health state, cleanup token, lifecycle owner, and timestamps. Containers and net
are generation-scoped; a private data volume is scoped to the logical lane service so a
healthy image rollout does not silently discard database state. Retired containers and
networks are removed after the replacement becomes healthy, while a volume remains as
long as an active generation references it. Docker and Podman are
detected at reconciliation time. A missing pinned image is pulled by digest; Trail then
long as an active generation references it. Docker and Podman are detected at
reconciliation time. A workspace may instead select Colima: Trail uses a stable
workspace-specific profile, starts it without host mounts or global context activation,
and addresses every operation through the profile's explicit Docker context. Explicit
setup reuses complete system tools or provisions pinned, digest-verified Colima, Lima,
and Docker CLI artifacts into a Trail-owned cache on supported macOS hosts. Ordinary
reconciliation never downloads tools. Managed processes isolate `COLIMA_HOME` and
`DOCKER_CONFIG` below Trail's user data directory. On macOS, `LIMA_HOME` uses the
private, shorter `~/.trail-lima/` root so workspace-scoped profiles stay within the
platform's Unix-domain socket path limit. Managed tools select Apple's `vz` backend.
Colima remains an external host provider rather than adapter authority,
and Trail never deletes
the VM profile implicitly. Because the contained profile cannot safely bind arbitrary
host paths into its Docker VM, file-secret services fail closed under Colima until a
VM-safe broker exists. A missing pinned image is pulled by digest; Trail then
verifies the observed repository digest before creating anything. Existing names are
adopted only when Trail ownership labels match exactly. A dead lifecycle owner is marked
`orphaned` on reopen and safely inspected on the next reconcile.

Colima can also be selected as the managed-command data plane. Trail resolves
the exact profile to its Lima instance and uses a deterministic tar protocol
instead of mounting the host lane. Source and accepted portable symlinks enter
an execution namespace under bounded limits; secret/internal state does not.
Runtime bindings stay on guest loopback because both the Colima Docker daemon
and command inhabit the VM. Trail validates the exported candidate and imports
only source changes before the existing checkpoint barrier. The adapter remains
a planner, the host Trail process retains publication authority, and the agent
provider remains a separately contained host control plane.

## Monorepos and multiple lanes

Discovery roots are explicit. A monorepo can have many overlapping environment graphs,
Expand Down
20 changes: 20 additions & 0 deletions docs/guides/maintenance-and-recovery.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,26 @@ trail env artifact quarantine list
resolution. Never delete rows or CAS files manually.
- After restore, rerun sync for each retained lane before managed execution.

## Interrupted Colima Guest Execution

`trail doctor` includes `managed_guest_executions` with bounded counts for
live, safely recoverable, ambiguous, and terminal receipts. Trail automatically
cleans an abandoned namespace only before candidate import. It fails closed for
an interrupted execute/export/import/checkpoint boundary because discarding or
reapplying state could overwrite lane work.

For a live command, use `trail lane exec-cancel <LANE>` or select its event
receipt with `--execution-id`. Cancellation is acknowledged only after the
owned guest process group has stopped and candidate import has been skipped.
If the owner process dies after the request, Trail validates the durable owner,
terminates the same guest process group, and cleans only that execution's
namespace.

For an ambiguous receipt, inspect the lane workdir and history, checkpoint
intentional source with `trail lane checkpoint <LANE>`, and retry after the
state is understood. Do not edit `.trail/managed-executions`, delete a guest
namespace, or stop the shared Colima profile as a repair shortcut.

## Schema-v1 Upgrade and Rollback Boundary

Trail still accepts exactly SQLite schema v1 and has no in-place database
Expand Down
6 changes: 6 additions & 0 deletions docs/integrations/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ task with:
trail.begin_turn -> trail.add_message -> trail.span_start/span_end or trail.add_event -> trail.apply_patch or trail.sync_workdir -> trail.end_turn
```

For commands that must see a filesystem, call `trail.lane_exec` with the open
turn's `turn_id`. This associates the resulting operation and lifecycle with
the turn. If `runtime.execution_backend=colima`, only that command data plane
runs in the no-mount Lima guest; the MCP host and Trail server remain the
contained host control plane. Test/eval tools use the same backend.

When a run pauses for approval or interruption, use `trail.run_pause` and later
`trail.run_resume`. If a branch goes sideways, use `trail.lane_rewind` with
`record_current=true` to preserve the failed head before returning to a
Expand Down
155 changes: 155 additions & 0 deletions docs/lanes/colima-sandboxed-execution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# Colima-Sandboxed Lane Execution

Trail can keep its lane database, mounts, agent coordination, and checkpoints on
the host while running managed lane commands inside the Lima virtual machine
owned by a contained Colima profile. This is an optional data-plane backend;
existing workspaces continue to execute on the host.

## When it helps

Use the Colima backend when a lane command may execute untrusted repository
code, install dependencies, invoke compilers, or contact lane-private services.
It gives these commands a VM boundary without giving the guest a host mount,
the Docker socket, `.trail/`, the real Git directory, the user's home, or
ambient credentials.

Typical uses include:

- an AI agent asking Trail to run a repository test, formatter, generator, or
build command;
- readiness test and eval gates that should use the same environment as agent
commands;
- several lanes using isolated service containers in one contained Colima VM;
- reviewing the exact source delta produced by a non-zero or timed-out command;
and
- retaining command, projection, checkpoint, cleanup, session, turn, and trace
evidence for review or handoff.

The coding-agent process itself remains a contained host control plane. It can
use Trail through CLI, HTTP, or MCP, while `trail.lane_exec` and gate commands
run as the guest data plane. Trail reports this split explicitly; it does not
claim that an arbitrary host agent binary moved into the VM.

## Setup

On supported macOS hosts, setup can install Trail's pinned Colima, Lima, and
Docker CLI tools without Homebrew or a separate Colima installation:

```sh
trail env runtime setup colima --execution-backend colima
trail env runtime provider status
```

Trail starts a dedicated profile with no host mounts, SSH-agent forwarding,
generated host SSH configuration, bridged address, Kubernetes, or global
context activation. Linux and unsupported macOS hosts can use the same backend
when compatible `colima`, `limactl`, and `docker` executables are already
available. Colima still downloads and owns its guest image; Trail does not
embed that image in the `trail` binary.

`--no-start` can provision tools for later use, but it cannot enable the Colima
execution backend because Trail must verify the running guest first.

## Execution flow

```text
host Trail control plane
resolve/sync services and mount lane view
build deterministic bounded source projection
|
| tar stream through verified limactl (no host mount)
v
contained Colima/Lima guest
create execution namespace -> run argv -> export candidate -> delete namespace
|
| bounded candidate stream
v
host Trail control plane
validate archive -> reject concurrent host edits -> import source delta
-> checkpoint lane operation -> dispose owned services -> unmount
```

Each execution uses a workspace- and execution-derived directory below
`/tmp/trail-executions` in the guest. Arguments are passed directly without
shell interpolation. Environment values are allowlisted and host lane paths are
translated to the guest workspace. Runtime service bindings remain on guest
loopback because Colima's Docker daemon and the managed command share the same
VM; Docker sockets and host paths are never injected.

The projection and candidate import are bounded by the lane workspace-view
entry, total-byte, and single-file limits (or conservative defaults). Paths are
normalized and checked for traversal, case collisions, unsupported entry kinds,
and escaping symlinks. Secret-class, Trail-internal, and real Git state are
excluded. Only validated source changes return to the lane; generated,
dependency, and scratch output remains disposable. A concurrent host source
change makes import fail closed.

## Commands and agent use

```sh
# Default guest timeout is 3600 seconds; accepted range is 1..86400.
trail lane exec fix-login --timeout-secs 900 -- cargo test

# Associate the checkpoint with an existing open turn.
trail lane exec fix-login --turn turn_... -- cargo test

# From another terminal, cancel the lane's only live guest execution.
trail lane exec-cancel fix-login

# Select an execution when several commands are live.
trail lane exec-cancel fix-login --execution-id exec_...

# Return to compatible host execution.
trail config set runtime.execution_backend host
```

HTTP `POST /v1/lanes/{lane}/exec` and MCP `trail.lane_exec` accept the same
`command`, optional `turn_id`, and optional `timeout_secs`. When `turn_id` is
present, Trail rejects an ended or cross-lane turn before launch and reports its
session, turn, and derived trace identity in the lifecycle receipt. This is the
recommended path for AI hosts: open a turn, call `trail.lane_exec` for command
work, inspect its structured lifecycle/checkpoint result, run gates, then end
the turn.

CLI `lane exec-cancel`, HTTP `POST /v1/lanes/{lane}/exec/cancel`, and MCP
`trail.lane_exec_cancel` expose the same cancellation operation. The optional
`execution_id` is required only when more than one cancellable execution is
live for the lane. Trail writes the cancellation request before acting,
terminates only that execution's recorded guest process group, skips candidate
import, cleans its owned namespace, and retains `terminal_cancelled` evidence.
The original blocking request returns `EXECUTION_CANCELLED` (CLI exit 17, HTTP
409, and the same structured MCP error). Cancellation never stops the Colima
profile or unrelated guest processes.
HTTP lane execution is dispatched on a workspace-scoped daemon worker, leaving
the authenticated listener responsive to the matching cancellation request.
Because one MCP stdio connection processes requests in order, an agent cancels
a blocking MCP execution from a second Trail MCP session (or through the CLI or
HTTP endpoint). The cancellation operation and durable receipt are identical.

The result distinguishes `succeeded`, `command_failed`, and `timed_out`;
cancellation is a distinct structured error rather than a command exit.
Non-zero and timed-out commands still proceed through candidate validation,
source checkpointing, service disposal, namespace cleanup, and lane unmount.
Candidate validation and infrastructure failures are returned as distinct
`EXECUTION_VALIDATION_FAILED` (CLI exit 18 / HTTP 422) and
`EXECUTION_INFRASTRUCTURE_FAILED` (CLI exit 19 / HTTP 503) Trail errors rather
than being disguised as command exits.

## Recovery and boundaries

Trail writes private execution manifests under `.trail/managed-executions/`.
Before another guest command it identifies live owners, safely removes abandoned
pre-import namespaces, and refuses to guess after ambiguous execute/export/import
states. `trail doctor` reports live, recoverable, ambiguous, and terminal receipt
counts without mutating them. Completed receipts are retained in a bounded set.

If doctor reports an ambiguous execution, inspect the lane and its current
workdir first. Checkpoint intentional source with `trail lane checkpoint
<LANE>`, then retry only after resolving the preserved state. Do not edit the
receipt or delete the guest namespace manually.

The backend does not provide a general-purpose remote shell, persist arbitrary
guest build output, expose host secrets, or stop/delete the Colima profile
during lane cleanup. File-secret OCI mounts remain unsupported. VM image trust,
kernel isolation, and Colima vulnerabilities remain part of the local Colima
trust boundary.
7 changes: 7 additions & 0 deletions docs/lanes/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,13 @@ The same receipt shape is used by exec, test, eval, terminal-agent, and
materialized ACP execution; older serialized lifecycle reports remain valid
with both receipts absent.

When `runtime.execution_backend=colima`, command and gate execution adds
no-mount guest projection, export, import, and cleanup phases to this lifecycle.
The host remains the authoritative lane/checkpoint and agent control plane. A
CLI `--turn` or HTTP/MCP `turn_id` associates the command checkpoint and
session/turn/trace provenance with an open lane turn. See
[Colima-sandboxed lane execution](colima-sandboxed-execution.md).

Omitting `--workdir-mode` creates a lazy `auto` layered lane on a qualified
native transparent backend. Spawn does not copy source or execute ecosystem
tools. The first managed command converges the desired environment; exact
Expand Down
8 changes: 8 additions & 0 deletions docs/lanes/work-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,14 @@ Trail Relay

Without such a relay, the host or script must call Trail explicitly.

When a host needs to execute repository code, it can call CLI/HTTP/MCP lane
execution with the open turn id. Trail then attaches the managed lifecycle,
checkpointed operation, and derived trace id to that turn. With the optional
Colima backend, the host agent remains the control plane while the command runs
inside a no-host-mount Lima guest; this changes containment, not the lane's
branch/session/turn model. See
[Colima-sandboxed lane execution](colima-sandboxed-execution.md).

## Two Ways to Change a Lane

### Structured Patch Flow
Expand Down
Loading
Loading