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 shimmers the in-flight thinking label, then collapses the reasoning into a 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. Optional JSON { title, issue }
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.

A Discuss session is Chat focused on one GitHub issue. POST /api/operator/chat/new may include { title, issue }; the issue scopes retrieve and context. The operator steers; Chat still does not implement.

On the GUI Board, Discuss sits next to Archive on an issue card and next to Terminate in the run modal (hidden without a GitHub issue). It starts a new session titled Discuss #N, focuses that issue, and switches to the Chat surface. The operator speaks first — Discuss does not auto-send. Older Discuss sessions stay in the sidebar.

On the TUI Board, e does the same: new Chat titled Discuss #N, focused on the selected issue, composer empty. d stays delete.

When a Decision is ready, Chat uses intent decide and returns a draft in decision_body. Nothing is posted to GitHub on that turn.

The GUI shows a Publish bar above the composer: an editable Decision preview and Publish to #N. The draft survives the next user turn and restores when you reopen the session. It clears on Publish success, focus clear, new chat, Discuss, or agent switch. Empty decide drafts (or no focused issue) do not show the bar.

In the TUI Chat panel, capital P publishes the Decision draft when decision_body is set and the input is empty (status: Chat · Decision draft · P publish to #N). Do not reuse y — that still files a GitHub issue from pending_title only. Intent decide does not prompt “press y to file”.

The operator Publishes explicitly:

MethodPathPurpose
POST/api/operator/chat/decisionPublish a Decision comment (issue, body, optional session_id)

Publish posts the comment with marker <!-- steelframe-decision --> (once) and removes steelframe:needs_human when present. That marker resumes waiting_for_info, including when the GitHub App authors the comment.

The model replies with a STEELFRAME_META trailer carrying an intent:

IntentEffect
answer / statusNo work filed
decideDrafts a Decision in decision_body; does not post
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.
  • decide only drafts; Publish is a separate explicit step.