Skip to content

Context documents (kapelle.controller.command, command: "context")

Backlog task f71b6ef0 (Console K8). A context document is read-only reference text that the Console attaches to an agent (every team that uses it) or to a team: a style guide, a glossary, a brief, a CSV. The worker puts the documents of its role into the guest, read-only, and tells the model where they are. Same channel as docs/skills.md and docs/mcp.md (kapelle.controller.command, plain core NATS request/reply, ControllerCommandServer).

What a document is

Field Rule
scope, owner agent + the agent's name, or team + the team's name
name ^[a-z0-9][a-z0-9._-]{0,62}$, unique per (scope, owner); it is the file name in the guest
media_type text/markdown, text/plain, text/csv, application/json, application/yaml
description at most 200 characters, shown to the model in the index
content UTF-8 text without NUL, 1 byte to 256 KiB

An owner holds at most 20 documents and 384 KiB of content in total. The 384 KiB is not a round number: the worker's request below is answered in one NATS message, and a NATS message may be at most 1 MiB. An agent's and a team's documents travel together (up to 768 KiB of content, plus the JSON escaping), so one owner's share is capped at 384 KiB to keep the answer under the limit. The worker keeps at most 20 documents and 1 MiB of what it receives (a defence, not the normal case). Binary files (PDF, images) are not supported.

Deleting an agent removes its documents; archiving a team removes the team's (a new team that takes the freed name must not inherit them).

Console API

GET /api/v1/agents/{name}/documents (list, no content), GET .../documents/{doc} (one, with content), PUT .../documents/{doc} (create: 201; replace: 200, needs If-Match), DELETE .../documents/{doc} (needs If-Match), and the same under /api/v1/teams/{name}/documents. Reading needs a viewer; writing an agent's documents an admin, a team's an operator. Every write is in the audit log. The schemas are DocumentSummary, Document and DocumentPut in console/api/openapi.yaml.

What the worker asks (request and reply)

Request (worker-originated only: source is required, a person gets "context documents are read by workers only"):

{"command": "context", "team": "payments", "args": ["list_for_role", "researcher"],
 "source": "agent:payments"}

args[1] is a role of the team; the controller resolves its agent (team_roles.agent_def). Reply: {"ok": true, "text": "2 document(s)", "entries": [...]}, each entry a JSON-encoded object {"scope", "name", "media_type", "description", "sha256", "size_bytes", "content"}. The agent's documents come first, then the team's; a team document with the same name as an agent document replaces it. An unknown role is {"ok": false, "error": "no role 'x' in team 'payments'"}.

What the worker does with them

kapelle_worker.agent.context_documents: asks once per (team, role) per minute, keeps at most 20 documents and 1 MiB, writes them into the guest under /workspace/context/<name> (the sibling context of the skills staging root the guest declares), makes them read-only, and appends an index (path, description, type, size, scope) to the model's system message. Best effort throughout: a controller that cannot answer, a document that fails to stage or a limit that is passed costs the run its documents with a log line, never the run. Only a guest that is gone fails the setup, as for skills.

Platform skills from the store (list_platform_skills)

The same op family serves the platform skill catalogue: roles/skills/<name>/ is imported into the platform_skills table (the text of SKILL.md and the text files of at most 64 KiB next to it; a binary or larger file is skipped with an import warning) and the worker asks for it with {"command": "context", "args": ["list_platform_skills"], ...} (see contracts/a2a-nats.md). The worker rebuilds each skill under a temp directory and parses it with the SDK loader like a team skill; a skill in the store replaces the file of the same name, the files stay as the fallback for one release (a worker without a controller answer uses them alone, as before).

Memory settings per team and role

Backlog task f71b6ef0 (Console K8, step 5). Three settings say how runs use team memory: inject (put the "Team memory" block into a conversation's first task, default true), max_entries (0 to 10, default 10; 0 turns the injection off, 10 is the memory search's own limit) and write_outcomes (write the outcome entry when a run completes, default true). They are stored as JSON on teams.memory_json and, as a role's own layer, on team_roles.memory_json; the role's keys lie over the team's over the defaults. GetTeamRoleResponse.memory carries the result to the worker (unset when neither layer says anything: the worker then behaves as before).

PATCH /api/v1/teams/{name} takes memory (the team's keys) and role_settings.<role>.memory (the role's own keys); the keys sent are set, the others stay. role_settings.<role>.memory: null clears the role's own layer, so the role follows the team again. In the team detail a role's memory holds the effective values plus own, the keys the role overrides ([] when it follows the team); the team's own settings.memory has the effective values and no own.

Console API for the catalogue

Backlog task 471a2639. GET /api/v1/skills (list: name, description from the front matter, size_bytes, revision, the paths of the files, no bodies), GET /api/v1/skills/{name} (with body and resources, a map path to text), PUT /api/v1/skills/{name} (body {body, resources}; 201 on create, 200 on replace with If-Match) and DELETE (If-Match, 204). Reads need a viewer, writes an admin; every write is in the audit log (skill.create, skill.update, skill.delete). No migration: the table is the one the importer fills.

  • revision is the catalogue revision a skill was last written at (the importer gives every row of one import the same number); a PUT sets it to the highest revision of the catalogue plus one.
  • A skill is valid when its name matches ^[a-z0-9]+(-[a-z0-9]+)*$ (at most 64 characters), SKILL.md is text of at most 64 KiB whose front matter has name equal to the path name and a description, and the files are relative paths (not SKILL.md, no ..), text, at most 64 KiB each, at most 20, 256 KiB in total with SKILL.md. The whole catalogue may not pass 768 KiB: the worker fetches it in ONE NATS reply and a NATS message is 1 MiB (the same reasoning as the 384 KiB per document owner). The importer is not bound by the totals; rows it already wrote stay as they are.
  • A row of the store that differs from its file is a conflict for the importer without --force, so an edit through the API is not overwritten by an import; a skill deleted through the API whose file is still in roles/skills/ is created again by the next import.
  • GET /api/v1/teams/{name}/skills lists the team's own stored skills (name, version, curated or proposed, source, updated_at), read-only: they stay managed by the chat commands and the agents' skill_propose.