Elamur: **Mova** · [Parse](https://mova.elamur.ai/free/en/parse/) · [Dictionary](https://md.dictionary.elamur.ai/free/en/) · [Translate](https://md.translate.elamur.ai/free/en/) · [Research](https://md.research.elamur.ai/free/en/) · [MCP](https://mcp.elamur.ai/) · [Groupants](https://md.groupants.elamur.ai/free/en/)

[Mova](https://md.mova.elamur.ai/free/en/) · [this page in HTML](https://mova.elamur.ai/free/en/docs/tools/octopus/)

[home](https://md.mova.elamur.ai/free/en/) / [docs](https://md.mova.elamur.ai/free/en/docs/) / [tools](https://md.mova.elamur.ai/free/en/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](https://md.mova.elamur.ai/free/en/docs/repo-docs/animals/#crab-and-its-deputy-planner); 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](https://md.mova.elamur.ai/free/en/docs/tools/crab/#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`.

- [← previous ocrfix](https://md.mova.elamur.ai/free/en/docs/tools/ocrfix/)
- [next → pageviews — Wikipedia page views for every language at once](https://md.mova.elamur.ai/free/en/docs/tools/pageviews/)
- [↑ back to tools](https://md.mova.elamur.ai/free/en/docs/tools/)
