Environment and sync files#
Environment#
Variable |
Read by |
|---|---|
|
the pack client; unset, |
|
the pack workspace; unset, |
|
override for who is sitting. Unset, the seat is the MCP client that connected, a runner’s own seat variable, or the login user. The process tree is not a seat. The holder is that seat tagged with the conversation’s session. When |
|
the tracker |
|
the deed store; the key signs handovers, and |
|
the claim graph |
|
the server’s cards directory |
|
the tools |
|
path to |
|
summon socket; else |
|
|
|
seconds the tracker push after a claim or finish waits; 5 when unset |
|
seconds |
|
|
|
the tracker checkout user rules sync through; unset, |
|
milliseconds each user-rule sync may take; 4000 after a write, 1500 at session start, 8000 for |
|
|
|
where |
|
the one |
|
the runner a decision panel’s members run on, through its |
|
how many decision-panel members start at once; unset, the machine’s parallelism clamped to 4; |
|
where the Jev state, cache and runtime key live (see Jev settings); |
~/.config/ljos/env is loaded for the keys above that the shell leaves unset.
A temporary HOME or working directory#
ljos treats a HOME under /tmp, /var/tmp, /private/tmp, /dev/shm or $TMPDIR as a test sandbox. There the hook’s command check is off, and the memory writer does not start on its own either. A memory command then says so and names packset ensure, which starts the writer by hand. A working directory under one of those, with a HOME outside them, is not a sandbox: cd /tmp && rm -rf ~/proj is judged. LJOS_GUARD=on keeps the check on under a temporary HOME, which is how a test suite exercises it; LJOS_OFF still switches it off. ljos doctor shows a guard row marked warn whenever the check is off, from LJOS_OFF or from a temporary HOME, the session start line says command guard off with the reason, and install.sh says so at the end of an install into a temporary HOME.
A runner’s table may also name start and resume, the argv that open
a session in a persona’s home and resume the latest one there; a
persona whose --runner is that table’s name reasons in that session
(ljos ask, vote --jev). hooks_named names the key a hooks file of
named hooks takes the seat’s hooks under, each command told its event
with ljos hook --event. A steps runner reads a permission answer that omits
decision as a deny, so the seat always answers allow, with the rule’s warning
when one matches.
The pane script resumes a runner that exits non-zero, at most three times
a minute, and leaves one that exits 0. agents names the directory the
runner reads agent definitions from: ~/.grok/agents, ~/.claude/agents
or ~/.cursor/agents. headless is the argv a decision panel’s member
runs, with {prompt_file}, {prompt}, {cwd} and {persona} filled;
when it is empty, the member runs grok with the seat’s fallback flags.
hooks_format = "cursor" writes hooks in the editor harness’s flat shape: version
1, and one {command, timeout} entry an event. The seat registers the
permission check, the prompt, both tool results, compaction, stop and session end
there. Events ~/.claude/settings.json already runs stay in that file.
beforeShellExecution, preToolUse, beforeMCPExecution,
beforeReadFile and postToolUseFailure are still written here, since
the editor harness runs that file’s hooks too and the terminal harness’s file does not name these.
If one of these hooks fails, the command runs. ljos doctor counts the
hook installed when those five are present and the terminal harness’s file carries
the seat hook, or when the editor harness’s file carries every event.
The hook reads an editor harness call off cursor_version in its stdin and answers
in the editor harness’s fields: permission on a permission check, with user_message and
agent_message; additional_context on a tool result; followup_message
on a held stop. It answers allow on every permission check, since
the editor harness blocks the command when the answer to a permission check is not JSON. A rule
that fires is allow with its warning in user_message and agent_message.
The IDE and the agent CLI read the same ~/.cursor/mcp.json.
A table with shell = true registers no server and no hook.
ljos onboard --harness example-host writes the skill and an env file beside
harnesses.toml. Source that file. LJOS_SEAT is the runner’s name,
because a shell-only agent has no MCP client, so the env file
names its seat. --skills DIR is where the skill goes.
ljos hook --prompt reads stdin as the prompt.
ljos hook and ljos policy exit 0 on a warning.
ljos hook --fail-on-deny and ljos policy --fail-on-deny
exit 1 when the answer is a deny, which is the cloud seat push rule.
Without the flag the exit stays 0.
[[tool]] tables in the same file declare the tools a runner lives in.
Two come as shapes, herdr and tmux. A table of the same name replaces one.
Every verb but pane_pointer is an argv or a list of argvs, with
{name}, {label}, {session}, {home}, {script}, {script_q},
{pane} and {line} filled. A refusal carries the tool’s own words.
Verb |
What it does |
|---|---|
|
exits 0 when the tool answers |
|
ways to open the persona’s pane; the first that exits 0 wins |
|
a JSON pointer to the pane id in |
|
types the pane script into a pane that starts as a shell |
|
restarts the script in a pane whose runner exited |
|
hands the runner a line |
|
types the line raw when |
|
exits 0 while the pane is there |
|
exits 0 once the runner takes input, retried for |
A runner’s table in harnesses.toml may name hooks, a JSON settings
file of the shape
{"hooks": {"<Event>": [{"matcher": "...", "hooks": [{"type": "command", "command": "..."}]}]}}.
onboard merges ljos hook into it on the prompt and session-end events
(matcher *) by default, or on the events the table’s hook_events lists
(PreToolUse takes the matcher Bash|Edit|Write|MultiEdit|NotebookEdit|apply_patch,
and a second group ^mcp__.+__ljos_.+$ for the seat’s MCP tools, and a
third group for a vissue write tool),
once each, and drops it from events no longer listed. A hook that fails, times out or cannot
start lets the action run, and ljos logs a warning. On SessionEnd the
hook fires the memories it injected during the session together and clears
the session’s record. The hook reads the runner’s JSON on stdin
(hook_event_name, tool_input.command, prompt) and answers
{"hookSpecificOutput": {"hookEventName": ..., "additionalContext": ...}},
or nothing when the pack has nothing on the cue. On PreToolUse a TCB
or pack rule is one warning line in additionalContext, and the call runs.
The hook never answers deny, ask or block. Plain text on
stdin is an argv line and answered in plain lines.
Event |
The hook does |
|---|---|
|
answers with the memories the prompt activates, preferences first, each once between compactions; with Jev on, asks which claims bear on the prompt and whether it corrects the agent or puts a choice |
|
fires the memories injected during the session together and clears the session’s record |
|
emits the held prompt note once, on the first tool result, for a runner that discards prompt-hook stdout (the build harness sends this event) |
|
searches the pack on the failed command and its error and answers with up to three standing claims that two scorers named and that share at least two content words with the cue, each once between compactions |
|
fires up to eight of the memories injected so far, drops all of them from the session’s record so a later prompt can bring them back, and keeps a note naming the issue the conversation still has claimed, for the next delivery |
|
after a compaction ( |
|
applies the TCB and pack rules to the command (matcher |
|
speaks only when the turn ran no tool; blocks one turn that was asked for a decision and cast no ballot; with no issue held, blocks one turn that used tools and never touched the seat; with Jev on, audits the turn once from the runner’s transcript; then holds one turn for unread interrupt mail ( |
|
names the issue the subagent’s conversation has claimed and asks for the ballot on a decision; with Jev on, audits the turn once |
The hook’s book names the runner that owns its sitting, and the next
session or MCP call finishes the sitting once that process is gone. The
owner is the nearest harness ljos knows by name above the hook, else the
nearest program that is neither a shell nor a wrapper, else the outermost
shell. The walk stops below a terminal, a multiplexer or init, none of
which owns a sitting. The book keeps the owner’s pid and start time, so a
pid used again by another process is gone. The walk reads /proc and
holds on Linux only. On macOS and other systems the hook’s parent owns
the sitting: a runner starts the hook through sh -c, which exits with
it, so there the next MCP call finishes the hook’s sitting, and
ljos_start files a new claim instead of keeping the one a Write took.
Sync files#
File |
Contents |
|---|---|
|
the repository’s scope and its age recipients; a |
|
one sealed log per machine. |
|
one file of user rules per machine, sealed to the scope’s recipients ( |