A check is the argv verdict.

Back to the field

$ uvx ljos-policyd check -- uv run pytest
allow
$ ljos-policyd capnp -- true | wc -c
40

The typed verdict is Cap'n PolicyDecision (phronesis; Ali Akber Saifee / indynull, HaoZeke). A Janet pack can deny more after that verdict:

(defn check [argv]
  (when (has-prefix? (get argv 0) "sudo")
    (deny "sudo")))

ljos-policyd check -- argv is the TCB when the binary is on PATH (or POLICYD_BIN). It prints allow or deny. capnp writes the packed message. The harness hook and ljos policy call it. The Janet pack may only deny more.

ljos policy -- argv prints the line, then the TCB verdict if the binary answered, then any pack rule that matches, then what the pack knows that bears on it. The binary prints a verdict. Pack rules stay in packset. A missing binary leaves the line open unless POLICYD_REQUIRED=1. Doctor names the row.

ljos hook on PreToolUse is the same gate. A deny is a blocked tool call. A force push asks, and the command does not run until you agree:

$ echo '{"hook_event_name":"PreToolUse","tool_input":{"command":"git push --force"}}' | ljos hook
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":"a force push rewrites shared history; the agent asks you first (ljos rule `built-in push`)"}}

On a prompt it searches and holds pack text; PostToolUse emits it. Nothing to say on a prompt is no output. A TCB deny still blocks. ljos onboard installs it on a runner's tool-call and prompt events. When the session ends, the memories the hook injected during it fire together, so what served one sitting is wired for the next.

Law can live in the pack: ljos rule 'PATTERN' --verdict deny|ask --why "..." writes a glob over the command line with a verdict, and the hook returns it as the runner's permission decision while ljos policy prints it beside the line. A rule is memory: dated, supersedable, exported in handovers.

A pack rule is tried on the whole line and on each command the line runs. The line splits on &&, ||, ;, | and & outside quotes, and leading NAME=value assignments and sudo, env, time, nohup come off, so cd repo && git push meets git push* and a commit message that names a command does not. A pattern that says re:, or carries \b, \s, \d, \w or an alternation group, is a regular expression anchored at the command's start.

Edit a pack rule only when argv law itself changes. Do not write comments, dates, ticket ids, or sitting notes into it. If the binary is down, leave the pack rules alone.

Other projects may speak one habitat and ignore ljos.

Habitats · Compare