Agent Safety Architecture
LumenFlow uses host-neutral, layered protection for agent work. Lifecycle commands, gates, repository hooks, and recorded evidence remain authoritative.
Universal Contract
Section titled “Universal Contract”Every agent consumes the same managed surfaces:
LUMENFLOW.md, followed byLUMENFLOW.local.mdwhen present.lumenflow/constraints.md,.lumenflow/rules/, and.lumenflow/skills/workspace.yaml- the LumenFlow CLI and pack runtime
- WU briefs, memory signals, checkpoints, stamps, and audit records
LumenFlow does not generate, inspect, or manage host-specific instruction or skill directories. A project-owned discovery file may point to the contract, but is not part of LumenFlow’s compatibility promise.
Fleet Lifecycle Authority
Section titled “Fleet Lifecycle Authority”Cleanup, recovery, completion, and orchestration derive authority from WU id, lane lock, session/lease evidence, lane branch, worktree path, done stamp, and audit records. Provider names, models, hosted runners, and UIs are execution metadata only.
Active or recently active WU leases block destructive cleanup. Restore a missing worktree with an intact lane branch through the lifecycle command:
wu:prune --execute records recoverable tracked removals in the workspace’s quarantine cleanup log. Raw git worktree pruning is an implementation
detail, not lifecycle authority.
Bootstrap And Prerequisite Safety
Section titled “Bootstrap And Prerequisite Safety”Agent-facing root scripts should invoke commands through the LumenFlow CLI launcher. The launcher keeps clean WU worktrees from failing command discovery before local CLI build artifacts exist.
Use the script guard after alias changes:
Use the prerequisite reporter before local database or model workflows:
The report omits local service keys and token-shaped values. Do not paste raw service startup, environment, or provider-token output into prompts, issues, or WU notes.
Delivery telemetry safe-text classifier
Section titled “Delivery telemetry safe-text classifier”Lifecycle telemetry (wu_telemetry.v1) redacts free-text fields before emission. The classifier
rejects two shapes: named credential keywords (api_key, token, secret, password,
authorization) immediately followed by : or = and a value, and SCREAMING_SNAKE_CASE
env-style assignments whose underscore-joined name contains a secret-bearing segment such as KEY,
TOKEN, SECRET, PASSWORD, AUTH, DSN, SESSION or COOKIE (for example DB_PASSWORD=...).
A plain configuration switch such as MODEL_ROUTE=direct is admitted. Ordinary capitalized work-item prefixes such as BUG:, FIXME:, or API: are
not credential shapes and are admitted. Every rejection carries a reasonCode and never echoes the
matched value, so the diagnostic is safe to log or count.
What the pre-commit hook checks and what it costs
Section titled “What the pre-commit hook checks and what it costs”Every commit runs .husky/pre-commit. It checks the staged set only (git diff --cached and
git show :path), never the working tree and never unchanged files:
| Check | Blocks |
|---|---|
| Lifecycle integrity | A hook, manifest or policy that no longer matches its recorded digest (lumenflow:enable) |
| WU transaction evidence | A staged WU YAML change that no governed lifecycle command produced |
| Main-branch block | A direct commit to main or master |
| Worktree discipline | A main-checkout commit on a non-agent branch while worktrees exist |
| Lockfile sync | A root package.json dependency change staged without pnpm-lock.yaml |
| WU YAML schema | A staged WU YAML that fails the schema |
| Secret scan | A staged file with a known key shape or a credential assignment |
| Absolute-path scan | A staged source file that embeds a machine-specific absolute path |
Inside a git hook, every check that needs node runs in one process: pnpm lumenflow:pre-commit-check
runs the built CLI check and the
repository checks in a single process instead of starting a launcher, a child, and a node helper
per check. The two shell scans start no node. pnpm lumenflow:pre-commit-check run by hand, in CI, with --ci or with --pre-push
still goes through the launcher.
Protection Layers
Section titled “Protection Layers”-
Universal contract —
LUMENFLOW.md, constraints, rules, and canonical skills define one workflow for every agent.wu:briefrenders the same contract regardless of execution metadata. -
Governed file operations — worktree guards and the vendor-neutral
file:write/file:editcommands reject paths outside the declared checkout. -
Pre-commit enforcement — the pre-commit hook blocks commits to
main, enforces worktree discipline, requires a lockfile change with apackage.jsondependency change, validates staged WU YAML, and scans the staged content for secrets and absolute paths. It reads only the staged index, so its cost follows the number of staged files (see What the pre-commit hook checks and what it costs). The hook has one fast path, decided from the staged set: it exits early only inside a governed write context and only when the staged set is exactly the ownedwu-events.jsonlledger, the one commitwu:briefmakes for its evidence. An operator commit, a WU YAML commit, or a ledger commit with any other file staged runs the full check. Neither fast path is a--no-verifybypass. -
Pre-push enforcement — the pre-push hook enforces branch and lifecycle policy before a push. The hook has one fast path, decided from the push itself: it exits early only when every ref update is a delete of a
refs/lumenflow/*ref or an update of one that names a parentless empty-tree commit, which is what a claim’s liveness marker is. A push tomain, a lane branch, arefs/lumenflow/*ref that carries files or history, or any mix is checked exactly as before.The check also judges the claim-source metadata refs
refs/lumenflow/claims/<wu-id>andrefs/lumenflow/claim-history/<wu-id>/<generation>. Each must be an empty-tree commit, read from the commit object’s own headers, and every parent must either be reachable from the configured integration target or be the exact tip of another update in the same push, which is then checked as a normal range. A liveness marker’s parent may also be its own claim’s published lane tip: the marker’s versioned descriptor must be for that WU and name that exact commit as its lane commit, and the lane branch the integration remote advertises must be at it or descend from it. A refresh that pushes only the marker is therefore admitted. It fails closed: a non-commit, a non-empty tree, a parent that is unpublished and not covered by a same-push update or by that descriptor, or that cannot be read, refuses the whole push, even at a shallow boundary (a published parent still passes). Malformed or missing hook input also refuses; empty input (git sends it for an up-to-date push) is accepted as no ref updates. The proof grants no claim publication authority.The handoff receipt
wu:briefpublishes,refs/lumenflow/handoff/<wu-id>/<digest>, is skipped by the range check only when it has the exact receipt shape: one parent, published on the integration target or pushed in the same push, and a tree holding only a regularreceipt.jsonblob. It is still secret-scanned. A handoff ref of any other shape, such as one that also carries a WU YAML change, gets the full check. If the receipt push reports success but the ref does not read back on the remote,wu:briefpushes once more. If the ref is still missing, it fails with an error that names the ref. -
Lifecycle commands —
wu:create,wu:claim,wu:prep,wu:done,wu:cleanup, and recovery commands own state transitions. -
Quality gates — configured gates block unsafe or invalid completion and write structured evidence.
-
Ownership guards — state-mutating commands validate the target WU’s session owner. Audited overrides require
--override-owner --reason "...". -
Memory and signals — handoffs, discoveries, directed signals, and checkpoints remain shared across hosts.
-
Audit and recovery evidence — events, stamps, gate records, override records, and quarantine manifests make lifecycle decisions inspectable and recoverable.
Authenticated web workspace operations
Section titled “Authenticated web workspace operations”Workspace create, connect, inventory, author, and install routes authenticate before consuming a
body or accessing the filesystem. Actual request bytes are bounded while streaming. The configured
host root provides path containment only; for an existing target, the top-level
workspace.yaml.id must exactly match the authenticated caller workspace identity.
Canonical checks reject symlink components before pack removal or writes. Pack installation uses a
host-pinned CLI entrypoint rather than repository-selected npx resolution. A mismatch or unsafe
path fails closed before pack generation, registry lookup, filesystem mutation, subprocess
execution, or any other body-driven side effect.
Hosts exposing local workspace pack installation set LUMENFLOW_WEB_CLI_ENTRYPOINT to an
absolute, host-owned pack-install.js path. Missing, relative, symlink-only, or non-file values
fail closed; the route never falls back to PATH, npx, or workspace dependencies.
Project-owned native hooks may surface violations earlier. They do not change LumenFlow authority and are not generated or maintained by LumenFlow.
Agent Confinement (Boxed Sessions)
Section titled “Agent Confinement (Boxed Sessions)”On Linux, wu-sandbox --box confines an agent session against the host, and its pushes reach
origin only through a host relay that refuses rewriting the integration branch and writing tags.
A boxed session reaches the repository’s origin only through a host relay, which holds the credential and refuses any push that rewrites or deletes the integration branch, writes a tag, or writes outside lane and claim refs. Landed history on the integration branch only grows; LumenFlow does not check what a box adds. Boxes of different repositories share no files, sockets or processes; boxes of one repository share its memory directory and its relay.
Boxed sessions are opt-in and Linux-only (x86_64 and arm64); macOS, Windows and WSL2 refuse --box.
Which layer mediates what. A tool the kernel dispatches (the built-in fs:*/proc:exec
capabilities and pack-registered tools such as git:status, git:sync-main) is mediated by the
kernel: scope checks, deny-wins policy, and an evidence receipt. Everything else an agent session
does — including the WU lifecycle commands (wu:claim, wu:prep, wu:done, wu:rebase), which
call git directly rather than through the kernel — is mediated by boxing the whole session at the
OS level, not by the kernel.
wu-sandbox --box exits with the harness’s own exit status when the harness ran, and its input
and output are the harness’s. Whenever no harness ran (a refused probe, a relay or proxy that did
not start, a failed first-start clone, an unreachable origin), it exits non-zero and prints a named
reason. Every start, verdict, failure and stop is one line in
the confinement log, and so is every relay session (relay.session-start,
relay.session-end) and every egress-proxy refusal (sidecar-log events).
Per-job egress and task-branch checks. A host embedding the egress proxy can pass a per-job
allowlist of exact host:port strings. Each entry is validated against the same strict grammar as
pack-declared network scopes, plus a required port and no CIDR or wildcard; one invalid entry refuses
the whole list before the proxy starts. Every policy denial (a host that is not listed, or an address
that fails vetting) is reported once to a typed onDenied sink, and createEgressViolationSink turns
it into one sandbox:violation event of type network_egress_blocked that names the host, port and
reason. A caller that holds the push credential can also evaluate a ref update against the task-branch policy: it accepts an update only for the one task branch and refuses the default, integration and any protected branch (even as a fast-forward), tags and deletions.
Profiles. --profile selects what a box is given:
| Profile | Credential | Origin (relay sockets) | Egress | Clone |
|---|---|---|---|---|
agent (default) | model session in persistent home | fetch and push | the profile’s hosts plus host rules | its own, cloned through the relay |
generic | none | fetch and push | host rules only | its own, cloned through the relay |
build | none | fetch only | host rules only | a fresh clone at a typed commit; single use |
test | none | none | none | a named box’s existing clone |
A test box allows nested user namespaces and the mount family so the engine’s own real-process
tests can run, and its posture line says so. wu-sandbox --conformance --box <name> runs those
suites in a test box against that box’s clone and fails when no real-process test executed or
any was skipped.
What a box can reach. wu-sandbox --check [--box <name>] [--profile <name>] prints the mounts,
environment variable names, egress rules with their reasons, sockets, the relay policy (the
integration branch and the writable refs), the credential file’s location (never its content), the
gate path and the engine facts, then starts one box with argv true and prints the posture.
Interactive sessions. When stdin and stdout are TTYs, wu-sandbox --box
inherits the terminal through every hop. --interactive selects this explicitly;
--no-interactive keeps the pipe protocol and SIGINT teardown. The session choice
is independent of profile. Interactive control messages use a dedicated socket, accepted once per start, so probe and handshake
bytes never enter harness stdio. The launcher forwards Ctrl-C and resize over that
connection to the exact harness child; Linux interactive sidecars and bubblewrap’s
outer parent use separate sessions to survive the terminal signal. Repeated Ctrl-C
preserves the launcher, sidecars and lock while the harness remains running.
SIGTERM still tears down and exits 143; terminal hangup cleans up and exits 129.
Default and Windows spawn behavior stays unchanged.
Authenticate a model harness inside its writable, persistent box home; no host
model credential is bound. --check adds current or last interactive mode and a
possible credential hint from bounded home-file metadata, without opening files or
printing their names or contents. The hint does not prove authentication.
Bootstrap inside a box. A box keeps its pnpm store at <clone>/.pnpm-store, links
.lumenflow/memory to the repository’s shared memory directory, and installs through the egress proxy.
Native modules build against the pinned Node toolchain’s headers (npm_config_nodedir), and pnpm’s
network concurrency is capped (pnpm_config_network_concurrency) to stay under the proxy’s
connection limit. The managed ignore block carries .lumenflow/memory and .pnpm-store/; a box
refuses to start if git would see either. Every governed lifecycle command, from wu:claim to
wu:done, runs unchanged inside a box.
Box dispatch leaves the legacy --id parser unloaded. Legacy mode retains
its synchronous parser, flags, help exits and existing argv behavior.
Session startup. A box keeps its clone and home between sessions. First clone, dependency installation and initial build are once-per-box bootstrap work. Each later session starts a fresh launcher and confined sidecars and performs its probe.
Builds and publishes. A build box clones a commit you name, builds release or docs
artifacts, and hands them to the host through a copy-out that accepts regular files only (symlinks,
hardlinks, devices, FIFOs and sockets are refused). Copied files have mode 0644 and directories
0755; source modes and process umask stay unchanged. In the box, pnpm release --build-only (with
--at and --out) packs every package with a sha256 manifest. On the host,
pnpm release --publish-only --artifacts uploads exactly the files that manifest names, matched by
sha256, and runs no checkout, install or build. A box cannot write tags, so the release tag is a
host step (--skip-tag).
Box-only mode (Linux). A repository can require boxed sessions. Set
unconfined_claims: refuse on the repository’s trusted_repos entry in the host config; from the
next command, wu:claim, wu:brief, wu:prep, wu:done, wu:recover, wu:rebase, the
pre-commit check and every micro-worktree writer (such as wu:edit) run outside a box exit with
requester-unconfined and append one line to the host log. Inside a box they run unchanged.
Host-only steps (release --publish-only, wu-sandbox --check) keep working. A
present but invalid host config refuses; no host config, or a repository it does not name, allows.
The check is a
guard-rail against unconfined lifecycle use.
Unsandboxed waiver. LUMENFLOW_SANDBOX_ALLOW_UNSANDBOXED=1 is the one operator waiver for
running an operator command without confinement: a wu:sandbox command, or, on a host that cannot
run boxes, a lifecycle command in box-only mode. It is off by default, warned, shown by
lumenflow:doctor and recorded in the host log each time it is honoured. The host owner can forbid
it with unsandboxed_waiver: forbid; a host enrolled as a compute runner forbids it by default. See
native kernel confinement.
Host-attested microVM boundary
Section titled “Host-attested microVM boundary”On Linux, a runner that executes one assignment in a single-use microVM can run the sandbox without nested bwrap, because the VM is the isolation boundary. The sandbox posture accepts it only through a signed host attestation, never a declaration. Commands run in an isolated process and the result is recorded as evidence. See the native kernel confinement page for the posture model.
Coverage Matrix
Section titled “Coverage Matrix”| Surface | Authority | Blocks violations |
|---|---|---|
| Universal contract | LumenFlow | Policy/injection |
| File-operation guards | LumenFlow | Yes |
| Git hooks | Repository | Yes |
| WU lifecycle commands | LumenFlow | Yes |
| Quality gates | LumenFlow | Yes |
| Session ownership | LumenFlow | Yes |
| Memory and audit evidence | LumenFlow | Completion checks |
| Native pre-write feedback | Repository | If configured |
| Boxed session origin pushes (Linux) | LumenFlow (host relay) | Integration-branch rewrite/delete, tag writes, off-namespace writes |
| Lifecycle outside a box (Linux) | Host config floor | When the repository requires boxes (requester-unconfined) |
| Unsandboxed waiver | Host waiver policy | When the host forbids the waiver |
The optional native-pre-write-feedback row improves feedback latency only. It does not grant, remove, or replace lifecycle authority.
Governed Local Main Sync
Section titled “Governed Local Main Sync”Raw git pull from the main checkout mutates local main without LumenFlow guards. For a clean,
fast-forward-only update, use:
The command fetches origin/main, requires a clean primary checkout on main, rejects WU
worktrees and in-progress merge/rebase state, verifies ancestry, and then fast-forwards. Divergent
or dirty checkouts fail with remediation guidance.
YAML Editing Policy
Section titled “YAML Editing Policy”Use governed commands for configuration and WU metadata:
These commands provide schema validation, atomic updates, type coercion, and audit evidence.
Universal Setup
Section titled “Universal Setup”One command produces the universal scaffold; there is no client or vendor flag:
Connecting the public contract to any host-native discovery or hook mechanism is a task for your repository maintainers. See Connect an AI Coding Assistant.
Lifecycle Enforcement Integrity
Section titled “Lifecycle Enforcement Integrity”Repository-owned pre-commit, pre-push, and CI delegators protect the lifecycle manifest, receipts,
journals, hooks, and managed CI block. lumenflow:doctor reports enforced,
non-compliant-disabled, missing, stale, tampered, or unavailable; only enforced is
certifying. Run pnpm lumenflow:enable to atomically repair every remediable state; restore
repository storage access before retrying unavailable. Lifecycle front doors independently reject no-verify, raw-git,
replayed-receipt, and synthetic-brief attempts.
Disabled and uninstalled consumers cannot claim lifecycle certification.