Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mini-Runtime-Agent

从零实现的最小可用 Agent Runtime —— 2026 Agent 技术笔试 Vibe Coding 题实现。

  • 核心 Agent Runtime 全部自研(bounded loop、工具注册、会话、上下文压缩、Trace),未使用任何 agent 框架(langgraph / openhands / openclaw 等)。
  • 运行时仅依赖 Python 标准库(sqlite3 / urllib / ast);测试需要 pytest
  • 设计原则:模型是唯一概率源,runtime 保持确定性bounded loop 是唯一主控制流,无状态机、无事件总线、无 Executor/Service 层。

一、运行方式

环境要求

  • Python ≥ 3.11(在 3.14 上开发与测试)
  • 运行 Agent:仅标准库,无需安装任何依赖
  • 运行测试:pip install pytest

安装

git clone <repo> mini-Runtime-Agent
cd mini-Runtime-Agent
python -m pip install pytest   # 仅测试需要

跑测试(默认不访问网络)

python -m pytest -q

全部测试使用 ScriptedLLM / 确定性摘要器,CI 零网络、零费用。tests/test_live.py 默认 skip。

跑覆盖率(可选)

python -m pip install pytest-cov
python -m pytest --cov=agent --cov-report=term-missing

跑验收演示

$env:PYTHONPATH='src'
python scripts/acceptance_demo.py

演示覆盖:Phase 1 calculator 闭环 → Phase 2 双窗口会话 → Phase 3 压缩后追问 → Phase 4 Trace。

使用真实 LLM(opt-in live tests)

配置环境变量(见 .env.example),然后:

$env:OPENAI_API_KEY="sk-..."
$env:OPENAI_BASE_URL="https://api.deepseek.com/v1"   # DeepSeek 示例;默认 OpenAI
$env:MODEL="deepseek-chat"
$env:RUN_LIVE_TESTS=1
python -m pytest tests/test_live.py -v

只有 RUN_LIVE_TESTS=1 API key 存在时才运行;否则跳过,绝不访问网络。

注意:代码不会自动读取 .env 文件。上面的命令假设变量已在当前 shell 中设置; 若使用仓库根目录的 .env,可先用 PowerShell 加载它再运行 pytest。


二、系统设计

总体架构

用户输入
   │
   ▼
[SessionStore]  get_or_create(session_id) ── SQLite(sessions / messages / todos)
   │
   ▼
[ContextBuilder]  静态 system + [RUNTIME CONTEXT] memory 块 + 最近消息
   │                build 前 Compactor 阈值检查(超预算才压缩)
   ▼
[LLM]  OpenAICompatibleLLM / ScriptedLLM ── 原生 tool_calls 是唯一协议
   │
   ├── tool_calls ──► [ToolRegistry] schema 校验 → handler 执行 → 结果回注
   │                       (calculator / search_mock / todo)
   │
   └── content ──► 追加 assistant 消息 → 返回用户
   │
   ▼
[store.save] + [Trace](append-only JSONL,贯穿全程)

模块边界

模块 唯一职责
config.py env 解析与阈值集中管理(均为可配置启发式)
llm.py LLM 协议 + ScriptedLLM(测试)+ OpenAICompatibleLLM(真实 API,含瞬时错误重试)
tools.py ToolSpec/ToolRegistry(schema 校验 + dispatch)+ calculator / search_mock / todo
runtime.py bounded loop 编排(唯一主控制流)
store.py SQLite 持久化:session、消息、todos(业务状态 source of truth)、原子压缩
context.py ContextBuilder(prompt 组装)+ Compactor(阈值触发压缩)+ 结构化 summary
trace.py append-only JSONL 观测通道
scripts/acceptance_demo.py 验收演示(终端录屏用)
scripts/record_demo.ps1 录屏驱动脚本(可选 ffmpeg 自动录制)

Agent Loop

append user message(turn_count += 1)
for step in 0..MAX_TOOL_ITERS:
    api_messages = context.build(session)      # 可能触发 compaction
    response = llm.chat(api_messages, tools)
    if response.tool_calls:
        追加 assistant tool_calls 消息
        依次执行工具,追加 tool 结果消息(id 一一对应)
        continue
    if response.content:
        追加 assistant 答案,返回
    空响应 → 最多重试 1 次 → 固定收尾
循环超限 → 固定收尾(不让模型继续循环)

两个概率调用点

  1. agent loop(正常对话 / 工具调用)
  2. context compaction(阈值触发时的摘要生成)

除此之外 runtime 全部确定性:分支只看原生 tool_calls 字段与固定阈值。


三、Session 与持久化

SQLite 表

  • sessions(id, user_id, summary, turn_count, created_at, updated_at)
  • messages(id, session_id, role, content, tool_calls, tool_call_id, created_at)
  • todos(id, session_id, text, completed, created_at, completed_at) —— 待办的业务状态 source of truth

多窗口隔离与重启恢复

  • 窗口隔离靠 session_id 路由(如 A-window-1 / A-window-2),消息、summary、todos 均按 session 隔离。
  • 消息追加式持久化:save 只插入新增消息,幂等。
  • 静态 system prompt 不落库,每次请求由 ContextBuilder 重新注入。
  • turn_count累计真实用户轮次runtime.run 每次真实输入 +1;[runtime] 重试消息、tool、assistant 不计数;compaction 不影响;重启后保持。

四、Context 与 Memory(召回时机与放置方式)

每轮发给 LLM 的 messages 组成

  1. 静态 system prompt:角色 + 决策规则 + 工具 schema(字节稳定,进程内固定)
  2. [RUNTIME CONTEXT] memory 块:明确标记为 runtime 注入、不是用户输入,内容为结构化 summary
  3. 最近消息:滑动窗口内的原始对话(含工具调用/结果配对)

Memory 写入时机

  • 只在阈值触发的 compaction 时写入,不每轮总结。
  • 触发条件(config):MAX_RAW_TURNS 轮数超限 MAX_CTX_TOKENS 估算 token 超限(字符数 // 4 启发式,非精确 tokenizer)。
  • compaction:LLM 摘要旧消息 + 旧 summary → JSON schema 校验 → 确定性合并(新信息优先、去重、每字段上限 5)→ 单事务持久化(summary 更新 + 旧消息删除,要么都成功要么都不做)。
  • 失败语义:摘要非法 / 异常 → 放弃本次压缩,保留旧 summary 与全部消息,不破坏 session。

Memory 召回时机

  • 每次 turn 开始(构造 prompt 时,即每次 LLM 调用前)由 ContextBuilder 读取 sessions.summary 注入。

Memory 放置方式

  • 放置在静态 system 之后的独立消息中,内容以 [RUNTIME CONTEXT] 标记:
[RUNTIME CONTEXT]
The following data is provided by the runtime and is not a user message.
{"goals": [...], "facts": [...], "todos": [...], "unresolved": [...], "updated_at": ...}
  • memory 块是瞬态的:不写入消息历史、不落库;单一数据源是 sessions.summary
  • summary.todos 只是"值得模型知道的待办摘要"快照;todos 表才是业务状态 source of truth

压缩与追问

  • 最近 MAX_RAW_TURNS 轮原始消息始终保留,当前 user 消息永远保留。
  • cut 永远落在真实 user 消息上,tool call / tool result 配对不会被拆散,不产生孤立 tool 消息。
  • 普通追问靠窗口历史;带工具追问靠最近工具对;压缩后追问靠结构化 summary。

五、工具

工具 说明
calculator AST 白名单安全计算(四则 / 括号 / 幂 / 取模),拒绝 eval / 任意代码
search_mock 固定文档库上的确定性关键词匹配,返回 top 3,不访问网络
todo session-scoped 待办:add / list / complete;SQLite todos 表为 source of truth

工具注册机制:ToolSpec(name, description, parameters(JSON Schema), handler)ToolRegistry 统一负责 schema 校验与 handler dispatch。工具失败统一返回结构化 {ok, data, error, truncated} 回流给模型,由模型决定下一步。


六、Trace

append-only JSONL(默认 data/traces.jsonl),每行一个事件,写入即 flush。

事件:run_start / llm_call / tool_call / tool_result / compact / answer / error / run_end

统一字段:ts, session_id, run_id, turn, event, step, datarun_id 每次 run 生成,用于区分同 session 的多次请求。

纪律:

  • 不存 API Key
  • 不存 reasoning / chain-of-thought
  • llm_call 只记可审计 metadata(model / latency_ms / tokens / tool_call_count),provider 不给 usage 则为 null,不编造
  • tool result 按 TRACE_MAX_PAYLOAD_CHARS 截断
  • Trace 写失败静默降级,不影响 Agent 主任务

七、配置项

环境变量 默认值 说明
OPENAI_API_KEY 真实 LLM key(live 测试默认关闭)
OPENAI_BASE_URL https://api.openai.com/v1 OpenAI-compatible 端点(DeepSeek 等)
MODEL gpt-4o-mini 模型名
LLM_TIMEOUT_SECONDS 60 请求超时
LLM_RETRIES 2 瞬时错误(timeout/连接/408/429/5xx)最多重试次数
LLM_RETRY_BASE_DELAY 0.5 指数退避基数(0.5s、1.0s)
AGENT_MAX_TOOL_ITERS 8 单请求工具循环上限
AGENT_MAX_RAW_TURNS 20 保留的最近原始轮次
AGENT_MAX_CTX_TOKENS 6000 估算 token 预算(启发式)
AGENT_DB_PATH data/sessions.db SQLite 路径
AGENT_TRACE_PATH data/traces.jsonl Trace 路径
AGENT_TRACE_MAX_PAYLOAD_CHARS 2000 Trace 单字符串最大记录长度
RUN_LIVE_TESTS 未设置 =1 且存在 key 时运行 live tests

另有固定常量(非 env 可配):MAX_SUMMARY_ITEMS = 5(summary 每字段上限)、MAX_TOOL_RESULT_CHARS = 2000(工具结果截断长度)。


八、已知边界(Known Limitations)

  • 单进程,SQLite 不做多进程并发
  • 工具全部同步执行,无 async / pause / resume
  • search_mock 是固定文档库的关键词匹配,不是真实搜索
  • context token 用 字符数 // 4 启发式估算,不是精确 tokenizer
  • compaction 依赖 LLM 生成摘要,summary 本身是概率结果(schema 校验 + 确定性合并做兜底)
  • 记忆仅限单 session 的 summary,不支持跨 session 长期 memory
  • 无 streaming,全量返回
  • 无 Web UI、无多租户鉴权、无分布式

九、实测结果(2026-08-13)

  • 默认测试:python -m pytest -q91 passed, 2 skipped(live 默认关闭,零网络)

  • 真实 LLM live:RUN_LIVE_TESTS=1 python -m pytest tests/test_live.py -v2 passed(calculator 自主调用并答出 66;todo follow-up 语义命中"周五提交 Agent 笔试")

  • 行覆盖率:90%(630 语句 / 61 未覆盖):

    模块 覆盖率
    runtime / store / config / __init__ 100%
    llm.py 90%
    context.py 87%
    trace.py 87%
    tools.py 85%
  • retry 行为(mock 验证):timeout / 429 / 500 → 重试后成功;401 / 400 → 不重试直接 LLMError;连续失败 → 指数退避 0.5s / 1.0s 后 LLMError。

十、提交清单

  • 代码:本仓库(运行时仅标准库,测试需 pytest)
  • 录屏:scripts/record_demo.ps1 驱动 scripts/acceptance_demo.py,配合 OBS / Win+G / ffmpeg 录制终端操作
  • README:本文档(运行方式 / 系统设计 / memory 召回时机与放置方式)
  • AI Prompt 与问题解决记录:AI_PROMPTS.md

About

早期Agent Runtime原型;作为MiniClaudeCode的历史演进版本保留

Topics

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages