How lockfile works
Two sides in two OS accounts. Only the console touches the file.
The source credentials, the plugins, the fetched files and the audit log live in the console account lockfile. The agent side, in your own account, holds none of them and gets 1s and 0s.
Two sides
agent account (CLI / MCP) lockfile account (console, human terminal)
lockfile plan/run/answers ──UDS──▶ approve ─▶ fetch ─▶ workspace (0700)
▲ JSON-RPC │ fd 3, read-only
│ ▼
└────── 1 / 0 per plugin ◀── strict check ◀── sandboxed plugin process
- The console (
lockfile console --profile P) runs in thelockfileaccount’s own session, in a terminal you keep in view. It serves the profile on a socket in/run/lockfile. - The agent side is the
lockfileCLI (status,sources,plugins,plan,run,answers,release) and thelockfile mcpserver with the same seven tools. It holds no secret, no plugin and no file.
The life of a plan
- The agent picks its questions.
lockfile pluginslists the installed plugins: each one’s question and what1and0mean. The agent knows the exact key from you or from a ticket: there is no listing and no stat. - The agent submits a plan.
lockfile plan --key K --plugin X --plugin Y(or thelockfile_planMCP tool). The console validates the request alone, without touching the source: the profile exists, the key is under the profile’sprefix, every plugin is installed and verified, and the plugin count stays withinmax_plugins_per_handle. - You approve. The console shows the approval screen: the key as the agent typed it, each plugin with its question, version and marketplace, and how many answers the plan reveals.
yapproves (the profile name on production),n <reason>denies. - The console fetches. The file goes into a fresh workspace (
0700) in the console account; an object abovemax_object_bytesis refused while streaming. Any fetch failure (missing, too big, denied, network) reaches the agent as one code,fetch_failed; the cause goes to the audit. - Each plugin runs in its own sandbox with the file as a read-only fd 3, and must write exactly
1or0and exit 0. Anything else isplugin_errorwithresult: null. - The answers come back together, released at the next multiple of
answer_quantumafter the run started. The agent gets a handle (f_…) for follow-up questions.
An approved plan runs once, for the peer that asked, within 10 minutes and with the policy unchanged. plan --run asks for approval and runs it in one call.
The approval screen
A first plan on a file
╭─ SAMPLES · file plan · 2 answers ─────
│ requested by uid 1000 (alice) · pid 48211 · exe /usr/local/bin/lockfile
│ agent: check the invoice reported as blank
│
│ key invoices/2026/10/abc123.pdf
│
│ file.non-empty@1.0.0 (official)
│ ? Does the file contain at least one byte? 1 = size > 0 0 = the file is empty
│ format.is-pdf@1.0.0 (official)
│ ? Does the file start like a PDF? 1 = `%PDF-` appears within the first 1024 bytes 0 = no PDF header
│
│ this plan reveals at most 2 answers about the file
╰─ Approve? [y/N]
A follow-up on the same file
╭─ SAMPLES · follow-up on f_8Kq2xR · 1 answers ─────
│ requested by uid 1000 (alice) · pid 48211 · exe /usr/local/bin/lockfile
│
│ handle f_8Kq2xR (no new fetch)
│
│ pdf.parses@1.0.0 (official)
│ ? Does the file parse as a PDF without errors? 1 = pdfcpu reads and validates it (relaxed mode) 0 = parsing or validation fails
│
│ this plan reveals at most 1 answers about the file
╰─ Approve? [y/N]
There is no size, type, hash or path on the screen: no source has been touched yet, and at desktop security level 1 the agent’s account can read this window. A plugin from an unofficial marketplace adds a red ▲ UNOFFICIAL MARKETPLACE line and is never auto-approved.
From the agent’s side
# What can I ask?
lockfile plugins --json
# Ask, and wait for the human
lockfile plan --key invoices/2026/10/abc123.pdf \
--plugin file.non-empty --plugin format.is-pdf --run --json
# One more question on the same file, no refetch
lockfile plan --handle f_8Kq2xR --plugin pdf.parses --run --json
# What was already answered, then delete the fetched file now
lockfile answers f_8Kq2xR --json
lockfile release f_8Kq2xR
| Command | MCP tool | What |
|---|---|---|
lockfile status | lockfile_status | the running consoles’ state |
lockfile sources | lockfile_sources | profiles of the running consoles (busy: waiting on the human) |
lockfile plugins | lockfile_plugins | questions, meaning of 1 and 0 |
lockfile plan --key K --plugin X… | lockfile_plan | ask the human to approve |
lockfile run PLAN_ID | lockfile_run | run an approved plan once |
lockfile answers HANDLE | lockfile_answers | answers already given |
lockfile release HANDLE | lockfile_release | delete the fetched file now |
Exit codes depend on the outcome only, never on an answer: 0 answered, 1 no console or error, 2 refused, 3 denied or no answer in time, 64 usage.
Refusals the agent can act on
Every refusal but E_FETCH_FAILED is computed from the request and the local install, before or without reading the file. Each one carries a stable code, a static hint and a retry rule: rewrite the request, ask the user, or stop.
| Code | Retry | Meaning |
|---|---|---|
E_KEY_OUTSIDE_PREFIX | rewrite | the key is not under the profile’s prefix |
E_UNKNOWN_PROFILE | rewrite | no console serves that profile |
E_TOO_MANY_PLUGINS | rewrite | the plan would pass max_plugins_per_handle |
E_HANDLE_EXPIRED | rewrite | the handle expired or was released; plan again with the key |
E_PLUGIN_NOT_INSTALLED | ask the user | the human must install it |
E_PLUGIN_YANKED | ask the user | the marketplace withdrew that version (reserved: not produced yet by v0.2.0) |
E_PLUGIN_TAMPERED | stop | an installed plugin does not match its lock entry (lockfile plugin verify) |
E_SANDBOX_UNAVAILABLE | stop | the plugin sandbox is unavailable (lockfile doctor) |
E_FETCH_FAILED | stop | the key could not be fetched (the cause is in the audit only) |
In the console
The console prints each outcome as plan pl_…: answered or refused <code>; the answers, causes, sizes and hashes go to the audit, read with lockfile audit [--since 24h] [--json] in the console account only.
:review: apply a pending loosening (a plugin installed or a marketplace added while the console runs).:status: the console state.:modeor Shift+Tab: auto mode, where plans run without a prompt. Never on production, never for unofficial plugins.:quit: stop the console; fetched files are deleted.
More:
- Usage guide: every command, profiles, the agent flow, MCP.
- Security model: what the agent can see, the boundaries, the residual risks.
- Plugins: the official marketplace and how to write a plugin.