Message Flow
This page traces the lifecycle of a message from an external channel through the Komand system and back, including tool execution, real-time updates, and error handling.
Inbound Flow
Section titled “Inbound Flow”1. User sends message on Telegram │2. Telegram webhook hits Gateway API │3. Channel adapter normalizes to InboundMessage │4. Gateway resolves SessionGrain key: "Telegram:{botId}:{userId}" │5. SessionGrain.HandleMessageAsync() ├── Lazy-initializes if new session (sets channel, sender, timestamps) ├── Validates channel matches existing session ├── Retrieves conversation history from session state └── Routes to bound AgentGrain with history │6. AgentGrain.ProcessMessageAsync(message, sessionId, history) ├── Sends system prompt + history to LLM via ChatCompletionProviderFactory ├── If LLM returns tool calls → enters tool loop (max 10 iterations, 2-min timeout) │ ├── Executes each tool via ToolGrain │ ├── Feeds tool results back to LLM │ └── Repeats until LLM returns a text response or limits reached ├── Returns AgentProcessResult with response + new conversation turns └── SessionGrain appends new turns to history │7. Gateway sends response back via channel adapter │8. User receives reply on TelegramChannel Adapters
Section titled “Channel Adapters”Channel adapters are thin ASP.NET webhook endpoints that normalise platform-specific payloads into a common message format and route them to the appropriate SessionGrain.
Supported Channels
Section titled “Supported Channels”| Channel | Transport | Status |
|---|---|---|
| WebChat | REST + SignalR | Implemented |
| Slack | Webhook | Planned |
| Microsoft Teams | Webhook | Planned |
| Discord | Webhook | Planned |
| Telegram | Webhook | Planned |
| Webhook | Planned | |
| Signal | Webhook | Planned |
| SMS | Webhook | Planned |
All channels normalise to the same InboundMessage/OutboundMessage format, so grains are completely channel-agnostic. Adding a new channel means writing a thin adapter — the grain layer requires no changes.
Message Format
Section titled “Message Format”All channels normalize messages to a common InboundMessage format:
{ "messageId": "msg-abc-123", "channel": "Telegram", "channelAccountId": "bot-456", "senderId": "user-789", "senderDisplayName": "Alice", "text": "Book a meeting with Bob tomorrow at 2pm", "timestamp": "2026-02-23T10:30:00Z", "attachments": [], "metadata": { "telegramChatId": "12345" }}Responses use a corresponding OutboundMessage format:
{ "sessionId": "Telegram:bot-456:user-789", "channel": "Telegram", "recipientId": "user-789", "text": "I've booked a meeting with Bob for tomorrow at 2:00 PM.", "timestamp": "2026-02-23T10:30:02Z", "attachments": []}Conversation Turns
Section titled “Conversation Turns”Inside the SessionGrain, messages are stored as conversation turns:
{ "role": "user", "content": "Book a meeting with Bob tomorrow at 2pm", "timestamp": "2026-02-23T10:30:00Z", "toolCallIds": null}{ "role": "assistant", "content": "I've booked a meeting with Bob for tomorrow at 2:00 PM.", "timestamp": "2026-02-23T10:30:02Z", "toolCallIds": ["exec-abc-123"]}Tool Execution Flow
Section titled “Tool Execution Flow”When the LLM decides to use a tool, the AgentGrain enters a tool-calling loop:
AgentGrain receives LLM response with tool_calls │For each tool call in the response:├── Resolves handler from ToolHandlerRegistry (built-in tools)│ or validates against SkillRegistryGrain (installed skills)├── Creates ToolGrain with unique executionId├── ToolGrain.ExecuteAsync(request)│ ├── Status: Pending → Running│ ├── Executes with enforced timeout (capped at MaxToolExecutionTimeoutMinutes)│ ├── Status: Running → Completed | Failed | TimedOut | Cancelled│ └── Returns ToolExecutionResult└── Stores tool result as a conversation turn │Feeds all tool results back to LLM │If LLM returns more tool calls → loop continues (max 10 iterations, 2-min timeout)If LLM returns text → loop ends, final response returnedBuilt-in Tool Handlers
Section titled “Built-in Tool Handlers”| Tool | Description |
|---|---|
echo | Echoes input text — used for testing |
calculator | Evaluates mathematical expressions with safe validation |
date_time | Returns current date/time and time zone operations |
web_fetch | Fetches web content (with DNS rebinding protection via NetworkGuard) |
Tool Execution Request
Section titled “Tool Execution Request”{ "executionId": "exec-abc-123", "toolName": "calendar-booking", "agentId": "default", "sessionId": "session-xyz", "parameters": { "attendee": "Bob", "date": "2026-02-24", "time": "14:00" }, "timeout": "00:05:00", "requestedAt": "2026-02-23T10:30:01Z"}Tool Execution Result
Section titled “Tool Execution Result”{ "executionId": "exec-abc-123", "status": "Completed", "output": "{\"eventId\":\"cal-789\",\"confirmed\":true,\"time\":\"2026-02-24T14:00:00Z\"}", "error": null, "completedAt": "2026-02-23T10:30:02Z", "duration": "00:00:01.234"}Session Binding
Section titled “Session Binding”Sessions are automatically created on first contact. By default, all sessions bind to the "default" agent. The session key is derived from the channel, account, and sender:
Key: "{Channel}:{ChannelAccountId}:{SenderId}"Example: "Telegram:bot-456:user-789"This key structure means:
- The same user on Telegram and Slack gets separate sessions
- The same Telegram user talking to different bots gets separate sessions
- The same Telegram user talking to the same bot always gets the same session
Rebinding
Section titled “Rebinding”You can rebind a session to a different agent:
curl -X POST http://localhost:5000/api/sessions/{sessionId}/bind \ -H "Content-Type: application/json" \ -d '{ "agentId": "sales-bot" }'After rebinding, all future messages in that session are routed to the new agent.
Timeout Handling
Section titled “Timeout Handling”Gateway Timeout
Section titled “Gateway Timeout”All grain calls from the Gateway have a 30-second timeout. If a grain doesn’t respond within this window, the API returns 504 Gateway Timeout:
{ "success": false, "error": "Request timed out after 30 seconds"}Tool Execution Timeout
Section titled “Tool Execution Timeout”Tool executions have a separate, configurable timeout managed by the ToolGrain. The timeout is specified per-request and capped at MaxToolExecutionTimeoutMinutes (default: 30 minutes).
If a tool exceeds its timeout:
- The
ToolGraintransitions toTimedOutstatus - The
AgentGrainreceives an error result - The LLM is informed the tool timed out and generates an appropriate response
Real-Time Updates
Section titled “Real-Time Updates”The WebChat channel uses SignalR for bi-directional real-time communication. The web dashboard connects to the SignalR hub for live message streaming.
Hub Methods
Section titled “Hub Methods”| Method | Direction | Purpose |
|---|---|---|
SendMessage | Client → Server | Send a message to the agent |
ReceiveMessage | Server → Client | Receive the agent’s complete response |
ReceiveToken | Server → Client | Receive a streaming token (agentId, token, isComplete) |
AgentTyping | Server → Client | Indicate the agent is generating a response |
The connection uses exponential backoff for reconnection and gracefully handles disconnects.
Streaming Flow
Section titled “Streaming Flow”The SignalR hub supports token-by-token streaming for responsive chat experiences:
1. Client calls SendMessage(agentId, content)2. Hub sends AgentTyping(agentId, true)3. Hub calls AgentGrain.PrepareStreamingResponseAsync()4. As LLM generates tokens → ReceiveToken(agentId, token, false)5. When complete → ReceiveToken(agentId, null, true)6. Hub sends ReceiveMessage with full response7. Hub sends AgentTyping(agentId, false)Frontend Integration
Section titled “Frontend Integration”The React frontend uses the @microsoft/signalr package to manage the connection:
- Auth store provides the Bearer token for authenticated connections
- Chat store manages message state and real-time updates via Zustand
- Correlation ID is sent as
X-Request-Idon every API call for end-to-end tracing
Audit Trail
Section titled “Audit Trail”Every significant action in the message flow generates an audit log entry. These are persisted to a dedicated komand_audit_log table in PostgreSQL.
For a typical message exchange, the audit trail captures:
MessageReceived— inbound message arrives at the GatewaySessionCreated— if this is the first message (new session)ToolExecutionStarted— if the agent invokes a toolToolExecutionCompletedorToolExecutionFailed— tool resultMessageSent— outbound response dispatched
Additional audit actions for other subsystems: CronExecuted, CronFailed, CredentialAccessed, CredentialStored, CredentialDeleted.
Each entry includes the actor ID, agent ID, session ID, and timestamp, enabling full reconstruction of any conversation.
Error Handling
Section titled “Error Handling”| Error | Response | Recovery |
|---|---|---|
| Unknown agent | 404 Not Found | Check agent ID exists |
| Session channel mismatch | 400 Bad Request | Use correct session key |
| Skill permission denied | 403 Forbidden | Grant required permissions |
| Grain timeout | 504 Gateway Timeout | Retry or check silo health |
| Tool execution failed | Error in LLM context | LLM generates error-aware response |
| Tool execution timed out | Timeout in LLM context | LLM informs user of timeout |
| Validation failure | 400 Bad Request | Fix input per error message |