llm-loop
Versatile Almost Local, Eventually Reasonable Assistant š«
Install / Use
npx skills add vakovalskii/NeuralDeskAppInstalls into whichever agent you are using.
Cursor Rules
Cursor IDE rules (v2)
Quality Score
Category
AI & Machine LearningSupported Platforms
Skill content
View source on GitHubLLM Agent Loop
Overview
The agent loop in runner-openai.ts implements a ReAct-style agent that can:
- Receive user messages
- Stream LLM responses
- Execute tools
- Continue until task completion
Loop Architecture
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Agent Loop ā
ā ā
ā āāāāāāāāāāāā āāāāāāāāāāāā āāāāāāāāāāāā ā
ā ā Build āāāāāŗā Call āāāāāŗā Stream ā ā
ā ā Messages ā ā LLM ā ā Response ā ā
ā āāāāāāāāāāāā āāāāāāāāāāāā āāāāāāāāāāāā ā
ā ā² ā ā
ā ā ā¼ ā
ā ā āāāāāāāāāāāā ā
ā ā No ā Tool ā ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāā Calls? ā ā
ā āāāāāāāāāāāā ā
ā ā Yes ā
ā ā¼ ā
ā āāāāāāāāāāāā ā
ā ā Execute ā ā
ā ā Tools ā ā
ā āāāāāāāāāāāā ā
ā ā ā
ā ā¼ ā
ā āāāāāāāāāāāā ā
ā ā Add ā ā
ā ā Results āāāāāāā
ā āāāāāāāāāāāā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Key Components
Message Array
OpenAI chat format:
type ChatMessage = {
role: 'system' | 'user' | 'assistant' | 'tool';
content: string | ContentPart[];
tool_calls?: ToolCall[]; // Only for assistant
tool_call_id?: string; // Only for tool
name?: string; // Tool name for tool messages
};
Streaming
Uses OpenAI SDK streaming with requestAnimationFrame throttling:
const stream = await client.chat.completions.create({
model: modelName,
messages: messages,
tools: activeTools,
stream: true,
stream_options: { include_usage: true }
});
for await (const chunk of stream) {
// Process delta content
if (chunk.choices[0]?.delta?.content) {
sendStreamEvent(chunk.choices[0].delta.content);
}
// Accumulate tool calls
if (chunk.choices[0]?.delta?.tool_calls) {
accumulateToolCalls(chunk.choices[0].delta.tool_calls);
}
}
Tool Execution Flow
// 1. Check permission mode
const permissionMode = settings?.permissionMode || 'ask';
if (permissionMode === 'ask') {
// 2. Send permission request to UI
sendPermissionRequest(toolUseId, toolName, toolArgs);
// 3. Wait for user response
const approved = await waitForPermission(toolUseId);
if (!approved) continue;
}
// 4. Execute tool
const result = await toolExecutor.executeTool(toolName, toolArgs, {
sessionId: session.id,
onTodosChanged: (todos) => {
// Persist and notify UI
}
});
// 5. Add result to messages
messages.push({
role: 'tool',
tool_call_id: toolCall.id,
name: toolName,
content: result.success ? result.output : `Error: ${result.error}`
});
Loop Detection
Prevents infinite loops when model repeatedly calls same tool:
const LOOP_THRESHOLD = 5; // Same tool N times = loop
const MAX_LOOP_RETRIES = 5; // Max attempts to break loop
// Track recent tool calls
recentToolCalls.push({ name: toolName, args: argsString });
// Check for loops
if (recentToolCalls.length >= LOOP_THRESHOLD) {
const lastCalls = recentToolCalls.slice(-LOOP_THRESHOLD);
const allSameTool = lastCalls.every(c => c.name === lastCalls[0].name);
if (allSameTool) {
loopRetryCount++;
if (loopRetryCount >= MAX_LOOP_RETRIES) {
// Stop with error
sendLoopError(toolName);
return;
}
// Add hint to help model break loop
messages.push({
role: 'user',
content: 'ā ļø You are stuck in a loop. Try a different approach.'
});
}
}
Error Handling
Retryable Errors
Network errors are automatically retried:
const isRetryableNetworkError = (error: unknown): boolean => {
const code = error.cause?.code;
const status = error.status;
// Retry on socket errors
if (['ECONNRESET', 'ETIMEDOUT', 'ECONNREFUSED'].includes(code)) {
return true;
}
// Retry on server errors
if ([429, 500, 502, 503, 504].includes(status)) {
return true;
}
return false;
};
Retry Logic
const MAX_STREAM_RETRIES = 3;
const RETRY_BASE_DELAY_MS = 500;
for (let attempt = 0; attempt <= MAX_STREAM_RETRIES; attempt++) {
try {
return await streamResponse();
} catch (error) {
if (!isRetryableNetworkError(error) || attempt === MAX_STREAM_RETRIES) {
throw error;
}
const delayMs = RETRY_BASE_DELAY_MS * 2 ** attempt;
await sleep(delayMs);
}
}
Session Logging
Each turn is logged to ~/.localdesk/logs/sessions/{sessionId}/:
turn-001-request.json # Full request (messages, tools, params)
turn-001-response.json # Full response (content, tool_calls, usage)
turn-002-request.json
turn-002-response.json
...
Token Tracking
Accumulated across all iterations:
if (streamMetadata.usage) {
totalInputTokens += streamMetadata.usage.prompt_tokens || 0;
totalOutputTokens += streamMetadata.usage.completion_tokens || 0;
}
// Final report
sendMessage('result', {
usage: {
input_tokens: totalInputTokens,
output_tokens: totalOutputTokens
}
});
Abort Handling
User can stop generation at any point:
let aborted = false;
return {
abort: () => {
aborted = true;
console.log('[OpenAI Runner] Aborted');
}
};
// Check in loop
while (!aborted && iterationCount < MAX_ITERATIONS) {
// ... streaming
if (aborted) break;
// ... tool execution
if (aborted) break;
}
Configuration
| Setting | Default | Description |
|---------|---------|-------------|
| MAX_ITERATIONS | 50 | Max agent loop cycles |
| REQUEST_TIMEOUT_MS | 5 min | LLM request timeout |
| MAX_STREAM_RETRIES | 3 | Network error retries |
| LOOP_THRESHOLD | 5 | Tool calls to detect loop |
Related Skills
claude-mem
93.4kPersistent Context Across Sessions for Every Agent ā Captures everything your agent does during sessions, compresses it with AI, and injects relevant context back into future sessions. Works with Claude Code, OpenClaw, Codex, Gemini, Hermes, Copilot, OpenCode + More
Understand-Anything
81.7kGraphs that teach > graphs that impress. Turn any code into an interactive knowledge graph you can explore, search, and ask questions about. Works with Claude Code, Codex, Cursor, Copilot, Gemini CLI, and more.
headroom
70.0kCompress tool outputs, logs, files, and RAG chunks before they reach the LLM. 20% fewer tokens for coding agents, 60-95% fewer tokens for JSON, same answers. Library, proxy, MCP server.
CowAgent
46.8kOpen-source super AI assistant & Agent Harness. Plans tasks, runs tools and skills, self-evolves with memory and knowledge. Multi-model, multi-channel. Lightweight, extensible, one-line install. (formerly chatgpt-on-wechat)
Security Score
Audited on May 15, 2026
