Skip to content

Agent Loop

scarecr0w12 edited this page Jun 23, 2026 · 6 revisions

Agent Loop

The agent loop is the core of CortexPrism. The agentTurn() orchestrator (81 lines in src/agent/loop.ts) calls 7 pipeline stages sequentially through a shared TurnContext, with supporting modules in post/ and helpers/. The full agent implementation lives in packages/ai/src/agent/.

agentTurn() Flow

agentTurn(opts)
  1. pipeline hooks (pre-assess stage)
  2. injectMemory(systemPrompt, hits)   ← prepend relevant memory with category, tags, entities
  3. MQM model selection (pre-llm hook) ← enforce/suggest/defer based on confidence
  4. persistMessage(userMessage)
  5. [TOOL LOOP — up to MAX_TOOL_ROUNDS=12]
      a. LLM call (buffered stream when tools registered)
      b. parseToolCalls(response)        ← extract <tool_call> XML + bare JSON fallback (5 formats)
      c. for each call:
         - validateToolCall()            ← Parallax policy regex check
         - [if sensitive data] → LLM supervisor review → human approval gate
         - tool.execute()
         - logEvent(tool_call)
      d. formatToolResults() → re-prompt with escalating deadline instruction
  6. persistMessage(agentResponse, stripped of tool calls)
  7. incrementTurn(sessionId)
  8. pipeline hooks (post-output stage)
  9. writeEpisodic(summary)             ← fire-and-forget
  10. reflectOnTurn() [if enabled]       ← fire-and-forget
   11. extractSkillFromSession()          ← fire-and-forget (≥2 tool calls)
   12. capture memori checkpoint         ← fire-and-forget, auto-prune to last 5
   return AgentTurnResult

Stage Modules (v0.49.0+)

The agent loop is decomposed into 7 pipeline stages under src/agent/stages/:

Stage File Purpose
Setup setup.ts Initialize session state, load preferences
History history.ts Load and inject conversation history
Assessment assessment.ts Run metacognition, plan creation
Prompt Builder prompt-builder.ts Build system prompt (soul + memory + skills + tools)
Model Selector model-selector.ts MQM model selection + auto model pool
LLM Stream llm-stream.ts LLM call, tool parsing, tool execution loop
Tool Executor tool-executor.ts Tool validation, execution, result formatting

Post-turn modules in src/agent/post/ handle response, background tasks, and cleanup. Helpers in src/agent/helpers/ provide preferences, nanoid generation, and tool-call stripping utilities.

Options

Option Type Purpose
userMessage string User input
userContentBlocks ContentBlock[] Multimodal content (images, documents)
provider LLMProvider Active LLM provider
model string Model name
sessionDb Db Per-session SQLite instance
sessionId string Session identifier
systemPrompt string System prompt (with injected memory + skills)
stream boolean Stream output chunks
onChunk function Chunk callback for streaming
registry ToolRegistry Registered tools
toolContext ToolContext Working dir, approval gate, policy state
embedder EmbeddingProvider For memory retrieval
enableReflection boolean Post-turn reflection
reasoningEffort string Extended thinking budget (low/medium/high)
signal AbortSignal Request timeout (90s default for tool rounds)

Tool Follow-up Loop

After each tool execution round, the agent receives tool results and is re-prompted:

  1. Tool results are formatted as <tool_result> XML blocks with structured error info
  2. Results are appended as a user message in the conversation with a Quartermaster tool suggestion hint (if prediction confidence > threshold)
  3. The LLM is called again with updated message history
  4. Up to 12 rounds (MAX_TOOL_ROUNDS) before hitting the ceiling
  5. When ≤2 rounds remain, an urgency nudge instruction stops tool calling, explicitly suggests file_write for file creation tasks, and demands producing the deliverable
  6. Follow-up instructions escalate per round to prevent infinite tool loops

All tool rounds use buffered stream() with a 90-second AbortSignal timeout to prevent hanging on slow providers.

Security Supervisor Integration

When tools access sensitive data (memory_search, db_query, browser, computer):

  1. Results are classified for sensitivity (SECRET/SENSITIVE/NORMAL/PUBLIC)
  2. SENSITIVE/SECRET results trigger the LLM supervisor review
  3. Supervisor evaluates agent intent, data sensitivity, and operational context
  4. High-confidence decisions auto-approve; low-confidence escalate to human
  5. Human approval (CLI prompt or Web UI modal with 60s timeout)
  6. Approved decisions cached per session (1-hour TTL) to prevent approval fatigue

Tool Call Parsing (v0.46+)

The parser recognizes 5 output formats to maximize compatibility across LLM providers:

  1. JSON inside <tool_call> tags: <tool_call>{"name": "...", "args": {...}}</tool_call>
  2. Nested XML: <tool_call_name>name</tool_call_name><tool_call_args>{...}</tool_call_args>
  3. Attribute syntax: <tool_call_name="name">...attributes...</tool_call_name>
  4. Direct-tool-name-as-tag: <file_read_enhanced><path>x</path></file_read_enhanced> (tool name used as tag with parameter children)
  5. Parameter tag format: <parameter name="key" string="true">value</parameter>

JSON sanitization pre-processes model output to fix:

  • Raw unescaped newlines in string values
  • Unquoted property names
  • Trailing commas

This ensures compatibility with providers like DeepSeek that emit non-standard JSON.

Buffered Streaming

When tools are registered, the LLM response is streamed into a buffer internally (not directly to the client). After the full response is received:

  • Structured content blocks from compatible providers (Anthropic, OpenAI-compatible) are preserved: content_block_start, input_json_delta, content_block_stop events
  • Tool calls are extracted via brace-depth walker (handles nested JSON correctly) or parsed from structured events
  • XML regions stripped before bare JSON fallback scan to prevent double-execution
  • Only clean prose (no <tool_call> XML, no <tool_result> XML, no bare JSON) is forwarded via onChunk
  • Reasoning content extracted separately and sent as optional reasoning message type
  • Client-side WebSocket handler double-checks stripping as a defensive safety net

Post-Turn Processing (Fire-and-Forget)

These tasks run asynchronously after the response is sent — they never block the agent:

Operation Destination Purpose
writeEpisodic() episodic_memory Store turn summary
extractAndStoreEntities() semantic_memory + graph_entities Build knowledge graph (filtered by ENTITY_STOP_WORDS)
detectAndPersistPreference() semantic_memory + MEMORY.md Capture user preferences
reflectOnTurn()storeReflection() reflection_memory Extract behavioral patterns (dedup by pattern text)
logEvent() lens.db Audit log entry (35+ event types)
extractSkillFromSession() procedural_memory Procedural knowledge (≥2 tool calls, params redacted)
captureMemoriCheckpoint() memori_checkpoints Full agent state snapshot (conversation, memory, tools, reasoning, workspace, metadata). Auto-prunes to 5 most recent per session. (v0.51.0+)

MetaCognition

packages/ai/src/agent/metacog.ts analyzes user messages before the LLM call to determine delegation strategy:

  • Complex code + explorationdelegate with suggested types [explore, code]
  • Research + independent subtasksparallelize with [research]
  • Pure explorationdelegate with explore
  • Destructive multi-stepplan_with_rollback with plan

The suggestedSubAgents field guides the LLM in choosing sub-agent types. Meta-cog signals include isExploratory, isCodeTask, isPlanningTask, and isComplex.

Pipeline Integration

10 hook stages run around the agent loop at:

  • pre-assess, post-assess — metacognition
  • pre-reason, post-reason — context injection, summarization, model selection
  • pre-tool, post-tool — tool orchestration (QM), output capture, loop detection
  • pre-reflect, post-reflect — reflection
  • pre-output, post-output — content safety, auto-TTS, audit, cost tracking

Debug Tracing

[loop] prefixed console.log statements track every tool round: turn ID, tool presence, stream mode, response length/preview, detected tool call names, per-tool execution results, prose emission length, and final response emission path.

See Also

Metacognition Integration

Before each turn, assessTask() from packages/ai/src/agent/metacog.ts classifies the user's message:

Signal Trigger Decision
Ambiguous intent Ends with ? or vague language ask_first
Destructive Contains "rm", "delete", "drop", "purge" ask_first
Complex task >20 words or multi-step instructions plan_with_rollback
Independent subtasks "and also", "then after", "first...second" parallelize
Code task Mentions "code", "function", "bug", "fix" plan_with_rollback
General None of the above direct

Confidence Escalation (#53)

When the confidence score falls below 0.35 for a direct decision, the system auto-escalates to ask_first:

if confidence < 0.35 and decision === 'direct':
    escalate to ask_first with a clarification prompt
    log escalation event to lens_events

Policy-Aware Planner (#57)

Every metacognition assessment is logged as a plan artifact via packages/ai/src/agent/planner.ts:

  • Plan artifacts include decision, confidence, signal breakdown, and suggested sub-agents
  • Plans are surfaced in the Workflows page sidebar
  • Policy checks can be inserted before plan emission

Goal Drift Detector (#60)

Each turn compares the current user input against the previous session goal using:

  • Keyword scoring — explicit drift phrases ("actually", "instead", "on second thought", "new plan")
  • Jaccard divergence — word set overlap between current and previous inputs
  • Drift events fire when the combined score ≥ 0.4
  • Events are stored in lens_events and surfaced on the Workflows page "Goal Drift" tab

Adversarial Self-Critique (#52)

After normal reflection (when enableReflection is true), a second adversarial pass runs with a more critical system prompt:

  • Actively hunts for missed edge cases, validation gaps, and security concerns
  • Stored in reflection_memory with category adversarial
  • Critique cards shown on the Metacognition page

Parallel Sub-Agent Dispatcher (#58)

When metacognition suggests parallelize, the system can fan out to multiple sub-agents:

  • Active sub-agent tasks tracked in packages/ai/src/agent/sub-agent-tracker.ts
  • Task board visible on Workflows page under the Sub-Agents tab
  • Shows running tasks (pulsing green dots) and recently completed tasks
  • Auto-refreshes every 3 seconds while the tab is active

Clone this wiki locally