MiniCode Rebuild 是一个从零、分阶段实现的本地终端 AI Coding Agent。当前项目优先建立可运行、可验证的最小闭环,再逐步加入模型适配、工具调用、安全边界、Agent Loop 和会话恢复。
阶段 0“仓库初始化与工程基线”至阶段 10“可观测性、质量与发布准备”已经完成;阶段 11“长期记忆与检索”、阶段 12“成本控制”和阶段 13“多模型路由与降级”均已完成并合并至 master。
目前已经具备:
- 安装 Python 包;
- 运行
minicode-rebuild命令; - 查看 CLI 帮助和版本;
- 使用 Provider 无关的消息、模型请求、响应和工具调用类型;
- 使用确定性的
MockModel编排模型层测试; - 通过 OpenAI-compatible Chat Completions 适配器调用真实服务;
- 注册带 JSON Schema 参数声明的 Python 工具,并导出模型可见声明;
- 在统一边界处理参数校验、未知工具、执行异常和超长结果;
- 安全解析工作区路径,阻止绝对路径、
..和符号链接逃逸; - 读取文件、列举目录、按 glob 查找路径和按正则搜索 UTF-8 文本;
- 限制单次读取窗口、目录/搜索结果、搜索文件大小和最终工具输出;
- 通过默认拒绝、一次授权和会话精确授权保护文件变更与命令执行;
- 原子创建或覆盖文件、执行精确编辑和事务式多替换补丁;
- 以参数数组和
shell=False在工作区内执行有界前台命令; - 在有最大步数的 Agent Loop 中调用模型、顺序执行工具并回填结构化结果;
- 明确区分最终响应、空响应、模型异常和步数上限四种停止原因;
- 使用交互式 CLI 连续对话,或通过 Headless 模式执行单次任务;
- 在终端查看工具调用状态、停止结果和基础会话统计;
- 通过友好配置错误、安全权限提示和 Ctrl-C/EOF 处理退出;
- 估算中英文与工具协议的上下文 token,按阈值自动压缩旧轮次;
- 定向裁剪超长工具结果,保留头尾证据和结构化元数据;
- 使用
/compact手动压缩,并在摘要器失败时回退到本地摘要; - 在工作区内持久化会话、统计和完整 transcript,并支持跨进程恢复;
- 在内置文件工具修改前记录 Checkpoint,先预览、再确认 Rewind;
- 使用修改后哈希阻止 Rewind 覆盖 Agent 之后发生的外部编辑;
- 扫描工作区
.minicode/skills/<name>/SKILL.md,仅注入有界元数据,并通过load_skill按需加载正文; - 在 Agent、会话与工具边界注册进程内 Hooks,隔离并显式报告 Hook 失败;
- 将脱敏生命周期元数据写入工作区 JSONL 日志,并通过时间线查看运行过程;
- 在工作区本地显式保存长期记忆,并通过有界词法检索按需召回;
- 将记忆结果标记为不可信历史数据,模型写入和删除仍经过权限边界;
- 为会话设置可选 token 预算,并在请求前估算输入和工具协议成本;
- 按剩余额度限制单次模型输出,预算不足时不调用 Provider;
- 按配置顺序尝试主模型与后备模型,只对明确的瞬时故障执行降级;
- 将路由失败和后备模型选择作为脱敏终端事件展示;
- 离线检查 Python、运行配置、Provider 配置、会话存储、Skills 与记忆存储 readiness;
- 使用 Ruff、Mypy、分支覆盖率、构建、安装和跨平台 CI 作为发布质量门禁;
- 执行自动化测试。
阶段 0 至阶段 10 的基础路线、阶段 11“长期记忆与检索”、阶段 12“成本控制”和阶段 13“多模型路由与降级”已经完成并合并。其余高级能力仍必须单独选择、设计、测试和提交。
长期记忆保存在当前工作区 .minicode-rebuild/memories.json,不会跨工作区共享,也不会把完整会话自动写入记忆。模型只得到记忆使用规则,不会在每轮自动加载全部内容;需要历史事实时,通过 search_memory 按需检索。
交互模式可由用户直接管理记忆:
/memory add Prefer pytest for regression tests
/memory list
/memory search pytest
/memory forget <memory-id>
/memory add 是用户显式持久化指令;/memory forget 会展示目标内容,并要求完整输入 yes。模型调用 save_memory 或 delete_memory 时仍走现有权限提示,Headless 模式默认拒绝,只有显式使用 --allow-mutations 才允许本次进程修改记忆。
存储最多 500 条记忆;单条内容、标签、查询、返回数量和结果预览均有上限。检索使用无网络、无第三方依赖的确定性词法评分,适合项目约定、用户明确偏好和长期任务事实,不等同于 embedding 语义搜索。检索结果始终带有“不可信历史数据”边界,不得覆盖当前系统或用户指令,也可能已经过时。
成本控制是显式启用的 Provider 无关 token 门禁。可以限制整个持久化会话的累计 token 用量,也可以单独限制每次模型响应的最大输出:
minicode-rebuild --token-budget 50000 --max-output-tokens 2000 "分析当前项目"
minicode-rebuild --interactive --resume latest --token-budget 50000请求发出前,运行时会估算消息与工具声明占用的输入 token,并从会话剩余额度中扣除输入预留,再把允许的输出上限映射到 OpenAI-compatible max_tokens。如果请求至少需要的输入和一个输出 token 都无法容纳,Agent 以 budget_exhausted 停止,不调用 Provider。恢复会话时,门禁会继续使用已经持久化的 Provider token 统计。交互模式可用 /budget 查看限制、已用量和剩余额度。
该能力用于阻止失控的多步调用,不等同于精确账单上限。输入预算使用跨 Provider 启发式估算;服务端实际计费、缓存 token、推理 token 和价格规则由 Provider 决定。当前请求的真实用量只能在响应返回后得知,因此可能小幅越过估算值,但后续请求会使用更新后的服务端统计重新检查。若 Provider 不返回 usage,累计会话用量也无法精确增长;需要硬货币限额时仍应在 Provider 账户侧设置配额。
真实模型模式可以在同一个 OpenAI-compatible 端点上配置一个主模型和最多四个有序后备模型:
$env:MINICODE_MODEL="primary-model"
$env:MINICODE_FALLBACK_MODELS="fallback-fast,fallback-stable"
minicode-rebuild "分析并修复当前错误"每次 Agent 模型步骤从主模型开始。只有连接失败、超时、HTTP 408/409/425/429 或 5xx 时,运行时才按顺序尝试下一个模型;HTTP 400/401/403 等永久错误、无法解析的响应、适配器编程错误和 KeyboardInterrupt 不会触发降级。每个候选最多调用一次,候选名称必须唯一,主模型不能在后备列表中重复。模型名称最多 256 个字符,并拒绝换行、ANSI 转义等不可打印控制字符。
降级过程只输出模型名称、错误类型和是否继续,不输出 Provider 响应正文、请求内容、API Key 或工具参数。成功的后备响应继续经过原有 Agent Loop、工具权限和 token 成本控制;同一个 ModelRequest.max_output_tokens 会传递给所有候选。
当前阶段只支持同一 API 基址与凭据下的多个模型 ID,不实现跨 Provider 凭据池、负载均衡、健康探测、自动能力评分或并行竞速。OPENAI_BASE_URL 不允许嵌入用户名或密码,避免 Readiness 输出泄露 URL 凭据。失败请求可能已经被 Provider 计费,但没有可靠 usage 时本地无法补记;需要硬成本保障时仍应结合 Provider 账户配额。
每次 CLI 会话默认把生命周期元数据追加到工作区 .minicode-rebuild/events.jsonl。日志只包含时间、事件名、session ID、工具名、成功状态、错误代码和停止原因;不保存用户提示、工具参数、工具输出或 API Key。该目录已从 Git 和模型通用文件工具中隔离。
查看最近 100 条脱敏事件:
minicode-rebuild --timeline
minicode-rebuild --timeline 20交互模式可以使用 /timeline。离线检查 Provider 与本地运行条件:
minicode-rebuild --readiness
minicode-rebuild --demo --readiness--readiness 不会向 Provider 发送请求或验证余额,只检查本地配置结构。普通模式缺少 API Key 时返回非零状态;--demo --readiness 不要求 Key。
项目 Skill 放在工作区的 .minicode/skills/<name>/SKILL.md。目录名使用字母、数字、点、下划线或连字符;可选 frontmatter 中的 name 必须与目录名一致。运行时只把名称和描述放入系统提示,模型判断相关后才调用只读的 load_skill 工具加载单个正文。交互模式可用 /skills 查看当前发现结果。
Hooks 是供 Python 嵌入方使用的进程内扩展点。HookManager 支持 agent_start、agent_stop、session_create、session_resume、session_save、before_tool 和 after_tool。Handler 收到只读数据快照;普通异常不会中断 Agent 或工具,但会写入 Hook report,并由默认终端运行时显示 [hook:error]。Hooks 不绕过工作区、权限、Checkpoint 或工具参数校验。
本阶段不加载任意 Hook 配置或外部脚本,也不实现 MCP;这些能力需要独立威胁模型和验收标准。
- Python 3.11 或更高版本
- pip
建议先创建虚拟环境:
python -m venv .venvWindows:
.\.venv\Scripts\python.exe -m pip install -e ".[dev]"macOS 或 Linux:
./.venv/bin/python -m pip install -e ".[dev]"安装后查看帮助:
minicode-rebuild --help查看版本:
minicode-rebuild --version也可以通过 Python 模块启动:
python -m minicode_rebuild --help阶段 1 默认使用 DeepSeek 的 OpenAI-compatible 接口:
| 环境变量 | 默认值 | 说明 |
|---|---|---|
MINICODE_MODEL |
deepseek-v4-pro |
模型名称 |
MINICODE_FALLBACK_MODELS |
无 | 同一端点上的逗号分隔有序后备模型,最多四个 |
OPENAI_BASE_URL |
https://api.deepseek.com |
API 基址或完整 /chat/completions 地址 |
MINICODE_MODEL_TIMEOUT |
120 |
请求超时秒数,必须是正整数 |
MINICODE_MAX_STEPS |
12 |
每轮最大模型调用步数,必须是正整数 |
MINICODE_SESSION_TOKEN_BUDGET |
无 | 可选的持久化会话累计 token 预算,必须是正整数 |
MINICODE_MAX_OUTPUT_TOKENS |
无 | 可选的单次模型响应 token 上限,必须是正整数 |
MINICODE_SYSTEM_PROMPT |
内置安全提示 | 覆盖本进程使用的系统提示 |
MINICODE_CONTEXT_TOKENS |
16000 |
单轮输入的启发式上下文预算 |
MINICODE_CONTEXT_TRIGGER |
0.8 |
达到预算比例后自动压缩,范围 (0, 1] |
MINICODE_KEEP_RECENT_TURNS |
4 |
压缩时保留的最近完整轮次数 |
MINICODE_TOOL_RESULT_TOKENS |
1500 |
单条历史工具结果的估算 token 上限 |
MINICODE_SUMMARY_TOKENS |
1200 |
结构化摘要的估算 token 上限 |
DEEPSEEK_API_KEY |
无 | DeepSeek API Key |
OPENAI_API_KEY |
无 | 通用 OpenAI-compatible API Key,优先级高于 DEEPSEEK_API_KEY |
项目不会自动加载 .env。运行调用代码前,应由终端、进程管理器或其他安全配置机制注入环境变量。缺少密钥时,真实适配器配置会给出明确错误;MockModel 不需要任何密钥。
无需密钥先运行完整 MockModel 工具演示:
minicode-rebuild --demo "inspect this workspace"使用真实 OpenAI-compatible 模型执行一次 Headless 请求:
$env:OPENAI_API_KEY="your-key"
minicode-rebuild "分析当前项目结构"也可以从标准输入读取单次任务:
"解释 README" | minicode-rebuild --headless启动基础交互模式:
minicode-rebuild --interactive列出当前工作区保存的会话,或恢复最近一次会话:
minicode-rebuild --list-sessions
minicode-rebuild --interactive --resume latest
minicode-rebuild --resume <session-id> "继续上次任务"交互模式提供 /help、/session、/sessions、/transcript、/checkpoints、/rewind-preview [checkpoint-id]、/rewind [checkpoint-id]、/skills、/memory、/timeline、/budget、/stats、/compact 和 /exit。/rewind 总会先显示预览,只有随后完整输入 yes 才修改文件;发现 Agent 写入后又有外部修改时会拒绝覆盖。
会话 JSON、长期记忆和事件日志位于工作区 .minicode-rebuild/,已从 Git 与内置通用文件工具中隔离。Checkpoint 只覆盖 write_file、edit_file 和 patch_file 的 UTF-8 文件变更;run_command 的任意副作用和专用记忆存储不在 Rewind 范围内。写文件、运行命令和模型发起的记忆变更仍会显示风险与操作详情,并要求选择一次允许、会话允许或拒绝。Headless 模式默认拒绝所有变更;只有明确传入 --allow-mutations 才会在本次运行内逐项自动批准,并在标准错误输出警告。
每轮会输出模型步数、工具次数、模型返回的 token 用量和压缩次数。上下文估算是跨 Provider 的保守启发式,不等同于服务端精确 tokenizer;工具结果会优先裁剪,旧轮次按用户输入边界摘要,并始终保留最近完整轮次和主系统提示。会话恢复加载的是受上下文预算约束的工作历史,并继续累计可选的会话 token 成本预算;/transcript 则保留完整、未压缩的用户消息、assistant 工具调用和工具结果。
最小的库调用边界如下:
from minicode_rebuild.config import ModelSettings
from minicode_rebuild.core import Message, MessageRole, ModelRequest
from minicode_rebuild.models.openai_compatible import OpenAICompatibleAdapter
settings = ModelSettings.from_env()
model = OpenAICompatibleAdapter(settings)
response = model.complete(
ModelRequest(
messages=(Message(role=MessageRole.USER, content="Hello"),),
)
)
print(response.content)阶段 2 提供可执行工具的最小公共边界。处理器只会在参数通过 schema 校验后运行;普通异常会转换为失败结果,工具输出也会统一限制长度。
from pathlib import Path
from minicode_rebuild.tooling import (
ToolContext,
ToolDefinition,
ToolRegistry,
ToolResult,
)
echo = ToolDefinition(
name="echo",
description="Return one text value.",
input_schema={
"type": "object",
"properties": {"text": {"type": "string"}},
"required": ["text"],
"additionalProperties": False,
},
handler=lambda arguments, context: ToolResult.success(arguments["text"]),
)
registry = ToolRegistry([echo])
result = registry.execute("echo", {"text": "hello"}, ToolContext(Path.cwd()))
print(result.output)本阶段的 schema 校验器有意只实现已文档化的 JSON Schema 子集;阶段 4 的写入、编辑和命令执行工具复用同一注册与结果边界。
阶段 3 提供四个可以直接注册的定义:read_file、list_files、glob_search 和 grep_files。所有路径先解析为真实路径,再检查是否仍属于 ToolContext.cwd;工作区内绝对路径可用,任何指向工作区外的绝对路径、.. 或符号链接都会被拒绝。
from pathlib import Path
from minicode_rebuild.tooling import ToolContext, ToolRegistry
from minicode_rebuild.tools import READ_ONLY_TOOLS
registry = ToolRegistry(READ_ONLY_TOOLS)
result = registry.execute(
"grep_files",
{"pattern": "ToolRegistry", "include": "**/*.py", "limit": 20},
ToolContext(Path.cwd()),
)
print(result.output)安全和输出边界:
read_file默认读取 8,000 个字符,单次最多 16,000 个字符,并返回继续读取所需的 offset;list_files、glob_search和grep_files都有结果数量上限;grep_files最多扫描 5,000 个文件,跳过超过 1 MiB、非 UTF-8 或不可读的文件,并把单行预览限制为 500 个字符;- 常见缓存、虚拟环境、构建和版本控制目录不会被递归搜索;
- 每个工具仍受注册表 20,000 字符的最终输出上限保护。
阶段 4 提供 write_file、edit_file、patch_file 和 run_command。它们默认拒绝执行,调用方必须通过 PermissionManager 注入明确决策;allow_once 只允许当前请求,allow_session 只复用完全相同的文件路径或命令签名。
from pathlib import Path
from minicode_rebuild.permissions import PermissionManager
from minicode_rebuild.tooling import ToolContext, ToolRegistry
from minicode_rebuild.tools import MUTATING_TOOLS
permissions = PermissionManager(prompt=lambda request: "allow_once")
registry = ToolRegistry(MUTATING_TOOLS)
result = registry.execute(
"edit_file",
{"path": "demo.py", "old": "value = 1", "new": "value = 2"},
ToolContext(Path.cwd(), permissions=permissions),
)
print(result.output)安全边界:
- 所有文件路径和命令工作目录必须位于
ToolContext.cwd内; - 文件变更先完整计算新内容并生成有限 diff,授权后通过同目录临时文件和
os.replace原子提交; edit_file默认要求唯一精确匹配,patch_file的所有替换必须先在内存中成功;run_command只接受单个可执行文件名和独立参数数组,始终使用shell=False;- 命令默认超时 30 秒、最大 300 秒,最终输出仍限制为 20,000 字符。
阶段 5 提供 run_agent_turn():它把用户消息和可选历史组装成 ModelRequest,向模型声明当前注册工具,执行模型返回的工具调用,再以 tool_call_id 关联的 JSON 工具消息继续请求模型。
from pathlib import Path
from minicode_rebuild.agent import run_agent_turn
from minicode_rebuild.core import ModelResponse
from minicode_rebuild.models import MockModel
from minicode_rebuild.tooling import ToolContext, ToolRegistry
result = run_agent_turn(
model=MockModel([ModelResponse(content="Done")]),
tools=ToolRegistry(),
context=ToolContext(Path.cwd()),
user_message="Inspect this project",
max_steps=12,
)
print(result.stop_reason.value, result.content)循环边界:
- 默认最多请求模型 12 步,必须显式使用正整数才能调整;
- 未知工具、非法参数和工具执行失败都会作为结构化工具结果回填,不会直接击穿循环;
- 普通模型异常转换为
model_error,KeyboardInterrupt和SystemExit保持可传播; - 空文本且没有工具调用时以
empty_response停止;持续调用工具时最终以max_steps停止; - 本阶段只提供同步库 API,CLI 接线、流式输出、重试、上下文压缩和会话持久化属于后续阶段。
python -m pytest -q完整质量门禁:
python scripts/release_check.py它依次运行 Ruff、Mypy、分支覆盖率测试、compileall、使用当前已安装构建依赖的 sdist/wheel 构建和无网络 MockModel 演示。当前覆盖率门槛为 85%。GitHub Actions 会在 Windows 与 Ubuntu、Python 3.11 与 3.13 上执行相同门禁。
只运行可复现演示:
python scripts/demo.py- Windows 使用
\.venv\Scripts\python.exe,macOS/Linux 使用./.venv/bin/python;项目业务命令仍通过参数数组和shell=False执行。 - 三个符号链接安全测试在未授予 Windows 创建符号链接权限时会跳过;CI 的 Ubuntu 任务覆盖通用路径、文件工具和记忆存储的真实逃逸路径。
- 终端输出、Skill、会话和事件日志统一使用 UTF-8;Windows 文件替换与权限位行为已有平台保护。
- 发布前确认 Ruff、Mypy、覆盖率测试、编译、构建、全新环境 wheel 安装和 Mock 演示全部通过。
- 检查 Git diff 中没有
.env、API Key、会话、事件日志、缓存、构建产物或无关目录。 - 核对 README、
--help、版本号、Python 版本范围、PR 测试结果和实际行为一致。 - 真实 Provider 验收需由用户自行提供有效 Key;默认质量门禁不发起计费请求。
- 从零实现,不整体复制参考项目。
- 每次只完成一个可验证阶段。
- 测试、文档、代码和 Git 记录保持同步。
- 不提交 API Key、
.env、缓存或本地虚拟环境。
完整的阶段设计、参考分析和真实验证结果见 docs/REBUILD_LOG.md。