diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c45eb19 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,26 @@ +name: CI + +on: + push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install Poetry + run: pip install poetry + + - name: Install dependencies + run: poetry install + + - name: Run tests + run: poetry run pytest diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..965717a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.venv/ +__pycache__/ +.pytest_cache/ +*.pyc diff --git a/docs/00-product-overview.md b/docs/00-product-overview.md new file mode 100644 index 0000000..60972bc --- /dev/null +++ b/docs/00-product-overview.md @@ -0,0 +1,105 @@ +# 00. 产品总览需求文档 + +## 1. 产品定义 + +这是一款面向软件工程任务的交互式 AI 执行系统。它不是单纯的聊天机器人,也不是只会调用几个工具的脚本外壳,而是一套把推理、工具、权限、任务拆解、记忆、扩展机制与用户交互统一起来的产品系统。 + +## 2. 产品目标 + +产品需要满足以下目标: + +1. 帮助用户完成真实的软件工程任务,而不只是提供建议 +2. 在执行过程中保持安全、可控、可恢复 +3. 让复杂任务可以分解、委派、验证、追踪 +4. 让系统可以扩展新的技能、插件与外部工具能力 +5. 让长期使用形成可积累的记忆和工作习惯 + +## 3. 核心用户 + +### 3.1 主要用户 +- 独立开发者 +- 工程师 +- 技术产品经理 +- 有代码任务但希望借助 AI 提升效率的操作者 + +### 3.2 用户的核心诉求 +- 我不只想问问题,我想让系统帮我做事 +- 我不只想生成代码,我想让它真正改动项目并验证结果 +- 我不只想要一次回答,我想让它持续推进任务 +- 我不只想在一个固定产品里工作,我希望它能接入我自己的工具和工作流 + +## 4. 产品要解决的核心问题 + +### 4.1 普通聊天模型的问题 +普通聊天模型的核心局限是: +- 只做一次性回答 +- 没有稳定执行能力 +- 没有工具治理 +- 没有任务状态 +- 没有长期上下文管理 +- 无法形成可扩展工作流 + +### 4.2 简单 Agent 的问题 +简单 Agent 虽然能调用工具,但通常会遇到: +- 行为发散 +- 工具滥用 +- 缺乏权限约束 +- 上下文污染 +- 任务过程不可追踪 +- 做完后不验证 +- 无法优雅扩展 + +因此,本产品的需求本质上是在解决: + +> 如何把“模型 + 工具”升级成一个可用、可控、可扩展、可产品化的软件工程执行系统。 + +## 5. 顶层产品能力 + +根据源码结构反推,这套产品至少需要以下一级能力: + +1. 系统提示词编排能力 +2. 工具发现、执行与治理能力 +3. 多 Agent 调度能力 +4. Skills / Plugins / MCP 扩展能力 +5. Memory / Session 管理能力 +6. 命令系统与交互界面能力 +7. 任务与后台执行能力 +8. 验证与质量保证能力 +9. Telemetry / Transcript / 可追溯能力 + +## 6. 顶层非功能需求 + +### 6.1 安全性 +- 危险操作必须可拦截 +- 外部工具结果必须被视为潜在不可信输入 +- 用户必须能控制权限边界 + +### 6.2 可恢复性 +- 会话可恢复 +- 任务状态可追踪 +- 子任务生命周期可清理 + +### 6.3 可扩展性 +- 外部工具可接入 +- 自定义技能可接入 +- 插件可注入命令、技能和行为约束 + +### 6.4 成本控制 +- 提示词拼装要考虑缓存 +- 上下文使用要考虑预算 +- 大任务要支持压缩和摘要 + +## 7. 产品价值主张 + +如果从需求层面概括,这套产品的价值主张不是“回答更聪明”,而是: + +- 更稳定地执行任务 +- 更安全地调用能力 +- 更可控地使用 AI +- 更容易把 AI 接入真实工程工作流 + +## 8. 一个产品经理视角下的总需求句 + +可以用一句话总结: + +> 用户需要的不是一个会聊天的模型,而是一个能够在真实工程环境中持续推进任务、遵守约束、调用工具、拆分工作、保留上下文并可被验证的 AI 软件工程操作系统。 diff --git a/docs/01-system-prompt-and-orchestration.md b/docs/01-system-prompt-and-orchestration.md new file mode 100644 index 0000000..41440fd --- /dev/null +++ b/docs/01-system-prompt-and-orchestration.md @@ -0,0 +1,143 @@ +# 01. 系统提示词与 Agent 编排需求文档 + +## 1. 系统提示词为什么不是一段固定文案 + +从源码结构可以反推,这个产品要求系统提示词具备“动态拼装”能力,而不是固定模板。 + +### 需求原因 +用户环境、工具集、语言、输出风格、会话状态、MCP 连接状态都可能变化。如果系统提示词不能动态组装,就会出现: +- 规则不匹配当前会话 +- 工具说明失效 +- 记忆无法注入 +- token 成本失控 + +## 2. 系统提示词层的核心需求 + +### 2.1 基础身份定义 +系统需要明确告诉模型: +- 你是执行型协作者 +- 你的主要任务是软件工程支持 +- 你的输出直接给用户看 + +### 2.2 做任务规范 +系统需要内建一套工程行为规范,例如: +- 不要乱加功能 +- 不要过度抽象 +- 不要假装验证过 +- 先读代码再改代码 +- 不要随意创建文件 + +这不是“风格偏好”,而是稳定性需求。 + +### 2.3 风险动作规范 +系统需要明确哪些操作有 blast radius,需要额外确认。例如: +- 删除 +- 推送 +- 外部可见动作 +- 修改共享状态 + +### 2.4 工具使用语法 +系统不仅要告诉模型“有什么工具”,还要告诉它: +- 什么时候读文件 +- 什么时候搜索 +- 什么时候编辑 +- 什么时候用 shell +- 什么时候并行调用 + +## 3. 动态区块需求 + +系统提示词至少需要支持以下动态区块: + +1. 环境信息 +2. 当前语言偏好 +3. 输出风格 +4. 会话局部规则 +5. 记忆内容 +6. MCP 指令 +7. scratchpad / 临时工作区规则 +8. token budget 提示 + +## 4. 为什么要有 Prompt cache boundary + +如果每次请求都完全重造提示词,会带来两个问题: +- 成本上升 +- 缓存命中下降 + +因此产品需要把系统提示词分成: +- 稳定前缀 +- 动态后缀 + +这样才能兼顾灵活性与成本控制。 + +## 5. Agent 编排的需求本质 + +系统需要支持把复杂任务拆给不同角色,而不是只依赖一个万能主 agent。 + +### 必须支持的角色至少包括 +- 通用执行角色 +- 探索角色 +- 规划角色 +- 验证角色 + +### 需求原因 +不同任务阶段对行为模式要求不同: +- 探索需要只读 +- 规划需要结构化输出 +- 实施需要可执行 +- 验证需要对抗性检查 + +如果用一个 agent 混合承担所有角色,稳定性会下降。 + +## 6. 子 Agent 设计需求 + +### 6.1 fork 子任务 +系统需要支持: +- 子任务继承上下文 +- 子任务尽量共享缓存前缀 +- 子任务减少主线程污染 + +### 6.2 background 子任务 +系统需要支持: +- 后台执行 +- 进度跟踪 +- 结果通知 +- 可恢复输出 + +### 6.3 isolation 子任务 +系统需要支持: +- worktree 隔离 +- remote 隔离(如果启用) + +## 7. Prompt 写给子 Agent 的需求 + +主 agent 必须能够给子 agent 交接充分背景,不能只丢一个模糊命令。产品层面要明确要求: +- 写清任务目标 +- 写清已知背景 +- 写清约束 +- 写清期望输出 + +## 8. 伪代码表达 + +```python +class SystemPromptBuilder: + def build(self, env, tools, memory, output_style, language, mcp_info): + static_sections = [ + intro(), + system_rules(), + task_rules(), + action_safety(), + tool_usage_rules(), + ] + dynamic_sections = [ + env_section(env), + language_section(language), + output_style_section(output_style), + memory_section(memory), + mcp_section(mcp_info), + ] + return static_sections + dynamic_sections +``` + +## 9. 产品经理视角下的总需求句 + +> 系统提示词层的核心需求,是把产品规则、工具语法、会话状态和环境约束组装成一套可动态生成、可缓存优化、可支持多角色协作的运行时控制平面。 diff --git a/docs/02-tools-permissions-and-execution.md b/docs/02-tools-permissions-and-execution.md new file mode 100644 index 0000000..6c943b3 --- /dev/null +++ b/docs/02-tools-permissions-and-execution.md @@ -0,0 +1,119 @@ +# 02. 工具、权限与执行链需求文档 + +## 1. 为什么工具系统是产品核心 + +如果模型不能操作环境,它只是建议生成器。这个产品的目标是帮助用户推进真实工程任务,因此必须具备正式的工具系统。 + +## 2. 工具系统需求 + +### 2.1 基础工具能力 +产品至少需要以下工具类别: +- 读文件 +- 改文件 +- 写文件 +- 搜索文件 +- 搜索内容 +- shell / 命令执行 +- todo / 任务管理 +- 用户追问 +- 启动子 agent +- 调用外部 MCP 工具 + +### 2.2 工具使用规范 +系统必须对模型明确规定工具使用优先级,避免: +- 用 shell 替代专用文件工具 +- 误删或误改文件 +- 低效重复操作 + +## 3. 权限系统需求 + +### 3.1 用户控制边界 +用户必须能控制哪些工具自动允许,哪些要询问,哪些禁止。 + +### 3.2 权限决策来源 +权限决策至少可能来自: +- 当前模式 +- 用户规则 +- 项目规则 +- Hook 决策 +- 特殊工具安全策略 + +### 3.3 被拒后的行为要求 +如果某次工具调用被拒绝,系统不能机械重试,而应: +- 理解拒绝信号 +- 调整方案 +- 必要时向用户澄清 + +## 4. 执行链路需求 + +工具执行不能是“模型决定 -> 直接运行”。产品需要一条正式执行链: + +1. 找到工具 +2. 校验输入结构 +3. 做额外 validateInput +4. 执行 PreToolUse hooks +5. 做权限决策 +6. 真正执行工具 +7. 记录 telemetry +8. 执行 PostToolUse hooks +9. 格式化结果回流给模型 + +## 5. 为什么需要 Hook + +Hook 的需求本质是: +- 让组织规则进入运行时 +- 让系统可以插入额外检查 +- 让工具调用具备动态治理能力 + +### Hook 至少要支持的行为 +- 返回消息 +- 阻断执行 +- 修改输入 +- 提供 allow / ask / deny 建议 +- 注入额外上下文 + +## 6. 为什么输入校验是必需的 + +模型本身会生成错误参数,因此产品必须在执行层拦住: +- schema 不合法 +- 参数越界 +- 缺字段 +- 类型错误 + +## 7. shell 类工具的特殊需求 + +shell 工具的风险高于读写文件类工具,因此需要: +- 更严格的权限策略 +- 可能的前置分类器检查 +- 更强的审计能力 + +## 8. 工具执行结果的产品要求 + +工具执行结果不仅要“返回成功/失败”,还要满足: +- 可读 +- 可追踪 +- 能被后续 Hook 处理 +- 能成为 transcript 的一部分 + +## 9. 伪代码表达 + +```python +def execute_tool(tool_name, raw_input, context): + tool = find_tool(tool_name) + validated = schema_validate(tool, raw_input) + validated = run_custom_validation(tool, validated) + + hook_result = run_pre_hooks(tool, validated, context) + decision = resolve_permission(hook_result, context) + if decision == 'deny': + return denied_result() + + final_input = maybe_update_input(validated, hook_result) + output = tool.call(final_input) + run_post_hooks(tool, final_input, output, context) + return output +``` + +## 10. 产品经理视角下的总需求句 + +> 工具系统必须从“可调用”升级到“可治理”:既要让模型拥有执行能力,也要在执行前后经过校验、权限、Hook、审计与结果回流,确保整个过程安全、稳定、可追踪。 diff --git a/docs/03-skills-plugins-mcp.md b/docs/03-skills-plugins-mcp.md new file mode 100644 index 0000000..a91c9e0 --- /dev/null +++ b/docs/03-skills-plugins-mcp.md @@ -0,0 +1,103 @@ +# 03. Skills、Plugins 与 MCP 需求文档 + +## 1. 为什么产品不能只靠内置能力 + +如果产品所有能力都硬编码在主程序里,会遇到几个问题: +- 难以扩展 +- 难以适配不同团队 +- 难以承载领域知识 +- 难以形成生态 + +因此,这套产品必须支持可扩展能力面。 + +## 2. Skills 的需求本质 + +Skill 不是普通帮助文档,而是一种可复用的工作流能力包。 + +### 2.1 Skill 需要承载什么 +- 某类任务的使用规则 +- 某类任务的上下文说明 +- 某类任务的执行 SOP +- 该任务适用的工具边界 + +### 2.2 为什么 Skill 必须是 first-class primitive +因为产品需要让模型在遇到特定任务时,优先加载相应能力,而不是每次都重新即兴发挥。 + +## 3. Skill 的产品需求 + +1. 系统要能列出当前可用技能 +2. 模型要能在合适时调用技能 +3. skill 内容要能注入会话 +4. skill 要能带 frontmatter 元信息 +5. skill 可以约束 allowed-tools +6. skill 需要避免重复加载 + +## 4. Plugin 的需求本质 + +Plugin 的角色不是给程序员加脚本,而是为模型注入新的行为表面。 + +### Plugin 至少要支持 +- 新命令 +- 新技能目录 +- frontmatter 配置 +- 运行时变量替换 +- 工具约束 +- 用户可调用与否的声明 +- effort / model 等提示 + +## 5. 为什么要有 MCP + +MCP 的需求本质是: +- 用统一协议接入外部工具 +- 让产品获得更多外部能力 +- 让工具与说明一起进入运行时 + +### MCP 需要满足 +1. 接入外部 server +2. 拉取工具定义 +3. 注入使用说明 +4. 在 agent 级别支持额外 server +5. 在生命周期结束时清理资源 + +## 6. 为什么模型需要“知道扩展能力存在” + +很多系统扩展做不起来,不是因为没有插件,而是模型根本不知道: +- 有哪些技能 +- 什么时候该用 +- 扩展工具怎么使用 + +因此产品必须把这些扩展能力转化成模型可感知的提示信息。 + +## 7. Plugin / Skill / MCP 三者关系 + +### Skill +解决“某类任务应该怎么做” + +### Plugin +解决“系统可以新增什么能力面” + +### MCP +解决“系统如何连接外部工具与外部能力” + +三者叠加后,产品才能具备生态能力。 + +## 8. 伪代码表达 + +```python +class ExtensionRuntime: + def load_skills(self, cwd): + return discover_skill_packages(cwd) + + def load_plugins(self): + return discover_plugins() + + def connect_mcp_servers(self, configs): + return [connect(server) for server in configs] + + def expose_capabilities_to_model(self, skills, plugins, mcp_servers): + return build_runtime_capability_listing(skills, plugins, mcp_servers) +``` + +## 9. 产品经理视角下的总需求句 + +> 产品必须提供一套可扩展运行时:Skill 负责封装工作流知识,Plugin 负责扩展命令与能力表面,MCP 负责接入外部工具与说明,三者共同让系统具备持续生长的能力。 diff --git a/docs/04-memory-and-session.md b/docs/04-memory-and-session.md new file mode 100644 index 0000000..eab14dd --- /dev/null +++ b/docs/04-memory-and-session.md @@ -0,0 +1,152 @@ +# 04. 记忆与会话系统需求文档 + +## 1. 为什么需要记忆系统 + +如果产品只靠当前窗口上下文工作,会出现几个问题: + +- 用户长期偏好无法沉淀 +- 项目背景每次都要重新解释 +- 复杂任务容易因上下文压缩而丢失关键事实 +- 系统无法逐步形成“协作连续性” + +因此,这个产品需要一个多层级记忆系统。 + +## 2. 记忆系统的产品目标 + +1. 保存长期有效的用户偏好和协作约束 +2. 保存项目级背景信息 +3. 在上下文压缩后仍能恢复关键事实 +4. 支持会话恢复与后续继续执行 +5. 为子任务和后台任务提供必要的上下文继承 + +## 3. 记忆层级反推 + +从目录结构与调用链可以推断,产品至少需要以下记忆层级: + +### 3.1 用户级记忆 +保存: +- 用户语言偏好 +- 输出风格偏好 +- 常见工作习惯 +- 可长期生效的协作规则 + +### 3.2 项目级记忆 +保存: +- 项目目录背景 +- 常用命令 +- 测试 / 构建约定 +- 项目规范文件中的持续性要求 + +### 3.3 会话级记忆 +保存: +- 当前任务目标 +- 当前已知上下文 +- 本轮执行中的中间状态 +- 当前阶段的局部决策 + +### 3.4 子任务级记忆 +保存: +- 子 agent 的任务目标 +- 子 agent 的局部上下文 +- fork 继承的父上下文 +- 验证 / 探索 / 规划等专职子任务的专属状态 + +## 4. 记忆系统的核心需求 + +### 4.1 持久化需求 +系统需要能保存以下内容: +- 用户长期偏好 +- 项目常识 +- 会话摘要 +- 可恢复的任务元数据 + +### 4.2 注入需求 +系统需要在合适的时机把记忆重新注入到模型上下文中,而不是永久塞在一次请求里。 + +### 4.3 压缩需求 +当上下文接近限制时,系统必须支持: +- 自动压缩历史 +- 保留关键事实 +- 降低 token 成本 + +### 4.4 恢复需求 +用户中断后再次回来时,系统要支持: +- 恢复当前任务状态 +- 恢复关键上下文 +- 继续之前的执行链 + +## 5. Session 管理需求 + +### 5.1 会话身份 +每个会话都需要唯一标识,便于: +- 恢复 +- 归档 +- 分析 +- 分享 +- 跟踪任务生命周期 + +### 5.2 会话摘要 +系统需要能够自动生成摘要,用于: +- 历史压缩 +- 后续恢复 +- 快速理解上下文 + +### 5.3 会话转录 +系统需要持久记录关键消息流,至少包括: +- 用户输入 +- 模型输出 +- 工具调用摘要 +- 子任务结果 +- 失败信息 + +## 6. 为什么记忆不能等于“把所有历史都塞进去” + +产品层面必须避免一种错误思路:把完整历史无脑拼进上下文。 + +这样会导致: +- 成本上升 +- 注意力稀释 +- 有效信息被噪声淹没 +- 子任务污染主线程 + +更合理的需求是: + +> 记忆应该是可选择地提取、压缩、恢复和注入,而不是无节制堆积。 + +## 7. fork 与记忆的需求关系 + +从多 agent 设计看,fork 子任务有两个特殊需求: + +1. 继承足够的上下文来完成任务 +2. 又不能把中间噪声带回主线程 + +因此系统需要: +- 支持父子上下文有边界地继承 +- 支持子任务输出被总结,而不是原样回灌 +- 支持 prompt cache 友好的上下文复用 + +## 8. 记忆系统的伪代码表达 + +```python +class MemorySystem: + def load_user_memory(self, user_id): + ... + + def load_project_memory(self, project_root): + ... + + def load_session_summary(self, session_id): + ... + + def build_runtime_context(self, user_memory, project_memory, session_summary): + return merge_relevant_context(user_memory, project_memory, session_summary) + + def compact_if_needed(self, messages): + if context_budget_low(messages): + return summarize(messages) + return messages +``` + +## 9. 产品经理视角下的总需求句 + +> 这套产品必须拥有一个分层记忆系统:它既能保存长期偏好与项目背景,又能在上下文预算受限时压缩历史,并在任务恢复、子任务继承和长期协作中重新注入关键事实。 diff --git a/docs/05-commands-ui-and-operator-experience.md b/docs/05-commands-ui-and-operator-experience.md new file mode 100644 index 0000000..17811c0 --- /dev/null +++ b/docs/05-commands-ui-and-operator-experience.md @@ -0,0 +1,86 @@ +# 05. 命令系统、界面与操作者体验需求文档 + +## 1. 为什么命令系统是一级产品能力 + +这类产品不是单轮对话工具,而是一个长期运行的操作者界面。因此,命令系统不是附属功能,而是操作面板。 + +## 2. 命令系统需求 + +产品需要支持用户快速控制以下对象: +- memory +- permissions +- hooks +- mcp +- skills +- tasks +- review / plan / status +- 输出风格 +- model +- sandbox +- 插件管理 + +## 3. 命令系统的产品目标 + +1. 降低复杂能力的学习成本 +2. 提供清晰的系统控制入口 +3. 把高级功能从自然语言里解耦出来 +4. 为插件和技能提供统一入口面 + +## 4. UI / TUI 的核心需求 + +### 4.1 任务可见性 +用户需要看到: +- 当前在做什么 +- 后台任务是否运行中 +- 子 agent 在做什么 +- 哪些动作在等待权限 + +### 4.2 状态反馈 +系统需要及时反馈: +- 进度 +- 错误 +- 被阻断原因 +- 工具运行状态 + +### 4.3 结构化展示 +系统需要把复杂状态结构化展示出来,例如: +- memory 面板 +- permissions 面板 +- skills 列表 +- hooks 状态 +- mcp 状态 +- tasks 状态 + +## 5. 为什么操作者体验是核心需求 + +如果系统很强,但用户看不懂当前状态,就会产生: +- 不信任 +- 不敢授权 +- 不知道何时干预 +- 不知道下一步怎么控制 + +因此产品必须让操作者感觉: +- 任务是透明的 +- 系统是可控的 +- 能力是可发现的 + +## 6. 后台任务与通知需求 + +一旦支持 background agents,系统就必须支持: +- 后台任务注册 +- 进度更新 +- 完成通知 +- 输出文件查看 +- 必要时 kill / cancel + +## 7. 命令系统的扩展需求 + +命令系统还必须允许: +- 内建命令 +- 插件命令 +- skill 入口 +- 条件启用的功能命令 + +## 8. 产品经理视角下的总需求句 + +> 这套产品不仅要有能力,还要有可操作性。命令系统与界面层的目标,是把复杂的 AI 运行时变成用户可发现、可控制、可追踪的操作者体验。 diff --git a/docs/06-verification-and-quality.md b/docs/06-verification-and-quality.md new file mode 100644 index 0000000..ce595a5 --- /dev/null +++ b/docs/06-verification-and-quality.md @@ -0,0 +1,92 @@ +# 06. 验证与质量保证需求文档 + +## 1. 为什么“做完”不等于“完成” + +在 AI 编程产品里,最大的风险之一是:模型会把“代码改了”误当成“任务完成了”。 + +因此,这个产品必须把“验证”设计成一个独立能力,而不是可有可无的附属步骤。 + +## 2. 验证系统的产品目标 + +1. 独立检查实现是否真的可用 +2. 防止只读代码就宣称完成 +3. 防止 happy path 偏见 +4. 让验证结果带证据而不是口头判断 + +## 3. 验证 Agent 的需求 + +### 3.1 独立角色 +验证角色需要与实施角色分离,避免实现者偏见。 + +### 3.2 默认心智模型 +验证角色的工作不是“帮实现找理由通过”,而是主动尝试发现问题。 + +### 3.3 必须禁止的行为 +验证角色不能: +- 修改项目文件 +- 安装依赖 +- 用写操作掩盖问题 + +### 3.4 必须支持的检查类型 +- build +- test suite +- lint / type-check +- 接口调用验证 +- UI 自动化验证 +- CLI 输入输出验证 +- migration 验证 +- adversarial probe + +## 4. 输出格式需求 + +验证结果必须满足: +- 有检查项标题 +- 有实际执行命令 +- 有真实输出 +- 有 PASS / FAIL / PARTIAL 结果 +- 最后有统一 verdict + +## 5. 为什么需要 adversarial probe + +只验证 happy path 会导致大量问题漏检。因此系统要要求验证阶段主动尝试: +- 边界输入 +- 并发场景 +- 空输入 / 非法输入 +- 重复请求 +- 不存在资源引用 + +## 6. 为什么验证必须可追溯 + +如果验证没有命令和输出,用户无法判断: +- 到底测没测 +- 测了什么 +- 失败在哪 + +因此,质量系统必须要求“证据化验证”。 + +## 7. 质量保证不只是测试 + +这套产品的质量保证包括: +- 提示词中对诚实汇报的要求 +- 验证角色的独立存在 +- 执行链的日志与 transcript +- 失败可追踪 +- 结果可复盘 + +## 8. 伪代码表达 + +```python +class VerificationRunner: + def verify(self, task, changed_files, context): + checks = build_verification_plan(task, changed_files) + results = [] + for check in checks: + cmd = check.command + output = run(cmd) + results.append(evaluate(output, check.expectation)) + return summarize_verdict(results) +``` + +## 9. 产品经理视角下的总需求句 + +> 产品必须把验证设计成独立、对抗性、证据化的质量系统:它不依赖实施者自我声明,而是通过命令、输出与统一 verdict 来证明任务是否真正完成。 diff --git a/docs/07-architecture-map.md b/docs/07-architecture-map.md new file mode 100644 index 0000000..3c0c94c --- /dev/null +++ b/docs/07-architecture-map.md @@ -0,0 +1,112 @@ +# 07. 架构能力地图 + +## 1. 顶层能力区 + +### A. 入口层 +对应需求: +- 提供 CLI 入口 +- 提供初始化流程 +- 提供 SDK 接入方式 +- 提供 MCP 入口 + +### B. Prompt 编排层 +对应需求: +- 动态生成系统提示词 +- 注入环境信息 +- 注入语言与输出风格 +- 注入记忆、MCP 说明、会话局部规则 + +### C. 工具执行层 +对应需求: +- 工具发现 +- 工具输入校验 +- 权限判断 +- Hook 拦截 +- 执行记录 +- 输出回流 + +### D. Agent 调度层 +对应需求: +- 启动子 agent +- 支持 fork / background / remote / teammate 模式 +- 管理 agent 生命周期 +- 管理 agent 上下文边界 + +### E. 扩展生态层 +对应需求: +- Skills +- Plugins +- MCP +- 命令扩展 +- 插件变量替换与配置注入 + +### F. Memory / Session 层 +对应需求: +- 项目记忆 +- 用户记忆 +- 会话摘要 +- transcript +- resume + +### G. 任务与后台层 +对应需求: +- 本地任务 +- 后台 agent 任务 +- 远程 agent 任务 +- shell 任务 +- 进度追踪与通知 + +### H. 质量保证层 +对应需求: +- verification agent +- 构建 / 测试 / lint / 类型检查 +- adversarial probe +- FAIL / PASS / PARTIAL 判定 + +### I. 界面与操作者体验层 +对应需求: +- TUI 展示 +- 状态栏 +- 权限提示 +- 任务进度 +- 命令系统 +- agent / skills / memory / hooks 可视化 + +## 2. 跨层系统性要求 + +### 2.1 安全要求跨层存在 +- Prompt 层要提醒风险 +- Tool 层要校验与限权 +- Hook 层要能阻断 +- UI 层要能提示用户 + +### 2.2 上下文管理跨层存在 +- Prompt 组装要考虑动态边界 +- Session 要考虑压缩与恢复 +- Agent 要考虑上下文隔离 +- Skills / MCP 要考虑按需注入 + +### 2.3 产品化要求跨层存在 +- 每个子系统不仅要能工作,还要: + - 可追踪 + - 可恢复 + - 可扩展 + - 可治理 + +## 3. 反推出来的组织设计 + +从架构地图可以反推出,这不是一个“以模型为中心”的产品,而是一个“以运行时操作系统为中心”的产品。 + +模型只是其中一个核心部件,真正的产品能力来自这些部分的组合: + +- prompt assembly +- tool execution pipeline +- permission governance +- agent orchestration +- extension surface +- memory/session management +- verification and traceability + +## 4. 产品经理视角下的总需求句 + +> 该产品的架构应被理解为一张能力地图:每一层都不是独立存在的功能点,而是在共同支撑一个目标——让 AI 在真实工程环境中成为一个可控、可扩展、可追踪的执行系统。 diff --git a/docs/08-agent-runtime-loop.md b/docs/08-agent-runtime-loop.md new file mode 100644 index 0000000..a6690ce --- /dev/null +++ b/docs/08-agent-runtime-loop.md @@ -0,0 +1,162 @@ +# 08. Agent 运行时主循环规格 + +> 目标:定义 Python 版本实现时最核心的运行时循环。该文档不是源码解释,而是实现规格。 + +## 1. 设计目标 + +主循环必须满足以下目标: + +1. 能持续推进任务,而不是只做一次回答 +2. 能在每轮后根据工具结果重新决策 +3. 能在权限阻断、上下文压缩、工具失败时继续可控运行 +4. 能触发子 agent、后台任务、验证任务 +5. 能生成可追踪的消息流与 transcript + +## 2. 主循环的输入 + +一次主循环至少需要以下输入: + +- `messages`: 当前消息序列 +- `system_prompt`: 已组装完成的系统提示词 +- `tool_registry`: 当前可用工具集合 +- `tool_use_context`: 执行上下文(cwd、权限、session、任务状态) +- `memory_context`: 注入后的记忆内容 +- `user_context`: 用户态上下文 +- `system_context`: 系统态上下文 +- `can_use_tool`: 权限判断函数 +- `max_turns`: 最大回合数 +- `task_budget`: 本轮任务预算(可选) + +## 3. 主循环的输出 + +主循环每轮可能产出: + +- 普通 assistant 消息 +- tool_use 请求 +- tool_result 回写 +- progress 消息 +- compact / summary 边界消息 +- 子任务启动事件 +- terminal state(完成 / 失败 / 中断) + +## 4. 循环状态 + +实现时必须维护显式状态,而不是散落在局部变量中。 + +建议状态字段: + +```python +class QueryState: + messages: list + turn_count: int + auto_compact_tracking: dict | None + pending_tool_summary: object | None + stop_hook_active: bool + max_output_recovery_count: int + has_attempted_reactive_compact: bool + transition_reason: str | None +``` + +## 5. 标准单轮流程 + +每轮执行顺序建议固定为: + +1. 预处理当前消息 +2. 计算 token / budget 状态 +3. 调用模型 +4. 解析返回内容 +5. 如有工具调用,进入工具执行链 +6. 将工具结果追加回消息 +7. 如需压缩,执行 compact +8. 决定是否继续下一轮 +9. 达到终止条件则返回 terminal state + +## 6. 终止条件 + +至少支持以下终止条件: + +- 模型明确结束任务 +- 达到 `max_turns` +- 用户中断 +- 权限阻断且无可行替代方案 +- 发生不可恢复异常 +- 任务被后台化或移交 + +## 7. 工具调用处理要求 + +如果模型返回一个或多个工具调用: + +- 必须按顺序或编排策略执行 +- 必须将每个 tool_result 写回消息流 +- 如果工具失败,失败信息也必须结构化回写 +- 严禁只在 UI 层显示,不回写到模型上下文 + +## 8. 压缩与恢复点 + +主循环必须内建以下钩子点: + +- 调用模型前检查是否需要压缩 +- 工具结果过长时应用 result budget +- prompt 过长时触发 compact / reactive compact +- resume 时从 compact boundary 后恢复有效消息 + +## 9. 子 agent 与后台任务接入点 + +主循环必须允许以下事件打断常规路径: + +- 启动 foreground subagent +- 启动 background subagent +- 接收 task notification +- 继续 resume 某个已有 agent + +## 10. Python 版推荐伪代码 + +```python +def query_loop(params): + state = init_state(params) + + while True: + if should_stop(state, params): + return terminal_result(state) + + state = maybe_compact(state, params) + request = build_model_request(state, params) + response = call_model(request) + state.messages.append(response.assistant_message) + + if response.tool_calls: + tool_events = run_tools(response.tool_calls, params) + state.messages.extend(tool_events) + state.transition_reason = 'tool_round' + continue + + if response.should_launch_subagent: + launch_subagent(response, params) + state.transition_reason = 'subagent' + continue + + if response.is_terminal: + return terminal_result(state) + + state.turn_count += 1 +``` + +## 11. 实现边界 + +Python 第一版务必保证: +- 单主循环是清晰、可测试的 +- 每轮状态可序列化 +- 每轮输出可回放 +- 每轮失败可定位 + +不要把主循环写成大量隐式副作用的脚本式逻辑。 + +## 12. 验收标准 + +程序员实现完成后,应满足: + +1. 可以连续多轮处理任务 +2. 工具结果能回流并影响下一轮 +3. 超长上下文会压缩而不是直接崩溃 +4. 子任务可以插入主流程 +5. transcript 可完整记录轮次 diff --git a/docs/09-message-model-and-state.md b/docs/09-message-model-and-state.md new file mode 100644 index 0000000..6ab11e1 --- /dev/null +++ b/docs/09-message-model-and-state.md @@ -0,0 +1,130 @@ +# 09. 消息模型与状态规格 + +## 1. 为什么消息模型必须先定义 + +这类 Agent 系统的核心不是“函数调用”,而是“结构化消息驱动状态变化”。如果消息模型不稳定,后面的 memory、resume、工具执行、agent 任务都会混乱。 + +## 2. 顶层消息类型 + +Python 版本建议至少定义以下消息类型: + +- `system` +- `user` +- `assistant` +- `tool_use` +- `tool_result` +- `progress` +- `attachment` +- `summary` +- `compact_boundary` +- `notification` +- `tombstone`(可选,用于删除/隐藏历史消息后的链修复) + +## 3. 核心消息字段 + +所有消息建议共享: + +```python +class BaseMessage: + id: str + type: str + created_at: float + parent_id: str | None + session_id: str +``` + +### assistant 消息 +```python +class AssistantMessage(BaseMessage): + content_blocks: list + usage: dict | None + stop_reason: str | None +``` + +### user 消息 +```python +class UserMessage(BaseMessage): + content_blocks: list + source: str | None +``` + +### tool_use 消息块 +```python +class ToolUseBlock: + id: str + name: str + input: dict +``` + +### tool_result 消息块 +```python +class ToolResultBlock: + tool_use_id: str + content: str | list + is_error: bool = False +``` + +## 4. 为什么 parent_id 很重要 + +消息链必须可追踪。这样才能支持: +- transcript 回放 +- resume +- 历史修复 +- compact boundary 后的有效链重建 + +## 5. progress 消息的处理原则 + +高频 progress 消息通常是 UI 态,而不是核心 transcript。实现时建议: + +- 可以显示在界面中 +- 但默认不参与核心 parent chain +- 不应污染 resume 后的模型上下文 + +## 6. attachment / notification 的用途 + +这类消息用于携带结构化系统事件,例如: +- hook 阻断 +- task notification +- permission 说明 +- MCP 附加上下文 + +不要把所有系统事件都挤进普通文本消息中。 + +## 7. 状态对象要求 + +除消息外,还要有显式状态对象: + +```python +class SessionState: + session_id: str + cwd: str + current_task_id: str | None + memory_summary: str | None + active_agent_ids: list[str] + current_permission_mode: str + token_budget_state: dict | None +``` + +## 8. compact boundary 的需求 + +压缩前后必须有清晰边界,方便: +- 知道哪些历史被总结了 +- resume 时只加载必要部分 +- 保持消息链清晰 + +## 9. transcript 存储要求 + +transcript 至少要支持: +- 顺序追加 +- 按 session 加载 +- 按 agent 侧链加载 +- 读取尾部摘要 +- 限制超大文件读取风险 + +## 10. 验收标准 + +1. 任一轮对话都能序列化为结构化消息 +2. tool_use 和 tool_result 可一一对应 +3. progress 不污染核心历史链 +4. 会话可按消息链恢复 +5. 子 agent transcript 能独立存储 diff --git a/docs/10-context-management.md b/docs/10-context-management.md new file mode 100644 index 0000000..4b43bb8 --- /dev/null +++ b/docs/10-context-management.md @@ -0,0 +1,98 @@ +# 10. 上下文管理与压缩规格 + +## 1. 目标 + +上下文管理的目标不是“保留一切”,而是让模型在有限预算内持续拿到最关键的信息。 + +## 2. 必须保留的信息 + +优先级最高的信息包括: + +1. 当前用户任务目标 +2. 系统硬规则与安全约束 +3. 用户明确偏好 +4. 最近关键工具结果 +5. 当前活跃子任务状态 +6. 记忆摘要 +7. 最近 compact 后的摘要 + +## 3. 可压缩的信息 + +以下信息应优先被压缩或裁剪: + +- 旧的长日志 +- 重复解释 +- 已完成步骤的冗余细节 +- 大量相似 read/search 结果 +- 旧的 progress 消息 + +## 4. 上下文预算机制 + +Python 版建议同时维护: +- 粗略 token 估计 +- 工具结果字符预算 +- 每轮输出预算 +- 全任务预算(可选) + +## 5. 压缩触发条件 + +建议在以下情况触发: + +- 请求前 token 估计超过阈值 +- 工具结果总量过大 +- 模型返回 prompt too long +- resume 重建会话时 + +## 6. 压缩策略层级 + +### 6.1 轻量裁剪 +先裁剪: +- 重复 progress +- 冗长工具输出尾部 +- 无关附件 + +### 6.2 摘要压缩 +把旧消息浓缩成 summary message。 + +### 6.3 边界标记 +插入 compact boundary,标记压缩点。 + +## 7. 子任务上下文要求 + +### fork 子任务 +- 继承必要父上下文 +- 尽量维持 cache-friendly prefix +- 子任务输出不要原样全部灌回主线程 + +### verification 子任务 +- 需要任务目标、改动文件、实现摘要 +- 不需要完整噪声过程 + +## 8. 工具结果预算 + +工具结果必须经过 budget 控制。否则: +- 长 grep +- 长 read +- 长 shell 输出 +会迅速污染上下文。 + +## 9. Python 版伪代码 + +```python +def manage_context(messages, budget): + messages = drop_ephemeral_progress(messages) + messages = trim_large_tool_results(messages, budget.tool_result_chars) + + if estimate_tokens(messages) > budget.max_input_tokens: + summary = summarize_old_messages(messages) + messages = build_post_compact_messages(summary, messages) + + return messages +``` + +## 10. 验收标准 + +1. 长任务不会因上下文无限增长而崩溃 +2. resume 后仍可恢复关键事实 +3. 子任务不会把噪声大规模回灌主线程 +4. 工具长输出会被预算裁剪 diff --git a/docs/11-task-model.md b/docs/11-task-model.md new file mode 100644 index 0000000..0d5065e --- /dev/null +++ b/docs/11-task-model.md @@ -0,0 +1,85 @@ +# 11. 任务模型与后台执行规格 + +## 1. 目标 + +任务系统负责把“一个 agent 在做什么”变成可追踪对象,而不是只存在于对话文本里。 + +## 2. 任务类型 + +Python 版建议至少支持: + +- `main_session_task` +- `local_agent_task` +- `background_agent_task` +- `shell_task` +- `verification_task` +- `remote_task`(可后置) + +## 3. 核心任务字段 + +```python +class TaskRecord: + task_id: str + type: str + session_id: str + parent_task_id: str | None + agent_id: str | None + description: str + status: str + created_at: float + started_at: float | None + finished_at: float | None + output_path: str | None + error: str | None + result_summary: str | None + progress: dict | None +``` + +## 4. 状态机 + +建议统一状态: +- `pending` +- `running` +- `waiting_permission` +- `backgrounded` +- `completed` +- `failed` +- `cancelled` +- `killed` + +## 5. 背景任务需求 + +background agent 至少要支持: +- 注册 +- 更新进度 +- 存储输出文件路径 +- 完成时通知主线程 +- 失败时通知主线程 +- 支持 kill / cancel + +## 6. 进度跟踪需求 + +任务进度至少记录: +- 工具调用次数 +- token 使用量 +- 最近活动 +- 最近摘要 + +## 7. 输出文件需求 + +后台任务建议将结果落盘到 output file,用于: +- 主线程查看 +- resume 后读取 +- 审计 + +## 8. 通知机制需求 + +任务结束时,系统必须向主线程注入结构化 notification,而不是只静默完成。 + +## 9. 验收标准 + +1. 每个子 agent 都能注册为任务 +2. 背景任务可查询状态 +3. 任务可输出结果文件 +4. 任务完成/失败会通知主线程 +5. 任务可被停止 diff --git a/docs/12-workspace-and-isolation.md b/docs/12-workspace-and-isolation.md new file mode 100644 index 0000000..a1e263a --- /dev/null +++ b/docs/12-workspace-and-isolation.md @@ -0,0 +1,59 @@ +# 12. 工作区与隔离策略规格 + +## 1. 目标 + +不同 agent 不能默认拥有相同的文件系统权限。产品必须允许根据角色与场景控制工作区隔离。 + +## 2. 隔离模式 + +Python 版建议至少定义: + +### 2.1 shared workspace +主线程默认工作区,共享项目目录。 + +### 2.2 read-only workspace +用于探索、规划、验证等只读角色。 + +### 2.3 temp workspace +用于临时脚本、临时测试产物。 + +### 2.4 worktree workspace +用于隔离修改型子任务,避免污染主分支。 + +### 2.5 remote workspace +用于远程执行环境(第一版可不做)。 + +## 3. 为什么隔离必须存在 + +没有隔离会导致: +- 子任务互相污染 +- 验证角色破坏项目 +- fork 实验影响主线程 +- 风险动作边界模糊 + +## 4. 角色与隔离建议 + +- Explore Agent -> read-only +- Plan Agent -> read-only +- Verification Agent -> read-only + temp writable +- General Agent -> shared 或 worktree +- 高风险实现任务 -> worktree + +## 5. 路径翻译需求 + +如果子任务运行在 worktree 中,而继承的上下文引用的是父工作区路径,系统需要能做路径翻译或重新读取。 + +## 6. 清理需求 + +隔离工作区结束后必须支持: +- 清理临时文件 +- 清理临时目录 +- 清理 worktree(如适用) +- 清理孤儿进程 + +## 7. 验收标准 + +1. 只读角色不能修改项目文件 +2. 验证角色只能在 temp 目录写测试脚本 +3. worktree 任务不污染主项目目录 +4. 任务结束后能清理隔离资源 diff --git a/docs/13-failure-recovery.md b/docs/13-failure-recovery.md new file mode 100644 index 0000000..6f7149c --- /dev/null +++ b/docs/13-failure-recovery.md @@ -0,0 +1,80 @@ +# 13. 失败处理与恢复规格 + +## 1. 目标 + +Agent 系统不能把失败当成异常边缘情况。失败是常态,系统必须有明确恢复策略。 + +## 2. 失败类型 + +建议至少区分: + +- `tool_input_error` +- `permission_denied` +- `hook_blocked` +- `shell_runtime_error` +- `model_api_error` +- `prompt_too_long` +- `mcp_connect_error` +- `task_killed` +- `resume_load_error` +- `session_storage_error` + +## 3. 各类失败处理原则 + +### tool_input_error +- 直接回写错误给模型 +- 不执行真实工具 + +### permission_denied +- 不重复原样调用 +- 引导模型调整方案 + +### hook_blocked +- 将阻断原因结构化返回 +- 允许模型或用户后续处理 + +### prompt_too_long +- 触发 compact / reactive compact +- 必要时重建消息序列后重试 + +### model_api_error +- 保留错误记录 +- 可尝试有限恢复 + +## 4. resume 恢复需求 + +系统必须支持: +- 读取 transcript +- 找到 compact boundary 后有效历史 +- 恢复 session_id 与 project dir +- 恢复活跃任务或至少恢复摘要 + +## 5. 子任务失败需求 + +子任务失败后不能静默消失,必须: +- 写入任务状态 +- 通知主线程 +- 保留错误摘要 + +## 6. 恢复策略级别 + +### 轻恢复 +- 调整输入 +- 重新请求权限 +- 简短重试 + +### 中恢复 +- 压缩上下文后重试 +- 重建子任务上下文 + +### 重恢复 +- resume 会话 +- 用户介入 +- 终止并保留证据 + +## 7. 验收标准 + +1. 常见失败都有明确处理路径 +2. 子任务失败不会丢失 +3. prompt too long 可进入压缩恢复 +4. 会话中断后可恢复 diff --git a/docs/14-configuration-system.md b/docs/14-configuration-system.md new file mode 100644 index 0000000..cba7023 --- /dev/null +++ b/docs/14-configuration-system.md @@ -0,0 +1,59 @@ +# 14. 配置系统规格 + +## 1. 目标 + +配置系统负责把产品的默认行为、用户偏好、项目约束和扩展能力统一管理,而不是散落在代码里。 + +## 2. 配置来源 + +Python 版建议支持以下来源: + +1. 全局用户配置 +2. 项目级配置 +3. session 级配置 +4. plugin / skill frontmatter 配置 +5. 环境变量 +6. CLI 参数覆盖 + +## 3. 配置优先级 + +建议优先级从高到低: + +1. runtime override / CLI 参数 +2. session 配置 +3. 项目配置 +4. 用户全局配置 +5. 默认配置 + +## 4. 必须可配置的项目 + +- 默认模型 +- 语言 +- 输出风格 +- permission mode +- hook 开关与 hook 配置 +- MCP server 配置 +- plugin 路径 +- skill 路径 +- token / task budget +- 自动 compact 开关 +- transcript 持久化开关 + +## 5. Agent 级配置需求 + +每个 agent 定义建议支持: +- agent_type +- when_to_use +- allowed_tools / disallowed_tools +- model +- memory scope +- mcp_servers +- background capability +- isolation mode + +## 6. 配置系统验收标准 + +1. 用户可在不改代码的情况下调整运行行为 +2. 项目可定义局部约束 +3. session 可临时覆盖配置 +4. plugin / skill 可附带配置元信息 diff --git a/docs/15-mvp-scope.md b/docs/15-mvp-scope.md new file mode 100644 index 0000000..5a9811b --- /dev/null +++ b/docs/15-mvp-scope.md @@ -0,0 +1,74 @@ +# 15. Python 版本 MVP 范围 + +## 1. 目标 + +避免 Python 版本一开始就无限扩张。MVP 只做能跑通核心闭环的最小系统。 + +## 2. MVP 必须包含 + +### 2.1 主循环 +- 多轮 query loop +- tool call -> tool result -> next turn + +### 2.2 基础工具 +- read file +- write file +- edit file +- grep/search +- bash + +### 2.3 基础权限系统 +- allow / ask / deny +- 被拒后不原样重试 + +### 2.4 基础 transcript / session +- 消息持久化 +- resume 会话 + +### 2.5 基础 memory +- 用户 / 项目记忆注入 +- 简单记忆文件结构 + +### 2.6 基础 agent orchestration +- main agent +- 至少一个 verification agent +- 可选一个 explore / plan agent + +### 2.7 基础 compact +- 超长消息时压缩历史 +- 工具结果 budget 裁剪 + +## 3. MVP 可以后置的能力 + +- 完整 TUI +- remote execution +- 多人 teammate / swarm +- 高级 telemetry +- 高级 tracing +- 复杂 MCP delta 更新 +- 高级 proactive / coordinator 模式 +- 完整插件生态 + +## 4. 推荐实现顺序 + +1. 消息模型 +2. 主循环 +3. 工具执行链 +4. transcript / resume +5. memory +6. verification agent +7. skills / plugin / MCP 里的最小一项 + +## 5. 成功标准 + +MVP 成功不等于“功能很多”,而是下面闭环能跑通: + +> 用户给任务 -> 系统多轮推进 -> 调用工具 -> 保留上下文 -> 必要时压缩 -> 完成后给结果 -> 可恢复会话 -> 可做基本验证 + +## 6. 不建议在 MVP 阶段做的错误方向 + +- 先做复杂 UI +- 先做很多 agent 类型 +- 先做大而全插件系统 +- 没有 transcript / resume 就上 background task +- 没有权限系统就直接开放 bash diff --git a/docs/16-python-implementation-notes.md b/docs/16-python-implementation-notes.md new file mode 100644 index 0000000..e470e8a --- /dev/null +++ b/docs/16-python-implementation-notes.md @@ -0,0 +1,69 @@ +# 16. Python 实现注意事项 + +## 1. 实现原则 + +目标不是逐字复刻原实现,而是用 Python 重建同样的产品能力结构。 + +## 2. 模块建议 + +建议 Python 项目按以下模块拆: + +- `runtime/`:query loop, orchestration +- `messages/`:message schemas +- `tools/`:tool registry and execution +- `permissions/`:permission engine +- `memory/`:memory loading and retrieval +- `tasks/`:background task model +- `storage/`:transcript and session persistence +- `agents/`:agent definitions +- `extensions/`:skills/plugins/mcp +- `verification/`:verification runner + +## 3. 推荐先用的数据模型 + +优先用: +- `pydantic` / `dataclasses` 定义消息与状态 +- `sqlite` 或 JSONL 先做 transcript +- 明确的 service 层代替隐式全局状态 + +## 4. 不要过早做的事 + +- 不要先优化 UI +- 不要先做复杂并发 +- 不要先支持十几种 agent +- 不要把 memory 做成向量库重系统 + +## 5. 第一阶段的最佳目标 + +先做出一个: +- 结构清晰 +- transcript 可回放 +- message 模型稳定 +- verification 能跑 +- compact 能工作 + +的 Python core runtime。 + +## 6. 推荐里程碑 + +### Milestone 1 +- message model +- query loop +- basic tools +- transcript + +### Milestone 2 +- permissions +- memory +- resume +- compact + +### Milestone 3 +- verification agent +- simple skill loading +- background local agent tasks + +### Milestone 4 +- MCP / plugin minimal support +- worktree isolation +- richer UI diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..63f4289 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,97 @@ +# 产品需求文档反推总览 + +> 目标:基于现有源码结构,反推出这套 AI 编程产品的核心需求与产品设计,而不是复述实现细节。 + +## 文档原则 + +- 只写需求、目标、交互、约束、边界条件 +- 不直接泄露原始源码实现 +- 如需描述机制,只用自然语言或 Python 风格伪代码 +- 文档站在产品经理 / 系统设计者视角,回答“为什么需要这个能力” + +## 文档结构 + +1. `00-product-overview.md` + - 产品定位 + - 核心用户 + - 核心问题 + - 顶层系统能力 + +2. `01-system-prompt-and-orchestration.md` + - 系统提示词层的需求 + - 为什么要做动态拼装 + - 为什么要做角色化 agent orchestration + +3. `02-tools-permissions-and-execution.md` + - 工具系统需求 + - 权限系统需求 + - Hook / 执行链路 / 安全要求 + +4. `03-skills-plugins-mcp.md` + - Skills 需求 + - Plugins 需求 + - MCP 集成需求 + +5. `04-memory-and-session.md` + - 记忆系统需求 + - Session 管理需求 + - 压缩、归档、恢复、摘要需求 + +6. `05-commands-ui-and-operator-experience.md` + - 命令系统需求 + - TUI / 状态栏 / 任务可视化需求 + - 操作者体验 + +7. `06-verification-and-quality.md` + - 验证 agent 需求 + - 质量保证需求 + - 失败报告与可追溯性需求 + +8. `07-architecture-map.md` + - 按模块汇总产品能力地图 + - 用于快速定位需求归属 + +9. `08-agent-runtime-loop.md` + - 主循环规格 + - 多轮执行与终止条件 + +10. `09-message-model-and-state.md` + - 消息模型 + - 会话与状态对象 + +11. `10-context-management.md` + - 上下文预算 + - 压缩与恢复 + +12. `11-task-model.md` + - 任务模型 + - 后台执行与通知 + +13. `12-workspace-and-isolation.md` + - 工作区隔离策略 + - 角色与写权限边界 + +14. `13-failure-recovery.md` + - 失败处理 + - 恢复机制 + +15. `14-configuration-system.md` + - 配置来源与优先级 + - Agent / Session 配置项 + +16. `15-mvp-scope.md` + - Python MVP 范围 + - 哪些先做,哪些后置 + +17. `16-python-implementation-notes.md` + - Python 版实现建议 + - 模块划分与里程碑 + +## 阅读建议 + +如果你想快速理解这套产品: + +1. 先看 `00-product-overview.md` +2. 再看 `04-memory-and-session.md` +3. 再看 `02-tools-permissions-and-execution.md` +4. 最后看 `03-skills-plugins-mcp.md` 和 `06-verification-and-quality.md` diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..d616199 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,96 @@ +# This file is automatically @generated by Poetry 2.3.3 and should not be changed by hand. + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +markers = "sys_platform == \"win32\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, + {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, +] + +[[package]] +name = "packaging" +version = "26.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, + {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["coverage", "pytest", "pytest-benchmark"] + +[[package]] +name = "pygments" +version = "2.20.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, + {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pytest" +version = "8.4.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, + {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, +] + +[package.dependencies] +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +iniconfig = ">=1" +packaging = ">=20" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] + +[metadata] +lock-version = "2.1" +python-versions = ">=3.11,<4.0" +content-hash = "3970d1c7a3ef6cf46d8cd1b9b926406243dd4d6996d6ec81549cbcbf747d6b96" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..687d11d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,20 @@ +[tool.poetry] +name = "ai-agent-deep-dive" +version = "0.1.0" +description = "Teaching-oriented Python coding agent inspired by modern agent runtimes" +authors = ["Xiao Tan "] +readme = "README.md" +packages = [{ include = "agt", from = "src" }] + +[tool.poetry.dependencies] +python = ">=3.11,<4.0" + +[tool.poetry.group.dev.dependencies] +pytest = "^8.3.5" + +[tool.poetry.scripts] +agt = "agt.cli:main" + +[build-system] +requires = ["poetry-core>=1.9.0"] +build-backend = "poetry.core.masonry.api" diff --git a/src/agt/__init__.py b/src/agt/__init__.py new file mode 100644 index 0000000..6f8345c --- /dev/null +++ b/src/agt/__init__.py @@ -0,0 +1,3 @@ +from .agent import Agent, Message, Tool, ToolResult + +__all__ = ["Agent", "Message", "Tool", "ToolResult"] diff --git a/src/agt/agent.py b/src/agt/agent.py new file mode 100644 index 0000000..ffed963 --- /dev/null +++ b/src/agt/agent.py @@ -0,0 +1,107 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any, Callable + + +@dataclass +class Message: + role: str + content: str + meta: dict[str, Any] = field(default_factory=dict) + + +@dataclass +class ToolResult: + ok: bool + content: str + meta: dict[str, Any] = field(default_factory=dict) + + +class Tool: + def __init__(self, name: str, description: str, handler: Callable[[dict[str, Any]], ToolResult]): + self.name = name + self.description = description + self.handler = handler + + def call(self, payload: dict[str, Any]) -> ToolResult: + return self.handler(payload) + + +class Agent: + def __init__(self) -> None: + self.messages: list[Message] = [] + self.tools: dict[str, Tool] = {} + self.memory: list[str] = [] + self.max_turns: int = 20 + + def register_tool(self, tool: Tool) -> None: + self.tools[tool.name] = tool + + def add_message(self, role: str, content: str, **meta: Any) -> None: + self.messages.append(Message(role=role, content=content, meta=meta)) + + def remember(self, text: str) -> None: + self.memory.append(text) + + def can_use_tool(self, tool_name: str) -> bool: + return tool_name in self.tools + + def load_skills(self, skills_dir: str | Path) -> list[str]: + skills_path = Path(skills_dir) + if not skills_path.exists(): + return [] + + loaded: list[str] = [] + for path in sorted(skills_path.rglob("SKILL.md")): + loaded.append(path.parent.name) + return loaded + + def model_step(self) -> dict[str, Any]: + return { + "type": "message", + "content": "这是一个教学用 Agent 骨架。下一步请在 model_step() 中接入你的模型逻辑。", + } + + def run(self, user_input: str) -> str: + self.add_message("user", user_input) + + for turn in range(self.max_turns): + step = self.model_step() + + if step["type"] == "message": + content = step["content"] + self.add_message("assistant", content, turn=turn) + return content + + if step["type"] == "tool_call": + tool_name = step["tool"] + tool_input = step.get("input", {}) + + if not self.can_use_tool(tool_name): + error = f"Tool not allowed or not found: {tool_name}" + self.add_message("tool_result", error, ok=False, tool=tool_name) + continue + + result = self.tools[tool_name].call(tool_input) + self.add_message( + "tool_result", + result.content, + ok=result.ok, + tool=tool_name, + tool_input=tool_input, + tool_meta=result.meta, + ) + continue + + raise ValueError(f"Unknown step type: {step['type']}") + + final_text = "Agent stopped because it reached max_turns." + self.add_message("assistant", final_text) + return final_text + + +def echo_tool(payload: dict[str, Any]) -> ToolResult: + text = str(payload.get("text", "")) + return ToolResult(ok=True, content=f"echo: {text}") diff --git a/src/agt/cli.py b/src/agt/cli.py new file mode 100644 index 0000000..aa4c22b --- /dev/null +++ b/src/agt/cli.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +import argparse +from pathlib import Path + +from .agent import Agent, Tool, echo_tool + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(prog="agt", description="Teaching-oriented AI agent CLI") + parser.add_argument("prompt", nargs="?", default="请开始", help="User prompt") + parser.add_argument( + "--skills-dir", + default="skills", + help="Directory containing skill folders with SKILL.md", + ) + parser.add_argument( + "--list-skills", + action="store_true", + help="List discovered skills and exit", + ) + return parser + + +def main() -> int: + parser = build_parser() + args = parser.parse_args() + + agent = Agent() + agent.register_tool(Tool("echo", "Echo input text", echo_tool)) + + skills = agent.load_skills(Path(args.skills_dir)) + if args.list_skills: + for skill in skills: + print(skill) + return 0 + + if skills: + agent.remember(f"loaded_skills={','.join(skills)}") + + reply = agent.run(args.prompt) + print(reply) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_agent.py b/tests/test_agent.py new file mode 100644 index 0000000..271e627 --- /dev/null +++ b/tests/test_agent.py @@ -0,0 +1,23 @@ +from agt.agent import Agent, Tool, echo_tool + + +def test_agent_returns_default_message() -> None: + agent = Agent() + agent.register_tool(Tool("echo", "Echo input text", echo_tool)) + + result = agent.run("hello") + + assert "教学用 Agent 骨架" in result + assert agent.messages[0].role == "user" + assert agent.messages[-1].role == "assistant" + + +def test_load_skills_finds_skill_md(tmp_path) -> None: + skill_dir = tmp_path / "skills" / "writing" + skill_dir.mkdir(parents=True) + (skill_dir / "SKILL.md").write_text("# writing", encoding="utf-8") + + agent = Agent() + skills = agent.load_skills(tmp_path / "skills") + + assert skills == ["writing"] diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..0e9b55f --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,30 @@ +from pathlib import Path +from subprocess import run +import sys + + +ROOT = Path(__file__).resolve().parents[1] + + +def test_cli_lists_skills(tmp_path) -> None: + skill_dir = tmp_path / "skills" / "analysis" + skill_dir.mkdir(parents=True) + (skill_dir / "SKILL.md").write_text("# analysis", encoding="utf-8") + + result = run( + [ + sys.executable, + "-m", + "agt.cli", + "--skills-dir", + str(tmp_path / "skills"), + "--list-skills", + ], + cwd=ROOT, + capture_output=True, + text=True, + check=False, + ) + + assert result.returncode == 0 + assert "analysis" in result.stdout