How GoClaw tracks conversations and manages message history.
A session is a conversation thread between a user and an agent on a specific channel. GoClaw stores message history in PostgreSQL, automatically compacts long conversations, and manages concurrency so agents don't trip over each other.
Every session has a unique key that identifies the user, agent, channel, and chat type:
agent:{agentId}:{channel}:{kind}:{chatId}
| Type | Key Format | Example |
|---|---|---|
| DM | agent:default:telegram:direct:386246614 |
Private chat |
| Group | agent:default:telegram:group:-100123456 |
Group chat |
| Topic | agent:default:telegram:group:-100123456:topic:99 |
Forum topic |
| Thread | agent:default:telegram:direct:386246614:thread:5 |
Threaded reply |
| Subagent | agent:default:subagent:my-task |
Spawned subtask |
| Cron | agent:default:cron:reminder-job |
Scheduled job |
This key format means the same user talking to the same agent on Telegram and Discord has two separate sessions with independent history.
Session Metadata: Each session tracks additional fields alongside the key:
label(display name),channel,model,provider,spawned_by(parent session ID for subagents),spawn_depth,input_tokens,output_tokens,compaction_count,context_window,last_prompt_tokens, andlast_message_count. These fields are queryable for analytics and debugging purposes.
last_prompt_tokensandlast_message_countare written by FinalizeStage at the end of every run and are read by the session-list query to display accurate token and message counts in the UI. Older sessions that pre-date this field fall back to an octet-length estimate (octet_length(messages) / 4 + 12000) so the UI always has a number to display.
Messages are stored as JSONB in PostgreSQL with a write-behind cache:
- Read — On first access, load from DB into memory cache
- Write — Messages accumulate in memory during a turn
- Flush — At the end of the turn, all messages write to DB atomically
- List — Session listing always reads from DB (not cache)
This approach minimizes DB writes while ensuring durability.
Before sending history to the LLM, GoClaw runs a 3-stage pipeline:
Keep only the last N user turns (and their associated assistant/tool messages). Older turns are dropped to stay within the context window.
Tool results can be large. GoClaw trims them in two passes:
| Condition | Action |
|---|---|
| Token ratio ≥ 0.3 | Soft trim: Tool results exceeding 4,000 chars → keep first 1,500 + last 1,500 |
| Token ratio ≥ 0.5 | Hard clear: Replace entire tool result with [Old tool result content cleared] |
Protected messages (never pruned): last 3 assistant messages. System message(s) and the first user message form a stable prefix that is never pruned.
Repair broken tool_use/tool_result pairs that were split by truncation. The LLM expects matched pairs — orphaned tool calls cause errors.
In v3 (enabled via pipeline_enabled feature flag), the agent loop is restructured into an 8-stage pipeline that replaces the v2 monolithic runLoop(). The session flow maps to these stages:
| Stage | What happens |
|---|---|
| ContextStage (once) | Inject context values, resolve per-user workspace, ensure per-user files |
| ThinkStage | Build system prompt, run history pipeline, filter tools (PolicyEngine), call LLM |
| PruneStage | Estimate token ratio; soft trim at ≥30%, hard clear at ≥50%; trigger memory flush if compaction threshold hit |
| ToolStage | Execute tool calls — single tool sequential, multiple tools parallel with result sorting |
| ObserveStage | Process tool results, handle NO_REPLY, append assistant message |
| CheckpointStage | Increment iteration counter; break on max iterations or cancellation |
| FinalizeStage (once) | Sanitize output, flush messages atomically, update session metadata, emit run event |
Memory consolidation in v3: The PruneStage triggers memory flush synchronously during the iteration loop (not only at end-of-session). This means long-running turns extract episodic facts before history is pruned, rather than waiting for the post-turn compaction phase. The same 75% context window threshold applies.
Both v2 and v3 expose identical external behavior; the pipeline difference is internal architecture.
Long conversations trigger automatic compaction:
Triggers:
- More than 50 messages in the session, OR
- History exceeds 75% of the agent's context window
What happens:
graph LR
T[Trigger<br/>50+ msgs or 75% ctx] --> MF[Memory Flush<br/>Extract facts → memory]
MF --> SUM[Summarize<br/>Condense history]
SUM --> INJ[Inject<br/>Summary replaces old msgs]
- Memory flush (synchronous, 90s timeout) — Important facts are extracted and saved to the memory system
- Summarize (background, 120s timeout) — Old messages are condensed into a summary
- Inject — The summary replaces old messages; at least 4 messages (or 30% of total, whichever is greater) are kept verbatim
A per-session lock prevents concurrent compaction. If a second compaction triggers while one is running, it's skipped.
GoClaw may also compact history during a long agent turn if the context exceeds the threshold mid-loop. The same 75% summarization logic applies. This is transparent to the agent — it continues running with the compacted history injected.
If the context budget is still exceeded after a compaction sweep (for example, when the system prompt and tool schemas alone nearly fill the window), GoClaw performs a secondary recovery sweep before returning an error. This overflow recovery path (introduced in PR #958) caps the number of retry attempts at one and surfaces an error to the caller only if the context is still over budget after the recovery sweep. In practice this prevents hard context-overflow failures for agents with very large tool schemas or system prompts.
| Chat Type | Max Concurrent | Notes |
|---|---|---|
| DM | 1 | Single-threaded — messages queue up |
| Group | 1 (configurable) | Serial by default; can be increased via ScheduleOpts.MaxConcurrent |
Group sessions may reduce concurrency when context usage is high.
Configuring concurrency: Both DM and Group default to serial processing (
MaxConcurrent: 1). Higher values (e.g. 3) can be set for team members or agent links viaScheduleOpts.MaxConcurrent.
| Mode | Behavior |
|---|---|
queue |
FIFO — messages processed in order |
followup |
New message merges with the queued one |
interrupt |
Cancel current task, process new message |
Queue capacity is 10 by default. When full, the oldest message is dropped (drop policy: old). The default debounce window is 800ms — rapid messages within this window are merged before processing.
/stop— Cancel the oldest running task/stopall— Cancel all tasks and drain the queue
| Problem | Solution |
|---|---|
| Agent "forgot" earlier messages | History was compacted; check memory for extracted facts |
| Slow responses in groups | Reduce group concurrency or context window size |
| Duplicate responses | Check queue mode; queue mode prevents this |
- Memory System — How long-term memory works
- Tools Overview — Available tools for agents
- Multi-Tenancy — Per-user session isolation