Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
92f4e5e
feat: microvm isolation, runbook scan and mechanical verification
Aug 28, 2026
8f51827
fix: microvm review findings on ship, checks, review, turn and scan l…
Aug 28, 2026
b63e5a9
test: never spawn claude setup-token from autoconfig tests
Aug 28, 2026
e77f715
fix: never set a boot user on the microvm turn sandbox spec
Aug 29, 2026
e0664f4
fix: tar directory trees for microsandbox copyFromHost/copyToHost
Aug 29, 2026
658215f
fix: stop double-parsing the runbook proposal before sanitizing it
Aug 29, 2026
efa454d
feat: add microvm guest-user and agent-install bootstrap
Aug 29, 2026
54f8687
feat: install the agent CLI in a cold-booted microvm dev turn
Aug 29, 2026
16b5513
feat: bake the agent CLI into the microvm project snapshot
Aug 29, 2026
e94b2c9
fix: pass the agent id when snapshotting after a runbook scan
Aug 29, 2026
60eeb50
feat: install the agent CLI in the microvm review sandbox
Aug 29, 2026
1a762b2
fix: fold the agent id into every microvm snapshot resolution
Aug 29, 2026
d2af449
docs: list runbook scan in the CLI help text
Aug 29, 2026
1e7732f
fix: forward provider secrets to the microvm runbook scan proposal
Aug 29, 2026
c612a4c
fix: cap the microvm sandbox list request at the SDK's own limit
Aug 29, 2026
df18e09
fix: never set a boot workdir on the microvm review sandbox
Aug 29, 2026
ec2905d
fix: remove a microvm sandbox the SDK provisioned before create failed
Aug 29, 2026
cbb6b9c
fix: probe microvm capability at boot so a microvm project never degr…
Aug 29, 2026
d8ba33d
fix: never set a boot workdir on the gitops sandbox
Aug 29, 2026
97ff43b
fix: read the validated runbook from the project root, never the task…
Aug 29, 2026
dc2ec9d
fix: sweep orphaned microvm sandboxes whenever the machine can run them
Aug 29, 2026
ba82953
fix: take the validated sha from the local runbook validation written…
Aug 29, 2026
554a1e5
fix: retry a sandbox create() that fails with a transient store datab…
Aug 29, 2026
693e3c0
fix: rebuild the single-use sandbox builder on every create() retry
Aug 29, 2026
eeaca01
fix: bump microsandbox SDK to 0.6.16, fixes a host-side zombie leak a…
Aug 29, 2026
9530270
Revert "fix: bump microsandbox SDK to 0.6.16, fixes a host-side zombi…
Aug 29, 2026
b0356f8
Reapply "fix: bump microsandbox SDK to 0.6.16, fixes a host-side zomb…
Aug 29, 2026
dbe2642
Revert "Reapply "fix: bump microsandbox SDK to 0.6.16, fixes a host-s…
Aug 29, 2026
ce23ced
fix: mark the gitops sandbox worktree a safe.directory before any git…
Aug 29, 2026
2c128ef
docs: note the real microvm run fixes in the changelog
Aug 29, 2026
0d6636e
fix: share one microsandbox driver per process
Aug 29, 2026
fb61610
fix: stop native sandbox instance and scrub store before sdk use
Aug 29, 2026
6391288
fix: mount the package cache volume atomically
Aug 29, 2026
b5e936e
test: make the kvm probe test host-independent
Aug 29, 2026
fefa70e
chore: bump microsandbox to 0.6.16
Aug 29, 2026
4082d00
test: reset process.exitCode after the failed scan test
Aug 29, 2026
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
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,31 @@
All notable changes to `codesema` (the npm package in `packages/cli`) are documented here.
Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning: [SemVer](https://semver.org).

## [0.20.0] - 2026-08-28

### Added

- **`isolation: "microvm"`: a task's turn runs in a disposable Microsandbox VM.** Beside `container` and `policy`, the runner can now boot a throwaway microVM (libkrun/KVM, `microsandbox` SDK 0.6.15 as an optional dependency) per turn: the worktree is copied in, the agent runs as a non-root user with the same tools as the cage, the result is copied back and the VM is destroyed. Never picked by `auto`; `codesema runner serve` refuses to create a `microvm` task when the workspace was not probed for it (`msb doctor` is the way out). The seam is `SandboxDriver` (`microsandbox-driver.ts`, with a `FakeSandboxDriver` for every test) and `runMicrovmTurn`.
- **The runbook: how a repository is installed, started and tested, validated by actually doing it.** `codesema runbook scan` lets an agent propose `.codesema/runbook.json` (image, install, services, healthchecks, tests, egress allowlist, `depends_on_files`), then replays it in a microVM and only keeps a runbook whose tests pass. The runner daemon claims and runs one queued hub runbook scan per tick in `microvm` mode. `@codesema/contract` 0.11.0 carries `RunbookConfig`, `RunbookValidation`, `RunbookScan` and their sanitizers.
- **Mechanical verification of a microvm task.** After a turn's commit and before the review, `verifyTask` replays the validated runbook's `tests` in a fresh VM restored from the project snapshot; a drift of `depends_on_files` since validation refuses the verification outright. The verdict (`TaskVerification`) is persisted, reported to the hub, folded into the reviewer's prompt as a mandatory chapter, and a refused or failed verification sends the task back exactly like red checks.
- **Warm per-project snapshot.** `microvm-snapshot.ts` builds one snapshot per project (image + `install` + services), keyed by a fingerprint of lockfiles, compose file and canonical runbook; older ones are purged, and a runbook needing a flat root disk (dockerd) falls back to a cold boot.
- **Checks, review and ship run in their own sandboxes for a microvm task.** `runChecks`, `runAdHocCheck` and `bootstrapWorktreeInstall` accept a `StepExecutor` (`microvmStepExecutor`); the reviewer runs in a read-only VM distinct from the dev VM (`runMicrovmReview`, network limited to the Anthropic hosts); the push and the merge request come from a dedicated `codesema-gitops-<taskId>` sandbox with the forge token carried as a placeholder secret only, never in argv or env. Orphaned `codesema-*` sandboxes are swept at boot.
- **Hub contract for the runbook loop.** New `hub-client` calls: `listRunbookScans`, `claimRunbookScan`, `reportRunbookScanResult`, `failRunbookScan`, `currentRunbook`, `verification`; the `merged` transition carries `changed_files` so the hub can mark a runbook stale.

### Changed

- **`TaskIsolation` gains `microvm`** (`@codesema/contract`, CLI config, web UI badge and plan): a task record carries `runbook_sha`/`runbook_integrity` when it was verified against a runbook.
- **The reviewer resolves the microvm snapshot, runbook and mechanical verification per task turn** (`resolveReviewContext`) instead of freezing them once per project, and finding repro checks of a VM-isolated task run through the injected microvm `StepExecutor` instead of always falling back to docker/podman.
- **The web workspace shows a microVM badge, plan label and build-image hint** for `microvm` tasks instead of folding them into `policy`.
- **Microsandbox SDK pinned to 0.6.16** (was 0.6.15), which requires an `msb` 0.6.16 runtime and a store created by it: the published 0.6.16 cannot open a store created by 0.6.15 (its migration `m20260818_sandbox_network_slot` is ordered before 0.6.15's last one), so an existing `~/.microsandbox` from 0.6.15 has to be moved aside and the project snapshots rebuilt.

### Fixed

- **Review findings folded in before release** (adversarial review of the microvm lots, 23 confirmed findings). Ship: an origin URL embedding credentials is stripped before it reaches the gitops sandbox, host-side `credential.helper`/`http.*.extraheader` are purged in the guest, and an unknown forge gets both `GH_TOKEN` and `GITLAB_TOKEN` declared instead of a guess; the forge token is picked from the project's actual remote, never from whichever env var happens to exist. Checks: `microvmStepExecutor` copies `/work` back after every step (an install step's result reached nobody before), mounts the linked worktree's git common dir and removes the synthetic `.git` pointer before the copy-back. Review: dual mode gave both lanes the same sandbox name; the review VM's destroy is guarded and its duration carries the same headroom as checks. Turn: `destroy()` is memoised and awaited after the worktree copy-back (a killed turn could lose its files), and the guest user name is validated before being interpolated in root scripts. Runbook scan: the claim is renewed every half-lease while the scan runs and no result is reported once the lease is lost; a scan whose `head_sha` does not match the local HEAD is skipped instead of validating the wrong commit. `merged` transitions now carry `changed_files` so the hub can mark a runbook stale. `sanitizeTaskVerification` refuses a record without `head_sha` and bounds its timestamps like the hub does; the real Microsandbox driver refuses a sandbox without a network policy, like the fake always did.
- **Real microVM run findings folded in** (local end-to-end on Microsandbox 0.6.15, 2026-08-29). `user` and `workdir` on the sandbox spec made every `create()` fail (the guest user is created and used from the scripts instead); `copyFromHost`/`copyToHost` only moved single files (directories are tarred); the runbook proposal was parsed twice and always rejected; no VM ever had the agent CLI (it is now baked into the project snapshot at build time, `registry.npmjs.org` allowed for that build only, installed at boot on a cold VM); provider secrets never reached the proposal VM; `listSandboxes` asked the SDK for more than its 100 cap; the boot probe never probed Microsandbox so a `microvm` project silently degraded to `policy`; the validated runbook was read from the task worktree where it never exists (read from the project root, with `.codesema/runbook.validation.json` written by `codesema runbook scan` carrying the validated sha); `git push` from the gitops sandbox was refused as dubious ownership; a sandbox the SDK provisioned before `create()` failed is now removed. Two driver defects made every `create()` after the first `destroy()` of a long-lived server fail with `FOREIGN KEY constraint failed` or `disk I/O error` and leave a `[libkrun VM] <defunct>` zombie: `destroy()` stopped a handle re-fetched with `Sandbox.get` instead of the native instance `create()` returned (the only one that reaps the VM process), and a per-destroy purge rewrote `~/.microsandbox/db/msb.db` under the live SDK connection. `destroy()` now stops the original instance first, the purge is gone, and the secret values the runtime leaves in the store WAL are scrubbed (`wal_checkpoint(TRUNCATE)` + `VACUUM`) once per process before the SDK is loaded, skipped with a notice while another process holds the store open.
- The package cache volume of the checks step is now mounted with `namedWith(name, 'ensure-exists')`: a `named()` mount is existing-only in the SDK and failed the first checks attempt of every task with `volume not found` even right after `ensureVolume()` had returned, so the volume is created or reused inside the same `create()` call.


## [0.19.0] - 2026-08-28

### Added
Expand Down
54 changes: 28 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ From the page you can also:

**Checks run in a sandbox.** Alongside the review, typecheck, tests and lint run in an ephemeral `docker` or `podman` container mounted on the task's worktree, with `--network none` and cpu/memory caps. The plan comes from your `checks` key, otherwise from what the repository already declares (lefthook hooks, CI workflow jobs, filtered through a command allowlist), otherwise from the lockfile and the `typecheck`/`test`/`lint` scripts of `package.json`. Checks never block a task: they are a second opinion next to the review.

**Tasks can be caged.** With a container runtime available, a task runs inside a container built from your `.devcontainer` (or `node:26`): the worktree is the only writable host mount, the git directory is mounted read-only, and the only network exit is a proxy restricted to the agent's own API domains (`isolationAllowedDomains`). Commits stay on the host, so your git credentials never enter the container. `isolation` picks the mode: `auto` (default, falls back to host hardening and says why), `container` (mandatory) or `policy` (always on the host). `claude` and `opencode` are cageable today.
**Tasks can be caged.** With a container runtime available, a task runs inside a container built from your `.devcontainer` (or `node:26`): the worktree is the only writable host mount, the git directory is mounted read-only, and the only network exit is a proxy restricted to the agent's own API domains (`isolationAllowedDomains`). Commits stay on the host, so your git credentials never enter the container. `isolation` picks the mode: `auto` (default, falls back to host hardening and says why), `container` (mandatory), `policy` (always on the host) or `microvm` (mandatory, a disposable Microsandbox VM instead of a container). `claude` and `opencode` are cageable today.

**Or run in a disposable microVM.** Set `isolation: "microvm"` and a task's whole turn runs inside a fresh [Microsandbox](https://microsandbox.dev) VM instead of a container — stronger isolation than a container's namespaces, at the cost of a heavier, beta dependency. Requires Linux with `/dev/kvm` readable and writable by your user (`msb doctor` checks this) and a filesystem with reflink support (btrfs, xfs) — without it every VM copies its whole root disk on boot instead of a near-instant clone. The agent runs as a non-root guest user with no view of the host beyond a copy of the task's worktree (copied in before the turn, copied back after — there is no live mount), and the same domain allowlist as the container cage, substituted by the VM's own network proxy: the agent's provider credentials reach the guest only as `$MSB_<name>` placeholders, never as a value it could read or leak. Unlike `container`, `auto` never picks `microvm` on its own — it is beta software with a heavier host requirement, so it only ever runs when you set it explicitly.

**Statuses.** A task moves through `queued`, `running`, `waiting_for_you` (the agent ended its turn on a question), `reviewing`, then `review_ok` or `review_ko`, then `shipped` once the branch is pushed and the merge request opened via `gh`/`glab`. `interrupted` covers a turn cut short by Ctrl-C, a crash or the Stop button: the worktree and the agent session are kept, and a Resume button restarts that exact turn. Nothing restarts by itself at the next boot.

Expand Down Expand Up @@ -122,31 +124,31 @@ Settings live in two files, and CLI flags win over both:

Some keys are global only: they govern the machine (its load, its disk) or give a consent (merging, spending turns), so a cloned repository cannot set them on your behalf. A repository file that does is ignored, and says so at startup.

| Key | Default | Scope |
| ---------------------------------------------------------- | ----------------------------------- | --------------- |
| `agent`, `agentId`, `model`, `effort` | from the wizard | both |
| `language` | asked once (`en`, `fr`) | both |
| `target` | auto-detected | both |
| `port` | `4400` | both |
| `timeout` | `900` seconds | both |
| `reviewMode` | `simple` (or `dual`) | both |
| `maxAutoFixRounds` | `2` | both |
| `isolation` | `auto` (`container`, `policy`) | both |
| `isolationAllowedDomains` | the agent's own API domains | both |
| `forgeCycleLabels` | `false` | both |
| `checks` | inferred from the repository | repository file |
| `watchdogInactivitySeconds` | `1800` | both |
| `watchdogToolBudgetSeconds` | `7200` | both |
| `watchdogHeartbeatSeconds` | `30` | both |
| `maxConcurrentAgents` | `4` | global only |
| `taskRetentionCount` | `20` finished tasks per project | global only |
| `maxTaskTurns` | `30` | global only |
| `mergePolicy` | `human` (or `auto`) | global only |
| `mergeStrategy` | unset (`merge`, `squash`, `rebase`) | global only |
| `deleteBranchAfterMerge` | `false` | global only |
| `allowMergeWithoutChecks` | `false` | global only |
| `runnerAutoMerge` | `true` | global only |
| `syncUrl`, `syncWorkspaceId`, `syncSecret`, `syncAutoPush` | unset | global only |
| Key | Default | Scope |
| ---------------------------------------------------------- | ----------------------------------------- | --------------- |
| `agent`, `agentId`, `model`, `effort` | from the wizard | both |
| `language` | asked once (`en`, `fr`) | both |
| `target` | auto-detected | both |
| `port` | `4400` | both |
| `timeout` | `900` seconds | both |
| `reviewMode` | `simple` (or `dual`) | both |
| `maxAutoFixRounds` | `2` | both |
| `isolation` | `auto` (`container`, `policy`, `microvm`) | both |
| `isolationAllowedDomains` | the agent's own API domains | both |
| `forgeCycleLabels` | `false` | both |
| `checks` | inferred from the repository | repository file |
| `watchdogInactivitySeconds` | `1800` | both |
| `watchdogToolBudgetSeconds` | `7200` | both |
| `watchdogHeartbeatSeconds` | `30` | both |
| `maxConcurrentAgents` | `4` | global only |
| `taskRetentionCount` | `20` finished tasks per project | global only |
| `maxTaskTurns` | `30` | global only |
| `mergePolicy` | `human` (or `auto`) | global only |
| `mergeStrategy` | unset (`merge`, `squash`, `rebase`) | global only |
| `deleteBranchAfterMerge` | `false` | global only |
| `allowMergeWithoutChecks` | `false` | global only |
| `runnerAutoMerge` | `true` | global only |
| `syncUrl`, `syncWorkspaceId`, `syncSecret`, `syncAutoPush` | unset | global only |

`maxParallelTasks` is the former name of `maxConcurrentAgents`. It is still honoured, with a warning at startup.

Expand Down
Loading
Loading