Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Capped Conversation History

Maintains a sliding window of conversation messages and persists them to the session as JSONL. Default provider of memory.history.

Details

IDnexus.memory.capped
DependenciesNone

Configuration

KeyTypeDefaultDescription
max_messagesint100Maximum messages to keep in the buffer
persistbooltrueWrite messages to context/conversation.jsonl in the session

Events

Subscribes To

EventPriorityPurpose
io.input10Records user messages
io.output10Records agent responses
tool.invoke50Records tool calls
tool.result50Records tool results
memory.store50Explicit memory storage requests
memory.query50Responds to history queries
memory.compacted50Replaces history with compacted version

Emits

EventWhen
memory.resultResponse to a memory.query

Behavior

  • Messages are stored in a rolling buffer of size max_messages
  • Oldest messages are dropped when the buffer is full
  • If persist: true, each message is appended to context/conversation.jsonl as it arrives
  • On memory.compacted, the buffer is replaced with the compacted messages
  • An llm.response produced by an internal sub-flow is not recorded. The filter reads task_kind off the response metadata, and the skipped set is plan, classify, summarise, compact, subagent and delegate — each of those loops keeps a history of its own. The main agent loops (react_main, planexec_step, orchestrator_decompose, orchestrator_synthesize) are deliberately absent because they are the conversation. Recording a sub-flow’s tool-calling response leaves two consecutive assistant turns in the user-facing history, which Gemini rejects with 400 INVALID_ARGUMENT.
  • A tool.result is recorded only when it belongs to the conversation’s own turn. Sub-flows dispatch their tools on the same bus, so the buffer learns the conversation’s TurnID from a tool.invoke whose ID a recorded assistant message declared, and drops a result carrying any other. Without it the history grew a tool message whose ToolCallID no assistant turn had asked for. The filter is inert until that turn is known and on a result with an empty TurnID — an engine caller may drive the tool bus with no turn at all — which is the same posture nexus.agent.react takes on its pending-call count. The existing ParentCallID filter still applies independently, for sub-calls fired from inside another tool.

Querying History

Other plugins can query conversation history:

bus.Emit("memory.query", events.MemoryQuery{
    Query:     "",    // Not filtered — returns all
    Limit:     50,    // Max messages to return
    SessionID: "...", // Current session
})
// Listen for memory.result event with the messages

Example Configuration

nexus.memory.capped:
  max_messages: 200
  persist: true