Clone
1
Harness parity
Jaroslav Beneš edited this page 2026-10-09 22:40:23 +00:00

Harness parity — LLeMbas CLI ⇄ LLeMbas

LLeMbas CLI (TypeScript) and LLeMbas (Python) are two independent projects that share one harness design: the same tools, the same permission floor, the same prompt texts, the same model quirks and metadata, and one protocol between them. Each implements it natively; neither ever executes the other. The specification is this repository's harness/ (see its README.md); the CLI embeds it at build time (src/harness.ts), LLeMbas vendors a pinned, hash-checked copy (scripts/fetch_harness.py, scripts/harness.lock.json, into src/lembas/harness_spec/).

This page maps every shared concept to its file in both, and says where they still differ. Read it before touching either harness, and keep it true — a wrong row is how one side silently stops matching the other. Paths: CLI under src/ unless they start with harness/, scripts/ or tests/; LLeMbas under src/lembas/ unless they start with scripts/ or tests/.

The rule

  • A harness change (tools, permissions, prompts, quirks, the loop, the library, the link) goes into the spec first, then into both implementations — or is recorded as an open item against the other project.
  • A bug found in one is searched for in the other, and the result — including "not present" — is recorded with the fix.
  • Every such fix brings a conformance case in harness/conformance/, which both test suites run.
  • An LLeMbas administrator's prompt overrides are a local setting, not a difference.

The spec itself

Concept LLeMbas CLI LLeMbas
Where the spec lives harness/ (source of truth), embedded by harness.ts harness_spec/ (vendored), read by services/harness_spec.py
Pinning and updating harness/VERSION scripts/fetch_harness.py --update vX.Y.Z (a CLI tag), scripts/harness.lock.json
Spec version reported HARNESS_VERSION (harness.ts), sent as harness_spec in initialize and the link's hello (acp/agent.ts, acp/link.ts) /.well-known/lembas.json (api/devices.py), /admin/updates (api/admin_updates.py)
Conformance runner tests/conformance.ts (areas → functions), tests/conformance.test.ts; bun run harness record (scripts/harness.ts) tests/test_harness_spec.py (AREAS, and PENDING with reasons)
Tool definitions kept equal to the spec tests/harness.test.ts tests/test_harness_spec.py (DEVICE_TOOLS, TOOLS_PENDING)

Conformance areas

Area LLeMbas CLI LLeMbas
hardline permission/hardline.ts hardlineCommand services/agent/shell.py hardline_command
split permission/bash.ts splitCommand services/agent/shell.py split_command
plain permission/hardline.ts plainCommands services/agent/shell.py plain_commands
arity permission/arity.ts prefix services/agent/shell.py prefix
always permission/evaluate.ts alwaysPatterns services/agent/policy.py always_patterns
permission permission/evaluate.ts evaluate pending — LLeMbas's modes are its own table (services/agent/policy.py POLICY)
chunk library/chunks.ts split services/library/chunks.py split
think provider/think.ts ThinkSplitter services/reasoning.py ReasoningSplitter
effort provider/effort.ts effortRefused, advertisedEfforts services/generation.py _effort_was_refused, _advertised_efforts
edit tool/replace.ts not run — the file tools are the CLI's
unidiff tool/unidiff.ts not run — the file tools are the CLI's
envelope tool/patch.ts not run — the file tools are the CLI's

Tools

Scopes come from each harness/tools/<name>.json: shared (both implement it), cli (the CLI's alone). LLeMbas runs no agent of its own — an agent chat runs on a linked CLI — so the execution tools are cli.

Tool (spec name) Scope LLeMbas CLI LLeMbas Differs
ask_user shared tool/question.ts; card tui/components/question.tsx ask_user in services/tools.py, services/interaction.py LLeMbas's own schema and card; same name
task shared tool/task.ts, app.ts spawn, project/agents.ts subagent_run in services/subagent.py name; LLeMbas's helper always on the parent's model
web_search shared tool/web.ts, search/ web_search in services/tools.py, services/search/ LLeMbas's own description
web_fetch shared tool/web.ts, search/fetch.ts fetch in services/tools.py, services/fetch.py name
memory shared tool/memory.ts, memory/store.ts memory_add, memory_forget in services/tools.py, services/library/memories.py shape
notes_search · note_view · note_manage shared tool/library.ts, library/store.ts notes_search, notes_get, notes_create, notes_edit, notes_delete in services/tools.py, services/library/notes.py names
skills_list · skill_view · skill_manage shared tool/skills.ts, skill/index.ts skill_get, skill_create, skill_edit in services/tools.py, services/library/skills.py; the index is in the prompt names; no list tool
knowledge_search · knowledge_get shared tool/library.ts, library/store.ts services/tools.py, services/library/documents.py, retrieval.py LLeMbas's own descriptions; same names
read · write · list cli tool/read.ts, tool/write.ts, tool/search.ts — by design
edit · multiedit cli tool/edit.ts, tool/multiedit.ts, tool/replace.ts — by design
apply_patch cli tool/apply_patch.ts, tool/patch.ts, tool/unidiff.ts — by design
glob · grep cli tool/search.ts — by design
bash · bash_output · bash_list · bash_kill cli tool/bash.ts, tool/jobs.ts — by design
todo cli tool/todo.ts; tui/components/todos.tsx drawn from the device's events above the composer —
plan_submit cli tool/plan_exit.ts; tui/components/plancard.tsx the plan card for a device chat (capability plan) —
tasks · decisions cli tool/project_tools.ts, project/board.ts — by design
session_search · settings · view_image cli tool/session_search.ts, tool/settings.ts, tool/view_image.ts — by design
Former names tool/names.ts reads aliases.cli and argument_aliases its tools still carry the names in aliases.llembas see Open items
How a call is drawn (block) tui/state.ts, tui/components/messages.tsx services/tool_blocks.py —
LLeMbas only report_*, schedule_*, image_generate, scratch_write, custom HTTP tools, remote MCP web features, by design

Permissions

Concept LLeMbas CLI LLeMbas
Modes manual edit auto plan harness/permission/modes.json; permission/evaluate.ts; config/schema.ts (unrestricted read as auto) services/agent/policy.py (the device's mode is clamped by the device)
Hardline floor, protected paths harness/permission/hardline.json; permission/hardline.ts services/agent/shell.py, services/agent/policy.py refusal
Judging a command line permission/bash.ts, permission/evaluate.ts (each command and its plain spelling) services/agent/shell.py
"Always allow" harness/permission/arity.json; permission/arity.ts, evaluate.ts services/agent/policy.py always_patterns
Default rules harness/permission/defaults.json; DEFAULT_RULES in evaluate.ts pending (see the permission area)
Approving once, edit then once, session, project, deny, deny with a reason — tui/components/permission.tsx; over ACP session/request_permission the approval card on a device chat; first answer on either side wins
Device limits the global remote: block → Limits in acp/agent.ts shown, never set

Loop, providers, prompts

Concept LLeMbas CLI LLeMbas Differs
The loop: step backstop, budgets, wrap-up, parallel calls harness/loop.json; session/engine.ts services/generation.py (_run, _wrap_up), services/settings_store.py budgets unset by default in the CLI
Capacity rule session/capacity.ts services/subagent.py, the connection's flags (db/models/connection.py) —
Dialects provider/openai-chat.ts, responses.ts, anthropic.ts, gemini.ts, ollama.ts services/llm/openai_client.py, anthropic_client.py LLeMbas: OpenAI chat and Anthropic, by design
Quirks of real servers harness/quirks.json; provider/common.ts, sse.ts, openai-chat.ts services/llm/openai_client.py, services/generation.py —
Reasoning effort provider/effort.ts, provider/learned.ts services/chat.py, services/generation.py —
Model metadata harness/models/schema.json; config/schema.ts, lembas/webui.ts reads it from /v1/models served at /v1/models (api/v1/openai.py) from the model rows —
Prompt texts harness/prompts/ (index.json: which are shared), prompt/assemble.ts services/prompts.py fragments, services/harness.py see below
tasks/continue.md session/commands.ts, session/engine.ts harness_spec.prompt("tasks/continue.md") in api/chats.py, services/generation.py, web/templating.py identical
Personality presets harness/prompts/personality/; prompt/personality.ts services/personalization.py (reads the vendored presets) identical
Compaction prune, then summarise, prompt put back (session/engine.ts) summary as a user + assistant turn, turns kept collapsed (services/compaction.py) shape
Library: chunking, search, fusion library/chunks.ts, library/store.ts services/library/chunks.py, retrieval.py —

Prompt texts. harness/prompts/index.json records, for each shared text, which LLeMbas fragment it is and whether LLeMbas's default is word for word the same. Identical today: tasks/continue.md and the six personality/*.md. Shared but still differing in LLeMbas: system/default.md (LLeMbas's core.* fragments are separate, editable pieces), family/*.md, modes/*.md, blocks/memory.md, blocks/skills.md, tasks/compact.md. The CLI's alone: system/identity.md, blocks/skills-empty.md, blocks/agents-*.md, tasks/review.md, tasks/changelog.md, tasks/init.md.

Connecting

Concept LLeMbas CLI LLeMbas
The protocol, written down harness/acp.md, checked against the code by tests/harness.test.ts vendored with the spec
ACP role agent — acp/agent.ts client — services/device_link.py
JSON-RPC framing acp/rpc.ts device_link.Link
The link acp/link.ts (dials out), service.ts api/devices.py device_link_socket (/api/devices/link)
_lembas protocol versions acp/agent.ts LEMBAS_PROTOCOL = 2; says 2 only to an instance whose discovery lists it device_link.PROTOCOL = 2, PROTOCOLS = (1, 2), ACP_VERSION = 1
Discovery lembas/client.ts reads protocols, at login and every dial /.well-known/lembas.json: protocol: 1, protocols: [1, 2]
Capabilities (files, commands, attachments, ask, plan, model, turns, shell) offered in initialize's _meta.lembas.capabilities used only when listed
One session, both sides acp/hub.ts, acp/share.ts announced sessions become chats; Chat.device_session_id
Turn ids acp/turns.ts messages keep the turn id; reattach asks _lembas/session/status
A web prompt made into a typed one acp/prompt.ts (/name via session/commands.ts, @path via project/attach.ts) the composer's @ and / lists, uploads as image / resource blocks
Deletes not yet acknowledged acp/pending.ts kept and resent until answered
Terminal shell marks acp/shellrc.ts (bash, zsh, fish) services/agent/terminal.py, services/agent/shell_marks.py (OSC 133 / OSC 7)
Device login (RFC 8628) lembas/client.ts, lembas/login.ts services/devices.py, api/devices.py (/device)
The account's models lembas/webui.ts, provider/refs.ts (<provider>/<model>) api/v1/openai.py
Personalization and usage lembas/personal.ts api/v1/me.py (/v1/me/personalization, /v1/usage)
Library over MCP library: lembas → mcp/index.ts LIBRARY_SERVER api/mcp_server.py (/mcp)
Search, fetch and speech through the instance search/webui.ts, voice/speech.ts api/v1/services.py

Open items

  1. Shared tools still in LLeMbas's own shape — ask_user (own schema), task (subagent_run), web_fetch (fetch), and the library tools (memory_*, notes_*, skill_*, no skills_list). tests/test_harness_spec.py names each in TOOLS_PENDING.
  2. The permission conformance area — LLeMbas's mode table is its own; the spec's modes.json and default rules are to be adopted. Commands themselves only ever run on a linked CLI, under the CLI's evaluator and the device's limits, so this affects what LLeMbas shows, not what runs.
  3. Prompt texts — beyond the seven identical ones, the shared texts differ from LLeMbas's fragments by what only a coding agent needs; a shared base with variables, as the tool descriptions have, is the next step.

Known gaps, kept deliberately

  • Dialects in LLeMbas. OpenAI chat and Anthropic cover its endpoints; the other three are not required for parity.
  • Background jobs, snapshots and undo are the CLI's: they happen on the machine where the work is.