轻量级OpenClaw——GenericAgent项目解读

GenericAgent:极简架构下的自我进化 Agent 框架

GenericAgent 是一个极简、可自我进化的自主 Agent 框架,核心代码仅约 3K 行。它通过 9 个原子工具和约百行的 Agent Loop,赋予任意 LLM 对本地计算机的系统级控制能力,覆盖浏览器、终端、文件系统、键鼠输入、屏幕视觉及移动设备(ADB)。

设计哲学:不预设技能,靠进化获得能力

GenericAgent 的核心理念是"不预设技能,靠进化获得能力"。每解决一个新任务,Agent 就将执行路径自动固化为 Skill,供后续直接调用。使用时间越长,沉淀的技能越多,形成一棵完全属于用户、从 3K 行种子代码生长出来的专属技能树

这种自我进化机制通过分层记忆系统实现,是 GenericAgent 区别于其他 Agent 框架的根本所在 。

分层记忆系统

GenericAgent 采用 5 层记忆架构来存储不同类型的信息:

层级 名称 存储位置 内容类型
L0 Meta Rules memory/memory_management_sop.md 记忆管理的元规则和公理
L1 Insight Index memory/global_mem_insight.txt 极简索引(≤30 行,<1K tokens)
L2 Global Facts memory/global_mem.txt 环境事实(路径、凭证、配置)
L3 Task Skills / SOPs memory/*.md, memory/*.py 任务 SOP 和工具脚本
L4 Session Archive memory/L4_raw_sessions/ 历史会话归档记录

记忆工作流程

记忆系统的工作流程分为记忆注入和记忆更新两个核心阶段:

graph TB subgraph "任务开始阶段" A[任务启动] --> B[get_global_memory
🔧 固定逻辑] B --> C[读取 L1 索引] B --> D[读取固定结构] C --> E[注入系统提示词] D --> E E --> F[LLM 执行任务
🧠 大模型决策] end subgraph "任务执行阶段" F --> G{需要保存上下文?
🧠 大模型决策} G -->|是| H[update_working_checkpoint
🔧 固定逻辑] H --> I[写入 working.key_info] I --> J[每轮自动注入
_get_anchor_prompt] J --> F G -->|否| F end subgraph "任务完成阶段" F --> K{发现值得记忆的信息?
🧠 大模型决策} K -->|否| L[任务结束] K -->|是| M[start_long_term_update
🔧 固定逻辑] M --> N[读取 L0 SOP] N --> O[LLM 按规则分类信息
🧠 大模型决策] O --> P{信息类型
🧠 大模型决策} P -->|环境事实| Q[file_patch 更新 L2
🧠 大模型调用工具] P -->|任务经验| R[生成 SOP 存入 L3
🧠 大模型调用工具] Q --> S[触发 L1 同步
🧠 大模型执行] R --> S S --> T[更新 L1 索引
🧠 大模型执行] T --> L end

核心特点

分层记忆系统有几个特别之处:

  1. 严格的行动验证原则:只有经过工具执行验证的信息才能存储,这是 L0 元规则的核心公理:"No Execution, No Memory"

  2. L1 极简索引:L1 层被严格限制在 30 行以内、< 1K tokens,确保每次任务开始时注入系统提示词而不占用过多上下文

  3. 单向同步机制:L2/L3 的变更会自动触发 L1 的同步更新,确保索引始终反映可用能力

  4. 禁止易失状态:严格禁止存储时间戳、PID、Session ID 等易失信息,确保记忆的长期有效性

提示词驱动的记忆更新

自我进化机制主要通过提示词注入和大模型自主决策实现。系统提示词在每次任务开始时构建,包含三层内容:

python 复制代码
def get_system_prompt():
    with open(os.path.join(script_dir, f'assets/sys_prompt{lang_suffix}.txt'), 'r', encoding='utf-8') as f: prompt = f.read()
    prompt += f"\nToday: {time.strftime('%Y-%m-%d %a')}\n"
    prompt += get_global_memory()  # 注入全局记忆
    return prompt

get_global_memory() 将 L1 索引注入系统提示词 。当 Agent 发现值得长期记忆的信息时,调用 start_long_term_update 工具,该工具返回详细的 next_prompt,指导 LLM 按照规则分类和存储信息。

这种设计的好处是灵活性和可控性:记忆更新完全由 LLM 理解和执行,而不是硬编码的规则。

浏览器注入实现

GenericAgent 通过 Chrome 扩展 + Python 服务器的 CDP(Chrome DevTools Protocol)桥接架构实现浏览器注入,核心是 tmwd_cdp_bridge 扩展和 TMWebDriver 服务器。

核心架构

graph TB subgraph "用户浏览器" EXT["tmwd_cdp_bridge 扩展"] CONTENT["content.js
页面注入"] BG["background.js
CDP 控制器"] end subgraph "Python 进程" TMWD["TMWebDriver 服务器
WebSocket/HTTP"] GA["GenericAgent
web_scan/web_execute_js"] end GA -->|工具调用| TMWD TMWD <-->|WebSocket/HTTP| BG CONTENT <-->|chrome.runtime.sendMessage| BG BG <-->|chrome.debugger| 浏览器标签页

关键特性

  1. CSP 移除 :扩展在安装时自动移除 Content-Security-Policy 头,允许在安全网站上执行 eval() 和内联脚本

  2. 页面注入脚本content.js 注入到所有网页,负责移除页面上的 CSP meta 标签、显示连接状态指示器、监听 DOM 变化并捕获 TID 命令

  3. CDP 命令执行background.js 通过 chrome.debugger API 执行 CDP 命令,支持批量执行、结果引用和获取 cookies(包括 HttpOnly 和 Partitioned)

  4. 保留登录态:通过直接控制用户浏览器并访问所有 cookies,完全保留用户状态,无需每次重新登录。

9 个原子工具

GenericAgent 提供了 9 个原子工具,赋予 LLM 对本地计算机的系统级控制能力 。这些工具定义在 assets/tools_schema.json 中 :

类别 工具名称 数量
代码执行 code_run 1
文件操作 file_read, file_patch, file_write 3
浏览器控制 web_scan, web_execute_js 2
记忆管理 update_working_checkpoint, start_long_term_update 2
人机交互 ask_user 1

其中专门用于浏览器的工具只有 2 个(web_scanweb_execute_js),其他工具覆盖代码执行、文件操作、记忆管理等领域,共同构成完整的系统控制能力。

Token 优化策略

GenericAgent 通过严格的 Token 优化机制,将实际发送给 LLM 的上下文控制在 30K 字符左右。代码中 context_win 的默认配置是 24K-28K 字符(不是 tokens),用作历史裁剪的阈值 。

压缩机制

  1. 历史压缩compress_history_tags 函数压缩历史消息中的标签内容(<thinking>, <tool_use>, <tool_result>),每 5 轮执行一次

  2. 历史裁剪trim_messages_history 在上下文超过阈值时裁剪早期消息,代码注释明确指出 "trim breaks cache, so compress more btw"

  3. HTML 优化optimize_html_for_tokens 对网页内容进行激进优化,移除所有 style 属性、截断长 URL、只保留必要属性

  4. 工具描述简化:工具描述不变时使用简化提示词,减少重复发送

  5. 工作记忆限制update_working_checkpointkey_info 限制在 200 tokens 以内

  6. 历史折叠_get_anchor_prompt 只保留最近 30 条历史记录,早期历史被折叠为摘要

设计权衡

这种极致省 Token 的设计会降低模型缓存命中率,因为动态裁剪和压缩会破坏 prompt cache 的连续性。这是一个明确的成本 vs 缓存的权衡:GenericAgent 选择用更高的缓存成本换取更低的 token 消耗,因为分层记忆系统可以弥补小上下文的不足。

任务执行机制

在 GenericAgent 中,"任务"(task)不等于用户的一次简单对话输入,而是一个完整的执行单元,可能包含多轮 LLM 交互和工具调用 。

任务 vs 对话

维度 用户对话输入 任务(Task)
粒度 单次消息 完整执行单元
轮数 1 轮 多轮(默认最多 40 轮)
工具调用 0 或 1 次 多次(可能数十次)
持续时间 秒级 分钟级甚至更长
状态管理 无状态 有状态(history_info, working memory)

Agent 循环机制

用户只输入一次,之后的所有轮次都是 agent_runner_loop 的自动执行,不需要用户再次交互 。例如,用户输入"帮我读取微信消息",这个任务可能包含:

  • Turn 1:LLM 决定安装依赖 → 调用 code_run
  • Turn 2:LLM 决定逆向数据库 → 调用 code_run
  • Turn 3:LLM 决定编写读取脚本 → 调用 file_write
  • Turn 4:LLM 决定测试脚本 → 调用 code_run
  • Turn 5:LLM 决定保存为 Skill → 调用 start_long_term_update
  • Turn 6:任务完成,返回结果

整个过程是一个任务,但包含 6 轮对话,全部由 agent 内部循环调用完成。

安全机制

GenericAgent 通过多层安全机制来保证安全性,主要体现在记忆治理规则、操作约束、访问控制和失败处理策略四个方面。

记忆治理规则(L0 宪法)

L0 层的 [CONSTITUTION] 定义了核心安全约束 :

markdown 复制代码
[CONSTITUTION]
1. 改自身源码先请示;./内可自主实验,允许装包和portable工具
2. 决策前查记忆,有SOP/utils必用;多次失败回看SOP;未查证不断言
3. 分步执行,控制粒度,限制失败半径;3次失败请求干预
4. 密钥文件仅引用,不读取/移动
5. 写任何记忆前读META-SOP核验,memory下文件只能patch修改(除非新建)

操作层面安全规则

[RULES] 部分定义了操作层面的安全约束:

  • 编码安全 :禁用 PowerShell cat/type,必须用 file_read;修改前必须先读取
  • 进程安全:禁止无条件杀 python(会杀自己),必须精确 PID
  • 窗口安全 :GUI 状态优先用 win32gui 枚举标题
  • Web JS 安全:输入用原生 setter+事件链,点击前检查 disabled

系统提示词安全原则

系统提示词中定义了行动原则 :

  • 探测优先:失败时先充分获取信息(日志/状态/上下文),关键信息存入工作记忆,再决定重试或换方案。不可逆操作先询问用户
  • 失败升级:1次→读错误理解原因,2次→探测环境状态,3次→深度分析后换方案或问用户。禁止无新信息的重复操作

工具执行安全限制

工具 安全限制
code_run 有超时限制(默认 60 秒)
file_patch 要求精确

Citations

File: README.md (L18-18)

markdown 复制代码
**GenericAgent** is a minimal, self-evolving autonomous agent framework. Its core is just **~3K lines of code**. Through **9 atomic tools + a ~100-line Agent Loop**, it grants any LLM system-level control over a local computer --- covering browser, terminal, filesystem, keyboard/mouse input, screen vision, and mobile devices (ADB).

File: README.md (L20-22)

markdown 复制代码
Its design philosophy: **don't preload skills --- evolve them.**

Every time GenericAgent solves a new task, it automatically crystallizes the execution path into an skill for direct reuse later. The longer you use it, the more skills accumulate --- forming a skill tree that belongs entirely to you, grown from 3K lines of seed code.

File: README.md (L34-36)

markdown 复制代码
## 🧬 Self-Evolution Mechanism

This is what fundamentally distinguishes GenericAgent from every other agent framework.

File: README.md (L160-168)

markdown 复制代码
1️⃣ **Layered Memory System**
> _Memory crystallizes throughout task execution, letting the agent build stable, efficient working patterns over time._

- **L0 --- Meta Rules**: Core behavioral rules and system constraints of the agent
- **L1 --- Insight Index**: Minimal memory index for fast routing and recall
- **L2 --- Global Facts**: Stable knowledge accumulated over long-term operation
- **L3 --- Task Skills / SOPs**: Reusable workflows for completing specific task types
- **L4 --- Session Archive**: Archived task records distilled from finished sessions for long-horizon recall

File: ga.py (L494-509)

python 复制代码
    def do_start_long_term_update(self, args, response):
        '''Agent觉得当前任务完成后有重要信息需要记忆时调用此工具。'''
        prompt = '''### [总结提炼经验] 既然你觉得当前任务有重要信息需要记忆,请提取最近一次任务中【事实验证成功且长期有效】的环境事实、用户偏好、重要步骤,更新记忆。
本工具是标记开启结算过程,若已在更新记忆过程或没有值得记忆的点,忽略本次调用。
**如果没有经验证的,未来能用上的信息,忽略本次调用!**
**只能提取行动验证成功的信息**:
- **环境事实**(路径/凭证/配置)→ `file_patch` 更新 L2,同步 L1
- **复杂任务经验**(关键坑点/前置条件/重要步骤)→ L3 精简 SOP(只记你被坑得多次重试的核心要点)
**禁止**:临时变量、具体推理过程、未验证信息、通用常识、你可以轻松复现的细节、只是做了但没有验证的信息
**操作**:严格遵循提供的L0的记忆更新SOP。先 `file_read` 看现有 → 判断类型 → 最小化更新 → 无新内容跳过,保证对记忆库最小局部修改。\n
''' + get_global_memory()
        yield "[Info] Start distilling good memory for long-term storage.\n"
        path = './memory/memory_management_sop.md'
        if os.path.exists(path): result = '自动读取L0内容:\n' + file_read(path, show_linenos=False)
        else: result = "Memory Management SOP not found. Do not update memory."
        return StepOutcome(result, next_prompt=prompt)

File: ga.py (L525-537)

python 复制代码
    def _get_anchor_prompt(self, skip=False):
        if skip: return "\n"
        h = self.history_info; W = 30
        earlier = f'<earlier_context>\n{self._fold_earlier(h[:-W])}\n</earlier_context>\n' if len(h) > W else ""
        h_str = "\n".join(h[-W:])
        prompt = f"\n### [WORKING MEMORY]\n{earlier}<history>\n{h_str}\n</history>"
        prompt += f"\nCurrent turn: {self.current_turn}\n"
        if self.working.get('key_info'): prompt += f"\n<key_info>{self.working.get('key_info')}</key_info>"
        if self.working.get('related_sop'): prompt += f"\n有不清晰的地方请再次读取{self.working.get('related_sop')}"
        if getattr(self.parent, 'verbose', False):
            try: print(prompt)
            except: pass
        return prompt

File: ga.py (L569-580)

python 复制代码
def get_global_memory():
    prompt = "\n"
    try:
        suffix = '_en' if os.environ.get('GA_LANG', '') == 'en' else ''
        with open(os.path.join(script_dir, 'memory/global_mem_insight.txt'), 'r', encoding='utf-8', errors='replace') as f: insight = f.read()
        with open(os.path.join(script_dir, f'assets/insight_fixed_structure{suffix}.txt'), 'r', encoding='utf-8') as f: structure = f.read()
        prompt += f'cwd = {os.path.join(script_dir, "temp")} (./)\n'
        prompt += f"\n[Memory] (../memory)\n"
        prompt += structure + '\n../memory/global_mem_insight.txt:\n'
        prompt += insight + "\n"
    except FileNotFoundError: pass
    return prompt

File: assets/tmwd_cdp_bridge/background.js (L5-15)

javascript 复制代码
  chrome.declarativeNetRequest.updateDynamicRules({
    removeRuleIds: [9999],
    addRules: [{
      id: 9999, priority: 1,
      action: { type: 'modifyHeaders', responseHeaders: [
        { header: 'content-security-policy', operation: 'remove' },
        { header: 'content-security-policy-report-only', operation: 'remove' }
      ]},
      condition: { urlFilter: '*', resourceTypes: ['main_frame', 'sub_frame'] }
    }]
  });

File: assets/tmwd_cdp_bridge/background.js (L84-115)

javascript 复制代码
async function handleBatch(msg, sender) {
  const R = [];
  let attached = null;
  const resolve$N = (params) => JSON.parse(JSON.stringify(params || {}).replace(/"\$(\d+)\.([^"]+)"/g,
    (_, i, path) => { let v = R[+i]; for (const k of path.split('.')) v = v[k]; return JSON.stringify(v); }));
  try {
    for (const c of msg.commands) {
      if (c.tabId === undefined && msg.tabId !== undefined) c.tabId = msg.tabId;
      if (c.cmd === 'cookies') {
        R.push(await handleCookies(c, sender));
      } else if (c.cmd === 'tabs') {
        const tabs = (await chrome.tabs.query({})).filter(t => isScriptable(t.url));
        R.push({ ok: true, data: tabs.map(t => ({ id: t.id, url: t.url, title: t.title, active: t.active, windowId: t.windowId })) });
      } else if (c.cmd === 'cdp') {
        const tabId = c.tabId || msg.tabId || sender.tab?.id;
        if (attached !== tabId) {
          if (attached) { await chrome.debugger.detach({ tabId: attached }); attached = null; }
          await chrome.debugger.attach({ tabId }, '1.3');
          attached = tabId;
        }
        R.push(await chrome.debugger.sendCommand({ tabId }, c.method, resolve$N(c.params)));
      } else {
        R.push({ ok: false, error: 'unknown cmd: ' + c.cmd });
      }
    }
    if (attached) await chrome.debugger.detach({ tabId: attached });
    return { ok: true, results: R };
  } catch (e) {
    if (attached) try { await chrome.debugger.detach({ tabId: attached }); } catch (_) {}
    return { ok: false, error: e.message, results: R };
  }
}

File: assets/tmwd_cdp_bridge/content.js (L3-24)

javascript 复制代码
// Remove meta CSP tags
document.querySelectorAll('meta[http-equiv="Content-Security-Policy"]').forEach(e => e.remove());

// Indicator badge at bottom-right (userscript style)
(function(){
  if(window.self!==window.top)return;
  const d=document.createElement('div');
  d.id='ljq-ind';
  d.innerText='ljq_driver: 已连接';
  d.style.cssText='position:fixed;bottom:8px;right:8px;background:#4CAF50;color:white;padding:4px 7px;border-radius:4px;font-size:11px;font-weight:bold;z-index:99999;cursor:pointer;box-shadow:0 2px 4px rgba(0,0,0,0.2);opacity:0.5;';
  d.addEventListener('click',()=>alert('会话活跃\nURL: '+location.href));
  (document.body||document.documentElement).appendChild(d);
})();

new MutationObserver(muts => {
  for (const m of muts) for (const n of m.addedNodes) {
    if (n.id === TID || (n.querySelector && n.querySelector('#' + TID))) {
      const el = n.id === TID ? n : n.querySelector('#' + TID);
      handle(el);
    }
  }
}).observe(document.documentElement, { childList: true, subtree: true });

File: assets/tools_schema.json (L1-72)

json 复制代码
[
  {"type": "function", "function": {
    "name": "code_run",
    "description": "Code executor. Prefer python. Multi-call OK, use script param. Reply code block is executed if no script arg; prefer for single call to avoid escaping. No hardcoding bulk data",
    "parameters": {"type": "object", "properties": {
      "script": {"type": "string", "description": "[Mutually exclusive] NEVER use this param when use reply code block."},
      "type": {"type": "string", "enum": ["python", "powershell"], "description": "Code type", "default": "python"},
      "timeout": {"type": "integer", "description": "in seconds", "default": 60},
      "cwd": {"type": "string", "description": "Working directory, defaults to cwd"},
      "inline_eval": {"type": "boolean", "description": "DO NOT USE except explicitly specified."}}}
  }},
  {"type": "function", "function": {
    "name": "file_read",
    "description": "Read file. Read before modify for latest context and line numbers",
    "parameters": {"type": "object", "properties": {
      "path": {"type": "string", "description": "Relative or absolute"},
      "start": {"type": "integer", "description": "Start line number (1-based)"},
      "count": {"type": "integer", "description": "Number of lines to read", "default": 200},
      "keyword": {"type": "string", "description": "[Optional] If provided, returns first match (case-insensitive) with context"},
      "show_linenos": {"type": "boolean", "description": "Show line numbers", "default": true}}}
  }},
  {"type": "function", "function": {
    "name": "file_patch",
    "description": "Replace unique old_content with new_content. Exact match required (whitespace/indentation). On failure, file_read to recheck",
    "parameters": {"type": "object", "properties": {
      "path": {"type": "string", "description": "File path"},
      "old_content": {"type": "string", "description": "Original text block to replace (must be unique)"},
      "new_content": {"type": "string", "description": "New content. Supports {{file:path:startLine:endLine}} to ref file lines, auto-expanded"}}}
  }},
  {"type": "function", "function": {
    "name": "file_write",
    "description": "Create/overwrite/append files. HUGE edits ONLY. Content must in <file_content>...</file_content> in reply body BEFORE the file_write call (not args). Supports {{file:path:startLine:endLine}}, auto-expanded",
    "parameters": {"type": "object", "properties": {
      "path": {"type": "string", "description": "File path"},
      "mode": {"type": "string", "enum": ["overwrite", "append", "prepend"], "description": "Write mode", "default": "overwrite"}}}
  }},
  {"type": "function", "function": {
    "name": "web_scan",
    "description": "Get simplified HTML and tab list. Removes hidden/floating/covered elements. Call after switching pages",
    "parameters": {"type": "object", "properties": {
      "tabs_only": {"type": "boolean", "description": "Show tab list only, no HTML"},
      "switch_tab_id": {"type": "string", "description": "[Optional] Tab ID to switch to"},
      "text_only": {"type": "boolean", "description": "Plain text only, no HTML"}}}
  }},
  {"type": "function", "function": {
    "name": "web_execute_js",
    "description": "Execute JS. Multi-call OK with different switch_tab_id. No guessing. Act accurately to reduce web_scan calls. Execute JS in ```javascript blocks if no script arg, prefer to avoid escaping",
    "parameters": {"type": "object", "properties": {
      "script": {"type": "string", "description": "[Mutually exclusive] JS code or script path. NEVER use this param when use reply code block"},
      "save_to_file": {"type": "string", "description": "file path; **only** for long result"},
      "no_monitor": {"type": "boolean", "description": "Skip page change monitoring, saves 2-3s. Only for reads, not for page actions"},
      "switch_tab_id": {"type": "string", "description": "[Optional] Tab ID to switch to before executing"}}}
  }},
  {"type": "function", "function": {
    "name": "update_working_checkpoint",
    "description": "Short-term working notepad, auto-injected each turn to prevent info loss in long tasks. Call during early/mid stages, not at end. When: (1) after reading SOP, store user needs & key constraints (skip for simple 1-2 step tasks); (2) before subtask switch or context flush; (3) after repeated failures, re-read SOP and must store new findings; (4) on new task, update content, clear old progress but keep valid constraints.\n\nDon't call: simple tasks (1-2 steps), task completed (use long-term memory tool)",
    "parameters": {"type": "object", "properties": {
      "key_info": {"type": "string", "description": "Replaces current notepad (<200 tokens). Incremental update: review existing, keep valid, add/remove/modify. Store: pitfalls, user requirements, key params/findings, file paths, progress, next steps. Don't store: ephemeral info, obvious context, old task info when user switched tasks. Prefer over-updating over losing key info"},
      "related_sop": {"type": "string", "description": "Related SOP names, tips for further re-read"}}}
  }},
  {"type": "function", "function": {
    "name": "ask_user",
    "description": "Interrupt task to ask user when needing decisions, extra info, or facing unresolvable blockers",
    "parameters": {"type": "object", "properties": {
      "question": {"type": "string", "description": "Question for the user"},
      "candidates": {"type": "array", "items": {"type": "string"}, "description": "Optional quick-select choices for the user"}}}
  }},
  {"type": "function", "function": {
    "name": "start_long_term_update",
    "description": "Start distilling long-term memory. Call when discovering info worth remembering (env facts/user prefs/lessons learned). Skip if memory already updated or in autonomous flow. Must call when a task that took 15+ turns is completed",
    "parameters": {"type": "object", "properties": {}}}
  }

File: llmcore.py (L33-64)

python 复制代码
def compress_history_tags(messages, keep_recent=10, max_len=800, force=False):
    """Compress <thinking>/<tool_use>/<tool_result> tags in older messages to save tokens."""
    compress_history_tags._cd = getattr(compress_history_tags, '_cd', 0) + 1
    if force: compress_history_tags._cd = 0
    if compress_history_tags._cd % 5 != 0: return messages
    _before = sum(len(json.dumps(m, ensure_ascii=False)) for m in messages)
    _pats = {tag: re.compile(rf'(<{tag}>)([\s\S]*?)(</{tag}>)') for tag in ('thinking', 'think', 'tool_use', 'tool_result')}
    _hist_pat = re.compile(r'<(history|key_info|earlier_context)>[\s\S]*?</\1>')
    def _trunc_str(s): return s[:max_len//2] + '\n...[Truncated]...\n' + s[-max_len//2:] if isinstance(s, str) and len(s) > max_len else s
    def _trunc(text):
        text = _hist_pat.sub(lambda m: f'<{m.group(1)}>[...]</{m.group(1)}>', text)
        for pat in _pats.values(): text = pat.sub(lambda m: m.group(1) + _trunc_str(m.group(2)) + m.group(3), text)
        return text
    for i, msg in enumerate(messages):
        if i >= len(messages) - keep_recent: break
        c = msg['content']
        if isinstance(c, str): msg['content'] = _trunc(c)
        elif isinstance(c, list):
            for b in c:
                if not isinstance(b, dict): continue
                t = b.get('type')
                if t == 'text' and isinstance(b.get('text'), str): b['text'] = _trunc(b['text'])
                elif t == 'tool_result':
                    tc = b.get('content')
                    if isinstance(tc, str): b['content'] = _trunc_str(tc)
                    elif isinstance(tc, list):
                        for sub in tc:
                            if isinstance(sub, dict) and sub.get('type') == 'text': sub['text'] = _trunc_str(sub.get('text'))
                elif t == 'tool_use' and isinstance(b.get('input'), dict):
                    for k, v in b['input'].items(): b['input'][k] = _trunc_str(v)
    print(f"[Cut] {_before} -> {sum(len(json.dumps(m, ensure_ascii=False)) for m in messages)}")
    return messages

File: llmcore.py (L90-102)

python 复制代码
def trim_messages_history(history, context_win):
    compress_history_tags(history)
    cost = sum(len(json.dumps(m, ensure_ascii=False)) for m in history) 
    print(f'[Debug] Current context: {cost} chars, {len(history)} messages.')
    if cost > context_win * 3: 
        compress_history_tags(history, keep_recent=4, force=True)   # trim breaks cache, so compress more btw
        target = context_win * 3 * 0.6
        while len(history) > 5 and cost > target:
            history.pop(0)
            while history and history[0].get('role') != 'user': history.pop(0)
            if history and history[0].get('role') == 'user': history[0] = _sanitize_leading_user_msg(history[0])
            cost = sum(len(json.dumps(m, ensure_ascii=False)) for m in history)
        print(f'[Debug] Trimmed context, current: {cost} chars, {len(history)} messages.')

File: llmcore.py (L514-514)

python 复制代码
        self.context_win = cfg.get('context_win', 28000)

File: llmcore.py (L771-775)

python 复制代码
        if self.auto_save_tokens and self.last_tools == tools_json:
            tool_instruction = "\n### Tools: still active, **ready to call**. Protocol unchanged.\n" if _en else "\n### 工具库状态:持续有效(code_run/file_read等),**可正常调用**。调用协议沿用。\n"
        else: self.total_cd_tokens = 0
        self.last_tools = tools_json
        return tool_instruction

File: agent_loop.py (L42-99)

python 复制代码
def agent_runner_loop(client, system_prompt, user_input, handler, tools_schema, max_turns=40, verbose=True, initial_user_content=None):
    messages = [
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": initial_user_content if initial_user_content is not None else user_input}
    ]
    turn = 0;  handler.max_turns = max_turns
    while turn < handler.max_turns:
        turn += 1; turnstr = f'LLM Running (Turn {turn}) ...'
        if handler.parent.task_dir: turnstr = f'Turn {turn} ...'
        if verbose: turnstr = f'**{turnstr}**'
        yield f"\n\n{turnstr}\n\n"
        if turn%10 == 0: client.last_tools = ''  # 每10轮重置一次工具描述,避免上下文过大导致的模型性能下降
        response_gen = client.chat(messages=messages, tools=tools_schema)
        if verbose:
            response = yield from response_gen
            yield '\n\n'
        else:
            response = exhaust(response_gen)
            cleaned = _clean_content(response.content)
            if cleaned: yield cleaned + '\n'

        if not response.tool_calls: tool_calls = [{'tool_name': 'no_tool', 'args': {}}]
        else: tool_calls = [{'tool_name': tc.function.name, 'args': json.loads(tc.function.arguments), 'id': tc.id}
                          for tc in response.tool_calls]
       
        tool_results = []; next_prompts = set(); exit_reason = {}
        for ii, tc in enumerate(tool_calls):
            tool_name, args, tid = tc['tool_name'], tc['args'], tc.get('id', '')
            if tool_name == 'no_tool': pass
            else: 
                if verbose: yield f"🛠️ Tool: `{tool_name}`  📥 args:\n````text\n{get_pretty_json(args)}\n````\n"
                else: yield f"🛠️ {tool_name}({_compact_tool_args(tool_name, args)})\n\n\n"
            handler.current_turn = turn
            gen = handler.dispatch(tool_name, args, response, index=ii)
            try:
                v = next(gen)
                def proxy(): yield v; return (yield from gen)
                if verbose: yield '`````\n'
                outcome = (yield from proxy()) if verbose else exhaust(proxy())
                if verbose: yield '`````\n'
            except StopIteration as e: outcome = e.value
            
            if outcome.should_exit: 
                exit_reason = {'result': 'EXITED', 'data': outcome.data}; break
            if not outcome.next_prompt: 
                exit_reason = {'result': 'CURRENT_TASK_DONE', 'data': outcome.data}; break
            if outcome.next_prompt.startswith('未知工具'): client.last_tools = ''
            if outcome.data is not None and tool_name != 'no_tool': 
                datastr = json.dumps(outcome.data, ensure_ascii=False, default=json_default) if type(outcome.data) in [dict, list] else str(outcome.data) 
                tool_results.append({'tool_use_id': tid, 'content': datastr})
            next_prompts.add(outcome.next_prompt)
        if len(next_prompts) == 0 or exit_reason:
            if len(handler._done_hooks) == 0 or exit_reason.get('result', '') == 'EXITED': break
            next_prompts.add(handler._done_hooks.pop(0))
        next_prompt = handler.turn_end_callback(response, tool_calls, tool_results, turn, '\n'.join(next_prompts), exit_reason)
        messages = [{"role": "user", "content": next_prompt, "tool_results": tool_results}]   # just new message, history is kept in *Session
    if exit_reason: handler.turn_end_callback(response, tool_calls, tool_results, turn, '', exit_reason)
    return exit_reason or {'result': 'MAX_TURNS_EXCEEDED'}

File: assets/insight_fixed_structure.txt (L4-9)

text 复制代码
[CONSTITUTION]
1. 改自身源码先请示;./内可自主实验,允许装包和portable工具
2. 决策前查记忆,有SOP/utils必用;多次失败回看SOP;未查证不断言
3. 分步执行,控制粒度,限制失败半径;3次失败请求干预
4. 密钥文件仅引用,不读取/移动
5. 写任何记忆前读META-SOP核验,memory下文件只能patch修改(除非新建)
相关推荐
不知名的老吴1 小时前
深度剖析NLP模型的实现步骤(一)
人工智能·深度学习·自然语言处理
A8ai1 小时前
Gemini大升级、AI眼镜首发、Android XR亮相,13天后见分晓
android·人工智能·xr
XD7429716361 小时前
科技早报|2026年5月10日:AI 编码开始拼成本、控制面和工程交付
人工智能·科技·开发者工具·科技早报
汽车仪器仪表相关领域1 小时前
Kvaser USBcan Pro 2xHS v2:双通道高速 CAN/FD 专业级 USB 接口,汽车与工业总线深度开发与诊断的核心工具
网络·人工智能·功能测试·fpga开发·汽车·可用性测试
RxGc1 小时前
斯坦福AI Agent报告解读:哪些方向真的落地了
人工智能·agent
华盛AI1 小时前
AI大模型竞品Anthropic Claude Opus 4.7深度分析
人工智能·算法
用户50869981945611 小时前
TRAE SOLO 远程操控电脑,轻松搞定微信公众号每日新闻初稿
人工智能·trae
摸鱼仙人~1 小时前
AI Coding与自动驾驶技术的深度类比及幻觉问题解决方案借鉴
人工智能·机器学习·自动驾驶
Bode_20021 小时前
智能制造系统中的物理因子划分依据
人工智能·制造