Connect an agent runner#
Wire the seat into an agent runner#
One entry, for any runner that speaks the Model Context Protocol (MCP) over stdio:
$ ljos onboard
{"mcpServers": {"ljos": {"type": "stdio", "command": "ljos-mcp", "args": [], "env": {}}}}
Paste it where the runner keeps its MCP servers. The env is empty on
purpose. The server names the seat after the client that connects, so a
runner that calls itself acme-cli is the seat acme-cli and a
runner that calls itself brio is brio, with nothing set per runner.
A shell the runner opens finds the same seat through LJOS_SEAT, or through the markers the
runner leaves in the environment. A derived name is a seat. Each conversation keeps its claims as
the seat tagged with its session, so two conversations of one runner hold two tickets.
$ ljos seat
seat acme-cli
holder acme-cli-39u
source the client name the MCP server received
The protocol goes where the agent reads its instructions: ljos protocol
prints it, the server serves the same text at ljos://protocol and names
it in its instructions, so a runner that loads neither skills nor
resources still reads it first. Each tool description opens with when to
call it.
A runner that registers servers by a command of its own, or reads them
from a config file, can be described once in
~/.config/ljos/harnesses.toml and onboarded in one verb.
ljos onboard --example prints the shape:
[[harness]]
name = "runner-with-a-command"
register = ["runner", "mcp", "add", "-s", "user", "ljos", "--", "{server}"]
registered = ["runner", "mcp", "get", "ljos"]
skills = "~/.runner/skills"
[[harness]]
name = "runner-with-a-config-file"
config = "~/.other/config.toml"
marker = "[mcp_servers.ljos]"
snippet = "\n[mcp_servers.ljos]\ncommand = \"{server}\"\nargs = []\n"
skills = "~/.other/skills"
$ ljos onboard --harness runner-with-a-command
ok runner-with-a-command mcp ran runner mcp add -s user ljos -- ljos-mcp
ok skill wrote /home/you/.runner/skills/ljos/SKILL.md
--dry-run reports what would be written.
The command harness runs a hook only after you trust it. After ljos onboard --harness codex,
open /hooks and trust each ljos hook before it runs.
Trust it again after an upgrade rewrites one. Until then the hook does not run,
and policyd sees none of the command harness’s commands. ljos doctor reports an untrusted hook when
the trust file has no trusted_hash, or is older than the hooks file.
The example carries the runners this seat has carried through one piece
of work, each in the shape it takes the server: a runner with an mcp add of its own by that command; a runner with a TOML config
by a table appended to it; opencode and omp by a JSON pointer set in
their MCP file; hermes by its mcp add with the tool question
answered. The same verb takes cursor (the IDE and the agent CLI share
~/.cursor/mcp.json), windsurf, zed, vscode, claude-desktop,
gemini, amazonq and kiro. Copy the tables for the runners on the
machine into harnesses.toml, then one verb each:
$ for h in opencode hermes omp; do ljos onboard --harness $h; done
$ ljos doctor --all | grep runner
ok runner mcp opencode: ljos registered
ok runner skill opencode: /home/you/.config/opencode/skills/ljos/SKILL.md
ok runner mcp hermes: ljos registered
ok runner skill hermes: /home/you/.hermes/skills/ljos/SKILL.md
...
A runner with a hooks file (hooks in its table) takes the memory hook
too; for the others the skill and the ljos://protocol resource carry
what the seat knows, and the sitting’s island brings the memories in.
Wire the seat into the editor harness#
$ ljos onboard --harness cursor
The command prints one ok line for the MCP entry, the hooks, the skill and the agent definitions, and each line names the file it wrote.
The editor harness’s IDE and its agent command line take the server from the same
~/.cursor/mcp.json and the hooks from ~/.cursor/hooks.json. It
does not hand a prompt hook’s context to the model, so the memories a
prompt activates arrive with the first tool result instead, as on the build harness.
A shell command that a rule names is allowed, with the rule’s
warning in the editor harness’s user_message. The editor harness runs the terminal harness hooks itself where
ljos onboard --harness claude already ran. The events the terminal harness’s file
names stay there. beforeShellExecution, preToolUse,
beforeMCPExecution, beforeReadFile and postToolUseFailure are
still written to ~/.cursor/hooks.json. If a hook fails, the command runs. The seat answers
both files in the editor harness’s shape.
Seat a shell-only agent#
Example Host runs shell commands on a Linux box. It is not an MCP runner and it has no command hook, so the env file names its seat.
$ ljos onboard --harness example-host --skills ~/.example-host/skills
ok skill wrote /home/you/.example-host/skills/ljos/SKILL.md
ok env wrote /home/you/.config/ljos/example-host.env; source /home/you/.config/ljos/example-host.env
$ source ~/.config/ljos/example-host.env
$ echo "$prompt" | ljos hook --prompt
$ ljos policy -- git push --force
--skills DIR writes the protocol there. ljos onboard --harness shell --skills DIR
is the same shape under the name shell. The env file sets
LJOS_SEAT to the runner’s name. Nothing is registered as an MCP server
or a hook. ljos hook and ljos policy exit 0 on every answer, and a rule
that fires is one warning: line in the text. The command runs either way,
unless the person turned on blocking; then --fail-on-deny exits 1 on
policyd’s deny.
Give an agent the protocol without a runner#
$ ljos protocol --full > instructions.md
The text is plain markdown: which store answers which question, the order of verbs before, during and after the work, and the refusals worth knowing. Put it where the agent reads its instructions.
Show the seat in the status bar#
In ~/.grok/config.toml:
[ui.status_line]
type = "command"
command = "ljos statusline"
In ~/.claude/settings.json:
{"statusLine": {"type": "command", "command": "ljos statusline"}}
The row reads the seat, the issue the conversation has open, and the claims due
for review. A session’s line is cached
for fifteen seconds. A pack that does not answer in 300 ms shows as
pack down.