Mova0.6 pre-release⌕
☰
home / docs / tools / Crab's deputy planner (mova-octopus)

Crab's deputy planner (mova-octopus)

The Octopus itself is a large language model (LLM); this crate is the Rust planner that lets its deputy, Crab, understand the Octopus's English. The overview of how the animals talk is in Animals and workers; this README holds the protocol, configuration and handler detail.

Crab is the deputy. The crab binary of this crate is the public Crab angel with this planner installed as its understanding:

crab serve CRAB_CONFIG DEPUTY_CONFIG [COMMAND_CORPUS]
crab ask SOCKET REQUEST_ID [urgent] < any-text
crab exam DEPUTY_CONFIG CORPUS...
crab review-queue OBSERVED [REVIEWED...]
crab promote OBSERVED DECISIONS REVIEWED
crab exercises DEPUTY_CONFIG NEW_OUT REVIEWED...
crab candidates REVIEW_QUEUE
crab audit CORPUS

The caller supplies only arbitrary text and an optional priority; Crab decides what to execute, refuses the rest with a review candidate, and never lets a prompt interrupt running work or acquire control operations. The standalone mova-octopus serve|angel transport below remains available but is not deployed.

The deputy reads ordinary, unmarked English from stdin. It invokes Mova's existing English annotator and world tree reader, records their real UD nodes and deterministic question frame, and proposes a bounded concrete plan against externally configured capabilities. No task label, command grammar, JSON envelope or large model is required in the prompt.

cargo build --manifest-path tools/octopus/Cargo.toml --locked --offline -j2
mova-octopus plan "$OCTOPUS_CONFIG" development-001 < request.txt
mova-octopus run "$OCTOPUS_CONFIG" invocation-001 < request.txt
mova-octopus angel "$OCTOPUS_CONFIG" "$PRIVATE_DIR/deputy.sock"     # permanent: restarts the server after failures
mova-octopus serve "$OCTOPUS_CONFIG" "$PRIVATE_DIR/deputy.sock"     # one server run, no restarts
mova-octopus ask "$PRIVATE_DIR/deputy.sock" run invocation-002 < request.txt

serve listens on a Unix socket inside an owned 0700 directory (refused otherwise), creates it 0600 and answers only peers with the owner's uid. A request is one NDJSON line of at most 16384 bytes: {"schema":1,"id":ID,"mode":"plan"|"run","prompt":TEXT,"priority":"urgent"|"normal","from":CALLER} (priority and from optional; from is a caller name such as crab or whale, 1–64 lowercase letters, digits and -). The prompt is arbitrary ordinary text. serve_requests/angel_requests hand the handler the whole request, including the declared caller and the connecting pid (SO_PEERCRED), so an animal can keep an allowlist and journal who asked; the caller name is a declaration by a process of the owner, not authentication. Requests are answered one at a time through the same Deputy::run; waiting requests are ordered urgent-first, then by arrival, and a running request is never interrupted, so a new prompt cannot destroy unfinished earlier work. STOP in the socket directory ends the server after the current answer. angel holds a lease, restarts the server after a load failure, error or panic with a 1–60 s doubling backoff, records angel-status.json, and stops only on STOP; a panicking handler fails only its own request. A prompt the deputy cannot understand is an explicit refusal with a review candidate: material for teaching Mova, not for a workaround.

Deputy::load refuses to start while WORLD_NO_REPAIR is set: the native repair override is not admitted in this profile. Configuration pins an existing syntax model by SHA256, supplies capability IDs, entity aliases, lexical intent rules, installed handler names and trusted handler parameters. It also supplies ledger and review queue directories. Concrete assignments, deployment paths, sockets, credentials and private logs belong outside public Mova. The public engine has no fixed operator identity or task list.

Each capability contains handler, parameters and intent. The intent has request_predicates, observations, entities, and optional subordinate_predicates lexical lists. These are developer configuration over native analyses, not a language that the user must write. The planner records matched observations/entities and zero-based native request-head and observation-node witnesses. Native UD ancestry must connect a request head with an observation. This bounded skeleton resolver is experimental and does not establish general task understanding.

Public installed adapters currently provide bounded read-only Crab observations (crab_observe, trusted socket and status/read_reports operation) and trusted regular-log diagnosis (log_tail, trusted path). Rust applications can register further configured CapabilityHandler implementations through Deputy::with_handler; tasks need not rewrite the upstream planner. A new handler needs its own admission, semantics and tests. The existing executable has these two adapters; it does not execute arbitrary shell, write code, annotate data or train models.

Asking another animal (relay.rs): the installed handler animal_ask relays the quoted English of a prompt to another animal's private socket and returns that animal's English answer. Its parameters are exactly {"animal": NAME, "socket": ABSOLUTE_PATH, "caller": CALLER} and its one slot is task (kind text, 1..1), checked when the configuration loads; the socket is never taken from a prompt. With entity spider, "Ask Sea Spider: «Is the queue empty?»" sends "Is the queue empty?" as caller crab (request id = the invocation id, so a repeated request returns the recorded answer) and waits at most 30 s. The same client path is the library function relay::ask_animal(socket, caller, id, english, urgent) for animals that hold a configured socket (the Whales). Tests cover configuration refusals and a round trip with a fake animal; removing the socket check turns the configuration test red.

The crab binary (src/bin/crab.rs) embeds the deputy in Crab and installs two in-process handlers: crab_observe (status/read_reports) and crab_control. A crab_control capability has parameters {"operation": "worker_start"|"worker_stop"|"worker_restart"|"worker_status", "worker": NAME}, checked when the configuration loads. The prompt only selects a configured capability; operation and worker come from configuration, and Crab checks the worker name against its own list. Vocabulary is configuration too: for example eel_start (request predicates and observations start, launch; entity eel), eel_restart (restart), eel_stop (stop) and eel_status (observation predicates plus status, run, work). With such a configuration "Start Eel.", "Restart Eel, please.", "Is Eel running?" and "Stop Eel." map to the four operations, while negation, conditionals and unconfigured predicates ("Kill Eel.", "Delete Eel.") are still refused. Outside Crab the handler is not installed and such a plan fails closed.

The reply to ask is compacted to a 48 KiB budget inside Crab's 64 KiB response limit: it keeps the prompt hash, plan, failure, followups and results, but when it is too large each result output is replaced by {"omitted": …} (its output_sha256 stays), and if it is still too large followups becomes {"omitted": true}. The full report stays in the deputy ledger (run mode).

The deputy's question frame

Every observation carries deputy_frame (frame.rs) beside the world reader's native_question_frame: the mood (imperative, modal_request, polar, wh, declarative), the subject node and the verb node, read from the native parse. "Is Crab running normally?" is polar | subj «crab» | verb run; "Could you start Eel?" is modal_request | subj «you» | verb start and is read as the imperative. When a present polar question is parsed with the copula's subject as the root ("Is the documenter running?": root «documenter», «running» acl; "Is Eel busy?"; "Is the Nautilus running?": compound + -ing root), the frame is repaired: the subject noun is not a predicate and the recovered verb or adjective is the clause head the resolver checks. Past questions ("Was Crab running?") are never repaired and stay refused. A frame constrains the request head; it is not a parse-quality verdict.

An ambiguity refusal names its candidates (failure.candidates, also listed in the reason): "Is the Dolphin running?" → "dolphin" names 5 configured entities; say which one; candidates: claude, copilot, documenter, opus, sol. A group alias may cover any set of same-kind entities, for example "nautilus": ["pompilius", "argonaut", …].

Review candidates

The review queue keeps one file per prompt and failure stage, <dedupe_key>.json. The first candidate is kept as written; reproduced_by lists every build (candidate id, source, model, configuration and native-source hashes, reason, times seen) that refused the same prompt at the same stage. Report followups keep their per-build candidate_id. Older one-file-per-build queues are grouped read-only by crab candidates QUEUE. An understood plan whose execution failed is not a review candidate.

Command school corpus

commands.rs keeps the corpus of the command school (commands, the school data). With the optional COMMAND_CORPUS argument, crab serve appends every prompt it reads to that JSONL file, anonymized (absolute paths, e-mail addresses, URLs and long hexadecimal or digit runs become <path>, <email>, <url>, <id>), together with Crab's observed reading. A line may also carry a reviewed expected reading (a plan or a refusal). crab exam DEPUTY_CONFIG CORPUS replays every reviewed line through the planner in plan-only mode (nothing is executed) and prints passed/examined counts by kind, the unreviewed count and the failures. crab audit CORPUS fails if any line would still change under anonymization.

Observed readings are plan, refuse or executed_failed (an understood plan whose execution failed, such as a worker start that met a conflict); an execution failure is never counted as a misunderstanding and cannot be a reviewed expectation.

The loop from observation to exam: crab review-queue OBSERVED REVIEWED… lists the observed texts no reviewed corpus answers yet, with their readings and arrival counts. The reviewer writes decisions as JSONL ({"text", "expected", "privacy_review": "passed", "note"}), and crab promote OBSERVED DECISIONS REVIEWED appends the admitted ones (origin: reviewed-observed) only if the text was really observed, passed privacy review, is anonymized, expects a plan or a refusal, and is not reviewed yet; it prints training_authorized: false — the exam and the school use these entries, training does not. crab exercises DEPUTY_CONFIG NEW_OUT REVIEWED… derives command-school exercises from reviewed single-capability plans: the determiner variant of the name ("Is Eel running?" ↔ "Is the Eel running?", "Start the Sea Turtle." ↔ "Start Sea Turtle.") and the same sentence about every other entity whose capability has the same handler and parameters except the target ("Is the Sea Turtle running?" → turtle-status); a name with a head noun ("the Sol Dolphin") is replaced only within its configured group. Each exercise records derived_from; crab exam takes several corpora and replays them all.

For an illustrative configured deployment, “Have a look at Dolphin’s recent logs and ask Crab whether everything is running normally.” becomes two named capability calls. One calls the existing public Crab Unix protocol; the other examines a bounded trusted log tail. Alias vocabulary and transport destinations are configuration, not role identity in the generic planner.

The complete prompt is retained with its hash, exact model hash, implementation/dependency hashes, native syntax observations, typed plan and bound outputs. Native feature bitsets are decimal strings, preserving u128 values that cannot safely pass through serde_json::Value as numeric u64. The question reader's frame is separate from trained pragmatics: no trained pragmatic engine is invoked, and that gap is explicit. Structural UD admission is not a grammar-quality verdict.

An unsupported predicate, unadmitted tree, unresolved entity, negation, conditional, quotation or ambiguous competing capability prevents the entire execution. A review-only candidate identifies the failure stage without blaming the syntax parser for a valid unsupported action. Candidates are highest priority but are not gold: privacy, held-out admission and semantic review must precede any Fish/Eel or skeleton follow-up. No automatic database insertion or GPU learning occurs.

Candidate IDs hash the serialized array of source, model, prompt and configuration hashes, native source-hash map, repair profile and failure. Repair metadata is separate from source hashes. Repeated identical candidates retain identical bytes; historical artifacts are never silently rebound.

A run reserves its invocation ID durably before adapter calls. A completed repeat returns the previous receipt, not a fresh observation. A conflicting ID is refused. A reservation lacking a receipt remains uncertain and is never automatically replayed. Use a fresh invocation ID for a new observation; preserve the old receipt.

Log diagnosis reads at most 65536 bytes, returns at most 32 lexical error candidates and preserves byte offsets and a tail hash. It establishes no causal explanation or current health. Empty logs are no health evidence. Crab transport success likewise does not prove child health.

The tests cover positive prompts, refusal controls, u128 trace serialization, mocked Unix IPC, caching/conflict/crash uncertainty and bounded logging. Set MOVA_OCTOPUS_TEST_MODEL to an existing native model for the native test; without it the integration test explicitly reports its skip.

Source groupants and a three-node registry fragment are included. The integrating checkout adds the fragment to its authoritative registry and ancestor bindings.

For long logs, the byte window may start inside a UTF-8 character. The diagnostic excludes its first partial line before decoding, preserves the original window hash and reports skipped_prefix_bytes and decoded_byte_start. Invalid UTF-8 inside the admitted suffix still fails. Reads stop at the descriptor length observed before reading; concurrent changes are not an atomic snapshot.

Typed arguments

A capability may declare slots: [{"name": "urls", "kinds": ["url"], "min": 1, "max": 16}], kinds url (absolute http/https URL) and host (DNS name or IPv4 literal), kinds disjoint within a capability, at most 16 arguments per prompt. Only when some capability declares slots (slots.rs), every URL- or host-shaped word is validated strictly (http/https only, no user information, a valid host, port 1–65535, printable ASCII; loopback, private, link-local and reserved names such as localhost, .local, .test refused unless slot_policy.allow_private_hosts) and replaced by a reserved proper-noun placeholder (Zurla, Zhostb, …) before sentence splitting and parsing. An argument is bound only through the native parse: its placeholder must reach the nearest request head of exactly one planned action through argument relations (obj, iobj, obl, nmod, appos, conj, compound, flat, list, dep, nsubj), with no clause boundary (relative, adverbial, complement clause, parataxis) and no exclusion dependent ("except", "instead", "besides", …) on the way, and that action's capability must have a slot of its kind; slot counts must then fit. Otherwise the whole prompt is refused with a review candidate at stage argument_invalid, argument_unbound, argument_ambiguous or argument_count. Bound values appear in the plan as arguments with native_argument_nodes. Configurations without slots read the text unchanged.

Two further optional intent fields: addressee: true makes the first entity alias the meant one when no alias is said (a channel that reaches only one animal, such as Sea Spider), and native_lemma_observations are matched by the native lemma only, never by the dictionary variants of world::ans::lemmas (whose variants of «a» include «can»): for modals such as «can», «may». Defaulted fields are not serialized, so configurations without them keep their hashes.

observes: true declares that a capability only reads state (a status, a count, a stored page, a permission check). A question — a polar or wh frame, or any sentence that ends in a question mark except a modal request ("Could you …?") — may select only such capabilities; an acting capability it would otherwise select is withheld and, if nothing observing remains, the prompt is refused as pragmatic_ambiguity ("Is Eel stopped?", "Did you stop Eel?", "Have the Whales resumed?" never stop or resume anything; "Stop Eel." and "Could you stop Eel?" do). The default is acting, so an undeclared capability refuses questions rather than acting on them. Loading refuses observes on the deputy's own acting operations (crab_control start/stop/restart, crab_consult enqueue, animal_ask); each animal's check_capabilities does the same for its own (Sea Spider fetch, Whale describe/pause/resume, Nautilus draft, Turtle check/gap). A polar frame is read through its subject whether or not the parse needed the frame repair: an observed subject noun admits the predicate ("Is the queue empty?" with «empty» as root).

realize.rs is a small deterministic English realizer for answers (counted nouns, is/are, was/were, has/have, past forms and participles from en::morph::Morph, lists, sentences, «I could not … because …»). socket::serve_prioritized and angel_prioritized pass the request's priority to the handler so an animal can order the work it queues; the transport order is unchanged.

A slot of kind id takes a catalog identifier: a word of lowercase ASCII letters and digits in at least three hyphen-joined parts, with at least one letter and one digit (smith-2024-tree-parsing), at most 128 bytes, masked as Zsluga, Zslugb, …. Identifiers are masked only when some capability declares an id slot, so other configurations read such words as text. A copular wh-question whose root is "what"/"which" ("What is the status of X?") is read through its nominal subject: the subject noun must be a configured observation, as a root predicate would. An imperative that the parser read as one noun phrase ("Show Crab's recent reports." → «show» a compound noun, «reports» the root) is repaired by the deputy frame (frame::repair_imperative): with no verb, auxiliary or subject in the sentence, a first word that is a bare singular base-form noun, a configured request predicate and the head of no determiner, possessor or modifier becomes the imperative verb and the old root its object. The observation keeps the native parse; the frame text says imperative repaired.

A slot of kind text takes a quoted span — «…», “…” or "…" — masked as a whole (Ztexta, …) before anything else, so its words are never read as instructions and URLs inside it stay text; an unclosed or nested quote, an empty span or one over 6000 bytes refuses the prompt (argument_invalid). An argument of any kind introduced by a colon after the request ("Ask the Opus Dolphin: «…»", "Survey this paper: paper-2023-gamma.") is that request's argument however the parser attached it (the 2026-11-05 en.bin reads both as vocative): the word before the placeholder is a colon, only punctuation follows it, nothing excluding hangs from it, and the nearest request head before the colon takes it, if its capability has a slot of that kind (slots::colon_head; the parse is tried first). A span before the request, a colon with no request before it, or words after the span stay with the parse alone. The crab_consult handler (installed by crab) hands such a bound task to Crab's consult queue for the Dolphin named in its trusted parameters ({"operation": "enqueue"|"state", "dolphin": NAME}, or {"operation": "list"}); see Crab's Dolphins. group_aliases maps a collective word to its members' aliases ({"dolphin": ["opus", "claude", "copilot", "documenter"]}): the singular word without a member's alias is refused as pragmatic_ambiguity unless a capability names that word itself as its entity; an intent with collective: true binds its entity only in the plural ("Which Dolphins are running?"). Questions with "which" are read like "what" questions.

Unit tests cover URL/host validation, masking, slot rules and the realizer. Planner behaviour with slots is exercised against the native model by tools/seaspider/tests/english.rs.

This page in Markdown → https://md.mova.elamur.ai/free/en/docs/tools/octopus/