Workspace(mounts, profiles=...) and
ws.create_session("agent_a", profile="guarded") (TypeScript:
new Workspace(mounts, { profiles }),
ws.createSession('agent_a', { profile: 'guarded' })).
- No profile means
profiles.defaultif defined, else unrestricted. The workspace’s default session follows the same rule, sodefaultalso governs barews.shell(...)andws.vfs. ws.session("agent_a", profile="guarded")returns one handle for both doors,handle.shell(line)andhandle.vfs.read(path). It creates the session or adopts an existing one.Session(ws, "agent_a")only adopts.- A line sets the session; a VFS call inherits it.
ws.vfs.read(path, session_id=...)applies only outside a running line.
How rules are read
- Command axis: rules without paths, by verb:
denybeforeask.allowdecides whether a word is a command at all. - Path axis: rules with paths, and hides, by anchor depth (literal
components before the first wildcard). The deeper entry wins whatever
its verb:
/runbook/frozen/*beats/runbook/*. Ties break by verb. - Hiding is not refusing. A hidden path is
ENOENTand leaves no record; a denied path is listed and fails when used.
commands
allow is the session’s tool set. A word not in it is
command not found (exit 127). Omitted means everything, empty means
nothing, and builtins count: allow: [cat] leaves no echo.
git push covers
git push origin main, * matches one word, a bare * every command.
The reason is only on the result’s
refusal record. Text surfaces append
policy denied: <reason> or requires approval: <reason> (ask <id>).
A rule also covers what a command reaches on its own: every entry of a
walk (grep -r, tar, cp -r, find -delete), files a command opens
(awk print >, sed w) and cached bytes. Only rules with paths and no
command reach the VFS-only doors (FUSE, ws.vfs, SFTP).
Not covered yet: file access inside a CLI or interpreter (git,
python3) and in getfattr, setfattr, ln, readlink; backends that
search on their own side (grep -r on a GitHub mount).
paths and vars
hide takes exact paths (with their subtree) or patterns. show reopens
a deeper subtree with a mode. A hide’s reason is for operators only.
vars.hide makes variables read as unset.
mounts
Narrows one mount: a lower mode, patterns anchored at the mount, and
ask / deny rules for lines working inside it by cwd or operand.
cwd, env, policy
cwd and env seed the session. policy is a script defining any of
pre_command(ctx), pre_vfs(ctx) and pre_session(ctx) (preCommand,
preVfs, preSession in JavaScript). pre_command returns None,
{"deny": reason} or {"ask": reason}; the others allow or deny. A file
defining no hook fails closed. The script may read files, through the
same gate.
ask_curl.py
Asks
Anask holds the line. The agent reads rm: Permission denied at exit
126 with a pending record:
ws.decisions until a host answers:
allow, scopeonce(default): the retry runs and spends it.allow, scopesession: every line the rule covers, from now on.deny: the retry is refused; running the line again asks again.
ask with paths and no command also asks from a file tool or
session.vfs when no line is running. allow once then covers one tool
call.
From the CLI
Over REST
id, session_id, agent_id, command, argv, cwd,
paths, reason, outcome (null while pending), scope and note.
An unknown id is 404, an answered one 409, and deny with scope
session 422.
In code
ws.decisions has pending(session_id), list(session_id) and
answer(ask_id, outcome, scope, note). Pass on_ask (onAsk) to answer
inline while the line waits.
How a grant is spent:
- Every command of a line is judged before it runs. A grant belongs to one place on the line, so a command spelled twice asks twice, and a loop body is one place.
- Grants are spent when the line ends, whether or not the command ran.
- Nested lines (
$( ),eval,source,xargs) use the outer line’s grants:echo $(cat secret.txt)asks once. - A background job keeps its line’s grants until it ends.
- A refused retry spends the refusal; the next run asks again.
cat $F, xargs cat, find -exec, a
path after a cd to a variable) is asked at its own gate, possibly after
earlier commands ran; a pending answer does not undo them.
Explain
To see what a profile would do to a line or a VFS call without running it, use Explain.Examples
- Roles over the same mounts: permissions.py, permissions.ts.
- A coded policy beside a profile
policy, and explain: policy.py, policy.ts; from YAML: workspace.yaml, guard.py, policy_yaml.py, policy_yaml.ts. - Asks answered inline: ask.py, ask.ts; from the ledger: ask_pending.py, ask_pending.ts.