-
-
Notifications
You must be signed in to change notification settings - Fork 119
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(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
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.
| 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) |
After each tool execution round, the agent receives tool results and is re-prompted:
- Tool results are formatted as
<tool_result>XML blocks with structured error info - Results are appended as a user message in the conversation with a Quartermaster tool suggestion hint (if prediction confidence > threshold)
- The LLM is called again with updated message history
- Up to 12 rounds (MAX_TOOL_ROUNDS) before hitting the ceiling
- When ≤2 rounds remain, an urgency nudge instruction stops tool calling, explicitly suggests
file_writefor file creation tasks, and demands producing the deliverable - 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.
When tools access sensitive data (memory_search, db_query, browser, computer):
- Results are classified for sensitivity (SECRET/SENSITIVE/NORMAL/PUBLIC)
- SENSITIVE/SECRET results trigger the LLM supervisor review
- Supervisor evaluates agent intent, data sensitivity, and operational context
- High-confidence decisions auto-approve; low-confidence escalate to human
- Human approval (CLI prompt or Web UI modal with 60s timeout)
- Approved decisions cached per session (1-hour TTL) to prevent approval fatigue
The parser recognizes 5 output formats to maximize compatibility across LLM providers:
-
JSON inside
<tool_call>tags:<tool_call>{"name": "...", "args": {...}}</tool_call> -
Nested XML:
<tool_call_name>name</tool_call_name><tool_call_args>{...}</tool_call_args> -
Attribute syntax:
<tool_call_name="name">...attributes...</tool_call_name> -
Direct-tool-name-as-tag:
<file_read_enhanced><path>x</path></file_read_enhanced>(tool name used as tag with parameter children) -
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.
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_stopevents - 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 viaonChunk - Reasoning content extracted separately and sent as optional
reasoningmessage type - Client-side WebSocket handler double-checks stripping as a defensive safety net
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+) |
packages/ai/src/agent/metacog.ts analyzes user messages before the LLM call to determine delegation strategy:
-
Complex code + exploration →
delegatewith suggested types[explore, code] -
Research + independent subtasks →
parallelizewith[research] -
Pure exploration →
delegatewithexplore -
Destructive multi-step →
plan_with_rollbackwithplan
The suggestedSubAgents field guides the LLM in choosing sub-agent types. Meta-cog signals include isExploratory, isCodeTask, isPlanningTask, and isComplex.
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
[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.
- Sub-Agents — Child agent spawning and lifecycle
- Security — Policy validation and LLM supervisor
- Request Flow — Full request lifecycle diagrams
- Pipeline Hooks — Hook system reference
- Model Quartermaster — MQM model selection in agent context
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 |
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
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
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_eventsand surfaced on the Workflows page "Goal Drift" tab
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_memorywith categoryadversarial - Critique cards shown on the Metacognition page
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
CortexPrism — Open-source AI agent operating system · Discord · Apache 2.0 License · Built with Deno 2.x + TypeScript
- Agent Loop
- Built-in Agents
- Metacognition
- Memory System
- Skills System
- Sub-Agents
- Built-in Tools
- Code Intelligence
- Code Sandbox
- Cross-Agent Context Protocol
- Prompt Lab
- PKM Assistant
- Voice Pipeline
- Computer Use
- Browser Tool
- Git & GitHub
- Scheduler & Jobs
- Dashboard
- Observability
- A2A Protocol
- MCP Gateway
- Distributed Nodes
- Memori Checkpoints
- Eval System
- Workflow Engine
- Triggers
- Projects
- TUI
- Glossary
- Update System
- Chrome Bridge
- Swarm
- AgentLint
- Model Benchmarking
- Smart Context
- Cost Optimizer