HTTP and WebSocket API

July 12, 2026 · View on GitHub

Every REST endpoint and WebSocket channel exposed by the in-package FastAPI server (kt web, kt serve, python -m kohakuterrarium.api.main). The API drives the Vue SPA and is suitable for any client that wants to control agents and terrariums from outside the process.

For the shape of the serving layer and session storage, read concepts/impl-notes/session-persistence. For task-oriented use, see guides/programmatic-usage and guides/frontend-layout.

Server configuration

  • Default host: 0.0.0.0.
  • Default port: 8001 (auto-increments if busy under kt web).
  • Override via python -m kohakuterrarium.api.main --host 127.0.0.1 --port 8080 [--reload].
  • KT_SESSION_DIR overrides the default session directory.
  • CORS is wide open: allow_origins=["*"], all methods, all headers.
  • No authentication. Treat the server as trusted-local.
  • Version string: 0.1.0. No /v1/ URL prefix.
  • FastAPI auto-docs: /docs (Swagger UI), /redoc (ReDoc).

When create_app(static_dir=Path) is called with a valid built SPA directory:

  • /assets/*: hashed build assets.
  • /{path}: SPA fallback, serves index.html for any unmatched path.
  • /api/* and WebSocket routes take precedence.

Response conventions

  • Status codes: 200 success, 400 bad input, 404 missing resource, 409 conflict, 500 server error. 201 is not used.
  • Payloads are JSON unless otherwise noted.
  • Error bodies are {"detail": "<message>"}. The Studio layer raises typed kohakuterrarium.errors exceptions; a single adapter in api/app.py maps them (NotFoundError → 404, ConflictError → 409, InvalidRequestError/ValueError → 400, other KTError → 500).

Terrariums

POST /api/terrariums

Create and start a terrarium from a config path.

  • Body: TerrariumCreate (config_path, optional llm, pwd).
  • Response: {"terrarium_id": str, "status": "running"}.
  • Status: 200, 400.
  • Side effects: terrarium spawned; privileged root: node initialised; creatures started; session store opened when configured.

GET /api/terrariums

List all running terrariums as an array of status objects (same shape as the single-terrarium GET below).

GET /api/terrariums/{terrarium_id}

Return a TerrariumStatus: terrarium_id, name, running, creatures (name → status dict), channels (list of channel names).

DELETE /api/terrariums/{terrarium_id}

Stop and clean up a terrarium. Response: {"status": "stopped"}. Side effects: all creatures stopped, channels cleaned, session store closed.

POST /api/terrariums/{terrarium_id}/channels

Add a channel at runtime.

  • Body: ChannelAdd (name, description; channel_type is accepted for legacy payload compatibility but ignored; every channel is broadcast).
  • Response: {"status": "created", "channel": <name>}.

GET /api/terrariums/{terrarium_id}/channels

List channels as [{"name", "type", "description"}].

POST /api/terrariums/{terrarium_id}/channels/{channel_name}/send

Inject a message into a channel.

  • Body: ChannelSend (content as str or list[ContentPartPayload], sender default "human").
  • Response: {"message_id": str, "status": "sent"}.
  • Side effects: message written to history; listeners fire their on_send callbacks.

POST /api/terrariums/{terrarium_id}/chat/{target}

Non-streaming chat. target is "root" or a creature name.

  • Body: AgentChat (message or content).
  • Response: {"response": <full text>}.

GET /api/terrariums/{terrarium_id}/history/{target}

Read conversation and event log. target is "root", a creature name, or "ch:<channel_name>" for channel history. Prefers SessionStore, falls back to the in-memory log.

  • Response: {"terrarium_id", "target", "messages": [...], "events": [...], "is_processing": bool}. Channel-history targets return messages and set is_processing to false.

GET /api/terrariums/{terrarium_id}/scratchpad/{target}

Return the target agent's scratchpad as {key: value}.

PATCH /api/terrariums/{terrarium_id}/scratchpad/{target}

  • Body: ScratchpadPatch (updates: {key: value | null}; null deletes).
  • Response: updated scratchpad.

GET /api/terrariums/{terrarium_id}/triggers/{target}

List active remote triggers: [{"trigger_id", "trigger_type", "running", "created_at"}].

GET /api/terrariums/{terrarium_id}/plugins/{target}

List loaded plugins with enabled/disabled state.

POST /api/terrariums/{terrarium_id}/plugins/{target}/{plugin_name}/toggle

Toggle a plugin. Response: {"name", "enabled"}. Calls load_pending() when enabling.

GET /api/terrariums/{terrarium_id}/env/{target}

Return {"pwd", "env"} with env keys containing secret, key, token, password, pass, private, auth, credential (case-insensitive) filtered out.

GET /api/terrariums/{terrarium_id}/system-prompt/{target}

Return {"text": <assembled system prompt>}.


Creatures (inside a terrarium)

GET /api/terrariums/{terrarium_id}/creatures

Map of creature name to status dict.

POST /api/terrariums/{terrarium_id}/creatures

Add a creature at runtime.

  • Body: CreatureAdd (name, config_path, listen_channels, send_channels).
  • Response: {"creature": <name>, "status": "running"}.

DELETE /api/terrariums/{terrarium_id}/creatures/{name}

Remove a creature. Response: {"status": "removed"}.

POST /api/terrariums/{terrarium_id}/creatures/{name}/interrupt

Interrupt the creature's current agent.process() without terminating it. Response: {"status": "interrupted", "creature": <name>}.

GET /api/terrariums/{terrarium_id}/creatures/{name}/jobs

Running and queued background jobs.

POST /api/terrariums/{terrarium_id}/creatures/{name}/tasks/{job_id}/stop

Cancel a running background job. Response: {"status": "cancelled", "job_id"}.

POST /api/terrariums/{terrarium_id}/creatures/{name}/promote/{job_id}

Promote a direct task to the background queue.

POST /api/terrariums/{terrarium_id}/creatures/{name}/model

Switch the creature's LLM without restart.

  • Body: ModelSwitch (model).
  • Response: {"status": "switched", "creature", "model"}.

POST /api/terrariums/{terrarium_id}/creatures/{name}/wire

Add a listen or send binding to a channel.

  • Body: WireChannel (channel, direction = "listen" or "send").
  • Response: {"status": "wired"}.

Standalone agents

POST /api/agents

Create and start an agent outside of any terrarium.

  • Body: AgentCreate (config_path, optional llm, pwd).
  • Response: {"agent_id", "status": "running"}.

GET /api/agents

List running agents.

GET /api/agents/{agent_id}

Return {"agent_id", "name", "model", "running", "is_processing", ...}. The status payload also includes tool/sub-agent and context details.

DELETE /api/agents/{agent_id}

Stop the agent. Response: {"status": "stopped"}.

POST /api/agents/{agent_id}/interrupt

Interrupt current processing.

POST /api/agents/{agent_id}/regenerate

Re-run the last assistant response using current model/settings. Response: {"status": "regenerating"}.

POST /api/agents/{agent_id}/messages/{msg_idx}/edit

Mutate a user message and replay from that point.

  • Body: MessageEdit (content).
  • Response: {"status": "edited"}.
  • Side effects: truncates history at msg_idx, injects new message, replays.

POST /api/agents/{agent_id}/messages/{msg_idx}/rewind

Truncate the conversation without re-running. Response: {"status": "rewound"}.

POST /api/agents/{agent_id}/promote/{job_id}

Promote a direct task to the background.

GET /api/agents/{agent_id}/plugins

List plugins and state.

POST /api/agents/{agent_id}/plugins/{plugin_name}/toggle

Enable/disable a plugin. Response: {"name", "enabled"}.

GET /api/agents/{agent_id}/jobs

List background jobs.

POST /api/agents/{agent_id}/tasks/{job_id}/stop

Cancel a background job.

GET /api/agents/{agent_id}/history

Return {"agent_id", "events": [...], "is_processing": bool}. The event stream includes sibling branches; clients can replay the active branch locally or call /branches for a compact branch map.

GET /api/agents/{agent_id}/branches

Return per-turn branch metadata for the branch navigator: {"agent_id": str, "turns": [{"turn_index": int, "branches": [int], "latest_branch": int}]}.

POST /api/agents/{agent_id}/model

Switch the agent's LLM.

  • Body: ModelSwitch (model).
  • Response: {"status": "switched", "model"}.

POST /api/agents/{agent_id}/command

Execute a user slash command (e.g. model, status).

  • Body: SlashCommand (command, optional args).
  • Response: command-dependent result.

POST /api/agents/{agent_id}/chat

Non-streaming chat.

  • Body: AgentChat.
  • Response: {"response": <full text>}.

GET /api/agents/{agent_id}/scratchpad

Return scratchpad key-value map.

PATCH /api/agents/{agent_id}/scratchpad

  • Body: ScratchpadPatch.
  • Response: updated scratchpad.

GET /api/agents/{agent_id}/triggers

Active triggers as [{trigger_id, trigger_type, running, created_at}].

GET /api/agents/{agent_id}/env

Return {"pwd", "env"} with secrets filtered.

GET /api/agents/{agent_id}/system-prompt

Return {"text": <system prompt>}.


Config discovery

GET /api/configs/creatures

List discoverable creature configs: [{"name", "path", "description"}]. Paths may be absolute or package references.

GET /api/configs/terrariums

List discoverable terrarium configs (same shape as above).

GET /api/configs/server-info

Return {"cwd", "platform"}.

GET /api/configs/models

List every configured LLM model/profile with availability.

GET /api/configs/commands

List slash commands: [{"name", "aliases", "description", "layer"}].


Registry and package management

GET /api/registry

Scan local directories and installed packages. Return [{"name", "type", "description", "model", "tools", "path", "source", ...}]. source is "local" or a package name.

GET /api/registry/remote

Return {"repos": [...]} from the bundled registry.json.

POST /api/registry/install

  • Body: InstallRequest (url, optional name).
  • Response: {"status": "installed", "name"}.

POST /api/registry/uninstall

  • Body: UninstallRequest (name).
  • Response: {"status": "uninstalled", "name"}.

Sessions

GET /api/sessions

List saved sessions.

Query params:

ParamTypeDefaultDescription
limitint20Max sessions.
offsetint0Skip N.
searchstr(none)Filter by name, config, agents, preview (case-insensitive).
refreshboolfalseForce rebuild of the session index.

Response:

{
  "sessions": [
    {
      "name": "...", "filename": "...", "config_type": "agent|terrarium",
      "config_path": "...", "agents": [...], "terrarium_name": "...",
      "status": "...", "created_at": "...", "last_active": "...",
      "preview": "...", "pwd": "..."
    }
  ],
  "total": 123,
  "offset": 0,
  "limit": 20
}

Side effects: the index is rebuilt on first request or after 30 seconds.

DELETE /api/sessions/{session_name}

Delete a session file. Response: {"status": "deleted", "name"}. Accepts stem or full filename.

POST /api/sessions/{session_name}/resume

Resume a saved session.

  • Response: {"instance_id", "type": "agent"|"terrarium", "session_name"}.
  • Status codes: 200, 400 (ambiguous prefix), 404, 500.

GET /api/sessions/{session_name}/history

Session metadata and available targets.

  • Response: {"session_name", "meta", "targets"} where targets contain agent names, "root", and "ch:<channel>" entries.

GET /api/sessions/{session_name}/history/{target}

Read-only saved history. target is URL-encoded; accepts "root", creature name, or "ch:<channel_name>".

  • Response: {"session_name", "target", "meta", "messages", "events"}.

GET /api/sessions/{session_name}/memory/search

FTS5 / semantic / hybrid search over a saved session.

Query params:

ParamTypeDefaultDescription
qstrrequiredQuery.
modeauto|fts|semantic|hybridautoSearch mode.
kint10Max results.
agentstr(none)Filter by agent.

Response: {"session_name", "query", "mode", "k", "count", "results"}. Each result: {content, round, block, agent, block_type, score, ts, tool_name, channel}.

Side effects: unindexed events get indexed (idempotent); uses the live embedder when the agent is running, otherwise loads from config.

GET /api/sessions/{session_name}/artifacts/{filepath}

Serve a binary artifact stored under the session's sibling <session>.artifacts/ directory.

  • filepath is relative to that artifacts root (for example generated_images/cat.png).
  • Absolute paths and traversal (..) are rejected with 400.
  • Missing session artifacts or files return 404.
  • Response is a FileResponse with MIME type guessed from the filename.

This is the HTTP surface used to read generated images persisted by provider-native tools such as image_gen.


Drive records

Records for the Drive runtime. A session is a Terrarium graph, so {session_id} is the graph id the operation runs against. The actor is derived from the authenticated request (a single-tenant caller is the operator console; an L4 user acts as itself, admin role granting graph-authority elevation) — never from the body. List rows redact spec/evidence; detail is returned only to an authorized caller. These endpoints exist only when the target Terrarium was constructed with a Drive runtime.

Method + pathPurpose
GET /api/sessions/{session_id}/drivesList redacted rows. Query: status, kind, owner, assignee, mine (bool), include_terminal (bool).
POST /api/sessions/{session_id}/drivesCreate a Drive owned by the caller by default.
GET /api/sessions/{session_id}/drives/{drive_id}Full detail (or a redacted row for an unauthorized caller).
PATCH /api/sessions/{session_id}/drives/{drive_id}CAS-checked non-identity patch (title/spec/priority/…).
POST /api/sessions/{session_id}/drives/{drive_id}/assignAssign/reassign to a graph member (graph authority).
POST /api/sessions/{session_id}/drives/{drive_id}/unassignUnassign (graph authority).
POST /api/sessions/{session_id}/drives/{drive_id}/ownerTransfer the ownership boundary (audited).
POST /api/sessions/{session_id}/drives/{drive_id}/transitionPause/resume/wait/block/cancel (generic transition).
POST /api/sessions/{session_id}/drives/{drive_id}/proposePropose a terminal transition (complete/fail) with evidence.
POST /api/sessions/{session_id}/drives/{drive_id}/approveApprove a pending terminal proposal.
POST /api/sessions/{session_id}/drives/{drive_id}/progressAppend a progress observation (append-only).
GET /api/sessions/{session_id}/drives/{drive_id}/deliveriesDelivery history (retry / recovery / dead-letter).
GET /api/sessions/{session_id}/drives/{drive_id}/progressAppend-only progress observations.
POST /api/sessions/{session_id}/drives/deliveries/{delivery_id}/replayReplay a dead-letter delivery (mints a new delivery).

Bodies and semantics:

  • Every mutating body carries expected_revision (except progress, which is append-only) and an optional idempotency_key. A stale revision is 409; a reused idempotency key with different content is also 409.
  • Detail/response shapes are the service DriveView serialization (record + assignment + derived availability/durability + actor-scoped allowed_actions).
  • propose returns the updated detail when policy accepts immediately, or {"proposal_id": ..., "target_status": ..., "pending": true} when a verifier or a distinct approver must finalize — then call approve with that proposal_id.
  • Drive is delivered at least once with logical dedupe; there is no exactly-once guarantee, and a resumed attempt may surface a recovery warning (see Drive).

GET /api/persistence/viewer/{session_name}/drives

Read-only persisted Drive rows for a saved (non-live) session, read straight from the session's Drive sidecar (<name>.kohakutr.drives) without resuming it. Rows are redacted and carry no allowed_actions (the viewer is read-only). 404 if the session file does not exist.

Status codes for Drive endpoints

Beyond the generic table, Drive errors map through the DriveError adapter in api/app.py:

StatusWhen
409revision conflict / idempotency conflict
403permission denied (actor lacks the capability)
422registration disabled / incompatible / not found, or an invalid transition
404Drive / session not found
400malformed body
429backpressure (per-graph / per-creature limit exceeded)

Error bodies are {"detail": "<message>"} like every other route.


Files

GET /api/files/tree

Nested file tree.

Query params: root (required), depth (default 3, clamped 1..10).

Response: recursive object {"name", "path", "type": "directory"|"file", "children": [...], "size"}.

GET /api/files/browse

Directory-browse view for filesystem UI.

Query params: path (optional).

Response: {"current": {...}, "parent": str|null, "roots": [...], "directories": [...]}.

GET /api/files/read

Read a text file.

  • Query params: path (required).
  • Response: {"path", "content", "size", "modified", "language"}.
  • Errors: binary files, permission denied → 400; missing → 404.

POST /api/files/write

  • Body: FileWrite (path, content).
  • Response: {"success": true, "size"}.
  • Side effects: parent directories created.

POST /api/files/rename

  • Body: FileRename (old_path, new_path).
  • Response: {"success": true}.

POST /api/files/delete

Delete a file or empty directory.

  • Body: FileDelete (path).
  • Response: {"success": true}.

POST /api/files/mkdir

Recursive mkdir.

  • Body: FileMkdir (path).
  • Response: {"success": true}.

Settings and configuration

API keys

GET /api/settings/keys

Return {"providers": [{"provider", "backend_type", "env_var", "has_key", "masked_key", "available", "built_in"}]}.

POST /api/settings/keys

  • Body: ApiKeyRequest (provider, key).
  • Response: {"status": "saved", "provider"}.

DELETE /api/settings/keys/{provider}

Response: {"status": "removed", "provider"}.

Codex

POST /api/settings/codex-login

Run the Codex OAuth flow server-side (server must be local). Response: {"status": "ok", "expires_at"}.

GET /api/settings/codex-status

Return {"authenticated", "expired"?}.

GET /api/settings/codex-usage

Fetch Codex usage for the past 14 days. Status: 200, 401 (token refresh failed), 404 (no login).

Backends

GET /api/settings/backends

{"backends": [{"name", "backend_type", "base_url", "api_key_env", "provider_name", "provider_native_tools", "built_in", "has_token", "available"}]}.

GET /api/settings/native-tools

Return metadata for every provider-native built-in tool: {"tools": [{"name", "provider_support", "description"}]}.

POST /api/settings/backends

  • Body: BackendRequest (name, backend_type default "openai", base_url, api_key_env, provider_name, provider_native_tools).
  • Response: {"status": "saved", "name"}.

DELETE /api/settings/backends/{name}

Response: {"status": "deleted", "name"}. Built-in backends cannot be deleted (400).

Profiles

GET /api/settings/profiles

{"profiles": [...]} with fields name, model, provider, backend_type, base_url, api_key_env, max_context, max_output, temperature, reasoning_effort, service_tier, extra_body.

POST /api/settings/profiles

  • Body: ProfileRequest.
  • Response: {"status": "saved", "name"}.

DELETE /api/settings/profiles/{name}

Response: {"status": "deleted", "name"}.

GET /api/settings/default-model

{"default_model"}.

POST /api/settings/default-model

  • Body: DefaultModelRequest (name).
  • Response: {"status": "set", "default_model"}.

GET /api/settings/models

Same as GET /api/configs/models.

UI prefs

GET /api/settings/ui-prefs

{"values": {...}}.

POST /api/settings/ui-prefs

  • Body: UIPrefsUpdateRequest (values).
  • Response: {"values": <merged>}.

MCP

GET /api/settings/mcp

{"servers": [{"name", "transport", "command", "args", "env", "url"}]}.

POST /api/settings/mcp

  • Body: MCPServerRequest.
  • Response: {"status": "saved", "name"}.

DELETE /api/settings/mcp/{name}

Response: {"status": "removed", "name"}.

Drive runtime

Node-targeted Drive settings over the Studio settings façade (never a raw file edit). Every endpoint takes an optional ?node=<id> query param (absent / _host = the host's own config home; a connected worker id routes to that worker's settings adapter). Reads are open; save and apply are admin-gated and are deliberately separate operations. See the drive-settings.yaml schema.

Method + pathPurpose
GET /api/settings/drivesSettings-file view: available/enabled registrations + load status.
GET /api/settings/drives/configThe raw validated settings + content-hash revision.
GET /api/settings/drives/runtime-statusThe running Drive runtime snapshot on the node.
POST /api/settings/drives/validateValidate a candidate settings mapping ({settings}).
PUT /api/settings/drivesPersist validated settings. Admin. Body {settings, expected_revision}; a stale expected_revision is 409.
POST /api/settings/drives/applyApply persisted settings to the live runtime. Admin.
  • Save ≠ apply. PUT only writes the file (optimistic-concurrency checked). POST .../apply returns {"result": "applied_live" | "restart_required" | "rejected", "desired_revision", "running_revision", "warnings"} so a UI never claims a saved file is running.
  • The registration catalog DTO (in the status payload) carries option_schema / option_defaults / prompt_preview for the Settings editor.
  • Availability is node-specific because package installation is node-specific; a settings mutation is an operator/admin control-plane action, not an ordinary Drive permission.

WebSocket endpoints

All WebSocket endpoints are bidirectional over a standard upgrade (no custom headers or subprotocols). Clients receive a stream of JSON frames and may send input frames. The server closes on error; there is no auto-reconnect or heartbeat; the client is responsible.

WS /ws/terrariums/{terrarium_id}

Unified event stream for an entire terrarium (root + creatures + channels).

Inbound frames:

  • {"type": "input", "target": "root"|<creature>, "content": str|list[dict], "message"?: str}: queues input for the target. Server acknowledges with {"type": "idle", "source": <target>, "ts": float}.
  • Other message types are ignored.

Outbound frames:

  • {"type": "activity", "activity_type": ..., "source", "ts", ...}: activity types include session_info, tool_call, tool_result, token_usage, job_update, job_completed, and more (see Event types).
  • {"type": "text", "content", "source", "ts"}: streaming text chunk.
  • {"type": "processing_start", "source", "ts"}.
  • {"type": "processing_end", "source", "ts"}.
  • {"type": "channel_message", "source": "channel", "channel", "sender", "content", "message_id", "timestamp", "ts", "history"?: bool}: history is true for the replay of messages that pre-date the connection.
  • {"type": "error", "content", "source"?, "ts"}.
  • {"type": "idle", "source"?, "ts"}.

Lifecycle:

  • Connection accepted immediately; terrarium missing → 404 before upgrade.
  • Channel history is replayed first.
  • Events stream in real time.
  • Client close is graceful; cleanup detaches outputs and removes callbacks.

WS /ws/creatures/{agent_id}

Event stream for a standalone agent.

Inbound frames: {"type": "input", "content": str|list[dict], "message"?: str}.

Outbound frames: same activity / text / processing_* / error / idle families as the terrarium stream. The first event is always {"type": "activity", "activity_type": "session_info", "source", "model", "agent_name", "ts"}.

WS /ws/agents/{agent_id}/chat

Simpler request-response chat channel.

Inbound: {"message": str}.

Outbound: {"type": "text", "content"}, {"type": "done"}, {"type": "error", "content"}.

Stays open across multiple turns.

WS /ws/terrariums/{terrarium_id}/channels

Read-only channel feed for a terrarium.

Outbound: {"type": "channel_message", "channel", "sender", "content", "message_id", "timestamp"}.

WS /ws/files/{agent_id}

File-change watch on an agent's working directory.

Outbound:

  • {"type": "ready", "root"}: watcher started.
  • {"type": "change", "changes": [{"path", "abs_path", "action": "added"|"modified"|"deleted"}]}: batched every 1 second. Hidden/ignored directories (.git, node_modules, __pycache__, .venv, .mypy_cache, …) are filtered.
  • {"type": "error", "text"}.

WS /ws/logs

Live tail of the server process's log file.

Outbound:

  • {"type": "meta", "path", "pid"}: sent on connect.
  • {"type": "line", "ts", "level", "module", "text"}: streamed.
  • {"type": "error", "text"}.

The server first replays the last ~200 lines, then streams new ones.

WS /ws/terminal/{agent_id}

Interactive PTY inside the agent's working directory.

Inbound:

  • {"type": "input", "data": str}: shell input (include \n to submit).
  • {"type": "resize", "rows": int, "cols": int}.

Outbound:

  • {"type": "output", "data": str} (UTF-8; invalid sequences replaced).
  • {"type": "error", "data": str}.

Implementation:

  • Unix: pty.openpty() + fork + exec.
  • Windows with winpty: ConPTY.
  • Fallback: plain pipes without PTY.
  • Initial {"type": "output", "data": ""} sent on connect.
  • On cleanup: SIGTERM then SIGKILL.

WS /ws/terminal/terrariums/{terrarium_id}/{target}

Same as the per-agent terminal, but resolves the creature or "root" inside a terrarium.


Schemas

Pydantic models used in request and response bodies.

TerrariumCreate

FieldTypeRequiredDefault
config_pathstryes
llmstr | Noneno
pwdstr | Noneno

TerrariumStatus

FieldTypeRequired
terrarium_idstryes
namestryes
runningboolyes
creaturesdictyes
channelslistyes

CreatureAdd

FieldTypeRequiredDefault
namestryes
config_pathstryes
listen_channelslist[str]no[]
send_channelslist[str]no[]

ChannelAdd

FieldTypeRequiredDefault
namestryes
channel_typestrnoaccepted for legacy compatibility, ignored (channels are broadcast)
descriptionstrno""

ChannelSend

FieldTypeRequiredDefault
contentstr | list[ContentPartPayload]yes
senderstrno"human"

WireChannel

FieldTypeRequired
channelstryes
direction"listen" | "send"yes

AgentCreate

FieldTypeRequiredDefault
config_pathstryes
llmstr | Noneno
pwdstr | Noneno

AgentChat

FieldTypeRequired
messagestr | Noneno
contentlist[ContentPartPayload] | Noneno

At least one of message or content must be provided.

MessageEdit

FieldTypeRequired
contentstryes

SlashCommand

FieldTypeRequiredDefault
commandstryes
argsstrno""

ModelSwitch

FieldTypeRequired
modelstryes

FileWrite

FieldTypeRequired
pathstryes
contentstryes

FileRename

FieldTypeRequired
old_pathstryes
new_pathstryes

FileDelete

FieldTypeRequired
pathstryes

FileMkdir

FieldTypeRequired
pathstryes

Content parts

ContentPartPayload is a discriminated union of TextPartPayload, ImagePartPayload, and FilePartPayload.

TextPartPayload

FieldTypeRequired
type"text"yes
textstryes

ImageUrlPayload

FieldTypeRequiredDefault
urlstryes
detail"auto" | "low" | "high"no"low"

ContentMetaPayload

FieldTypeRequired
source_typestr | Noneno
source_namestr | Noneno

ImagePartPayload

FieldTypeRequired
type"image_url"yes
image_urlImageUrlPayloadyes
metaContentMetaPayload | Noneno

FilePayload

FieldTypeRequiredDefault
pathstr | Noneno
namestr | Noneno
contentstr | Noneno
mimestr | Noneno
data_base64str | Noneno
encoding"utf-8" | "base64" | Noneno
is_inlineboolnoFalse

FilePartPayload

FieldTypeRequired
type"file"yes
fileFilePayloadyes

ScratchpadPatch

FieldTypeRequired
updatesdict[str, str | None]yes

null values delete the key.

ApiKeyRequest

FieldTypeRequired
providerstryes
keystryes

ProfileRequest

FieldTypeRequiredDefault
namestryes
modelstryes
providerstrno""
max_contextintno128000
max_outputintno16384
temperaturefloat | Noneno
reasoning_effortstrno""
service_tierstrno""
extra_bodydict | Noneno

BackendRequest

FieldTypeRequiredDefault
namestryes
backend_typestrno"openai"
base_urlstrno""
api_key_envstrno""
provider_namestrno""
provider_native_toolslist[str]no[]

DefaultModelRequest

FieldTypeRequired
namestryes

UIPrefsUpdateRequest

FieldTypeRequiredDefault
valuesdict[str, Any]no{}

InstallRequest

FieldTypeRequired
urlstryes
namestr | Noneno

UninstallRequest

FieldTypeRequired
namestryes

MCPServerRequest

FieldTypeRequiredDefault
namestryes
transportstrno"stdio"
commandstrno""
argslist[str]no[]
envdict[str, str]no{}
urlstrno""

Event types

Events are persisted to SessionStore and streamed over WebSockets. Every event carries type, source (originating agent/creature name), and ts (Unix seconds).

  • text: streaming text chunk.
    • content: str.
  • activity: assistant_message_edited: emitted when a post_llm_call plugin rewrites the final assistant message.
  • activity: diverse type discriminated by activity_type, e.g. session_info, tool_call, tool_result, token_usage, job_update, job_completed, model_switch, interrupt, regenerate, edit, rewind, promote, background_result, memory_compact, memory_search, memory_save, assistant_message_edited.
    • Additional fields depend on activity_type: args, job_id, tools_used, result, output, turns, duration, task, trigger_id, event_type, channel, sender, content, prompt_tokens, completion_tokens, total_tokens, cached_tokens, round, summary, messages_compacted, session_id, model, agent_name, max_context, compact_threshold, error_type, error, messages_cleared, background, subagent, tool, interrupted, final_state.
  • processing_start, processing_end.
  • user_input: content: str | list[dict].
  • channel_message: channel, sender, content, message_id, timestamp.

Session storage

Sessions live in ~/.kohakuterrarium/sessions/ with extension .kohakutr (legacy .kt still accepted). Binary artifacts may live in a sibling <session>.artifacts/ directory. See concepts/impl-notes/session-persistence for the table layout and resume path.

Notes for integrators

  • HTTP chat endpoints are non-streaming. For streaming, use the matching WebSocket.
  • Channel history is included on WS connect for both /ws/terrariums/{id} and /ws/terrariums/{id}/channels; historical frames carry "history": true.
  • /ws/files/{agent_id} requires the agent to have a working directory.
  • Terminal clients must send a resize frame whenever the local terminal is resized.

See also