Most agent frameworks conflate three separate problems: what the agent does, whether it's allowed to do it, and whether it did it correctly. xenarchos separates these explicitly — using two already-shipped tools to handle the second and third, so the project itself only has to solve the first.
What it is
xenarchos runs YAML-defined skills: ordered lists of shell commands that execute through a confirm gate, confined by xenarch-guard, and recorded as agentkit run artifacts.
bash
xenarchos init myskills
xenarchos run myskills/setup.yaml --out runs/baseline.json
edit the skill or let the environment drift
xenarchos run myskills/setup.yaml --out runs/new.json
agentkit diff runs/baseline.json runs/new.json
agentkit diff is not xenarchos code. It's agentkit's own diff command, running unmodified against xenarchos's output. That's the point.
A skill file
yaml
name: backup-notes
default_timeout_s: 30
steps:
id: create-backup
command: "cp -r ./notes ./backup"
guard:
allow_read: [./notes]
allow_write: [.]
target: 0id: verify-backup
command: "diff -r ./notes ./backup"
guard:
allow_read: [./notes, ./backup]
target: 0
Each step declares exactly what filesystem access it needs. xenarch-guard enforces that at the kernel level — the command cannot reach anything not listed, regardless of what it tries.
The confirm gate
Before each step runs (unless --yes), xenarchos prints the resolved command and its guard policy and requires a typed y:
[xenarchos] step 1/2: create-backup
command: cp -r ./notes ./backup
guard: allow-read: ./notes
allow-write: .
network: connect blocked, bind blocked
Run this step? [y/N]
Bare Enter defaults to no — security-tool convention, not general CLI convention. Declining stops the walk immediately: the remaining steps never run and never appear in the written artifact. --yes skips the prompt but still prints every step's command and policy before it runs, so an unattended run leaves the same audit trail an attended one does.
How the two dependencies are actually used
xenarch-guard is invoked as a subprocess wrapping each confirmed step:
xenarch-guard --allow-read ./notes --allow-write . -- /bin/sh -c "cp -r ./notes ./backup"
xenarchos never touches Landlock or seccomp itself. It builds the argv from the skill's guard: block and runs it. The exit code it observes is the step's real exit code — xenarch-guard execve()s into the command rather than forking, so there's no translation layer.
agentkit is used as a Python library, not a CLI. xenarchos constructs agentkit.Suite and agentkit.Task objects directly from the parsed YAML and calls agentkit.run_suite() — subprocess isolation, per-task timeout enforcement, and scoring all come from agentkit unchanged. The run artifact xenarchos writes is a byte-for-byte valid agentkit Run — agentkit diff and agentkit view work against it with zero xenarchos-specific code.
One correctness problem worth naming
agentkit's task filter is if task_ids: — a falsy check. Passing an empty list (every step declined) is treated as "no filter, run everything." This is the opposite of the intended behavior. The fix: when confirmed_ids is empty, xenarchos builds an empty Run directly rather than calling run_suite at all. This only shows up by actually reading agentkit's filter logic, not by reading its README.
Timeout is layered, deliberately
agentkit kills the adapter's subprocess on timeout. That subprocess is xenarchos's adapter, which started xenarch-guard as a child. Killing the adapter doesn't kill xenarch-guard's process tree — those orphan and keep running. xenarchos closes this gap by running the guard command in its own process group (start_new_session=True) and installing a SIGTERM handler that calls os.killpg before the adapter process dies. When agentkit tries to terminate the adapter, the handler kills the whole guarded process tree first.
What's explicitly not in v0.1
No persistent context store — agentkit's run.json is already the history. No scheduled or unattended execution — that's v0.2, and only after the confirm-gate loop has been proven with a human first. No natural-language intent resolution — steps are literal commands, same reasoning agentkit uses for not bundling an agent.
Result
17/17 tests passing, including three integration tests against a real xenarch-guard binary that verify confinement is load-bearing (not decorative), timeout kills the whole process tree, and agentkit diff catches a real regression. The flagship test: a step with allow_write: [./workspace] that attempts to write outside that path fails when guard is enforced and passes with --unsafe-no-guard — proving confinement, not the command, changed the outcome.
github.com/xenaarch-dev/xenarchos
Top comments (0)