Skip to content

Capability Layer

Last verified against implementation: 2026-07-15

Vita's capability layer is the typed, transport-independent front door to the small set of operations that multiple runtimes actually need. A capability is a named operation such as vita.tasks.create, vita.health.sleep_gate, or vita.telegram.send. It declares its input and output schemas, side-effect class, backing source, examples, and implementation once. Python, the CLI, the internal HTTP API, and the Workshop browser SDK all dispatch through the same scripts.capabilities.execute() function.

The layer does not attempt to wrap every API router or data directory. It exists to promote proven, repeatedly used operations out of ad hoc source imports and direct file access. This keeps the supported vocabulary small enough to atrophy when it stops being useful while giving important mutations a consistent contract.

The current registry contains 55 capabilities across 10 namespaces. The number and schemas are generated from code; use live discovery rather than treating the count in this narrative page as an API contract.

See Workshop for the primary browser consumer, Telegram for topic participation and direct sends, Scheduling for the proactive queue, and Precedent for the task system behind vita.tasks.*.


Why this layer exists

Before the capability layer, the same operation could be reached through a FastAPI route, a direct scripts/ import, a hand-written CLI, or a raw read of data/. Each path could disagree about validation, errors, freshness, and whether a mutation had actually landed. A caller often had to grep the source to learn whether failure meant an exception, -1, an empty result, or a partially written file.

The capability layer addresses that failure class with six decisions:

  1. One execution path. Every live transport calls execute(), so behavior parity is structural rather than a convention between adapters.
  2. Typed boundaries. Pydantic input and output models generate the machine schema and reject known bad inputs, including timezone-naive scheduled datetimes.
  3. Stable, non-throwing results. Capability-level failure is data in a v1 envelope with a stable error.code, not a sentinel or transport exception.
  4. First-class side effects. Every operation is classified as read, write, send, or spawn; transports can refuse unsafe classes before an implementation runs.
  5. Mutation attribution. Executed non-read calls append provenance with the capability, actor, transport, request ID, outcome, and bounded input.
  6. Generated reference. Schemas, namespace reference pages, and MCP-shaped tool declarations are deterministic projections of the registry.

This is intentionally a promoted interface over existing owner modules, not a replacement for them. For example, vita.tasks.create still delegates to Precedent and vita.schedule.enqueue_message still delegates to the proactive queue. The capability wrapper supplies the shared boundary; the domain module continues to own its storage and business logic.


Architecture

                         declaration plane

 scripts/capabilities/namespaces/*.py
   @capability(name, models, side_effect, source, examples)
                              β”‚ import-time registration
                              β–Ό
                    CapabilityDef registry
                              β”‚
          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
          β”‚                   β”‚                     β”‚
          β–Ό                   β–Ό                     β–Ό
   discovery + spec      generated reference    MCP-shaped schemas
   GET /capabilities     docs/api/capabilities/  mcp-tools.json
   GET /spec             spec.json + *.md        (server not mounted)


                          execution plane

 Python       CLI         internal HTTP        Workshop JavaScript
   β”‚           β”‚                β”‚                       β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                              β–Ό
             scripts.capabilities.execute(name, input, context)
                              β”‚
       resolve β†’ transport policy β†’ idempotency β†’ validate input
                              β”‚
                              β–Ό
                    existing owner implementation
                              β”‚
                              β–Ό
             v1 envelope + usage counter + mutation provenance
                              β”‚
                     successful mutation only
                              β–Ό
                       idempotency cache

There are two important separations in this design:

  • The registry and codec describe what can be called. They are pure enough to generate stable, diffable artifacts without timestamps.
  • The executor decides whether a call may run and normalizes its result. Transport adapters remain thin and do not reimplement domain behavior.

Registry shape

scripts/capabilities/core.py defines CapabilityDef and the @capability(...) decorator. Every definition supplies:

Field Meaning
name Stable vita.<namespace>.<verb> name; lowercase letters and underscores only
description Human and generated-reference description
side_effect One of read, write, send, or spawn
input_model Pydantic model used to validate input and generate JSON Schema
output_model Declared output schema
impl Python callable receiving the validated input and a Context
source Human-readable backing-store or service attribution
examples Example input objects; contract tests validate them against the input model

Namespace membership is explicit in core.py::NAMESPACE_MODULES; it is not a directory glob. load() imports those modules once, and registration occurs at import time. Explicit membership makes deployment order and the supported surface reviewable. Duplicate names and names outside the required pattern fail registration.

The canonical import is scripts.capabilities. The package aliases the bare capabilities module name to the same module object because both repository root and scripts/ can be on PYTHONPATH; this avoids accidentally creating two in-memory registries in one process.


Execution lifecycle

scripts/capabilities/execute.py::execute() is the only execution path. A call passes through these stages in order:

  1. Load and resolve. Explicit namespace modules are loaded and the name is resolved. A missing name returns unknown_capability.
  2. Normalize transport. Known transports are python, http, js, cli, mcp, and the reserved public class. An unknown value becomes js, the most restricted live transport.
  3. Enforce transport policy. Browser/js calls cannot invoke send or spawn. The reserved public class permits reads only. A refusal returns transport_forbidden before input validation or implementation execution.
  4. Check replay protection. A non-read call with an idempotency key checks the 48-hour cache. A matching entry returns the original successful envelope with meta.idempotent_replay: true; a key used by another capability returns idempotency_reuse_mismatch.
  5. Validate input. input_model.model_validate() converts the raw object into the declared model. Pydantic errors become invalid_input, with the structured validation list in error.details.errors.
  6. Run the implementation. The wrapper calls the existing domain owner with the typed input and a Context containing actor, transport, request ID, optional Workshop page, and optional idempotency key.
  7. Build the envelope. A BaseModel result is serialized in JSON mode; a raw result passes through. A CapabilityError maps to its declared stable code. Selected upstream exception classes map to upstream_unavailable/admission_rejected; any other implementation exception becomes internal_error and is logged.
  8. Record gauges and attribution. Calls that reached the implementation attempt update the daily usage counter. Every executed mutation, whether it succeeded or failed, appends a provenance line. Reads do not append provenance.
  9. Cache successful mutations. A successful non-read call with an idempotency key stores its complete envelope for later replay. Failed calls are not cached.

Instrumentation failures are deliberately non-fatal to the domain result: usage, provenance, and idempotency-store errors are logged, but do not replace an otherwise valid capability response. This preserves availability, although it also means the ledgers are observability aids rather than a transactional commit record.

Freshness context

The Context lets an implementation override meta.as_of and meta.source with data-derived truth. For example, a Garmin reader can report the synced file's timestamp instead of the wall-clock time of the request. If it does not override them, a successful response uses execution time and the definition's source string.

Successful envelopes also receive duration_ms. Early failures such as an unknown capability, forbidden transport, replay-key mismatch, or invalid input contain the base metadata but do not currently carry the full as_of/source/duration_ms set. Callers should not assume those optional diagnostic fields exist on every error.


The v1 envelope

Every server-side transport returns one of these shapes:

{
  "ok": true,
  "data": {
    "is_blocked": false,
    "avg_sleep_hours": 6.8,
    "days_with_data": 7,
    "threshold_hours": 6.5,
    "message": "sleep gate open (sanitized example)"
  },
  "error": null,
  "meta": {
    "v": 1,
    "capability": "vita.health.sleep_gate",
    "request_id": "example-request-id",
    "actor": "agent:training-plan",
    "transport": "python",
    "side_effect": "read",
    "as_of": "2026-07-15T08:00:00-07:00",
    "source": "data/garmin/*.json + data/sleep/overrides.jsonl",
    "duration_ms": 4
  }
}
{
  "ok": false,
  "data": null,
  "error": {
    "code": "invalid_input",
    "message": "input failed validation for vita.schedule.enqueue_message",
    "retryable": false,
    "details": {
      "errors": [
        { "type": "timezone_aware", "loc": ["send_at"] }
      ]
    }
  },
  "meta": {
    "v": 1,
    "capability": "vita.schedule.enqueue_message",
    "request_id": "example-request-id",
    "actor": "agent:reminders",
    "transport": "python",
    "side_effect": "send"
  }
}

The examples above are sanitized envelope examples; consult the generated schema for the complete data shape. The stable contract is the top-level ok, data, error, and meta split. Capability-level failures are values, not exceptions, so callers branch on ok and error.code.

Stable server error catalog

Code Retryable Meaning
invalid_input no Typed input validation failed; details carry Pydantic errors
unknown_capability no The name is absent from the registry
transport_forbidden no The transport cannot invoke this side-effect class
admission_rejected yes Queue admission policy rejected the operation
duplicate_suppressed no Domain deduplication suppressed an in-window duplicate
denylisted no The trigger or operation is denylisted
not_found no A referenced entity does not exist
conflict no Uniqueness or current-state conflict
upstream_unavailable yes A backing service or index is unavailable
idempotency_reuse_mismatch no The key was already used for another capability
internal_error no An unexpected implementation exception occurred

Codes live in scripts/capabilities/envelope.py::ERROR_CODES. Adding a code is additive; renaming an existing code would break the v1 contract.

The Workshop SDK adds client-only network_error, page_not_allowed, and sdk_blocked results. It can also synthesize unknown_capability from its discovery map. These do not pass through the server error catalog and carry meta.client_side: true.


Side-effect and mutation policy

Side-effect class describes the operation, not merely the storage mechanism:

Class Definition Examples
read Does not mutate domain state sleep summary, memory search, calendar events
write Mutates local files, SQLite, or Precedent state task update, page preference, curriculum score
send Causes communication to leave the machine, immediately or through a delivery queue Telegram send, queued reminder
spawn Starts or schedules an agent/job deferred prompt, education packet generation

The mechanically enforced policy is deliberately small:

Transport Read Write Send Spawn
Python yes yes yes yes
CLI yes yes yes yes
Internal HTTP with X-Vita-Transport: http yes yes yes yes
Workshop/JavaScript (js) yes yes no no
Reserved public class yes no no no

The browser SDK also applies a per-page allowlist and blocks send/spawn client-side, while the server repeats the side-effect check. The allowlist is an authoring guardrail; the server policy and internal-host perimeter remain the real boundary.

The HTTP capability router is not allowlisted on Vita's public tunnel domains. Within localhost/Tailscale, the layer follows Vita's identity-is-the-perimeter model: there is no endpoint-level authentication. X-Vita-Actor is attribution supplied by the trusted caller, not cryptographic identity. Raw internal HTTP can request the powerful http transport, so it must not be treated as a public API.

Provenance

scripts/capabilities/store.py::record_provenance() appends every executed non-read call to data/capabilities/provenance.jsonl with:

{
  "at": "2026-07-15T08:00:00-07:00",
  "capability": "vita.tasks.create",
  "side_effect": "write",
  "actor": "agent:planning",
  "transport": "cli",
  "request_id": "example-request-id",
  "ok": true,
  "input": "{\"text\": \"review the launch checklist\"}"
}

Optional page and error_code fields are added when relevant. The serialized input is truncated to 800 characters. Validation failures and transport refusals occur before the implementation and are not provenance entries; idempotent replays return before writing a second entry.

Provenance answers β€œwhich supported interface attempted this mutation?” It does not prove that instrumentation and domain data committed atomically. Each wrapper still depends on its owner module to use safe writes or a proper SQLite transaction.

Idempotency

Mutation callers may provide an Idempotency-Key header or the equivalent Python/CLI argument. The contract is:

  • only write, send, and spawn calls use the key; reads ignore it;
  • only successful envelopes are stored;
  • entries expire after 48 hours and are pruned during lookup;
  • replaying the same key for the same capability returns the original envelope and request ID, plus meta.idempotent_replay: true;
  • reusing the key for another capability returns idempotency_reuse_mismatch;
  • the key is not a payload hash. Reusing it for the same capability with different input still replays the original result.

Use a key that identifies one intended mutation, not merely a UI session. The store is data/capabilities/idempotency.db; tests can redirect all layer-owned state with VITA_CAPABILITIES_DATA_DIR.

Domain-level deduplication remains separate. For example, the scheduling namespace can report duplicate_suppressed or denylisted even when the generic idempotency cache was not used.


Transports and discovery

Python

Python is the direct, fully privileged transport:

from scripts.capabilities import execute

result = execute(
    "vita.health.sleep_gate",
    {"component": "intervals"},
    actor="agent:training-plan",
    transport="python",
)

if result["ok"]:
    blocked = result["data"]["is_blocked"]
else:
    code = result["error"]["code"]

actor is required. Import from the package root rather than a namespace module so the registry alias and single execution path remain intact.

CLI

The CLI is implemented by scripts/capabilities/cli.py and exposed through ./run:

# compact discovery
./run capabilities list

# full schema, optionally filtered by namespace
./run capabilities spec --ns vita.schedule

# read call
./run capabilities call vita.health.sleep_gate \
  --input '{"component":"intervals"}' \
  --actor 'cli:operator'

# idempotent mutation
./run capabilities call vita.tasks.create \
  --input '{"text":"review the launch checklist","due_date":"2026-07-17"}' \
  --actor 'agent:planning' \
  --idempotency-key 'planning-review-2026-07-17'

call prints the complete JSON envelope. It exits 0 for ok: true, 1 for a capability error envelope, and 2 when the --input argument is not valid JSON.

Internal HTTP API

api/src/routers/capabilities.py mounts three routes on the internal API at port 33800:

Method and path Purpose
GET /api/v1/capabilities Compact discovery: name, namespace, class, description
GET /api/v1/capabilities/spec Full generated spec; filter with ?namespace=vita.tasks
POST /api/v1/capabilities/call/{name} Execute; request body is the raw input object
curl -s http://localhost:33800/api/v1/capabilities/call/vita.tasks.create \
  -H 'Content-Type: application/json' \
  -H 'X-Vita-Transport: http' \
  -H 'X-Vita-Actor: agent:planning' \
  -H 'Idempotency-Key: planning-review-2026-07-17' \
  --data '{"text":"review the launch checklist","due_date":"2026-07-17"}'

Request headers are:

Header Behavior
X-Vita-Transport Router accepts http or js; missing/unknown values become js
X-Vita-Actor Attribution; defaults to http:anonymous unless a page is present
X-Vita-Page Workshop page identity; default actor becomes browser:<page>
Idempotency-Key Optional replay protection for non-read operations

Capability errors return HTTP 200 with ok: false; branch on error.code, not HTTP status. A malformed request that never reaches the layer, such as a non-object JSON body, returns HTTP 422.

Workshop JavaScript SDK

workshop/lib/vita.v1.js::createVita() discovers the registry, requires a page identity, applies exact or vita.namespace.* allow patterns, and sends calls to the HTTP route with X-Vita-Transport: js and X-Vita-Page.

import { createVita } from '/workshop/lib/vita.v1.js';

const vita = await createVita({
  page: 'planning-workbench',
  allow: ['vita.tasks.*', 'vita.workshop.*'],
});

const result = await vita.call(
  'vita.tasks.create',
  { text: 'review the launch checklist', due_date: '2026-07-17' },
  { idempotencyKey: vita.newIdempotencyKey() },
);

The SDK returns server envelopes verbatim and converts fetch/allowlist failures into client-side envelopes. Its keepalive option lets a write survive same-tab navigation. vita.prefs is a convenience wrapper around vita.workshop.prefs_get and prefs_set for view-chrome state only.

MCP projection

MCP is not a live transport yet. docs/api/capabilities/mcp-tools.json is a generated, transport-ready projection whose tool names replace dots with underscores. It includes each input schema plus MCP annotations:

  • readOnlyHint is true only for read capabilities;
  • destructiveHint is currently true for write and send capabilities.

No MCP server is mounted and nothing dispatches these declarations today. The projection exists so a real consumer can pull the adapter into existence without inventing a second registry. Until that happens, describing MCP as an available way to call Vita would be inaccurate.


Current namespace organization

This is the registry snapshot generated on 2026-07-15. The full field-level contract belongs to the live spec and generated namespace pages.

Namespace Class mix Verbs Backing domain
vita.calendar 1 read events Canonical read over data/calendar/events.json
vita.education 10 read, 5 write, 1 spawn activity, choose, complete_packet, frontier, generate_packet, graph, kids, map, node, packets, progress, queue, retests, score, set_level, set_status Curriculum graph, per-kid ledgers, teacher workflow, packet library, Gumball integration
vita.health 3 read garmin_day, sleep_gate, sleep_summary Garmin daily files and sleep overrides
vita.links 2 read, 5 write add, archive, frequents, list, reorder_pins, update, visit Start-page link store and read-only Chromium history
vita.memory 1 read search Hybrid vector + BM25 search index
vita.people 1 read, 5 write agenda_add, agenda_list, agenda_reorder, agenda_resolve, agenda_update, create Precedent person agenda vaults
vita.schedule 1 read, 1 write, 1 send, 1 spawn cancel, enqueue_message, enqueue_prompt, queue_status VitaScheduler proactive queue
vita.tasks 3 read, 6 write add_note, agenda, complete, create, delete, habit_log, habits_list, list, update Precedent on port 5424
vita.telegram 3 write, 2 send deregister_participant, open_topic, poll_replies, register_participant, send Bot API, topic ledger, participant tables/inbox
vita.workshop 2 read, 1 write pages, prefs_get, prefs_set Live page index and page-preference store

Namespaces are deployable vocabulary, not a claim that every domain operation has been standardized. Large existing systems such as feed, reports, photos, and email still primarily use their established routers and owner modules. They should enter this registry only when a second consumer or recurring hard-way access pattern creates real demand.


Specification, generated reference, and drift control

scripts/capabilities/codec.py is a pure projection of the loaded registry. It sorts capabilities, emits no timestamps, and generates:

Artifact Purpose
docs/api/capabilities/spec.json Complete machine spec: envelope, HTTP contract, transports, errors, namespaces, schemas, examples
docs/api/capabilities/mcp-tools.json MCP-shaped input schemas and side-effect annotations
docs/api/capabilities/index.md Generated envelope/error/transport summary and capability index
docs/api/capabilities/vita.<namespace>.md Field tables and examples for one namespace

The files under docs/api/capabilities/ are generated artifacts; do not edit them by hand. They are repository references, separate from the authored MkDocs pages under docs/pages/. The full machine spec is also available live from the internal /api/v1/capabilities/spec endpoint.

Regenerate after any definition, schema, description, example, or namespace change:

./run gen-capabilities

Check without writing:

./run capabilities gen --check

The gen --check path compares every generated artifact. The contract test in tests/test_capabilities.py::test_spec_artifacts_are_current independently compares the committed spec.json with the live registry, while test_spec_covers_all_capabilities verifies registry coverage. Other tests assert envelope shape, stable errors, timezone-aware boundaries, transport denials, provenance, idempotency, namespace behavior, and that declared examples validate.

This division is intentional:

  • This page explains architecture, policy, and limitations to a reader who cannot inspect the repository.
  • Generated namespace pages and spec.json are the exact callable reference and should change mechanically with the code.

Layer-owned data and state

The registry itself is in-memory code. The shared layer owns only a small amount of instrumentation and replay state:

Path Format Purpose
data/capabilities/provenance.jsonl append-only JSONL Executed mutation attribution and bounded input
data/capabilities/usage.db SQLite Daily calls/errors by capability and transport
data/capabilities/idempotency.db SQLite Successful mutation envelopes, retained for 48-hour replay
data/sleep/staging/capabilities.json generated JSON Optional nightly digest consumed by the /sleep gather path
docs/api/capabilities/ generated JSON/Markdown Committed machine and field-level reference

VITA_CAPABILITIES_DATA_DIR relocates the first three stores. Contract tests use a temporary directory so they never modify production instrumentation. Namespace implementations own and isolate their own domain state separately.

Usage rows contain counts only: capability, local day, transport, calls, and errors. Mutation content belongs in provenance, while read inputs are not recorded. Calls rejected before implementation execution and idempotent replays are not included in the usage counter.


Promotion and demotion loop

The layer is designed to maintain itself without turning β€œAPI coverage” into a target. It has one gauge for operations that may be dying and another for hard-way access that may deserve promotion.

Demotion digest

scripts/capabilities/digest.py::build_digest() combines the registry, usage.db, and recent provenance into:

  • call and error counts per capability;
  • last-called date and transport mix;
  • never_called, stale, and error_heavy flags;
  • mutation volume by transport and actor over the last seven days.

The current thresholds are quiet for more than 60 days for stale, and at least five calls with an error rate of 20% or higher for error_heavy. These are explicitly gauges, not targets. A stale flag asks whether a capability should be pruned; it is not a reason to manufacture usage.

./run capabilities digest
./run capabilities digest --stage

--stage writes data/sleep/staging/capabilities.json. The digest reports tracking_since because atrophy flags are not meaningful until the counter has accumulated enough history.

Promotion miner

scripts/capabilities/promotion_miner.py::mine() passively scans recent Claude Code tool-use transcripts for repeated data-plane access performed the hard way: direct reads, greps/globs, shell access to data/, memory/, profile/, or goals/, and sqlite3 calls into databases.

The current algorithm:

  1. scans files modified in the last seven days, newest first;
  2. stops at an 800 MiB total scan ceiling and reports truncation;
  3. clusters calls by access kind and data domain;
  4. drops clusters seen in fewer than two sessions;
  5. ranks the remainder by the declared proxy sessions Γ— calls;
  6. returns the top 15;
  7. labels a known promoted domain as migration and a new domain as promotion.

It deliberately excludes source-code reads as development noise and reports failure as UNKNOWN, never as an empty candidate list. The score is a proxy for repeated friction, not token cost or business value. The nightly digest places candidate selection back in a human taste loop: promote the operation only if a real consumer and stable owner contract exist.


Adding or promoting a capability

A new capability should be pulled into the layer by demonstrated demand, not by a coverage goal:

  1. Name the waiting consumer and hard-way path. If no caller needs the operation, leave the owner module alone.
  2. Keep one owner. Wrap the existing domain function; do not copy its business logic into the capability namespace.
  3. Declare the boundary. Add Pydantic input/output models and a @capability definition under scripts/capabilities/namespaces/<domain>.py. Give it an honest side-effect class, source, description, and sanitized examples.
  4. Add explicit membership. For a new namespace, append its module to core.py::NAMESPACE_MODULES.
  5. Normalize failures. Convert expected domain failures into existing CapabilityError codes. Add a new stable code only when the distinction is meaningful to callers.
  6. Preserve owner integrity. Use the domain's atomic writer or SQLite owner module. The generic executor does not make an unsafe implementation atomic.
  7. Test the contract. Cover validation, failures, side-effect transport policy, real owner behavior, and mutation isolation. Add an example that validates against the input model.
  8. Regenerate and check. Run ./run gen-capabilities, the capability tests, and ./run capabilities gen --check.
  9. Migrate one real caller. The capability has not earned its existence until a consumer stops using the hard path.
  10. Watch the gauges. Let usage and promotion/demotion reporting reveal whether the wrapper is useful, broken, or obsolete.

Known limitations and improvement opportunities

The layer is useful precisely because its boundary is explicit. The following gaps are real, not implied future features:

  1. No mounted MCP server. The JSON tool projection is generated but cannot be invoked. Build the adapter only when an external MCP consumer is waiting.
  2. Output schemas are declared but not universally enforced at runtime. BaseModel results serialize safely, but a wrapper returning a raw dict passes through without output_model.model_validate(). Validating every result would make the machine spec a stronger runtime guarantee.
  3. Atomicity is delegated. Provenance and idempotency are not in the same transaction as a domain mutation, and the registry does not lint an implementation's write method. Owner-module transactions and atomic_* calls remain essential.
  4. Policy is coarse. The registry stores side-effect class, but not per-capability authorization, review gates, rate limits, or destructive confirmation policy. Domain wrappers enforce their own admission and dedup rules.
  5. Internal HTTP trusts caller headers. Actor/page values are useful provenance inside the trusted perimeter, not authentication. A future wider network surface would need server-derived identity and explicit allowlists.
  6. Idempotency does not bind the payload. Same key + same capability replays even if a caller accidentally changes the input. Persisting an input hash would turn that mistake into a conflict.
  7. Instrumentation is best-effort. A failed usage/provenance/idempotency write logs and returns the domain result. A health signal should make such gaps visible without making the primary operation unavailable.
  8. Early failures are undercounted. Usage and provenance do not capture unknown names, transport refusals, input failures, or replays. A separate boundary-rejection gauge would improve migration and abuse diagnostics.
  9. Promotion mining is runtime-biased. It reads the Claude Code transcript corpus only, so direct access from Codex, services, or other runtimes is invisible. A shared multi-runtime tool-event ledger would give a truer demand signal.
  10. MCP annotations are incomplete for spawn. The current projection marks write and send as destructive but not spawn; a future live adapter should review that policy before exposing tools.
  11. Generated references live outside the MkDocs source directory. docs/api/capabilities/ is the committed machine reference while this authored site builds from docs/pages/. The live internal spec closes the machine-consumer gap, but serving field-level generated references on the docs site would improve human discoverability.

These are improvement candidates, not reasons to route around the layer. A new direct file reader or bespoke endpoint recreates the original split-brain problem; improve the shared boundary when a real consumer exposes a gap.


File map

Path Responsibility
scripts/capabilities/core.py Registry, decorator, context, explicit namespace loading, side-effect enum
scripts/capabilities/execute.py Single dispatch path, policy, validation, error mapping, envelope, instrumentation
scripts/capabilities/envelope.py Frozen v1 envelope helpers and stable error catalog
scripts/capabilities/codec.py Deterministic spec, Markdown, and MCP projections
scripts/capabilities/cli.py list, spec, call, gen, and digest commands
scripts/capabilities/store.py Provenance, usage counters, 48-hour idempotency cache
scripts/capabilities/digest.py Demotion/health gauge and /sleep staging output
scripts/capabilities/promotion_miner.py Passive repeated hard-way-access miner
scripts/capabilities/namespaces/*.py Pydantic contracts and wrappers around domain owners
api/src/routers/capabilities.py Thin internal HTTP discovery/spec/call transport
workshop/lib/vita.v1.js Browser discovery, page allowlist, client envelopes, calls, preferences
docs/api/capabilities/ Generated machine spec, MCP schemas, index, and namespace references
tests/test_capabilities.py Core contract plus namespace behavior and artifact drift tests
tests/test_promotion_miner.py Miner clustering, recurrence, truncation, and failure-honesty tests
data/reports/2026-07-11-vita-api-standardization-survey.md Original design survey and failure-class traceability

The shortest reliable way to inspect the live surface is:

./run capabilities list
./run capabilities spec
./run capabilities gen --check