AI agent state hooks (useKoderAgent / useKoderChat)

Framework-neutral state-machine contract for any AI chat / agent surface in a Koder product — message list, streaming state, append handlers, abort, retry, tool-call lifecycle. Companion to patterns/ai-chat-surface.kmd (which ratifies the 6 primitives); this spec ratifies the state machine driving them. Modeled after Ant Design X `useXAgent` / `useXChat`.

Pattern — AI agent state hooks

*tatus* v0.1.0 — Draft. Contractonly; perframework implementations (useKoderAgent for React, KoderAgentController for Flutter, KoderAgentViewModel for Kotlin / Swift) live in the respective SDK backlogs.

R1 — Three roles, one machine

The state machine has three logical roles a consumer may attach to:

Role Responsibility
*gent* Owns the transport: opens connection, streams tokens, surfaces lifecycle events.
*hat* Owns the message list: append user message, append assistant streaming, mutate history, dispatch retry.
*ool host* Owns toolcall lifecycle (when AI emits a tool call): present permission prompt, dispatch tool, append toolresult to history.

A simple chatonly surface attaches Chat alone. An agentheavy surface (Kortex assist with tool use) attaches all three.

R2 — Required state shape

Every framework binding MUST expose at least:

Field Type Notes
messages Message[] Ordered history (user / assistant / system / tool)
streaming boolean True between requestSend and streamEnd / error
lastError Error | null Most recent transport / model error (cleared on requestSend)
abortable boolean True iff a stream can be aborted now
pendingToolCalls ToolCall[] Tool calls awaiting user permission per specs/ai-ui/permission-prompt.kmd

R3 — Required actions

Action Effect
send(text, attachments?) Append user message → start stream
regenerate(messageId) Re-run from the prior turn; discards subsequent history
edit(messageId, newText) Mutate user message → regenerate from that turn
branch(messageId) Fork: create a new conversation from this turn (per specs/ai-ui/conversation-history.kmd)
abort() Cancel in-flight stream; partial response retained in history with aborted: true
clear() Reset to empty (does NOT delete conversation in storage; that's a separate delete() for explicit removal)

R4 — Lifecycle events (emitted to subscribers)

onUserMessageonStreamStartonStreamToken (many) → onToolCall (optional) → onStreamEnd | onError | onAbort

Consumers MAY hook any subset. The state shape (R2) is the canonical view; events are for side effects (telemetry, sound, haptics).

R5 — Tool-call lifecycle (subset)

When AI emits a tool call:

  1. pendingToolCalls gets the entry, streaming: false.
  2. Consumer surfaces permission-prompt.kmd UI.
  3. On approve: framework binding dispatches to the registered tool;

    on result, messages gains a tool entry; streaming: true resumes.

  4. On deny: append tool entry with denied: true; streaming: true

    resumes (AI sees denial in context).

R6 — Multi-conversation

The hook is scoped per-conversation. Switching conversations (per specs/ai-ui/conversation-history.kmd) instantiates a new hook with its own state. Consumers maintain a list of hook instances or use a provider that supplies the active one.

R7 — Concurrency

  • Only one send can be in flight per hook instance.
  • A second send while streaming: true is rejected with a clear

    error; consumers should disable the Sender button (#066 Sender primitive R3 morphs to Stop).

R8 — i18n + a11y

State is localeneutral; userfacing strings come from patterns/ ai-chat-surface.kmd + patterns/ai-feature-visual-language.kmd disclosure microcopy. Hooks MUST surface streaming state in a form the Bubble primitive can route to aria-live="polite".

Não-escopo

  • Transport protocol (SSE / WebSocket / chunked HTTP) — separate

    specs/ai-ui/transport.kmd if pursued.

  • Token counting / cost display — covered by specs/ai-ui/cost-display.kmd.
  • Memory / RAG retrieval — separate spec.

Source: ../home/koder/dev/koder/meta/docs/stack/specs/patterns/ai-agent-state-hooks.kmd