Skip to content

Chat

Chat is the operator Q&A surface. The TUI and both GUI targets (Desktop and Web) use the same agent API. Chat answers questions, files work, and remembers facts — it never implements code itself.

Work still goes through GitHub issues. When Chat files work, the agent creates the issue, applies pickup labels, and starts task_lifecycle immediately.

Send a message to a specific agent. Optional focus (an issue or PR number) scopes retrieve and the answer:

  • TUI: the Chat panel.
  • GUI: the Chat surface. Focus comes from a Dashboard delivered row.

Endpoints:

MethodPathPurpose
POST/api/operator/chatBlocking JSON answer
POST/api/operator/chat/streamServer-sent events
GET/api/operator/chatHistory (?limit= &session=)
POST/api/operator/chat/stopStop this operator’s in-flight turn
POST/api/operator/chat/clearClear the conversation

The SSE stream emits JSON data frames, each with a type:

typeMeaning
statusLive phase, e.g. retrieve 1/3 · 2 hits
thinkingChain-of-thought token
deltaAssistant reply token
doneFull ChatResponse (reply, intent, history, retrieve hits)
errorTurn failed

The reply is streamed as best-effort Markdown, then replaced by the finalized text on done. A second, grounded draft replaces the first; it is never concatenated.

When the coding runtime emits reasoning tokens, they arrive as thinking events. The GUI shows them in a collapsed Thinking accordion above the reply; the TUI shows them inline while they stream. The finalized reasoning is persisted with the assistant turn (reasoning, capped at 12,000 characters) and reappears when you reopen the chat.

Every Chat turn retrieves session memory in-process — the same cosine path workers use, no HTTP hop. The agent:

  1. Runs up to 3 retrieve rounds per turn.
  2. Keeps only semantic hits with a cosine score at or above the Chat floor (a named constant, not a config key).
  3. Injects the hits as a Prior Steelframe context: block. If retrieve falls back to recency summaries, it injects (no semantic hits) instead of pretending they were retrieve.
  4. After drafting, checks whether the reply cites a kept hit (#issue, session_id, or a distinctive token). If not, it widens once and answers again.

The response carries retrieve_hits (injected count) and retrieved (the chunks, with source, score, issue/PR, paths, and a text preview). The GUI shows these in an expandable retrieve N hits row under the reply.

Retrieve errors never fail the turn — Chat still answers from task JSON and the repo checkout.

Chat history is grouped into sessions, one table per operator.

MethodPathPurpose
GET/api/operator/chat/sessionsList sessions, newest activity first
POST/api/operator/chat/newClose the open session and start a fresh one
GET/api/operator/chat/session/{session}Messages + stored summary
DELETE/api/operator/chat/session/{session}Delete a session and its turns

Starting a new chat summarizes the previous session in the background and titles the new one from your first message (up to 60 characters). Reopening a session pins the next message to it so the conversation continues in place.

The model replies with a STEELFRAME_META trailer carrying an intent:

IntentEffect
answer / statusNo work filed
file_work, open_issue, create_issue, propose_workFiles a GitHub issue, starts task_lifecycle
file_memoryFiles a committed-memory issue (adds steelframe:memory)

Filed issues get your configured pickup_labels (default steelframe:work-on-this) plus steelframe:from-tui. The done event includes filed_issue_number, filed_issue_url, and filed_task_started.

  • Chat works in a checkout of the project repo. Treat it as a shared, read-only cache: Chat is told not to modify, commit, or push.
  • Committed memory (docs/memory/) outranks retrieve. Ask Chat to remember a lasting fact and it files a steelframe:memory issue; a worker opens the PR.
  • An empty message is rejected with 400 message is required.
  • The blocking POST /api/operator/chat path has no live status; use the SSE stream for thinking/retrieve feedback.
  • Stop aborts the in-flight turn; it is not persisted, so the next message starts fresh.