Org Chat
The main agent-interaction endpoint. Every path that results in an agent doing work — web UI chat, MCP do() tool, inbound WhatsApp/Slack/email messages — ultimately calls this.
POST /api/org/chat/message
Sends a message to a specific node and streams the agent’s response.
Request
POST /api/org/chat/message
Content-Type: application/json
Authorization: Bearer <token>
{
"structure_id": "uuid-of-the-workspace",
"role_id": "executive-office-cmo",
"message": "Launch this week's paid campaigns.",
"attachments": [
{
"name": "marketing-plan.pdf",
"content_base64": "...",
"content_type": "application/pdf"
}
]
}| Field | Type | Required | Notes |
|---|---|---|---|
structure_id | string (UUID) | ✅ | The workspace holding the target node. |
role_id | string | ✅ | The target node’s role (e.g. executive-office-cmo). |
message | string | ✅ | Natural-language request for the agent. |
attachments | array | ❌ | Optional files staged to the agent’s artifact shelf. |
Response
200 OK — a text/event-stream of newline-delimited JSON events:
data: {"type":"thought","content":"Looking up skills for 'launch campaigns'...","timestamp":"2026-04-20T14:30:00Z"}
data: {"type":"tool_call","content":{"tool":"list_skills"},"timestamp":"..."}
data: {"type":"tool_result","content":{"matched":"ad-campaigns"},"timestamp":"..."}
data: {"type":"text","content":"I'll run the ad-campaigns skill.","timestamp":"..."}
data: {"type":"tool_call","content":{"tool":"validate_prereqs"},"timestamp":"..."}
...
data: {"type":"complete","content":{"summary":"Launched 3 campaigns","artifacts":["campaign-config.json"]},"timestamp":"..."}Event types:
| Type | Payload | Meaning |
|---|---|---|
thought | {content: string} | Agent’s internal reasoning step. |
tool_call | {tool, args} | Agent invoked a tool inside its container. |
tool_result | {tool, result} | Tool returned. |
text | {content} | Streamed text the agent produced (partial reply). |
decision | {title, body} | Agent committed a decision to the workspace log. |
milestone | {title} | Agent flagged a milestone. |
skill_learned | {slug, name} | Agent learned a new skill — persisted as markdown. |
complete | {summary, artifacts[]} | Terminal event. |
error | {message} | Terminal event; something went wrong. |
How the stream terminates
Either complete or error is always the last event. Long-running skills can run for minutes — the stream stays open and emits events as the agent progresses.
If the HTTP connection drops mid-stream, the agent keeps running. Re-read with GET /api/org/chat/history (below) to pick up where you left off.
POST /api/org/chat/message/sync
Same as above but non-streaming. Returns once the agent’s turn completes. Use for short tasks (under 30s) or when your HTTP client can’t handle streaming.
Response
200 OK:
{
"events": [ ... full event list ... ],
"summary": "string",
"artifacts": [ ... ]
}GET /api/org/chat/history
Returns recent chat history for a node.
Request
GET /api/org/chat/history?structure_id=<uuid>&role_id=<role>&limit=50Response
200 OK — array of historical messages (both user-side and agent-side), newest first.
Upstream detail
org_chat_service.stream_message() (cto-gui-libvirt-backend/app/services/org_chat_service.py:1986) is the dispatcher. It:
- Enriches the request with workspace context + agent starting context.
- Routes to either the agent’s container (if deployed) or the local Claude SDK fallback.
- Streams events back as they’re produced.
- Persists the final state + any learned skills + artifacts to disk.
This is the endpoint the direktor-mcp do() tool forwards to. Every surface into the agent flows through this single funnel.