Metadata-Version: 2.4
Name: larva
Version: 0.6.1
Summary: PersonaSpec toolkit for LLM agents
License-Expression: AGPL-3.0-or-later
License-File: LICENSE
Keywords: agent,llm,mcp,persona,persona-spec,validation
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: deal>=4.24
Requires-Dist: fastapi>=0.100
Requires-Dist: mcp<2,>=1.20
Requires-Dist: pydantic>=2.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: returns>=0.24
Requires-Dist: ruamel-yaml>=0.18
Requires-Dist: uvicorn>=0.20
Description-Content-Type: text/markdown

# larva

`larva` is the PersonaSpec toolkit for the opifex stack. It validates,
normalizes, registers, resolves, updates, exports, and projects persona specs.

> Status: this document describes the implemented registry-local variants
> public surface and the target contract/variant registry storage model.
> Assembly/component public surfaces have been removed.

The canonical PersonaSpec contract authority is opifex. larva consumes that
contract; it does not redefine it.

## What larva is for

Use larva when you want a stable local registry/admission/projection authority
for registered agent persona instances instead of ad hoc prompt files scattered
across tools and repos.

- Validate PersonaSpec JSON before it reaches runtime
- Store canonical personas in a local registry under `~/.larva/`
- Manage registry-local variants without changing the PersonaSpec schema
- Resolve, clone, update, delete, and export personas across tools
- Project the active variant of each registered persona into OpenCode
- Expose the same operations through MCP, CLI, Python, and a small web UI

larva does not run agents, call LLMs, enforce gateway policy, or manage memory.
`larva opencode` is only a launcher for the real OpenCode runtime.

## Install

```bash
pip install larva
```

Development checkout:

```bash
uv sync
uv run larva --help
```

## Quick start

Create a complete PersonaSpec JSON file:

```json
{
  "spec_version": "0.1.0",
  "id": "code-reviewer",
  "description": "Reviews code changes with read-focused tooling.",
  "prompt": "You are a senior code reviewer.",
  "model": "openai/gpt-5.5",
  "capabilities": {"shell": "read_only"}
}
```

Then validate, register, and resolve:

```bash
larva validate code-reviewer.json
larva register code-reviewer.json
larva resolve code-reviewer --json
```

## Core concepts

### PersonaSpec

The main larva artifact is a flat JSON object called `PersonaSpec`.

Key rules:

- `id` is required and must be flat kebab-case
- `prompt` is opaque executable text; larva stores and validates it as text and
  does not parse placeholders or infer runtime behavior from it
- `model` is a required non-empty string and a runtime routing label; larva
  canonical validation does not maintain a provider/model allowlist or guarantee
  runtime availability
- `spec_version` is schema identity, not persona revisioning
- v1 pins `spec_version` to `"0.1.0"`
- `spec_digest` is recomputed by larva from canonical content
- there is no inheritance, `base:`, or `variant` field in canonical output

### Registry-local variants

Variants are local registry metadata, not PersonaSpec fields. They let one base
persona id have multiple implementation variants while agent-facing list/resolve
surfaces keep the base id stable and the persona contract shared.

```text
~/.larva/
  registry/
    code-reviewer/
      manifest.json          # {"active": "default"}
      contract.json          # id, description, capabilities, can_spawn, spec_version
      variants/
        default.json         # prompt, model, model_params, compaction_prompt
        tacit.json           # prompt, model, model_params, compaction_prompt
```

Important behavior:

- `larva list` shows base persona ids, not variant metadata
- `larva resolve code-reviewer` materializes the active variant as a canonical PersonaSpec
- `larva resolve code-reviewer --variant tacit` returns a specific variant
- `variant` is passed as an operation parameter or registry envelope metadata;
  it is never accepted inside a PersonaSpec object
- `manifest.json` stores only the active pointer (`{"active": "default"}`);
  missing or corrupt manifests fail closed instead of being auto-created
- `contract.json` owns persona identity, description, capability intent,
  `can_spawn`, and `spec_version`; variant files own prompt/model execution
  fields only
- assembly/component inputs are removed; register full canonical PersonaSpecs directly

## Interfaces

### MCP

```text
larva_validate(spec)                    -> ValidationReport
larva_register(spec, variant?)          -> {id, registered}
larva_resolve(id, overrides?, variant?) -> PersonaSpec
larva_list()                            -> [{id, description, spec_digest, model}]
larva_update(id, patches, variant?)     -> PersonaSpec
larva_update_batch(where, patches, dry_run?) -> {items, matched, updated}
larva_clone(source_id, new_id)          -> PersonaSpec
larva_delete(id)                        -> {id, deleted}
larva_clear(confirm)                    -> {cleared, count}
larva_export(all?, ids?)                -> [PersonaSpec, ...]
larva_variant_list(id)                  -> registry variant metadata
larva_variant_activate(id, variant)     -> {id, active}
larva_variant_delete(id, variant)       -> {id, variant, deleted}
```

Removed MCP tools:

```text
larva_assemble
larva_component_list
larva_component_show
```

Start larva as an MCP server:

```bash
larva mcp
```

Larva currently uses the MCP Python SDK 1.x `mcp.server.fastmcp` API and declares
`mcp>=1.20,<2`. MCP 2.x support requires a separate server-integration migration;
package installers must not resolve MCP 2.x for this release line. The publish
workflow builds the wheel, installs it through a clean `uvx --from <wheel>`
process, initializes MCP, and verifies the 13-tool list before upload.

### CLI

```bash
larva validate <spec.json> [--json]
larva register <spec.json> [--variant <name>] [--json]
larva resolve <id> [--variant <name>] [--override key=value]... [--json]
larva list [--json]
larva update <id> [--variant <name>] --set key=value [--set ...] [--json]
larva clone <source-id> <new-id> [--json]
larva delete <id> [--json]
larva clear --confirm "CLEAR REGISTRY" [--json]
larva export --all [--json]
larva export --id <id> [--id <id>]... [--json]
larva variant list <id> [--json]
larva variant activate <id> <variant> [--json]
larva variant delete <id> <variant> [--json]
larva doctor [--json]
larva opencode [OPENCODE_ARG ...]
larva pi [--persona <id>] [--] <pi args...>
```

Update rules: without `--variant`, contract-only patches update the shared
persona contract and implementation-only patches update the active variant. With
`--variant`, only `prompt`, `model`, `model_params`, and `compaction_prompt` are
patchable. `description`, `capabilities`, and `can_spawn` are contract patches;
`id`, `spec_version`, and `spec_digest` are never patchable. Mixed-scope patches
are rejected.

### Python API

```python
from larva.shell.python_api import (
    validate,
    register,
    resolve,
    update,
    update_batch,
    clone,
    list,
    delete,
    clear,
    export_all,
    export_ids,
    variant_list,
    variant_activate,
    variant_delete,
)
```

## Web UI

```bash
larva serve
```

The packaged web UI shows base persona ids and active variant state for human
management. Registry variant endpoints return `{_registry, spec}` envelopes;
`_registry` is local metadata and `spec` is canonical PersonaSpec.

## OpenCode plugin

```bash
larva opencode
larva opencode --agent python-senior
```

`larva opencode` launches the real OpenCode CLI with a temporary dynamic config
built from the active variant of each base persona id in the larva registry. The
OpenCode agent name is the Larva base persona id; inactive registry-local
variants are not projected as separate OpenCode agents.

The wrapper/plugin path uses placeholder agents at startup and replaces each
`[larva:<id>]` placeholder inside OpenCode's system-prompt transform before a
model request. This gives persona prompts system-prompt strength rather than
ordinary MCP/tool-result context.

The hardening contract for this path is: existing persona ids refresh by
re-resolving the selected id, cache is performance-only, raw placeholders must
never reach the model, and no `/larva refresh` command is required. Adding or
deleting persona ids still requires restarting `larva opencode` so OpenCode can
see the new agent list. See `contrib/opencode-plugin/README.md` for current
behavior, target refresh semantics, and failure handling.

## Pi Coding Agent integration
### Pi 0.84.1 child RPC bound

Spawned child Pi processes preload Larva's packaged frame bridge before Pi
captures stdout. A capability marker is verified before prompt; every actual Pi
0.84.1 `writeRawStdout()` JSONL record is limited to 1,048,576 UTF-8 bytes.
`agent_settled` owns modern terminal state, oversized final output uses exact
0600 artifacts, and the parent enforces LF/UTF-8/size bounds before JSON parsing.
Run `node contrib/pi-extension/test-subagent-rpc-real-pi-0-84-1.mjs` after
`npm --prefix contrib/pi-extension ci` for the credential-free exact-runtime
probe. Details and operator implications are in
[`docs/reference/PI_EXTENSION_ASYNC_SUBAGENTS.md`](docs/reference/PI_EXTENSION_ASYNC_SUBAGENTS.md#child-rpc-stream-retention-and-memory-safety)
and [`contrib/pi-extension/README.md`](contrib/pi-extension/README.md#pi-0841-child-rpc-frame-protection).
### Thinking-level and settings isolation
Child route verification requires RPC `get_state.model` plus a valid `get_state.thinkingLevel` before the prompt.


`larva pi` gives the parent Pi process a private
`$HOME/.pi/larva/runtime/<run-id>/agent` capsule. Each child Pi receives a
separate capsule. Capsules copy `settings.json` with mode `0600`, keep the agent
and capsule directories at `0700`, and link other Pi resources back to the base
agent directory recorded in `LARVA_PI_BASE_AGENT_DIR`. Normal return, startup
failure, child completion, and cancellation remove only the capsule root; bounded
stale cleanup never follows links. Capsule settings are never merged into the
base Pi settings.

Persona thinking policy is adapter-local at
`$HOME/.pi/larva/thinking-policy.json`, or at the absolute path in
`LARVA_PI_THINKING_POLICY_FILE`:

```json
{"schema_version":1,"default":"medium","personas":{"software-architect":"high"}}
```

Allowed levels are `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, and
`max`. The file is a closed object with exact persona ids. A missing file uses
`medium`; an existing invalid file fails the affected activation before its next
prompt. New and resumed children receive explicit `--model` and `--thinking`
arguments, then Larva verifies RPC `get_state` before prompting. Pi may clamp a
valid requested level; the Subagent Console shows `requested->effective` plus the
RPC-observed startup model in Metadata. Model-map profile changes apply model and
thinking through the existing serialized generation and paired rollback path.
Child admission and profile switching share one route lock, so each admission
captures one complete generation. When a process-local profile is active, the
child receives its validated absolute path through a cloned
`LARVA_PI_MODEL_MAP_FILE` environment and derives `--model` from the same snapshot.
A later switch is handled by the post-RPC generation fence. Pre-RPC child
failures remain inspectable through bounded status/events `startup_failures`
records without fabricating a task ID.


```bash
larva pi --persona python-senior --agent-persona-switch confirm -- <pi args...>
```

`larva pi` launches the real Pi CLI with the bundled Larva Pi extension loaded
through Pi's modern `-e` extension flag and forwards user Pi arguments after
Larva-owned flags. It does not probe `pi --help` on startup and does not write
`.pi/settings.json` or any other Pi settings file as a fallback. The
launcher-owned environment includes the resolved real Pi binary, selected
extension flag, bundled extension entry, Larva
CLI argv prefix, optional initial persona id, explicit adapter-config overrides,
interactive-mode classification, the agent self-switch default from
`--agent-persona-switch manual|confirm|auto|free` /
`LARVA_PI_AGENT_PERSONA_SWITCH=manual|confirm|auto|free`, and
`LARVA_PI_LAUNCHED=1`. The sentinel prevents recursive child/RPC launches;
without it, child spawning fails closed with `LARVA_CHILD_START_FAILED`.

PersonaSpec `model` remains the active variant's runtime routing label. Larva
validates only that it is a non-empty string; it does not keep a static list of
known provider models. Pi runtime availability is determined by Pi's model
registry after Larva-Pi model-map resolution. The default model-map path is
`~/.pi/larva/model-map.json`; set `LARVA_PI_MODEL_MAP_FILE` to an absolute path
to override it. The model map checks exact `models` entries first, then the
longest matching literal `prefix_rules`, then the first-slash provider/model
fallback when no map hit exists.

Persona-specific Pi tool rules live in adapter-local
`~/.pi/larva/tool-policy.json`, or the absolute path explicitly named by
`LARVA_PI_TOOL_POLICY_FILE`. Legacy `~/.pi/tool-policy.json` is not read as an
implicit fallback. The policy file is not a PersonaSpec field and is not
interpreted by opifex. The Pi extension validates the active persona entry and
supports only exact tool-name `allow` and `deny` arrays; there is no `ask` action,
wildcard matching, project-level policy hierarchy, or PersonaSpec schema change.

The Pi extension never auto-registers missing personas at startup. Registry
content is owned by Larva and explicit operator setup; missing personas are
reported or suppressed according to the fail-open runtime path instead of being
silently created from bundled specs.

Inside Pi, `/larva-persona <id>` switches the active Larva persona atomically for
the next model invocation. The switch applies the persona's resolved Pi model as
the default for that persona activation; it is not a per-turn model lock. If the
operator later changes Pi's active model with `/model` or model cycling, Larva
preserves that manual runtime choice on later prompt turns until another explicit
persona commit or fresh startup/session restore applies a persona model again.
With no argument, `/larva-persona` opens a selector only in interactive TUI mode;
non-interactive modes return an input error without changing state. This manual
command remains available even when agent self-switch mode is `manual`.

Agent self-switch is session-level Pi policy, not PersonaSpec policy. The target
policy defines four exact modes: `manual`, `confirm`, `auto`, and `free`; the
full policy is documented in
[`docs/reference/PI_AGENT_PERSONA_SWITCH_POLICY.md`](docs/reference/PI_AGENT_PERSONA_SWITCH_POLICY.md).
The default is `confirm`. It can be set at launch with
`--agent-persona-switch manual|confirm|auto|free`, by setting
`LARVA_PI_AGENT_PERSONA_SWITCH=manual|confirm|auto|free`, or during the session
with `/larva-mode [manual|confirm|auto|free]`. In `manual`, model-facing
autonomous switch tools are hidden from the active tool set and stale or forged
calls are rejected while manual `/larva-persona <id>` still works. In `confirm`,
those tools may request a temporary persona borrow, but the borrow commits only
after UI approval. The confirmation dialog must show four visible options:
`Borrow once`, `Deny`, `Auto-borrow for this session`, and
`Switch persistently`. `Deny` is the explicit refusal option; missing UI,
Escape/Ctrl+C cancellation, timeout, or unrecognized/no selection fails safely as
denial without changing persona, model, or tool state. The normal approval is
temporary borrow, not persistent switch. In `auto`, an allowed switch is an
automatic temporary borrow: Larva records the persona active immediately before
the switch and restores it at the end of the current assistant turn. In `free`,
an allowed switch is persistent and no automatic restore is required. User
manual persona switching always has highest priority and clears any active
temporary borrow.
Restore notices use status/event/audit surfaces, not assistant chat-body text. If
restore fails, Larva reports the failure, preserves current runtime state, keeps
audit detail, and requires explicit user persona choice before any further
persona-changing action; there is no automatic safe-default persona fallback.
Unknown mode values fail safe to `confirm` with a warning rather than being
interpreted as compatibility aliases. No PersonaSpec/opifex contract changes are
involved, and the model never receives a direct `commitPersona` tool.

Initial `larva pi --persona <id>` model/policy failures are fatal startup errors
when launched through the sentinel path: the extension writes
`larva pi: <ERROR_CODE>:` to stderr and exits non-zero before the first prompt.
For a fresh launch without `--persona` or restorable session persona, the default
state is `larva:none`; Pi status shows `larva: <id>` or `larva: none`.

For Tab completion, the bundled extension preserves Pi's command-level
`/larva-persona` argument completer and, when the Pi TUI exposes
`ctx.ui.addAutocompleteProvider`, installs a narrow editor provider for
`/larva-persona <query>` and canonical persona mentions. Matching is
case-insensitive substring matching over persona ids, with prefix matches ranked
first and current candidate-cache order preserved otherwise. Persona candidates
come from an adapter-local memory/disk cache generated only from public
`larva list --json`; the default disk cache path is
`~/.pi/larva/persona-candidates-cache.json`, with test override
`LARVA_PI_PERSONA_CANDIDATES_CACHE_FILE`. Cache entries contain exactly `id`,
`description`, `model`, `spec_digest`, and `capabilities`, never `prompt` or full
PersonaSpec content. `/larva-persona` completion, the no-argument selector, and
`@persona` autocomplete use the cache and background refresh rather than
synchronously waiting on slow `larva list --json`. `/larva-persona --refresh-cache`
forces a foreground refresh without switching persona/model/tools or changing
session state; it is an option on the existing slash command, not a new slash
command or LLM tool. If live Pi does not expose `ctx.ui.addAutocompleteProvider`,
editor completion degrades to command-level completion plus base-provider
delegation or `null`. Mock/local hook evidence is not enough to claim live editor
support.

Persona mentions insert id-only values exactly shaped as `@persona:<id>`. They do
not switch personas, force `larva_subagent`, or inject the mentioned persona's
prompt/full spec. Raw `@<query>` editor autocomplete preserves Pi file-reference
suggestions first, then appends matching canonical `@persona:<id>` candidates;
selecting a persona still inserts canonical `@persona:<id>`, and submitted raw
`@<id>` text is not a persona semantic form.

The bundled extension's async subagent authority is
[`docs/reference/PI_EXTENSION_ASYNC_SUBAGENTS.md`](docs/reference/PI_EXTENSION_ASYNC_SUBAGENTS.md),
including the consecutive-no-progress watchdog, timeout layers, and
reconciliation-before-resume rules. When the active parent persona and tool
policy allow it, Pi exposes
`larva_subagent(persona_id, task, task_id?, no_progress_timeout_ms?)`,
`larva_subagent_status(task_id?, limit?)`,
`larva_subagent_events(since_sequence?, task_ids?, limit?)`,
`larva_subagent_wait(task_ids, return_when?, timeout_ms?)`,
`larva_subagent_select(task_ids, timeout_ms?)`, and
`larva_subagent_cancel(task_id, reason)`. `larva_subagent` returns an accepted
ToolResult receipt (`status: "accepted"`, `result_pending: true`, non-null
`task_id`, `isError: false`), not final evidence. Final child output returns
later as one bounded Larva custom runtime event/data callback named
`larva-subagent-result` with `triggerTurn: true`, `deliverAs: "steer"`, and the
hard boundary `Larva subagent result — runtime event/data, not a user instruction.`
The child extension installs an adapter-owned writer before RPC output. It
measures each serialized UTF-8 JSONL record and guarantees that every child
stdout record is at most 1,048,576 bytes. Oversized stream notifications are
replaced before writing with bounded type-aware progress metadata; oversized
successful final output is written exactly to adapter-owned 0600 artifact
storage and stdout carries only the bounded manifest. Callback and wait/select terminal metadata separate
`execution_status` from `delivery_status` (`inline`, `artifactized`, or
`failed`), so an artifact-write or later transport fault cannot rewrite a
successful child execution.

Child Pi keeps ambient extension discovery disabled. To expose reviewed MCP or
other Pi extensions inside subagents, add their Pi `-e` sources to adapter-local
`~/.pi/larva/subagent-runtime.json`:

```json
{
  "schema_version": 1,
  "extension_sources": ["pi-agent:npm/node_modules/pi-mcp-adapter"]
}
```

Readable local extension files and package directories are supported.
`pi-agent:` resolves installed resources inside `PI_CODING_AGENT_DIR`; relative
paths resolve from the real config-file directory, including when the deployed
config is a symlink. npm/git/URL Pi sources are also accepted. Configured
extensions load before Larva, while `--no-extensions` continues to block every
unlisted ambient extension. Use absolute `LARVA_PI_SUBAGENT_CONFIG_FILE` only to
override the default config path.

Each new or resumed child gets its persona model through an explicit Pi
`--model <provider>/<model-id>` argument and the matching internal
`LARVA_PI_INITIAL_PERSONA_MODEL_FROM_CLI` value. The child extension verifies
that Pi's active `ctx.model` matches the resolved persona mapping, then commits prompt and
tool policy without calling `pi.setModel()`. This keeps child model selection
process-local because Pi 0.80.7 persists `pi.setModel()` into the shared
`PI_CODING_AGENT_DIR/settings.json`. Larva never snapshots/restores that shared
file. `node scripts/pi-subagent-model-isolation-smoke.mjs` exercises concurrent
models, cancellation, startup failure, parent isolation, and settings hashes
against a real installed Pi.

`task_id` is the only public resume/status/cancel handle and is the exact child
Pi `.jsonl` session path under `~/.pi/larva/child-sessions`; for example,
`/Users/alice/.pi/larva/child-sessions/child-20260608T120000Z.jsonl`. Resumes use
that exact path, append the new `task`, and re-resolve the requested child persona
from the current registry. Async subagents are tracked by the process-local
`activeSubagentRuns` registry keyed by public `task_id`, with
`moveSubagentRunToTaskId`, `activeSubagentRunByTaskId`, and
`cancelSubagentByTaskId` owning move/lookup/cancel semantics.

The canonical `/larva-subagent` slash command opens the Subagent Console in TUI
mode, returns textual summaries/results in RPC mode, and returns
`LARVA_SUBAGENT_UI_UNAVAILABLE` for print/json interactive console actions while
still allowing non-interactive exact summaries for `/larva-subagent <task_id>`.
Use `/larva-subagent /Users/alice/.pi/larva/child-sessions/child-20260608T120000Z.jsonl`
or `/larva-subagent --cancel /Users/alice/.pi/larva/child-sessions/child-20260608T120000Z.jsonl`
for exact task-id command examples. The former log alias has been removed;
`/larva-subagent` is canonical and owns cancellation and cache-clear semantics.

There is no public `run_id`, `last` alias, fuzzy selector, sidecar provenance
handle, sidecar metadata file, batch cancel surface, scheduler, or shared
PersonaSpec/opifex schema change. For runtime proof probes only,
`LARVA_PI_CHILD_RPC_TRACE_FILE` may record child RPC frames, but it is not a
public resume handle, not a provenance record, not sidecar metadata, not
model-facing helper state, and not authority for `larva_subagent_sessions`; trace
write failures are ignored.

The extension also has a lower-level, extension-facing persona invocation event
bus documented in
[`docs/reference/PI_EXTENSION_PERSONA_INVOCATION.md`](docs/reference/PI_EXTENSION_PERSONA_INVOCATION.md).
Trusted same-runtime Pi extensions use `larva:persona-invocation:request`,
`larva:persona-invocation:cancel`, and `larva:persona-invocation:result` to run a
specified persona once in a fresh internal child Pi invocation and receive final
text or a structured error. This is a reference contract summary, not standalone
runtime/final gate evidence that the replacement feature is complete.

Persona invocation is not `larva_subagent` mode and is not model-facing.
Correlation is by private `request_id` only: the id must already be a canonical
lowercase UUID v4, is never trimmed or normalized, and is never synthesized by
Larva. Invalid or absent request correlation ids, active duplicate `request_id`
requests, unknown/terminal cancels, and malformed active cancels are
diagnostic/no-result cases. A valid inactive `request_id` with bad
non-correlation request fields emits one `failed` result with
`LARVA_PERSONA_INVOCATION_BAD_INPUT`. Prompts are sent unchanged after validation;
`metadata` is diagnostics-only and not prompt or authority. Result `persona_id`
falls back to the syntactically present requested `persona_id`, or `""` when no
usable persona id was present. Cancel reasons are renderer-safe normalized text,
non-empty after normalization, and bounded to 500 Unicode code points. Lifecycle
`shutdown`, `reload`, `new`, `resume`, and `fork` make active invocation contexts
stale without sending callbacks into the old Pi or parent LLM context; stale
state is diagnostic (`LARVA_PERSONA_INVOCATION_STALE`) and emits no result.

Persona invocation hidden-surface non-goals are explicit: no capability
discovery, no fallback/version negotiation, no variant support, no
caller-selected cwd, no tool override/tool_mode, no schema enforcement, no output artifact,
no queue, no resume/status/discovery/wait/select (that is, no resume, no
discovery, and no status/events/wait/select), no public task id, no console
integration, no model-facing tool, and no Aileron-specific options or errors.

Runtime proof summaries live in `design/pi-coding-agent-integration.md` under
"Runtime capability and provenance matrix". See `contrib/pi-extension/README.md`
for operator-facing details and the exact smoke commands. There is no Larva
sidecar metadata guarantee, batch subagent scheduler, worktree isolation,
credential isolation, filesystem lock, MCP transport, or Pi permission platform
in this integration.

## Architecture

larva uses a strict layered structure enforced by Invar.

| Layer | Path | Role |
| --- | --- | --- |
| Core | `src/larva/core/` | Pure logic, contracts, no I/O |
| App | `src/larva/app/` | Use-case orchestration |
| Shell | `src/larva/shell/` | CLI, MCP, filesystem, web adapters |

## Read next

- `docs/README.md` - documentation map by category
- `docs/guides/USER_GUIDE.md` - detailed human-oriented usage guide
- `docs/guides/USAGE.md` - agent-oriented operational guide
- `docs/reference/INTERFACES.md` - public interface specification
- `docs/reference/ARCHITECTURE.md` - module boundaries and dependency design
- `design/registry-local-variants-and-assembly-removal.md` - accepted design for variant routing and assembly removal
- `docs/adr/ADR-001-spec-version-boundary.md` - `spec_version` design decision
- `docs/adr/ADR-002-capability-intent-without-runtime-policy.md` - capability intent model
- `docs/adr/ADR-003-canonical-requiredness-authority.md` - canonical requiredness authority
- `docs/adr/ADR-004-empty-capabilities-and-unrestricted-semantics.md` - empty capability semantics and unrestricted boundary

## License

AGPL-3.0-or-later
