Reference

Command line#

Verb

Habitat

Does

sitting ISSUE [--assignee NAME] [--cards DIR] [--playbook NAME] [--anyway]

all

open a sitting: doctor, sync when the repository names a scope, cards, due, island, blockers, playbook, workflow when the issue carries one, panel when the issue is a decision, recall, timeline, reclaim, claim; reclaim hands back claims quiet longer than LJOS_LEASE_SECS (900; 0 is off) whose conversation is gone, and keeps one whose runner still runs; stops at the first store down; the name defaults to this conversation’s holder (ljos seat); --playbook copies a recipe before recall; absent, title-match else sit; the name is a tracker playbook: note until finish or release

finish ISSUE [--status S] [--lesson TEXT] [--outcome OPTION] [--beta B] [--gen G] [--assignee NAME] [--close]

all

close a sitting: complete the session node, file --lesson as a proposal at L0 (derived) with origin agent-derived or write it at once as the person’s when no runner marks the environment and a terminal is on its input, fire the island, then learn; ljos accept ID promotes that proposal to explicit and writes the lesson; --gen names the claim’s generation (absent, the live one; a stale one is refused); --assignee names the holder (absent, this conversation’s holder); a runner is refused. A person closes the ticket when the work is accepted, else the ticket stays open and nobody holds it

calibrate -p PROJECT [--rounds N]

consensus, pack

trust rows from the project’s voting history (Dawid-Skene accuracy)

remember TEXT [--as PERSONA] [--transient|--standing]

pack

one lesson, stored as written, origin user-declared; as a persona, it carries that persona’s entity and opens its next brief; typed by the person it is standing at once, and the hook shows it from the next prompt it bears on; run under an agent runner it is an episode, kept and not a refresher until a review promotes it; --transient or --standing picks one either way

memories list|review|tui|prune

pack

review the memories agents wrote on their own: list newest first with provenance, unreviewed agents’ memories by default; review keeps, drops, edits or merges them; tui browses them; prune names duplicates and never-recalled memories and writes only with --apply

prefer TEXT [--deny PATTERN] [--ask PATTERN] [--user --why REASON]

pack, user config

one standing preference, origin user-declared; each --deny or --ask also writes that rule with the preference as its reason, so the hook enforces it. --user writes a user rule instead, shared by every seat on this machine, never reviewed, and not also stored as a seat preference; --why is required with it. See User rules

accept ID [--quote WORDS --at TIME --via SEAT]

pack

promote one open proposal from L0 (derived) to explicit and write it into the pack; the origin stays; under an agent runner, only a proposal another conversation filed, or one the person said yes to in their own words; --quote takes those words, --at when they were said, RFC 3339, and --via the seat that carried them, all three together, and the acceptance keeps them; a proposal the same text already wrote with remember or prefer is not written again

forget ID [--why DEED]

pack

tombstone a claim, naming the deed that withdrew it

retire-notes

pack

tombstone the note memories an older ljos note wrote; runs once per seat, and the tracker copies stay

search QUERY [-n N] [--rerank] [--as-of TIME] [--explain]

pack

ranked claims with how many scorers named each and its age; --rerank scores the top hits with the writer’s cross-encoder; --as-of asks the pack as it stood then; --explain opens with what the score and the fraction mean

timeline ISSUE [-n N]

tracker, deed store, pack

the logbook, the cited deeds and the activated memories as one dated list, oldest first, each line with its age and the gap since the one before

consolidate [--apply]

pack

the pairs where a later claim rewrites an earlier one; --apply closes the earlier ones’ windows

conflicts [-n N]

pack

candidate contradictions by geometry: the lowest passes between single memories, read by the optional landscape habitat. The habitat is not part of this repository. When it is not on PATH the command says so and writes nothing

hubs [-n N]

pack

the claims this seat’s memory turns on, highest first: a weighted PageRank over the links

island CUE [--fire]

pack

the memories a task activates: search hits spread along the links; --fire wires the strongest eight together

due

pack

sweeps the pack first (reviews left due past twice their interval lapse; never-recalled claims missed three times are forgotten), then the claims whose review is due, unreviewed ones first, one line on the clock, and one on what the sweep moved

hud [--foreground] [--toggle|--show|--hide] [--install-desktop]

none

exec sibling ljos-hud; missing is 127, hide-miss is 0; the musl CLI never links iced

habit [NAME [VALUE]] [--unit U] [--every 7d] [--source S]

pack

with a value, take a reading: the earlier reading closes and is kept as what it was, the next is due one cadence on; without, list each habit’s latest reading with the change since the one before

graded ID [--lapsed]

pack

one review graded

trust FROM TO WEIGHT [--why DEED]... [--about DOMAIN]...

pack

one trust row; scoped when --about is given

persona NAME --anchor A --view TEXT [--about DOMAIN]... [--runner RUNNER] [--replace] [--check]

pack

a voter with a view; A in [0, 1] is how far it moves off its ballot; writes one unscoped inbound trust row; --runner names a [[harness]] that reasons as the persona in a session it keeps; a name another seat or conversation defined is refused unless --replace; --check says what the define would do and which personas share its view or domains, and writes nothing

persona NAME --retire [--replace]

pack

withdraws the persona, so it casts no more ballots; only the conversation that defined it may, unless --replace

personas

pack

the roster: name, anchor, domains, view, one per line

agents [--harness NAME] [--dry-run]

pack, runner

every persona as an agent definition, ljos-NAME.md, in each runner’s agents directory (or one runner’s): a subagent the runner spawns by name that briefs, casts one ballot before reading the others, notes why and stops; definitions whose persona left the pack are removed

statusline

pack

one line for a runner’s status bar (Grok’s [ui.status_line], Claude Code’s statusLine): the seat, the issue this conversation has claimed, the claims due; reads the runner’s status JSON on stdin; each session’s line is cached for fifteen seconds

ask NAME TEXT

persona session

hands the persona a question or a task in its own pane, opening it (and resuming its session) when it is closed

supervise NAME --harness H --exit clean|crash --at SECONDS

runner

classify one harness exit and print restart, stop or give-up, and on a restart the herdr line that would start the child. It prints the line and does not run it. --restart is permanent (the default), transient or temporary; three restarts inside sixty seconds give up

upgrade [VERSION] [--dir DIR]

release

installs a published release beside the running ljos: the archive the release workflow built for the tag, checked against the sha256 published beside it, each binary an executable that names the version; the old ones kept as NAME.bak-pre-VERSION. The only way an agent moves the seat’s binaries, which policyd keeps it from writing

playbook ISSUE NAME

pack

bind a recipe to the issue and copy its full body; the name is a tracker playbook: note until finish or release

playbooks

pack

the closed set sit, arena, land, company-panel, overnight, feature; pack latest per name, the built-in seed when missing

workflow ISSUE [--start NAME] [--round]

tracker

a parent’s workflow: stage, round and next step; --start feature notes workflow: feature and files the design child, --round files the next panel round once every child is closed

brief NAME ISSUE

pack, tracker

what a subagent playing the persona starts from: view, the bound playbook’s full recipe, five named principles, the arena rubric, what the seat knows on its domains, the working set

panel ISSUE [--out DIR] [--jev]

pack, tracker

one brief per persona whose domains the issue speaks to as DIR/<name>.md, then the settle line; refused until a playbook is bound. --jev asks Jev for every ballot first and casts them only when every seat is sure and all agree; otherwise it writes the briefs

open-panel --prompt-file FILE --log FILE [--cwd DIR] [--session ID]

tracker, runner

open a panel for a decision prompt: file or reuse the issue, sit it, and start one headless member per brief. The prompt is already in the file, so the hook can return. --session keeps a second opener from starting a second panel

learn ID --outcome OPTION [--rule record|hedge] [--beta B] [--share S]

tracker, pack

reweigh voters by what turned out right: by default each voter’s record of hits and misses as log-odds weights, its accuracy shrunk toward the panel’s pooled accuracy; hedge shrinks refuted voters by B with S recovery; either way the outcome is kept

evidence ACCESSION / current ACCESSION

deed store

intact; still the tip

record check DIR / record check --tracker ROOT

none / tracker

DIR holds schema-v1 question and ballot entries; --tracker holds every ballot under ROOT, a persona’s and a judge’s included, to one test: the voter’s newest ballot-sig: note names the choice, verifies, and is under the key .ljos-seat-keys pins for that voter and no other seat. One FILE:LINE: ISSUE: REASON line per ballot that does not stand, and exit 1. An unsigned ballot committed before signing began is legacy. It only reads, so CI can run it

file TITLE [--parent ID|--top] [-p PROJECT] [-t TYPE] [--tags T] [--priority P] [--body TEXT]

tracker

file work found while sitting: a child of the held issue unless --parent names another or --top none, in the parent’s project; a projected board takes it into its inbox under the printed id; the tracker file is committed

note ID TEXT...

tracker

a dated progress note on the issue, committed; on an issue another seat holds it says so: the note is an assist, and ljos send reaches the holder; the note stays on the ticket and is never written into memory

withdraw ID [--note TEXT] [--as NAME]

tracker

take back this seat’s own ballot, or with --note a note it wrote; nothing is deleted, and the withdrawal is a dated note beside it; --as names the persona a ballot was cast under

reopen ID

tracker

put a finished or cancelled issue back to the state the close took it from, whoever closed it; a person has to be present; the close stays on the logbook with the reopen under it, and the caller does not take the claim

log --seat SEAT

tracker

tracker commits by one seat. The subject is chore(issues): ID VERB by SEAT

usage --since YYYY-MM-DD

tracker

how many tracker commits each seat made from that day on

digest [since TIME or last] [--json]

tracker, deed, pack

one view of what each seat did, newest first: tickets filed, claimed and closed, votes, consensus, deeds, claims, guard refusals and rewrites, and memory. last continues from the previous run

send SEAT TEXT... [--group NAME] [--interrupt] [--issue ID]

pack

write to one seat, or with --group to the other members of that group; --interrupt leads the next prompt; --issue threads the message on that vissue issue and the atom takes the issue’s scope, so ljos sync carries it in that repository’s sealed log

inbox [--all]

pack

mail this seat has not read, then receipts on what it sent that it has not been shown; listing writes no receipt; ljos hook --prompt writes a receipt for each message it shows; a pack that does not answer is an error, and so is a writer that refuses kind message; --all includes mail already read

read ID

pack

write a read receipt for one message; a second read writes nothing

reply ID TEXT... [--interrupt]

pack

answer the sender, keep the issue, and carry reply:ID

group NAME [--add SEAT]... [--remove SEAT]...

pack

list a group, or add and drop members; membership is atoms and travels in the sealed log

wake [SEAT] [--command CMD [--timeout S]|--webhook URL [--key-file F] [--key-env VAR]|--clear|--fire]

machine

how mail wakes a seat that is idle: a shell line this machine runs with LJOS_SEAT set to the seat, or a URL it posts the envelope to (never the body); send and reply start it in its own session and do not wait on it; policyd judges a command when it is set and on every run, and a deny or an ask keeps it from running; --fire runs it now and prints what happened; alone, show the wake; kept in ~/.config/ljos/wake.toml

deed ID [--add ACCESSION]... / --remove ACCESSION --ticket ID

tracker

cite with --add, or list; --remove drops one citation, appended to the ledger, and the heading is not rewritten

claims [ARGS]

tracker

who has claimed what: vissue claims with its flags (--by NAME, --json)

recall ID

tracker

the working set

vote ID [--for OPTION] [--confidence P] [--used ACCESSIONS|none] [--expect OPTION|JSON] [--as PERSONA] [--jev] [--withdraw]

tracker

cast as this seat, never another, or read the tally; --confidence is a probability in (0, 1]; --used lists the deed accessions the ballot used, comma-separated, or none, and is required with --for; --expect records the forecast of the others on the same command; --withdraw takes back the ballot and its forecast, and the logbook keeps what it was. --as NAME --jev asks Jev for the persona’s ballot: cast with the option’s probability and a forecast at confidence 0.8 or above, noted and left for a subagent under it

predict ID --expect OPTION|JSON [--as PERSONA]

pack

forecast the others’ shares; two or more and consensus names the answer more popular than forecast (Prelec, doi:10.1038/nature21054)

rule PATTERN [--verdict deny|ask] --why TEXT [--user]

pack, user config

argv law the hook and policy enforce: a glob, or a regular expression (re: or a \b, \s or (a|b) in it) anchored at the start, tried on the whole line and on each command it runs; What a rule reads lists which those are. --why is the reason a stopped reader sees. --user writes the law into the user config instead of the seat pack, shared by every seat and never reviewed

rules [--json]

pack

the rules the hook and policy judge by: verdict, pattern and reason, deny first

consensus ID [--compare]

consensus, tracker

first what the seat ballots alone decide for the push check; then the settle under the pack’s rows that apply to the issue, with every persona’s anchor, as advice; an issue tagged broad runs bounded confidence; once five issues have an outcome, voters shown to err together are discounted; --compare adds the tracker’s own settle, and so does a missing model crate; an issue with no ballots is refused before a settle runs; --json prints one JSON document, the seat tally at the top level and each reading under its own key

claim [ID] [--next] [--role ROLE] [--slack N] [--assignee NAME]

claim graph

claim a session node. With --next, or with no id, it claims the first ready node in claimdag’s order for the name, the way claim ID claims one; --role and --slack set the affinity and the depth slack. A busy refusal names what the name has still claimed

release ID [--assignee NAME]

claim graph

hand the session node back unfinished: ready, generation moved; a node another conversation of this seat holds (a worker it started, or the one that started it) is released under that name

reap [--idle 6h] [--apply]

claim graph

release claims idle past a window and mail each seat. Without --apply it prints what would happen. --idle is how long a claim may sit with no claim or tracker commit: 6h, 90m, 2d

complete ID [--status done|failed|cancelled] [--gen G] [--assignee NAME]

claim graph

finish the session node; does not close the ticket; --gen and --assignee as on finish; a node another conversation of this seat holds is completed under that name, and another seat’s is refused

cards [--dir DIR]

cards

print USER.md and MEMORY.md

approve REQUEST_ID

runtime

record explicit consent for one pending hook request; one matching attempt, fifteen-minute lifetime, deny rules still apply

policy [--fail-on-deny] ARGV...

policy, pack

the line as it would run, the trusted computing base (TCB) answer if ljos-policyd gave one (from 0.35.0 that may be a rewritten line), then a matching pack rule, then what the pack knows that bears on it; --fail-on-deny exits 1 on a deny, and without the flag the exit stays 0

hook [--limit N] [--prompt [--text]] [--fail-on-deny] [--runner NAME] [--git-commit] [--git-hook post-commit|post-rewrite]

policy, pack

hook JSON or argv on stdin; --runner reads the payload of copilot, gemini, windsurf, factory, kiro, cline, qwen or crush and answers in that runner’s shape; --prompt reads stdin as the prompt, and --text with it prints what the agent would read as plain text instead of JSON; on PreToolUse the TCB and pack rules, a deny is permissionDecision, and from 0.35.0 a rewrite goes back as the updated input for Claude Code, Codex, Grok Build and Cursor’s preToolUse, and another runner gets a refusal that names the new line; --fail-on-deny exits 1 on a deny and without the flag the exit stays 0; on a prompt the hits scoring at least 0.6 of the best that name its subject, or with a pack that sends cosines the encoder’s first hit at 0.66 or more and a hit sharing a word within 0.05 of the best cosine, preferences first then lessons oldest first, each with its age, three at most, each once between compactions; on a failed tool, up to three that share at least two content words with the command and its error. --git-commit books the commit git just created and reads no stdin; --git-hook names post-commit or post-rewrite. Git hooks

git-hooks install|uninstall|status

git

install, remove, or show the global git hooks that book commits. install prints installed and the directory; uninstall prints uninstalled; status prints where the hooks are and what core.hooksPath says. Git hooks

bump-plan BUNDLE --project P --parent I [--generation G] [--dry-run]

tracker

one child issue per module the bundle’s lock builds, under I, blocked by the modules built before it along the edges of its software bill of materials; ids are a hash of module and generation, so a rerun keeps what exists and adds what is missing; --dry-run prints the rows

findings STATE [--remember] [--all] [--issue I]

pack, deed store, tracker

the typed findings of an eb-stack campaign state, one per line: id, status, class/disposition, stage, recipe, the fix or the summary; --remember files one lesson per finding a person or a seat resolved (recipe, step, error line, fix), origin tool-echo at L0 (derived), under the recipe’s name, its package and the class; ljos accept ID promotes it to explicit and writes it; --all takes the ones a later attempt superseded too; --issue cites the state file as a deed on the issue

sync [--key] [--import] [--export]

tracker, pack

share the seat’s memory across machines through the tracker repository: pull and take other machines’ sealed logs, then write and push this machine’s; --key prints this machine’s age public key (made on first use) and stops; --import only pulls; --export only writes and pushes

merge-atoms CURRENT PATH

tracker

re-export this machine’s atoms over a conflicted tracker log. git calls this as the ljos-atoms merge driver, %A then %P

handover --out DIR [--project P]... [--issue I]... [--to user@host:path] [--since-size N] [--scope NAME]

all

pack, seal, sign; --to copies the bag to another seat over ssh; --since-size puts the bridge from the size the receiver kept into the bag; --scope carries only the atoms tagged scope:NAME, sealed with age to that scope’s recipients, and no atom in plaintext

receive DIR [--since KEPT] [--keep FILE] [--import]

all

check, and import the atoms with origin peer; --since is the head --keep wrote at the last receive from this sender, and the bag’s bridge must start there; a sealed scope file opens with the identities that scope allows, and stays closed without them

doctor [--all]

all

whether ljos is ready, and the rows that need action; exit 1 when a required part does not answer or the note queue is damaged. --all (alias --verbose) prints every row, the ones that answer included. A panes row names the tool that opens persona panes; with jev.toml present, a jev row carries the month’s calls, cached answers and spend

seat [add NAME | grant SEAT close=own --quote TEXT --at TIME --via SEAT]

tracker

who is sitting; add writes NAME into the tracker’s vissue.toml seat list; grant records a quoted yes that SEAT may close tickets it filed or holds

protocol

none

print the sitting protocol

onboard [--harness NAME] [--skills DIR] [--dry-run] [--example]

runner

alone, print the one mcpServers entry any runner takes; with a runner named in ~/.config/ljos/harnesses.toml, register ljos-mcp with it, install the protocol as its skill, and write the personas into its agents directory; a shell runner (shell = true, grokbot, or shell) writes the skill and an env file and registers no server and no hook; changed runner files and their previous bytes are recorded for offboard

offboard [--runner NAME] [--dry-run]

runner

restore runner files changed by onboard and remove files created only for ljos; --runner limits removal to one runner; --dry-run reports without writing; later edits are preserved; an older installation without a manifest is removed by its ljos markers

The manifest and backups are under ${XDG_STATE_HOME:-~/.local/state}/ljos/onboard. They are removed after every recorded runner is offboarded.

ljos file "TITLE" --options A,B writes Options: A, B as the first body line. ljos options ISSUE A,B appends that line and refuses fewer than two. Withdrawing a ballot writes ballot-withdrawn: VOTER HARNESS NONCE. The tally leaves that ballot out, and so does the push check. A later cast from that session says the ballot was withdrawn.

send SEAT TEXT writes to one seat. A seat the pack has never heard from is refused, with the seats it knows, since mail waits under the name it was sent to; --new-seat writes to one that has not started yet. send --group NAME TEXT writes to the other members of that group. --interrupt leads the next prompt. --issue ID threads the message on that vissue issue, and the atom takes the issue’s scope so ljos sync carries it in that repository’s sealed log. inbox lists unread mail and writes no receipt. A pack that does not answer is an error. A writer that refuses kind message is an error too: the command exits non-zero, names packset 0.13.0, and does not print an empty inbox. When the writer reports a version, the error names that version. read ID writes one receipt. reply ID TEXT answers the sender and keeps the issue. group NAME --add SEAT records a member. ljos hook --prompt shows at most eight unread messages, interrupts first, and writes one receipt for each message it shows. The rest stay unread, and the note says how many remain. The receipt is an atom whose text is four lines: read ID, by SEAT, at STAMP and msg ID. The sender’s inbox prints that receipt once, as the message id, the reader and the stamp. A second prompt writes nothing. A shell writes the receipt before the hook exits. Cursor holds the note: the prompt prints nothing and writes no receipt, and the receipt is written when the note is delivered, on postToolUse as additional_context, or on Stop when the turn ran no tool, as followup_message. When the pack does not answer, the hook prints mail could not be checked and still exits 0. A shell seat polls ljos inbox.

send and reply start the recipient’s wake, when it has one, after the message is written. ljos wake --command CMD runs CMD under sh -c with LJOS_SEAT set to the seat, so ljos inbox in it reads that seat’s mail. With a prefix wake, LJOS_SEAT is still the run seat that matched the prefix. LJOS_WAKE_FROM, LJOS_WAKE_MSG, LJOS_WAKE_ISSUE and LJOS_WAKE_INTERRUPT name the message. ljos wake --webhook URL --key-file FILE posts the envelope (seat, sender, id, issue, interrupt, and via when a prefix matched) to an https URL with the key in that file as a bearer token, and never the body. A key is a prefix only when it ends in one trailing - after a non-empty stem; names such as -, -- and a-- are exact names. The sender waits on neither, and a wake that fails or runs past its timeout is a line in ~/.local/state/ljos/mail/wake.log. On Stop, unread interrupt mail holds the turn once and is receipted, so a running session reads it before it goes idle. A seat the pack has never heard from is refused with the known seat it most likely meant.

ljos deed --remove ACCESSION --ticket ID drops that citation. The removal is appended to the ticket. The heading is not rewritten.

remember and prefer write origin user-declared. finish --lesson files a proposal at L0, packset’s derived level, with origin agent-derived, and writes the lesson at once as user-declared when the person types it in a terminal of their own and no runner marks the environment. findings --remember files a proposal at L0 with origin tool-echo, because a campaign file is tool output. The prompt hook files a correction as a proposal in the person’s words, origin user-declared, and does not write it. A proposal from tool output or another seat (peer) stays at L0 until ljos accept ID promotes it to explicit and writes it. An agent’s own lesson starts at L0, and the same accept writes it. Accept keeps the origin. Under an agent runner accept takes only a proposal another conversation filed, such as a worker’s. The writer records the acceptance. The same text typed with remember or prefer writes it as user-declared and satisfies the proposal, and the held proposal is closed. An atom taken by receive --import or by sync has origin peer, whatever origin the sender wrote.

The claim graph records the held ticket too: a tracker id maps to one node (FNV-1a 128 of the id) and a name to one actor; a 32-hex id passes through.

On Linux the pack client reads who owns the socket on its port from /proc/net/tcp. A PACKSET_PORT that another user listens on is refused. With nothing set and 8761 another user’s, the seat uses port 18761 plus its user id modulo 10000 and exports it as PACKSET_PORT, so packset ensure starts its writer there. PACKSET_URL is used as given. Without XDG_RUNTIME_DIR, the hud socket and seat records go in ljos-UID under the temporary directory, mode 0700, or in ~/.cache/ljos/run when that name is another user’s or a symlink.

User rules#

ljos prefer --user TEXT --why REASON writes one user rule. The file is $XDG_CONFIG_HOME/ljos/user-rules.json, or ~/.config/ljos/user-rules.json when that directory is unset. Every seat on this machine reads it. The row does not decay and it is not reviewed, and the command does not also store a seat preference. --why is required with --user. --promote ID copies that lesson into a user rule and leaves the lesson in the pack. A promoted rule is stated ahead of a prompt only when the pack answers for that lesson within 400 ms, under the same id, stored as the person’s; a pack that is slow, down or answers otherwise leaves it out of the prompt. The row keeps a hash of the lesson’s kind, text, ts, valid_from and stored origin, and a record under that id that hashes otherwise, such as an id retracted and posted again, is another lesson; a rule promoted before the hash was kept is not stated until it is promoted again. The read goes only with this seat’s pack token, and only after the same read without it was refused, so a listener that answers without the token, or a packsetd run with PACKSET_AUTH=off, confirms no promoted rule. ljos rules lists a promoted rule the pack does not confirm apart from the user rules, as unverified with the reason. The hook still enforces such a rule’s pattern and its mention and reply denials, and the denial says the rule is unverified and why. --forget ID leaves a tombstone so a later sync does not bring the row back. --sync pulls and pushes user rules through the tracker repository. Nothing leaves the machine unless that repository’s .ljos/sync.toml names a scope. Then each machine’s rules are sealed with age to the scope’s recipients as .ljos/user-rules/<host>.json.age, and a plaintext <host>.json this machine wrote before is removed. Plaintext files other hosts left are still read. A write and the session start run the same pass and follow LJOS_TRACKER_GIT; --sync always pushes. --deny-mention HANDLE, --deny-name NAME, --deny-reply-to HANDLE and --deny-reply-name NAME may be repeated. ljos rule --user PATTERN --verdict deny|ask --why TEXT writes the argv law into the same file instead of the seat pack. Without --user, both commands keep writing the seat pack.

Threat model#

The pack token is sent only to the socket address the writer’s URL resolved to, and only after that address has refused an unknown atom id with no token and with a token that is not this seat’s. The request that carries the token may be the next connection; a later request proves again. A redirect is not followed, and a proxy in the environment is not used. A process of the same user that reads the token file can still answer as the pack. The proof cannot tell that process from packsetd; a writer that signs its answers, or that runs as another user, would.

Git hooks#

ljos git-hooks install writes a stub for every standard git hook and sets the global core.hooksPath to that directory. The directory is $LJOS_GIT_HOOKS when that is set, otherwise $XDG_DATA_HOME/ljos/git-hooks, otherwise ~/.local/share/ljos/git-hooks. It prints installed and the directory. A core.hooksPath that was already set is saved as previous-hooks-path in that directory. When core.hooksPath still names that directory, ljos git-hooks uninstall puts the saved path back, or unsets it. It then removes the stubs and prints uninstalled. ljos git-hooks status prints hooks: and the directory, or not installed, then core.hooksPath: and the configured value, or (unset). ljos onboard --harness grokbot installs the same hooks.

Each stub chains to the repository’s .git/hooks/<name> when that file is executable, otherwise to the saved path’s hook of the same name, and keeps that hook’s exit status. post-commit and post-rewrite also run ljos hook --git-commit --git-hook NAME. Booking never fails the hook. A line it prints goes to stderr, and a habitat that did not answer is a line in the book log. LJOS_OFF set to 1, true, yes or on, in either case, skips the booking, and the stub still chains.

ljos hook --git-commit books the commit git just created. post-commit reads no stdin. post-rewrite reads the old and new shas and moves the citation onto the new commit. A dropped commit, an all-zero new sha, loses its citation. --git-hook is post-commit or post-rewrite. An amend or a rebase moves the deed onto the new sha and does not leave the replaced sha cited. The sitting’s lesson is written again for the new sha and closes the one that named the replaced sha. A post-commit in the middle of a rebase or a merge does not book. post-rewrite does. A commit already cited on an open sitting in that checkout is not opened as a second book.

The hook books as a seat. The holder is SEAT@TAG, the same name ljos claim uses for that conversation. LJOS_SESSION_ID alone is kept as written. LJOS_SEAT wins when it is set and is not seat, you, agent, or a name that slugs to runner. Otherwise ljos derives the name from the markers the runner leaves in the environment, and a derived name is a seat. When nothing named the seat, an ANTIGRAVITY_CONVERSATION_ID books as antigravity and an OPENCODE_SESSION_ID books as opencode. A Claude Code or Codex session, named by CLAUDECODE, CODEX_THREAD_ID or CODEX_SESSION_ID, is not booked. An opencode session is booked by this hook, including a commit from its shell or a subprocess. An agy session is booked the same way when no seat was named. Without these hooks, Stop and SessionEnd cite each commit HEAD’s reflog says the checkout wrote since the sitting began, so a commit from a script, or one chained before a step that failed, still has a deed. A pull, a merge or a checkout adds none.

The env file ljos onboard writes sets LJOS_GIT_HOOKS to ${LJOS_GIT_HOOKS:-${XDG_DATA_HOME:-$HOME/.local/share}/ljos/git-hooks}, the same directory the hooks install into. ljos doctor reports an env file that sets a different directory.

Commits under [git_hooks] skip in ljos.toml are not booked, and neither are /tmp, /var/tmp, /dev/shm, the tracker (VISSUE_ROOT or ISSUE_ROOT), CLAIMDAG_DIR, and the deedar store. The table is read from $LJOS_CONFIG, from ./ljos.toml, from ~/.config/ljos/ljos.toml, and from ~/.config/ljos/seats/$LJOS_SEAT/ljos.toml. A reinstall rewrites the hook scripts and leaves that file alone. examples/ljos.toml shows the table.

MCP tools#

The seat serves these over the Model Context Protocol (MCP).

Writers: ljos_sitting, ljos_finish, ljos_calibrate, ljos_persona, ljos_playbook, ljos_remember, ljos_prefer, ljos_forget, ljos_trust, ljos_learn, ljos_graded, ljos_island, ljos_deed, ljos_vote, ljos_predict, ljos_rule, ljos_request_approval, ljos_claim, ljos_release, ljos_complete, ljos_consolidate, ljos_findings (a reader without remember), ljos_bump_plan (a reader with dry_run), ljos_handover, ljos_receive, ljos_send, ljos_read, ljos_reply, ljos_group. Readers: ljos_search (with as_of), ljos_conflicts, ljos_inbox, ljos_timeline, ljos_digest, ljos_due, ljos_habit (a writer with a value), ljos_evidence, ljos_current, ljos_recall, ljos_consensus, ljos_brief, ljos_personas, ljos_playbooks, ljos_cards, ljos_policy, ljos_doctor. Resources: ljos://protocol, ljos://cards/USER.md, ljos://cards/MEMORY.md. Prompts: start_a_sitting, run_a_panel, check_a_handover. Every tool description opens with when to call it.

By default the server lists the core set, in this order: ljos_rule, ljos_request_approval, ljos_prefer, ljos_remember, ljos_search, ljos_start, ljos_guard_check, ljos_file, ljos_options, ljos_note, ljos_recall, ljos_sitting, ljos_finish, ljos_claim, ljos_release, ljos_send, ljos_inbox, ljos_reply, ljos_vote, ljos_consensus and ljos_doctor. LJOS_MCP_TOOLS=all lists every tool above: the core set first, then the rest by name, then the persona and playbook tools. LJOS_MCP_TOOLS also takes names split on commas or spaces, with or without the ljos_ prefix, where core stands for the core set. A call to a tool the server does not list is refused with the shell command to run instead. A tool that fails answers with isError set and the reason as text, so the model reads it as a tool result.

Environment#

Variable

Read by

PACKSET_URL

the pack client; unset, http://127.0.0.1:8761 (PACKSET_PORT moves the port), or this user’s own port when 8761 is another user’s; off means no pack

PACKSET_WORKSPACE

the pack workspace; unset, seat

LJOS_SEAT

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 vissue.toml lists seats, a tracker write from a seat it does not list is refused

VISSUE_ROOT, VISSUE_AGENT

the tracker

DEEDAR_URL, DEEDAR_HOST_SIGNING_KEY

the deed store; the key signs handovers, and ~/.config/deedar/host.key is used when the variable is unset

CLAIMDAG_DIR

the claim graph

LJOS_CARDS_DIR

the server’s cards directory

LJOS_MCP_TOOLS

the tools ljos-mcp lists; unset, the core set (see MCP tools); all lists every tool; else names split on commas or spaces

LJOS_HUD_BIN

path to ljos-hud; else sibling of ljos, then PATH

LJOS_HUD_SOCKET

summon socket; else $XDG_RUNTIME_DIR/ljos/hud.sock

LJOS_TRACKER_GIT

commit commits the ticket’s issues.org, and the sitting’s and finish’s sealed sync log, without pushing; off skips the commit and push, and the sync in passing; unset, commit and push. ljos sync by hand always pushes

LJOS_TRACKER_PUSH_WAIT

seconds the tracker push after a claim or finish waits; 5 when unset

LJOS_INBOX_SYNC_WAIT

seconds ljos inbox --sync COMMIT keeps fetching until the remote has the commit; 30 when unset, 0 fetches once

LJOS_USER_RULES_SYNC

off stops user rules from being pulled or pushed through any tracker, and ljos prefer --user --sync refuses; unset, they sync through a tracker whose .ljos/sync.toml names a scope

LJOS_USER_RULES_TRACKER

the tracker checkout user rules sync through; unset, repos in ~/.config/ljos/sync.toml, then ISSUE_ROOT or VISSUE_ROOT, then the seat’s vissue config, then the working directory when it is a tracker

LJOS_USER_RULES_SYNC_MS

milliseconds each user-rule sync may take; 4000 after a write, 1500 at session start, 8000 for --sync when unset

LJOS_TRACK_WORK

on makes the hooks ask a conversation with no issue to open one and sit; read from the environment, else ~/.config/ljos/env; unset, they say nothing until it sits

XDG_CONFIG_HOME

where ljos/harnesses.toml is read from; ~/.config when unset

LJOS_PANE_TOOL

the one [[tool]] persona panes open in; unset, the first of the file’s tools and the built-in herdr and tmux shapes that answers

LJOS_PANEL_RUNNER

the runner a decision panel’s members run on, through its headless argv; unset, the seat’s own runner, else Grok

LJOS_PANEL_CONCURRENCY

how many decision-panel members start at once; unset, the machine’s parallelism clamped to 4; 0 starts every member; LJOS_MAX_PARALLEL is the same knob

XDG_STATE_HOME, XDG_CACHE_HOME, XDG_RUNTIME_DIR

where the Jev state, cache and runtime key live (see Jev settings); XDG_RUNTIME_DIR also keeps the hud socket and the seat records

~/.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. The seat answers allow when no rule 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 Cursor’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 Cursor runs that file’s hooks too and Claude’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 Claude’s file carries the seat hook, or when Cursor’s file carries every event.

The hook reads a Cursor call off cursor_version in its stdin and answers in Cursor’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 still answers allow when there is no verdict, since Cursor blocks the command when the answer to a permission check is not JSON. An ask on preToolUse, which Cursor does not enforce, becomes a deny. 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 grokbot 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 --fail-on-deny and ljos policy --fail-on-deny exit 1 when the answer is a deny. 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

detect

exits 0 when the tool answers

open

ways to open the persona’s pane; the first that exits 0 wins

pane_pointer

a JSON pointer to the pane id in open’s output

run

types the pane script into a pane that starts as a shell

respawn

restarts the script in a pane whose runner exited

prompt

hands the runner a line

type_line

types the line raw when prompt is refused or absent

alive

exits 0 while the pane is there

ready

exits 0 once the runner takes input, retried for ready_s seconds

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 deny is permissionDecision deny and blocks. Plain text on stdin is an argv line and answered in plain lines.

Event

The hook does

UserPromptSubmit

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

SessionEnd

fires the memories injected during the session together and clears the session’s record

PostToolUse

emits the held prompt note once, on the first tool result, for a runner that discards prompt-hook stdout (grok sends this event)

PostToolUseFailure

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

PreCompact

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

SessionStart

after a compaction (source compact), on a runner that takes its context, answers with that note; a fresh session hears one line. It says that ljos is on, the memory and due counts, the policy state, and up to three preferences

PreToolUse

applies the TCB and pack rules to the command (matcher Bash|Edit|Write|MultiEdit|NotebookEdit|apply_patch); a deny blocks. It keeps the call’s agent_id for each ljos line, and on a seat MCP tool (^mcp__.+__ljos_.+$) only notes a subagent’s call, so the ballot binds the session plus that agent. A vissue write tool is refused by the same tracker write check, and the refusal names ljos finish

Stop

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 (decision: block, or followup_message in Cursor) and receipts it

SubagentStop

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.

What a rule reads#

A rule is tried on every command a line runs, the first and the rest. The line is split on &&, ||, ;, |, & and new lines outside quotes, and each piece is read again inside: the bodies of $(...), backticks, <(...), >(...) and subshells; the script of sh -c, eval, env -S, trap, alias, su -c, script -c, flock -c and nix-shell --run; a here-string or here-document fed to a shell, and echo or printf piped into one; the commands of find -exec; and the line ssh runs on its host. Leading NAME=value words, wrappers (sudo, doas, env, time, nohup, exec, command, nice, ionice, timeout, setsid, stdbuf, xargs, flock, chronic, unbuffer, watch), case patterns and function definitions come off, and quotes, $'...' and escapes come off each word, so git 'push', $'git' push and git -c alias.p=push p read as git push. A line nested more than eight deep is refused whole.

A rule reads the line as written. It does not see a command whose name comes from a variable ($g push), a script run from a file, an alias or git alias defined in an earlier call, a recipe in a Makefile or justfile, or a call made from inside another language (python -c, perl -e). A rule stops an agent’s mistakes; it is not a sandbox. For a push that must never happen, protect the branch on the forge.

Checks ljos keeps#

policyd answers every command with one of four answers. ljos keeps two checks of its own, because only ljos knows the tracker and the sitting.

Answer

What happens

allow

the command runs

ask

the runner’s permission prompt, or ljos approve

deny

the command does not run, and the reason says why

rewrite

coming in 0.35.0: the command runs in a fixed form

On the wire, a rewrite is one line of three tab-separated fields: allow, rewrite and the new command. The new line runs only when the guard also allows it. The hook hands it back as the updated input for Claude Code, Codex, Grok Build and Cursor’s preToolUse. A runner without that field gets a refusal that names the new command, so the old line never runs. The first rewrite runs the command through uv run.

Each swap also shows a note naming both commands and leaves a row in $XDG_DATA_HOME/ljos/rewrite.jsonl. Set rewrite = false in a repository’s ljos.toml to run its lines as written. The log keeps a row for a swap it declined too.

If ljos-policyd is missing, slow or crashes, the command runs and ljos logs a warning. Nothing in ljos turns a fault into a refusal.

The tracker write check refuses a command, or a file tool, that writes the tracker’s files by hand. Ticket text is data. A ticket body that names a path or a command is never read as one.

The push check asks, and never denies, on a push onto a protected branch when the ticket of the current sitting has no settled consensus. It covers develop and main unless consensus_branches in ljos.toml names others. With one active seat in vissue.toml, that seat’s own vote settles it. This solo mode is coming in 0.35.0. With two or more seats, the normal quorum applies.

Every other push rule is policyd’s.

Push

Answer

an ordinary push of a branch, a tag, or a lease push to a branch that is not protected

runs

a lease push or a force push onto a protected branch

asks

a push onto a protected branch, with no settled consensus on the sitting’s ticket

asks

deleting a remote branch, or rewriting published history

asks or denies, by policyd’s table

A cloud seat has one push rule. It may push the branches named in its ljos.toml and develop, with no force and no delete. Opening or merging a pull request is not refused. Branch protection on the remote does the rest.

The remote is the one the command names, else the branch’s upstream, else origin, read in the directory a cd or git -C before the push names.

Sync files#

File

Contents

.ljos/sync.toml (in the tracker repository)

the repository’s scope and its age recipients; a [projects] table sends one project’s lessons to another scope

.ljos/atoms/

one sealed log per machine: a base <host>.jsonl.age, the chunks sealed after it in <host>.chunks/, and a <host>.digest

.ljos/user-rules/

one file of user rules per machine, sealed to the scope’s recipients (<host>.json.age); a plaintext <host>.json is from before the seal

Jev settings#

~/.config/ljos/jev.toml. The file is absent or enabled = false: Jev is off.

Key

Default

Meaning

enabled

false

turn Jev on; the call sends the prompt and claims off the machine

backend

jev

which judge answers: jev, chat (one JSON-mode chat completion at endpoint plus /chat/completions, model model) or command (the command argv, request on stdin, answers on stdout)

command

unset

the argv of the command backend; it prints {"answers": ...} inside budget_ms

key_file

unset

file holding the key, one line, mode 0600

key_cmd

unset

command that prints the key on its first line, as an argv list; asked once per login

model

jev-1.13.0

model name; typesafe/jev-1.13 on OpenRouter

budget_ms

2000

how long the hook waits for the answer

endpoint

https://api.typesafe.ai/v1/systemone

API endpoint; https://openrouter.ai/api/alpha/decisions for OpenRouter; the base URL (.../v1) for chat

monthly_usd

4.0

the month’s spend in US dollars past which the hook stops asking

min_words

4

a prompt with fewer words is skipped

min_candidates

2

fewer candidate claims than this is skipped

usd_per_mtok_in

0.042 on jev, required on chat

US dollars per million input tokens, for an API whose answer carries no cost. A chat judge with no price is not asked

bears_at

0.5

probability at which a claim bears on the prompt

cue_at

0.5

probability at which the prompt counts as a correction or a choice

escalate_below

0.8

a persona ballot under this confidence goes to a subagent

cache_days

7

days an identical request is answered from the cache; 0 turns it off

key_env

unset

environment variable holding the key

[judges.NAME]

none

a further judge: backend, command, key_file, key_cmd, key_env, model, endpoint, budget_ms, usd_per_mtok_in, and weight (1.0) in a pool

[route]

every decision to default

which judges answer each decision: prompt, ballot, audit, review, each a list of judge names; default is the judge the top-level keys describe

Path

Contents

$XDG_STATE_HOME/ljos/jev-cost.toml

the month’s calls and spend

$XDG_STATE_HOME/ljos/jev-log.jsonl

every Jev answer: claim ids, a prompt hash, latency, timeout or failure, backend and model. ljos judge-score joins it with outcomes

$XDG_CACHE_HOME/ljos/jev

the answer cache: a hash of the request and the reply, directory mode 0700, file mode 0600. The prompt and the claim text are not stored

$XDG_RUNTIME_DIR/ljos/jev-key

the default judge’s key from key_cmd, mode 0600; jev-key-NAME for a named judge

The four routes are prompt, ballot, audit and review. backend = jev posts to endpoint (TypeSafe’s API, or OpenRouter at https://openrouter.ai/api/alpha/decisions with model typesafe/jev-1.13). backend = chat posts one JSON-mode completion to endpoint plus /chat/completions and must set usd_per_mtok_in. With backend = command, the request stays on the machine. TypeSafe’s terms say they will not train on customer data. They keep a perpetual licence to derive telemetry from inputs. Zero data retention needs an enterprise agreement and is off by default. This seat does not train on Jev’s answers. A judge ballot is a trust row with to judge:MODEL, and learn updates it.

The learning rules#

learn adds the outcome to each voter’s record of hits and misses. A voter’s accuracy is read off its record and shrunk toward the panel’s pooled accuracy by empirical Bayes (Efron and Morris, doi:10.1080/01621459.1975.10479864). The prior weakens as the records spread beyond binomial noise; the first outcome leaves every voter alike, and a long record keeps the differences it shows.

The weight every other voter gives a voter is the log odds of that accuracy, Nitzan and Paroush’s weight (doi:10.2307/2526438); the best voter weighs one and none weighs less than 0.01. A panel with nobody above chance weighs every voter 0.01. Rows are written complete, so the settle sees the whole graph, and each carries its voter’s record. learn also writes an outcome atom, the issue and the option it closed on.

--rule hedge runs the multiplicative weights rule of Hedge (doi:10.1006/jcss.1997.1504) instead. Every voter whose ballot the outcome refuted shrinks in every other voter’s row by beta (default 0.5), floored at 0.01. A vindicated voter keeps its weight, and a missing row starts at 1.

consensus reads each issue’s ballots beside the option it closed on once five issues have an outcome, and asks ljos-consensus correlation how far the voters share their mistakes. A pair counts only when its correlation passes the one-sided test of independence at five percent. A voter’s inbound weight in the settle is multiplied by 1 / (1 + the sum of its counted correlations) (settle --discount-of); exact clones count as one voice under it if their record has a hit and a miss, and a looser cluster as more than one. A line before the settle names the outcomes read, the independent voices the panel has and each voter discounted. Nothing is passed or printed when no pair counts.

A row carries the domains it is scoped to. An unscoped row applies to every issue; a scoped row applies when one of its domains is a word of the issue’s title. learn writes its rows scoped to the entities of the island the issue’s title activates, eight at most, so a voter refuted on one topic keeps its standing on the rest; a scoped learn starts from the unscoped row when it has none of its own and leaves the unscoped row standing. When both apply, the scoped row is the one the settle sees. --share S then lifts every row by S of its distance to one, the fixed-share rule of Herbster and Warmuth (doi:10.1023/A:1007424614876). A voter refuted long ago is not held down forever, and the best voter can change. The default is zero, plain Hedge. An outcome also moves the anchor of every persona it refuted toward one by 1 - beta of the gap: a persona that keeps being wrong listens more.

A persona is a persona atom: a name, an anchor in [0, 1], a view, and the domains it speaks to. consensus passes the anchor of each persona on the ballot to both settles as --susceptibility-of, and gives every other voter on that ballot, the seat included, an anchor of 0.5. A ballot with no persona settles as plain DeGroot. At 1 a persona keeps none of its ballot. An anchor below 0.5 settles at 0.5: under Friedkin-Johnsen with equal trust, the share of its ballot a voter keeps is its weight, and a stubborn voter pulls the rest toward it (Acemoglu, Como, Fagnani and Ozdaglar, doi:10.1287/moor.1120.0570). A persona at 0 would count as two seats and tie a 2-to-1 vote on its own. Trust rows still weigh voters as they say. Before the default, the seat sat at 1, so any anchored persona took the whole settle. The settle prints in words: the shares, who leads and by how much, polarization, each voter’s influence, the share of its own ballot each voter keeps. Above the settle, consensus prints what the seat ballots decide: the option every seat ballot names and the seat ballots it counted, or why it decides nothing, with the persona and judge ballots it left out. This is what the push check reads. With one active seat in vissue.toml, that seat’s own vote settles it. This solo mode is coming in 0.35.0. The settle below is advice and decides no push. When trust rows exist, the standing beside the settle names the voters on this issue; the other names in the rows are counted, and --json lists them. consensus ID --json prints one JSON document, and nothing else on stdout. The seat tally stays at the top level under push_cite, with the keys it already uses (issue, pusher, stands, and choice with counted, or refusal). The model crate’s settle is under settle. surprising and reputation are present when those readings ran. consensus ID --compare also runs vissue consensus, the tracker’s settle, and that output is under tracker in the same document. The tracker also runs when ljos-consensus is not installed. Without --compare, and with the model crate installed, the document has no tracker key. An issue with no ballots is refused before any settle runs, in the command and in the MCP tool, and the message says how to cast one (ljos vote ID --for OPTION, or ljos panel ID). A vissue with --file-dissent files dissent children only under that flag; an older one files them on every run.

A ballot’s --confidence is recorded and weighs no settle and no seat tally. consensus says on how many ballots it was stated. learn ID --outcome OPTION scores it once the outcome is known: Brier and log score and calibration per voter.

Without an outcome, calibrate estimates each voter’s accuracy from the project’s issues with two or more ballots, by expectation maximisation over the items’ hidden answers (Dawid and Skene, doi:10.2307/2346806). It then writes the log odds of each accuracy as the weight every other voter gives that voter. The most reliable voter weighs one, and no weight falls below 0.01.

Crates#

Crate

Carries

ljos-cli

the library and ljos

ljos-mcp

the MCP server

ljos-consensus

DeGroot and Friedkin-Johnsen settles, Seldon export (separate repository)