Skip to content

Agent Loop

scarecr0w12 edited this page Jun 19, 2026 · 3 revisions

Agent Loop

The agent loop is the core of CortexPrism. It processes one complete user→agent exchange through pipeline hooks, memory injection, MQM model selection, LLM calls, security-gated tool execution, and post-turn storage.

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=8]
     a. LLM call (buffered stream when tools registered)
     b. parseToolCalls(response)        ← extract <tool_call> XML + bare JSON fallback
     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)
  return AgentTurnResult

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
  3. The LLM is called again with updated message history
  4. Up to 8 rounds (MAX_TOOL_ROUNDS) before hitting the ceiling
  5. When ≤1 rounds remain, a hard instruction stops tool calling and demands a final response
  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

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:

  • Tool calls are extracted via brace-depth walker (handles nested JSON correctly)
  • 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)

MetaCognition

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 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 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 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