Wire the seat into an agent runner#

Describe the runners on the machine once, in ~/.config/ljos/harnesses.toml. A runner registers MCP servers either through a command of its own or through a config file it reads; the file holds one table per runner, in one of those two shapes, plus the directory it loads skills from. 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"

Then one verb per runner:

$ ljos onboard --harness runner-with-a-command
ok  runner-with-a-command mcp   ran runner mcp add -s user ljos -- /home/you/.cargo/bin/ljos-mcp
ok  skill   wrote /home/you/.runner/skills/ljos/SKILL.md
$ ljos doctor | grep runner
ok  runner mcp  runner-with-a-command: ljos registered
ok  runner skill    runner-with-a-command: /home/you/.runner/skills/ljos/SKILL.md

--dry-run reports what would be written. For a runner not in the file, ljos onboard --harness json prints the server entry to paste:

{"mcpServers": {"ljos": {"type": "stdio", "command": "/home/you/.cargo/bin/ljos-mcp", "args": [], "env": {}}}}

The skill is the protocol ljos protocol prints, under a front matter the runner reads. 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. Twenty-three tools, fourteen of them writers, two prompts (start_a_sitting, check_a_handover), three read-only resources. Each tool description opens with when to call it.

Inject memory at the point of action#

Add hooks to the runner’s table and onboard again:

[[harness]]
name = "runner-with-a-command"
register = ["runner", "mcp", "add", "-s", "user", "ljos", "--", "{server}"]
registered = ["runner", "mcp", "get", "ljos"]
skills = "~/.runner/skills"
hooks = "~/.runner/settings.json"
$ ljos onboard --harness runner-with-a-command
ok  host key    /home/you/.config/deedar/host.key exists
ok  runner-with-a-command mcp   ljos registered
ok  hook    memory hook: add it on UserPromptSubmit in /home/you/.runner/settings.json
ok  skill   /home/you/.runner/skills/ljos/SKILL.md is current

From then on, before each prompt, the runner pipes it to ljos hook and the memories it activates come back as context, preferences first. Do not add PreToolUse as a search event. A turn issues many tool calls and one prompt. On a tool call the hook only applies pack rules.

Grok Build. ljos onboard --harness grok writes ~/.grok/hooks/ljos.json. The hook searches on the prompt, holds the text, and emits it on PostToolUse. The page Grok Build is the install.

A policy daemon does the same with the argv:

$ echo "cargo test --release" | ljos hook
- [preference] Build and test on the cluster through Slurm, never on the laptop.
- [lesson] The lme dense run needs 40G and sessions only; windows and sessions ran past four hours.
$ ljos policy cargo test --release
cargo test --release
What this seat already knows that bears on this ...

Give an agent the protocol without a runner#

$ ljos protocol > 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.

Ask what a task involves#

$ ljos island "write the handbook tutorial for the seat"
1.000  seed  ab8cb4d1bfca05ca60730141343dc177  3 weeks ago  Encoding under the write lock capped the pack at
0.899  seed  51778929d855529fcb8df3f4d1c4d692  yesterday    A new claim enters the review clock on write. Tr
0.890  seed  27e39df0198b67538f8487b1b44e9dfd  today        The decay slot reads the review clock when PACKS

The first column is activation relative to the strongest; seed marks a claim search found itself, the rest were reached along the pack’s links; the age column says when each was written. Add --fire when you go on to use the island: the strongest eight fire together and their links gain weight.

Ask when#

$ ljos timeline proj-1a2b
timeline of proj-1a2b: Ship the fuse change?
2026-08-30  13 days ago     tracker created
2026-09-02 09:14    10 days ago +3 d    memory  [lesson] RRF lost two points to CombMNZ on turns.
2026-09-02 11:48    10 days ago same day    tracker TODO -> STARTED
2026-09-02 11:48    10 days ago same day    tracker claimed by you
2026-09-05 16:02    7 days ago  +3 d    deed    deed-run-fuse-at-ten produced by you
2026-09-12 08:30    today   +7 d    tracker note: the fused panel held at ten conversations

Three stores, one dated list, oldest first: the tracker’s logbook, the deeds the issue cites with the time each was produced, and the memories the issue’s title activates with the time each was written. Each line carries its date, its age, the gap since the line before, and the store it came from. The reader gets time as data rather than stamps to subtract, and a later line supersedes an earlier one on the same matter. ljos sitting prints the last twelve as its timeline section.

ljos search TOPIC --as-of 2026-09-01 asks the pack as it stood then: memories withdrawn since included, memories written since left out. The pack tombstones rather than erases, which is what makes the question answerable.

Start a sitting, and end one#

$ ljos sitting proj-1a2b
== doctor
ok  vissue  ...
ok  seat    alice (from LJOS_SEAT)
== cards
== due
unreviewed  conclusion  3f9c... The lexical default is BM25+.
1 due; 4 scheduled, next at 2026-09-13T09:12:00Z
== island: Ship the fuse change?
1.000   seed    ab8c... 3 weeks ago CombMNZ over RRF for fusing two ballots.
== recall
...
== timeline
timeline of proj-1a2b: Ship the fuse change?
2026-08-30  13 days ago     tracker created
...
== claim
gen=2

One verb, seven sections, in the protocol’s order; it stops at the first store that does not answer and claims nothing. The start_a_sitting prompt gives the same order to a runner that prefers single tools. A claim refused as busy names the issue you still hold; ljos complete finishes it and ljos release hands it back.

$ ljos finish proj-1a2b --lesson "CombMNZ held on turns. RRF lost two points." --outcome ship
remembered 51778929d855529fcb8df3f4d1c4d692
fired the island for "Ship the fuse change?": 8 memories
completed the session node for proj-1a2b as done
learned from outcome "ship": 6 trust rows rewritten
the ticket stays proj-1a2b's state; `vissue update proj-1a2b -s DONE` closes it

Vote with personas#

$ ljos persona reviewer --anchor 0.2 --view "Reads for what breaks in production." --about release
$ ljos persona reader --anchor 0.8 --view "Reads as a first-time user of the docs." --about docs
$ ljos vote $id --for hold --as reviewer
$ ljos vote $id --for ship --as reader
$ ljos consensus $id

Each persona is one atom in the pack; its ballots carry its name. The settle takes its anchor: the reviewer at 0.2 barely moves off hold, the reader at 0.8 is nearly a plain voter. A trust row scoped with --about docs weighs only on issues whose title says docs.

Move the trust rows without an outcome#

$ ljos calibrate -p demo
alice weighs bob at 0.917
alice weighs carol at 0.643
...

Every issue of the project with two or more ballots is an item; Dawid and Skene’s estimate gives each voter an accuracy from how often it agrees with the answer the others make likely, and each accuracy becomes the weight every other voter gives that voter. Run it once a project has a few voted issues, and again when it has many more.

Keep the cards#

Cards are two files a person writes: USER.md and MEMORY.md. The seat reads them and never writes them; ljos cards prints them. The pack is where the seat writes.

Sign what you hand over#

$ head -c 32 /dev/urandom > ~/.config/deedar/host.key && chmod 600 ~/.config/deedar/host.key
$ export DEEDAR_HOST_SIGNING_KEY=~/.config/deedar/host.key
$ ljos doctor | grep 'host key'
ok  host key    /home/you/.config/deedar/host.key (32-byte seed)

handover then signs the manifest and the log head. A receiver adds signer = ed25519:<hex> to their deed store’s layout and receive reports (accepted).

Receive from the same sender again#

$ ljos receive /tmp/bag2 --since /tmp/bag/head.txt

The bridge shows the sender’s log grew from the head you kept and was not rewritten.

Close what a later lesson replaced#

$ ljos consolidate
closes 3f9c…  The default fuse is Borda.
    for 51ee…  The default fuse is CombMNZ.
1 of 97 live memories would close; `ljos consolidate --apply` closes them
$ ljos consolidate --apply
closes 3f9c…  The default fuse is Borda.
    for 51ee…  The default fuse is CombMNZ.
1 of 97 live memories closed

A write closes the earlier claim it rewrites on arrival (same opening words, a new object; a correction; an explicit supersedes). A pack written before that rule, or filled by a handover, holds pairs the rule never saw; consolidate reports them, --apply closes them. The closed claim keeps its window: ljos search --as-of finds it at the time it was live.

Retire a claim with its reason#

$ ljos forget 3f9c... --why deed-review-2026-09

The pack tombstones the claim and records the deed that withdrew it. Disagreeing with a claim is not showing it wrong; a retraction names its evidence.

Set trust by hand#

$ ljos trust alice carol 0.9 --why deed-postmortem-2026-09

One row, one weight in (0, 1], the deeds it rests on. A later row for the same pair supersedes it.

Settle under the pack’s trust#

ljos consensus ID reads the live trust rows and passes them to both settles. To anchor voters to their own ballots (Friedkin-Johnsen) on the model crate alone, pass the rows as JSON tuples:

$ ljos-consensus settle --issue ID --susceptibility 0.8 --trust '[["alice","carol",0.9]]'

Check the seat#

$ ljos doctor
ok  vissue  ~/.local/bin/vissue
ok  deedar  ~/.local/bin/deedar
ok  claimdag  ~/.local/bin/claimdag
ok  packset  ~/.local/bin/packset
ok  packsetd  ~/.local/bin/packsetd
ok  ljos-consensus  ~/.local/bin/ljos-consensus
ok  ljos-mcp  ~/.local/bin/ljos-mcp
ok  pack  http://127.0.0.1:8761 workspace git:github.com/leidarljos/ljos
ok  host key  ~/.config/deedar/host.key (32-byte seed)
ok  deed store  size=34 root=3d8e015509923724097e9f33d3a044fe0764f17bd5387769530ed5bfb6ada
ok  tracker  vissue 0.10.0
ok  claim graph  a4a8fa1b8f05d259877be54da99f06bc  claimed  task  69f91712  gen=2  ljos-a6

Exit 1 when the tracker, the deed store, or the pack does not answer. A missing claim graph is reported and is not a failure: the first claim creates it.