Grok Build
The pack only helps a Grok session if it reaches the model.
Grok discards UserPromptSubmit stdout. It passes PostToolUse
additionalContext to the model after the first tool. ljos hook
searches on the prompt, keeps the text, and emits it once on
PostToolUse. With LJOS_TRACK_WORK=on, a conversation with no issue is
told, on that first result, to open one and sit, and Stop blocks such a
turn once when it used tools and never touched the seat. A prompt that asks which answer is right forks a panel opener before
the model speaks. The opener files or reuses the decision and starts
one headless member per brief. The model is told not to pick and not
to ssh. Stop still blocks the turn once if it picks with no ballot.
PreToolUse runs the Trusted Computing Base (TCB) check and the pack
rules; a deny blocks. Nothing needs a sync.sh.
Install#
$ ljos onboard --harness grok
That writes ~/.grok/hooks/ljos.json once, naming ljos by the absolute
path beside ljos-mcp, so a Grok started outside a login shell still finds
it. Then
/hooks then r once. Later cargo binstall ljos is live on the
next event. Name ljos-mcp on PATH in ~/.grok/config.toml.
ljos onboard --harness grok bumps LJOS_MCP_GENERATION in that table
when the crate version moved, so Grok’s config watcher respawns the
server; a session restart is not required.
Why these events#
Event |
What |
What Grok does with stdout |
|---|---|---|
|
searches, keeps the text |
discarded |
|
emits the kept text once; with no issue open, the first result says to file and sit |
passed to the model after the tool |
|
searches the pack on the failed command and its error; up to three standing claims that share at least two content words with them |
passed with the failed result |
|
TCB and pack rules; a deny blocks |
a turn has many tool calls |
|
fires up to eight memories injected so far and lets a later prompt bring them back; keeps a note naming the issue the conversation still has open |
ignored; the next |
|
with no issue open, blocks one turn that used tools and never touched the seat |
|
|
fires injected memories |
ignored |
Grok’s hook dispatcher, xai-grok-hooks/src/dispatcher.rs, hands a hook’s
added context to the model after the three tool events, Stop and
SubagentStop. It drops it after a prompt or at the start of a session.
The frozen file is
crates/ljos-cli/assets/grok/ljos.json.
Its {ljos} is filled in at onboard. PreToolUse gets 10 seconds, the
TCB check’s budget, as do the two tool-result events; the prompt, Stop
and SubagentStop get 15, and PreCompact and SessionEnd 5, Grok’s
default.
Grok’s stdin is camelCase (hookEventName, sessionId, toolInput), and
ljos hook reads it as the snakecase fields. A deny blocks on a top-level
decision of deny beside hookSpecificOutput. An ask rule is the same
pair with ask: Grok shows that as the in-chat permission prompt. A runner
that cannot ask (stdin carrying turn_id) still gets the ask rewritten to
a deny.
A panel member is a supervised child#
A child that exits is classified before it is started again.
ljos supervise NAME --harness grok --exit crash --at SECONDS prints
restart, stop or give-up, and on a restart the herdr line
herdr agent start persona-NAME --no-focus --cwd HOME -- sh SCRIPT.
The same command with --harness claude or --harness codex covers the
other runners: the policy is one, and only the runner name changes. permanent restarts however it exited, transient only on a
crash, temporary never. Three restarts inside sixty seconds give up.
The command prints the decision. It does not start the pane.
Smoke#
$ echo '{"hook_event_name":"PreToolUse","tool_input":{"command":"echo"}}' | ljos hook
$ echo '{"hookEventName":"pre_tool_use","toolInput":{"command":"git push --force"}}' | ljos hook
{"decision":"ask","reason":"git-force-push: it rewrites shared history, so the agent asks you first (ljos rule `git-force-push`)","hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":"git-force-push: it rewrites shared history, so the agent asks you first (ljos rule `git-force-push`)"}}
$ echo '{"hook_event_name":"PostToolUse","session_id":"s"}' | ljos hook
An allow prints nothing unless a prompt has already kept pack text. A TCB deny still blocks.
Personas as Grok agents#
Grok spawns a subagent by its agent definition, a Markdown file with YAML
front matter in ~/.grok/agents. ljos onboard --harness grok and
ljos agents --harness grok write each persona there as ljos-NAME.md.
Grok Build’s own parser, AgentDefinition::parse in
xai-grok-agent/src/config.rs, accepts each file. Its front matter sets
capabilityMode: execute: it reads and runs commands and has no edit
tool. The persona runs ljos brief NAME ISSUE, casts one ballot before
reading the others, notes why, and stops. A panel is one spawn_subagent
call a persona, with subagent_type ljos-NAME and the issue id as its
prompt; the main agent then runs ljos consensus ISSUE.
On a prompt that asks which answer is right, the seat opens a panel
itself and starts its members with the table’s headless argv. Grok’s is
grok --prompt-file ... --yolo --max-turns 6 --effort low --disallowed-tools Agent --cwd ....
Status line#
[ui.status_line]
type = "command"
command = "ljos statusline"
The row shows the seat, the issue this conversation has open and the claims due; each session’s row is cached for fifteen seconds.
The runners file#
[[harness]]
name = "grok"
config = "~/.grok/config.toml"
marker = "[mcp_servers.ljos]"
skills = "~/.config/ljos/skills"
hooks = "~/.grok/hooks/ljos.json"
agents = "~/.grok/agents"
headless = ["grok", "--prompt-file", "{prompt_file}", "--yolo", "--max-turns", "6", "--effort", "low", "--disallowed-tools", "Agent", "--cwd", "{cwd}"]
resume = ["grok", "--continue"]