A pod for artifacts: a virtual filesystem your AI can reason in, your users can shell into, and your infrastructure can version, encrypt, and synchronize — in the browser and on Linux.
Status: shipped through plan Phase 6.6. Everything below marked ✅ ships in
@artipod/coretoday — the Node/Docker core, the browser sandbox, OCI layering, encryption & authority, and sync (the browser app lives at examples/artipod-spa). 🔮 marks the remaining design work (Phase 7 live streams), tracked phase-by-phase in artipod-layer-plan.md. Previous implementation-state READMEs are archived inattic/(v0.1, v0.3 — the v0.3 one documents the pre-merge Node/Docker API, including podman support, read-only mounts, and the main mount).
An artipod is a self-contained, portable workspace — a declarative set of mounts over a virtual filesystem, plus everything that operates on it:
- a bash isolate (real bash semantics, browser and server) ✅
- AI agent tools with VS Code-compatible schemas, an agent loop, and context/prompt building ✅
- OCI layering for revision control: every pod is image/volume layers + a writable upper; snapshot, checkout, diff, commit, push, pull ✅
- encryption & authority: ciphertext at rest, leased keys, offline grants, delegated managers ✅
- sync: content-addressed, resumable, relay-friendly — browser ↔ server ↔ home base ✅
Three consumer surfaces, one layer:
| Surface | What it gets |
|---|---|
| AI reasoning | buildPrompt() context, VS Code-schema tools (read_file, apply_patch, …, bash), agent loop, /proc introspection — all confined to the pod |
| Revision control | OCI snapshots: cheap (reference-based) checkpoints of everything, including shell side effects; time-travel, branch, diff, compact |
| Synchronization | push/pull of digest-addressed layers through registries, proxies, or relays; offline-first by construction |
Coming from Docker or Podman? The muscle memory transfers (
artipod run -it alpine:3.22,artipod pods), but the model inverts: in Docker the image is the artifact and the container's writable layer is scratch; here the writable state is the artifact — versioned, encrypted, pushable. And a pod is not a Kubernetes Pod — it's durable state that execution attaches to, not scheduled compute. Full orientation and concept map: docs/containers.md.
Artipod manages durable pod state and execution attached to that state. Its Docker backend runs commands against pod mounts; it is not a general HTTP application host or a Cloudflare Containers lifecycle adapter. Application routing, scaling, deployment, and production access policy belong to the embedding application, such as mieweb/cloud.
artipod serve is a quick POC and reference host for Artipod capabilities, not the prescribed production deployment system. Reuse the library APIs in your own host where appropriate. See the container orientation for the ownership boundary.
npx artipod run -it # fresh pod → artipod-bash, kept under ~/.artipod/pods
npx artipod run -it alpine:3.22 # a registry image, cloned in writable
npm install -g artipod # permanent `artipod` on PATH (alias for @artipod/core)
artipod pods # past runs — the `docker ps -a` of pods
artipod run -it 500edf8b # resume a kept pod by id prefix
artipod run -it field/notes:1 # a ref you pushed earlier
artipod import ~/proj team/proj:1 # folder → image in the store (no pod)
artipod run -it --base ~/skel --base ./patches # stack folders as layers (later wins)(npx github:mieweb/artipod also works — it compiles from source on first run and caches.)
Pods are kept on the real filesystem by default, so exit loses nothing — create-on-write: a
fresh pod that saw no writes is quietly removed again. --rm makes the pod ephemeral (RAM only
— add --disk to back it by a deleted-on-exit temp dir when changes may not fit in memory),
artipod rm <pod> deletes kept pods and artipod prune removes the untagged ones (-a for
all; tag inside the shell with artipod commit --tag <name>:<tag>), --dir <path> keeps a pod
at a path of your choosing, --store <path> (default ~/.artipod/store) backs
push/pull/clone and REF lookup, -c '<cmd>' runs one line and exits. Inside the shell,
artipod lists the pod verbs (snapshot, commit, push, hydrate, …).
Host folders enter the layer model two ways. artipod import <dir> <name:tag> snapshots a
folder into the store as an image ref without booting a pod — content-addressed, so
re-importing an unchanged tree is a no-op and only changed files cost bytes; artipod run -it <name:tag> then materializes it like any other ref. --base <dir>[:<podpath>] does the
import at boot and materializes the folder into the pod (default target /); repeat it to
stack folders in order — later --base wins on conflicts, and the stack sits on top of REF
when one is given. Neither ever writes back to the host folder, and committing inside the
shell freezes the merged result as a layer whose parent chain records the imported bases.
For a live window onto the host instead of a snapshot, -v <dir>:<podpath>[:ro|:cow]
mounts a folder docker-style (repeatable): rw by default (writes inside the shell land in the
real folder), :cow keeps writes in RAM so the host is never touched, :ro marks it
read-only for the tool layer and keeps it out of commits. rw/cow mounts are commit roots —
mount under /mnt (commit-excluded) when it's just source material to copy from.
import { initFileSystem, createSandbox } from '@artipod/core/sandbox';
const { zfs } = await initFileSystem(); // IndexedDB (default) or OPFS
const sandbox = createSandbox({ zfs }); // just-bash over ZenFS
const r = await sandbox.exec('git clone https://github.com/user/repo && ls repo | head');import { ArtiPod, ArtiMount } from '@artipod/core';
const pod = new ArtiPod({
workspaceDir: '/data/workspaces', // auto-creates the writable 'main' mount
mounts: [new ArtiMount('src', '/data/project/src', /* readonly */ true)],
});
await pod.initialize();
await pod.startContainer('./container/Dockerfile'); // hardened: CapDrop ALL, seccomp, no network
const out = await pod.executeCommand('grep -r TODO /context/src | wc -l');See docs/linux.md for the full server story (realizers, OCI-layout store, systemd).
import { createToolRegistry } from '@artipod/core/tools';
import { ToolCallingLoop } from '@artipod/core/agent';
const tools = createToolRegistry(pod); // read_file, apply_patch, bash, … — pod-confined
const loop = new ToolCallingLoop(client, tools);
await loop.run('Summarize the README, then fix the failing test.');
// tool-executing turns auto-snapshot (pod.agentLoopOptions(), default on) — `artipod snapshot diff` shows what the model didThe agent is confined to the pod. Anything outside it requires sudo — which the agent cannot self-approve. See docs/security-model.md.
One line to give any web app a drop-down artipod console (Quake-style):
import { installConsole } from '@artipod/core/console';
installConsole({ sandbox, hotkey: 'Ctrl+`' }); // Ctrl+` / Ctrl+~ toggles the overlaySee docs/console.md.
Single package, ESM subpath exports (browser/node split via export conditions):
@artipod/core ArtiPod, ArtiMount, pod manifest, pod events
@artipod/core/tools VS Code-schema tools + bash, OpenAI & MCP serializers
@artipod/core/prompts prompt templates + buildPrompt
@artipod/core/sandbox just-bash isolate, ZenFS adapter, storage backends
@artipod/core/agent tool-calling loop, OpenAI-compatible + local ONNX clients
@artipod/core/proc /proc providers (host state as files)
@artipod/core/host headless UI controllers (terminal session, file buffer, tree)
@artipod/core/console Ctrl+~ drop-in overlay console
@artipod/core/manager pod hosting, PodStore, keyring, leases, policy
@artipod/core/server fetch-style hosting handlers: pod store, exec, git/OCI proxies (node-only)
@artipod/core/oci blob store, layer FS, snapshots, transports
@artipod/core/docker hardened Docker execution (node-only)
- Disk holds only ciphertext + wrapped keys; usable keys live in a memory keyring, on server-issued leases with a TTL. Lock = the key evaporates; login restores it.
- Offline is first-class: signed offline grants (e.g. 24 h) wrap keys to a device; delegated manager certificates let a ship/station/site issue leases with no home-base round trip.
- The agent is confined to its pod.
sudois the only escape, it requires explicit human approval, and the human may only approve if admin policy grants them that right. - Relays never need plaintext — content addressing verifies end-to-end, so untrusted hops can cache and forward.
- Honesty: a browser can enforce cryptography, not process boundaries — see the threat-model tables in docs/encryption.md before assuming more.
Built for real disconnection profiles: a 24-hour offline clinic visit, a light-minutes-away station where all operations are local and sync is merely delayed, and an intermittently-connected ship where laptops relay through an on-board server. Walkthroughs in docs/encryption.md.
| Doc | Contents |
|---|---|
| docs/containers.md | Orientation for Docker/Podman/Kubernetes users: concept map, where each runtime fits, the pod-term collision |
| docs/on-disk-layout.md | What lands on disk: ~/.artipod, the per-pod /.artipod store, plaintext vs ciphertext |
| docs/browser.md | Browser implementation: ZenFS backends, OPFS/IndexedDB, ingest API, devices |
| docs/multi-tab.md | Multi-tab concurrency: shared cow uppers, per-tab caches, last-write-wins hazards, Yjs/SharedWorker roadmap |
| docs/linux.md | Linux/server implementation: realizers, Docker hardening, stores, deployment |
| docs/bash-isolate.md | The bash isolate in browser and server: semantics, sessions, limits |
| docs/encryption.md | Encryption at rest, keyring, leases, offline grants, delegation |
| docs/security-model.md | Agent confinement, sudo, approval flow, admin policy |
| docs/dossier.md | The dossier pattern: long-lived entities (patients, cases, customers, tickets) with open workstreams and sealed, immutable milestones |
| docs/console.md | The Ctrl+~ installable console module |
| artipod-layer-plan.md | The living implementation plan (phases, decisions, worklogs) |
MIT