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.
revisionis 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.mdis text of at most 64 KiB whose front matter hasnameequal to the path name and adescription, and the files are relative paths (notSKILL.md, no..), text, at most 64 KiB each, at most 20, 256 KiB in total withSKILL.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 inroles/skills/is created again by the next import. GET /api/v1/teams/{name}/skillslists the team's own stored skills (name, version,curatedorproposed, source, updated_at), read-only: they stay managed by the chat commands and the agents'skill_propose.