Integration

How the seat meets the rest of the world: the memory systems it learns from, the concurrency designs it voted on, the runners it sits in, and the dogfooding that tests it every day. The explanation says what the seat is; this page says what it plugs into and why.

The seat among FOSS memory systems#

Agent memory systems in the literature and on the market do one of a few jobs. The seat integrates with them rather than re-running them:

System

What it does

How the seat meets it

Mem0 (doi:10.48550/arXiv.2504.19413)

a model extracts facts from a conversation and decides per fact whether to add, update, delete or leave the store

the seat’s proposals path does the same behind a check: a proposal becomes a claim only on an explicit accept. At SessionEnd the seat keeps up to five cue-word sentences from the person’s and the agent’s prose as unverified agent notes the prompt hook never injects, and LJOS_EXTRACT=on turns that on. The read is off by default, and a note is written only when the conversation has an open sitting. With no sitting the hook prints one line and writes nothing, because packset keeps only notes that belong to a ticket.

Zep (doi:10.48550/arXiv.2501.13956)

a temporal knowledge graph with edges carrying the time an edge became true and stopped being true, plus fused lexical, dense and graph retrieval

the pack carries the same two times on every claim (written-at and the live window a superseding claim closes) and fuses a prefix scan, BM25+ and a dense ballot; a Zep graph can be read as trust rows and island links, not as a second store

Letta, formerly MemGPT (doi:10.48550/arXiv.2310.08560), with sleep-time compute (doi:10.48550/arXiv.2504.13171)

a small core memory the model edits with tools, plus recall and archival stores reorganised between turns

the seat’s review clock (ljos due, graded) is the reorganisation, but it runs on Free Spaced Repetition Scheduler (FSRS) retrievability rather than on a model pass, so it works with no model at all

HippoRAG (doi:10.48550/arXiv.2405.14831, doi:10.48550/arXiv.2502.14802)

a knowledge graph retrieved by personalised PageRank from the entities a question names

the island (ljos island) is the same idea over Hebbian link weights, read beside the fused hits for orientation rather than in their place, because measured as a ranking it lost; see the explanation

MemoryBank (doi:10.48550/arXiv.2305.10250), Generative Agents (doi:10.48550/arXiv.2304.03442)

forgetting on an Ebbinghaus curve refreshed by recall; ranking by recency, importance and relevance plus reflection

forgetting here is by review, not by age: FSRS retrievability scales search, and a never-recalled lesson lapses like a missed review

Seldon (seldon-code/seldon)

the Ordinary Differential Equation (ODE) engine for opinion dynamics

ljos-consensus settle --seldon writes its inputs and reads its output, so the discrete step is checked against a second implementation without linking its GNU General Public License (GPL) code

The one design choice the others do not make is the privacy of write: what is remembered is what was said with Remember or Prefer, stored as written. Integration never crosses that line: foreign facts arrive as proposals, trust rows, or cited deeds, each attributable and retractable.

Three concurrency designs and the vote#

Three designs were on the table for the seat’s concurrent life: many agents writing at once. The vote went as follows, and the code follows it.

A Go rewrite for goroutines: no. The bottleneck is not compute, it is one writer. The pack is one daemon owning one Lightning Memory-Mapped Database (LMDB) file (packsetd); the claim graph is one in-process graph with the host as sole mutator (claimdag); deed appends take a file lock so two processes minting at once write whole lines. The packset README records zero errors at 32 clients, and a herd of 32 writing 6400 distinct claims into one workspace keeps 6382 live and 18 closed. Goroutines parallelise what is already fast and leave the single writer single; a rewrite would trade the Rust type-level guarantees (closed enums for kind, status and role; compare-and-swap on generation) for a runtime that does not change the contention shape. Concurrency here is checked with a hammer test: packsetd serves many readers and one writer, and the hammer rerun is the regression check.

An Erlang-style plugin tree: no as an Open Telecom Platform (OTP) port, yes as the pattern. What Erlang would buy is supervision: a crashed worker restarts, and a bad message is refused at the boundary. The seat already has that shape without the runtime. ljos-policyd (or phronesis_check_shell behind it) refuses at the boundary. A policyd that is missing or slow does not stop the work: the command runs and ljos logs a warning. ljos doctor names each habitat and whether it answers, and a missing one is reported rather than guessed around. The claim graph lives in $XDG_RUNTIME_DIR so a reboot clears leases instead of resurrecting stale locks. A new plugin therefore looks like a habitat: one store, one CLI, one Model Context Protocol (MCP) surface, doctor-visible. That is cheaper than an OTP tree and keeps every store a file a person can read.

herdr as the tooling integration: yes, and it is the ongoing work. herdr is the session multiplexer the seat already speaks: personas reason in a herdr pane when herdr is up and fall back to a tmux window when it is not (crates/ljos-cli/src/persona_session.rs), the runner works in the persona’s home as its own seat, and the person’s watch-and-talk window is that same pane. The direction is more of this same system: herdr panes as supervised seats, policyd refusing writes to the seat’s own files, approvals flowing through ljos approve on any runner. The Grok Build page is the reference integration; each runner registers the events its table names.

Deep runner integration#

ljos onboard --harness NAME registers the MCP server, the hooks and the skill for one runner. NAME takes claude, codex, grok, cursor, antigravity, opencode, omp, hermes, copilot, gemini, windsurf, factory, kiro, cline, qwen, crush, zed, vscode, claude-desktop, amazonq, aider (conventions file, advisory), auggie, amp, goose, openhands, continue, roo, kilo, grokbot or shell. The contract is the same everywhere and the envelope differs:

Runner

Memory injection

Shell check

Ask

Notes

Claude Code

UserPromptSubmit context, skills, ljos://protocol

PreToolUse deny blocks

native permission prompt

plugin ljos@leidarljos; sitting and finish commands are in the repo

Codex

hooks plus MCP server list

hook deny blocks

none: Codex does not prompt; an ask is rewritten to a deny that offers ljos approve

prompt and PreToolUse; shell_command payloads carry the command and session cwd only, so ljos cannot read a workdir override

Grok Build

PostToolUse additionalContext after the first tool (UserPromptSubmit stdout is discarded, so ljos hook keeps the text and emits it once)

PreToolUse, 10 seconds, the Trusted Computing Base (TCB) check’s budget

ask rewritten to deny where stdin carries turn_id

frozen file crates/ljos-cli/assets/grok/ljos.json; LJOS_MCP_GENERATION bump respawns the server; the full page is Grok Build

Antigravity (agy)

hooks plus skill

hook deny blocks

ljos approve, ljos_request_approval, or reply approve ID

pipeline stages arrive separated by a \| word so a download and the shell it feeds are judged together

opencode

assets/opencode/ljos.ts

hook deny blocks

runner prompt or ljos approve

same contract, TypeScript hook

omp

assets/omp/ljos.ts

hook deny blocks

runner prompt or ljos approve

same contract, TypeScript hook

hermes

MCP server list plus skill

advisory: onboard registers no hook

none: no hook

nothing leaves the machine unless a remote judge is turned on

Cursor

prompt note on the first tool result; skill; ~/.cursor/agents

beforeShellExecution and preToolUse; a missing policyd allows

Cursor’s permission prompt on a shell command

the IDE and the agent CLI share ~/.cursor/mcp.json and ~/.cursor/hooks.json

GitHub Copilot CLI

skill; no prompt note, since Copilot drops a command hook’s prompt output

PreToolUse in ~/.copilot/hooks/ljos.json, permissionDecision

Copilot’s permission prompt

the same file in a repository’s .github/hooks reaches Copilot’s cloud agent

Gemini CLI

BeforeAgent additionalContext

BeforeTool on run_shell_command, decision: deny

none: an ask is a deny that says so

hooks and MCP server share ~/.gemini/settings.json; timeouts there are milliseconds

Windsurf (Devin Desktop)

skill

pre_run_command, exit 2 with the reason on stderr

none

~/.codeium/windsurf/hooks.json

Factory droid

UserPromptSubmit context

PreToolUse on Execute, exit 2 with the reason on stderr

droid’s permission prompt

~/.factory/hooks.json, keyed by event at the top level

Kiro

skill

PreToolUse on the shell tools, exit 2 with the reason on stderr

none

~/.kiro/hooks/ljos.json, Kiro CLI V3 and IDE 1.0

Cline

UserPromptSubmit contextModification

PreToolUse, cancel: true

none

two scripts in ~/Documents/Cline/Hooks, macOS and Linux

Qwen Code

UserPromptSubmit context

PreToolUse on run_shell_command, exit 2 with the reason on stderr

Qwen’s confirmation

~/.qwen/settings.json

Crush

skill

PreToolUse on bash, exit 2 with the reason on stderr

none

~/.config/crush/crush.json; Crush fires only PreToolUse so far

Auggie

skill

PreToolUse on launch-process, permissionDecision

none: an ask is a deny that says so

~/.augment/settings.json runs ~/.augment/ljos-guard-auggie.sh

Amp

skill

Bash delegated first in amp.permissions, exit 2

Amp’s prompt, from exit 1

~/.config/amp/settings.json runs ~/.config/amp/ljos-guard-amp.sh

goose

skill in the plugin

PreToolUse on shell, exit 2

none

user plugin ~/.agents/plugins/ljos; server in ~/.config/goose/config.yaml

OpenHands

UserPromptSubmit additionalContext

PreToolUse on terminal, exit 2

none

~/.openhands/hooks.json and ~/.openhands/mcp.json

Continue

rules file; SessionStart hook

advisory: hooks in ~/.continue/settings.json are written, cn 1.5.47 does not fire them

runner prompt

~/.continue/mcpServers/ljos.json

Roo Code, Kilo Code

skill

advisory: no tool hook

runner prompt

the extension’s mcp_settings.json

Grok Bot

the skill, then ljos hook --prompt

ljos policy --fail-on-deny exits 1

the person runs the command

no MCP server and no hook file; source the env file onboard writes

Codex 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 Codex’s commands. ljos doctor reports an untrusted hook when it can see one.

The runners from Copilot down call ljos hook --runner NAME. That command reads the runner’s payload as Claude Code’s, runs the same hook, and writes the answer back in the runner’s field names and exit code. On a tool call, the relay lets the command run and logs a warning in three cases. The inner hook crashes, exits non-zero without an answer, or runs past GUARD_TIMEOUT. That is 5 seconds unless LJOS_GUARD_TIMEOUT names another number of seconds. A policyd that is missing, hangs, crashes, or prints no answer does not stop the command either. POLICYD_REQUIRED=1 refuses the line when no binary answers. On any other event, such as a prompt, the relay waits 9 seconds, past the inner hook’s own 8 second deadline, and a fault lets the event through with a note.

When a hook fails#

A check works only when its hook runs. What happens when the hook breaks is the runner’s call, and the runners disagree. The table says what happens to the command when the seat’s hook crashes, times out, or prints something the runner cannot read. “Runs” means the command goes ahead unchecked.

Runner

Hook exits non-zero (not 2) or crashes

Hook times out

Answer is not JSON

Source

Claude Code

runs; only exit 2 blocks

runs

runs

hooks reference

Codex

runs; only exit 2 with a reason on stderr blocks

runs

runs

hooks, pretooluse.rs

Cursor

runs: the seat does not set failClosed

runs

beforeShellExecution blocks it

Cursor hooks docs

GitHub Copilot CLI

blocked

runs

falls through to Copilot’s own permission flow

hooks reference

Gemini CLI

runs, with a warning

not documented

runs

hooks reference

Windsurf (Devin Desktop)

runs; only exit 2 blocks

not documented

not read; the exit code decides

Cascade hooks

Factory droid

runs; only exit 2 blocks

not documented

not read; the exit code decides

hooks

Kiro

blocked

not documented

not read; the exit code decides

hook actions

Cline

not documented

not documented

not documented

cline/cline

Qwen Code

runs; only exit 2 blocks

not documented

not read; the exit code decides

hooks

Crush

runs; only exit 2 blocks

runs

not read; the exit code decides

hooks

The seat does not turn a fault into a refusal. A ljos-policyd that is missing, hangs, crashes, or prints no answer lets the command run, and ljos logs a warning. So do a relay that cannot read its input and a wait past LJOS_GUARD_TIMEOUT. CI and a fresh install are never locked out. The hook files the seat writes give each runner a timeout of 10 to 20 seconds. A check that takes longer meets the “times out” column. None of this helps when the ljos binary itself is gone. On Copilot and Kiro that blocks every shell command, and on every other runner the commands run unchecked.

When the seat’s own hook fails on a permission event, it writes an allow in the runner’s JSON, so a runner that blocks on a missing answer still runs the command.

Two rules hold on every runner. First, policyd refuses a command that writes the seat’s own files: its binaries, its hook entries and its policy files. An agent cannot rewrite the law it runs under. ljos adds one more refusal, for a command or a file tool that writes the tracker’s files by hand. Second, ljos policy -- COMMAND prints the answer for one command without running it, so every integration is testable without the runner. scripts/smoke.sh does exactly that.

policyd can also answer a command with a fixed one. This is coming in 0.35.0. The new line runs only when the guard also allows it. Claude Code, Codex, Grok Build and Cursor’s preToolUse take it through the hook’s updated input. Every other runner in the table gets a refusal that names the new command, so the old line never runs.

Dogfooding#

This repository eats its own cooking. Sittings are claimed through the claim graph, lessons are remembered with ljos remember and reviewed with ljos due, panel decisions are settled with ljos consensus and calibrated with ljos calibrate, and releases cite their deeds. When an integration breaks, the breakage is filed as an issue in the tracker first, because a conversation that has no issue is told to file one and sit. The measure of this page is whether the next runner takes an afternoon: one envelope, the events that runner names, one contract.