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
98 changes: 98 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,57 @@ atomic/
| **atomic-remote-client** | `atomic-enterprise/atomic-remote` | HTTP client library for push/pull/clone |
| **atomic-docs** | `atomic-docs/` | Documentation site ([docs.atomic.dev](https://docs.atomic.dev/)) |

## Atomic + smolvm: Concurrent Agent Sandboxes

When several agents work a repository at once, each needs an isolated build
surface (no `node_modules` / `target` collisions) while still sharing one
history. Atomic does this with **copy-on-write working trees over a single
canonical graph** — no per-agent forks, no virtualized I/O.

- **The working tree is cloned; the graph is not.** A sandbox is a reflink
(copy-on-write) clone of the working tree — APFS `clonefile`, Btrfs/XFS
`FICLONE`, ReFS block-clone — done in-process via the `reflink-copy` crate.
Five agents off a 200 MB base cost a few MB and run at bare-metal filesystem
speed. There is exactly **one** pristine; a sandbox carries a small
`.atomic-sandbox` pointer, so `status`, `record`, `diff`, and `vault query`
all work inside it and land in the canonical graph.
- **No FUSE, no virtio, no VM in the hot path.** Filesystem-protocol
virtualization (virtio-fs) is fatal for the metadata-heavy workloads agents
run (git, cargo, npm). Copy-on-write on the real filesystem needs none of it.

```bash
# Give each agent a private, copy-on-write working tree
atomic sandbox create agent-1 --from dev # forks a per-agent draft view
atomic sandbox create agent-2 --from dev # isolated artifacts, shared graph
```

### Shipping a sandbox: `stage` and `seal`

A sandbox can be packaged as a **standard OCI image** (consumable by smolvm,
podman, containerd — built in-process, no shelling out) for two purposes:

| Command | Shape | Purpose |
|---------|-------|---------|
| `atomic sandbox stage` | layered: shared base + thin delta | inner-loop CI / circuit-breaker runs — base is pulled once and cached, only the delta ships each iteration |
| `atomic sandbox seal` | flattened, single self-contained layer | a deployable runtime — "run this exact version" anywhere |

```bash
# Inner-loop CI artifact: base layer (dev) + delta layer (only what changed)
atomic sandbox stage agent-1 --base dev -o ./ci-image

# Deployable runtime image of the full merged state
atomic sandbox seal dev -o ./runtime --entrypoint /app/run --env PORT=8080
```

Both embed provenance (the view names and Merkle states) in the image
annotations, so a shipped image traces back to exact graph state. The runtime
is [smolvm](https://github.com/binsquare/smolvm) — lightweight microVMs that
boot OCI images in under 200 ms — making these images the unit of work for
shared-infrastructure CI and circuit-breaker workflows.

See [docs/CONCURRENT-AGENT-SANDBOXES.md](docs/CONCURRENT-AGENT-SANDBOXES.md) for
the full design.

## Design Principles

1. **Mathematical Soundness** — Operations are well-defined transformations with provable properties
Expand Down
11 changes: 10 additions & 1 deletion atomic-cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
use std::path::{Path, PathBuf};

use atomic_core::types::{Base32, Hash};
use atomic_repository::Repository;
use atomic_repository::{Repository, SANDBOX_POINTER};
use chrono::{DateTime, Local, Utc};

use crate::error::{CliError, CliResult};
Expand All @@ -79,6 +79,7 @@ pub mod record;
pub mod remove;
pub mod restore;
pub mod revise;
pub mod sandbox;
pub mod split;
pub mod stash;
pub mod status;
Expand Down Expand Up @@ -140,6 +141,7 @@ pub use remote::Remote;
pub use remove::Remove;
pub use restore::Restore;
pub use revise::Revise;
pub use sandbox::Sandbox;
pub use split::Split;
pub use stash::Stash;
pub use status::Status;
Expand Down Expand Up @@ -275,6 +277,13 @@ pub fn find_repository_root_from(start_path: &Path) -> CliResult<PathBuf> {
return Ok(current);
}

// A sandbox working tree has no `.atomic/` of its own — it carries a
// pointer to the canonical graph. Treat the sandbox root as a valid
// repository root; `Repository::open*` resolves the pointer.
if current.join(SANDBOX_POINTER).is_file() {
return Ok(current);
}

// Move to parent directory
if !current.pop() {
// Reached the root without finding a repository
Expand Down
Loading
Loading