Compare commits

..

5 Commits

Author SHA1 Message Date
Shawn Bot
75ca806146 Fix tests for fake LLM response 2026-04-02 10:54:15 +00:00
Shawn Bot
76f9c1ca75 Update README with teaching agent usage 2026-04-02 10:50:55 +00:00
Shawn Bot
3fe4d0a060 Improve agent documentation for teaching 2026-04-02 10:19:32 +00:00
Shawn Bot
3e979daa61 Add teaching Python agent CLI with Poetry and CI 2026-04-02 10:09:34 +00:00
Shawn Bot
0b4e0c0ae9 Trim README to links and notes only 2026-04-01 16:26:51 +00:00
28 changed files with 2602 additions and 316 deletions

26
.github/workflows/ci.yml vendored Normal file
View File

@@ -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

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
.venv/
__pycache__/
.pytest_cache/
*.pyc

348
README.md
View File

@@ -10,337 +10,53 @@ PDF 下载 / PDF Report:
- 本仓库仅保留面向学习与评论的分析材料,不提供源码目录。
- 第二版 PDF 已完成。
---
## Teaching Agent Code
# AI Agent Deep Dive Notes
这个仓库现在还包含一个**教学用的最小 Python Agent 项目**,用于演示一个 AI Agent 的核心结构应该怎么组织。
> 这是一份围绕现代 Coding Agent 产品设计的学习型研究笔记重点关注整体架构、提示词系统、Agent 编排、Skills、Plugins、Hooks、MCP、权限与工具调用机制以及这些系统为什么会让 Agent 产品更稳定、更好用。
### 核心代码位置
---
- Agent 核心代码:[`src/agt/agent.py`](./src/agt/agent.py)
- CLI 入口:[`src/agt/cli.py`](./src/agt/cli.py)
- 教学文档:[`docs/`](./docs)
## 目录
### 这个教学项目的定位
1. 研究范围与结论总览
2. 源码结构全景:它为什么更像 Agent Operating System
3. 系统提示词总装:提示词系统的真实地位
4. Prompt 全量提取与模块级拆解
5. Agent Prompt 与 built-in agents 深挖
6. Agent 调度链深挖:从调度器到运行时主循环
7. Skills / Plugins / Hooks / MCP 生态深挖
8. 权限、Hook、工具执行链深挖
9. 为什么 Claude Code 这么强:从源码看它真正的护城河
10. 关键文件索引与后续可继续深挖方向
这个最小 Agent 项目是为了教学而设计的,特点是:
---
- 尽量保持结构清晰
- 尽量减少不必要的工程复杂度
- 所有核心代码集中在一个很小的范围内,方便学习
- 当前重点放在Agent 主循环、Fake LLM 接口、Skills 发现、CLI 骨架
# 1. 研究范围与结论总览
### 如何运行最小 Agent
## 1.1 这次到底研究了什么
本项目使用 Poetry 管理依赖。
这份材料的核心目标,是围绕现代 Coding Agent 产品做系统性拆解,重点包括:
#### 1. 安装依赖
- Coding Agent 的整体架构
- 主系统提示词如何动态拼装
- AgentTool / SkillTool 的模型侧协议
- built-in agents 的角色分工
- Agent 调度链路如何跑通
- Plugin / Skill / Hook / MCP 如何接入并影响运行时
- Permission / Tool execution / Hook decision 如何协同
- 它为什么在体验上比“普通 LLM + 工具调用器”强很多
```bash
poetry install
```
## 1.2 关键确认事实
#### 2. 运行最小 Agent CLI
本次研究重点关注了以下核心文件和模块:
```bash
poetry run agt "你好"
```
1. 主系统提示词、Agent 编排、Skill 调用、工具执行链与权限治理相关模块
#### 3. 查看 Skills
## 1.3 先给最重要的总判断
```bash
poetry run agt --skills-dir ./skills --list-skills
```
这类成熟 Coding Agent 产品的强,不是来自某个“神秘 system prompt”而是来自一个完整的软件工程系统
### 当前实现说明
- Prompt 不是静态文本,而是模块化 runtime assembly
- Tool 不是直接裸调,而是走 permission / hook / analytics / MCP-aware execution pipeline
- Agent 不是一个万能 worker而是多种 built-in / fork / subagent 的分工系统
- Skill 不是说明文档,而是 prompt-native workflow package
- Plugin 不是外挂,而是 prompt + metadata + runtime constraint 的扩展机制
- MCP 不是单纯工具桥,而是同时能注入工具与行为说明的 integration plane
当前版本是一个**教学型最小实现**,还没有接入真实远程模型 API。
一句话总结
目前内置的是一个可替换的 Fake LLM
- 用户输入什么
- 它就会用流式文本块的方式返回一个测试响应
> 这类成熟 Agent 产品的价值,不是一段 prompt而是一整套把 prompt、tool、permission、agent、skill、plugin、hook、MCP、cache 和产品体验统一起来的 Agent Operating System。
---
# 2. 源码结构全景:它为什么更像 Agent Operating System
## 2.1 顶层结构暴露出的系统复杂度
从相关产品结构看,这类成熟 Coding Agent 至少会包含这些重要模块:
- `src/entrypoints/`:入口层
- `src/constants/`prompt、系统常量、风险提示、输出规范
- `src/tools/`:工具定义与具体实现
- `src/services/`:运行时服务,例如 tools、mcp、analytics
- `src/utils/`:底层共用能力
- `src/commands/`slash command 与命令系统
- `src/components/`TUI / UI 组件
- `src/coordinator/`:协调器模式
- `src/memdir/`:记忆 / memory prompt
- `src/plugins/``src/utils/plugins/`:插件生态
- `src/hooks/``src/utils/hooks.js`hook 系统
- `src/bootstrap/`:状态初始化
- `src/tasks/`:本地任务、远程任务、异步 agent 任务
这已经说明它不是简单 CLI 包装器,而是一个完整运行平台。
## 2.2 入口层说明它是平台,而不是单一界面
可见入口包括:
- `src/entrypoints/cli.tsx`
- `src/entrypoints/init.ts`
- `src/entrypoints/mcp.ts`
- `src/entrypoints/sdk/`
也就是说它从设计上就考虑了:
- 本地 CLI
- 初始化流程
- MCP 模式
- SDK 消费者
这是一种平台化思维:同一个 agent runtime可以服务多个入口和多个交互表面。
## 2.3 命令系统是整个产品的操作面板
`src/commands.ts` 暴露出非常多系统级命令,例如:
- `/mcp`
- `/memory`
- `/permissions`
- `/hooks`
- `/plugin`
- `/reload-plugins`
- `/skills`
- `/tasks`
- `/plan`
- `/review`
- `/status`
- `/model`
- `/output-style`
- `/agents`
- `/sandbox-toggle`
这说明命令系统不是“锦上添花”,而是用户与系统运行时交互的重要控制面。
## 2.4 Tools 层才是模型真正“能做事”的根
从 prompt 和工具名能确认的重要工具包括:
- FileRead
- FileEdit
- FileWrite
- Bash
- Glob
- Grep
- TodoWrite
- TaskCreate
- AskUserQuestion
- Skill
- Agent
- MCPTool
- Sleep
工具层的本质是把模型从“回答器”变成“执行体”。Claude Code 的强,很大程度来自这层做得正式、清晰、可治理。
---
# 3. 系统提示词总装:提示词系统的真实地位
## 3.1 真正的主入口:`src/constants/prompts.ts`
这份文件是整个系统最关键的源码之一。不是因为它写了一大段神奇文案,而是因为它承担了:
- 主系统提示词的总装配
- 环境信息注入
- 工具使用规范注入
- 安全与风险动作规范
- Session-specific guidance 注入
- language / output style 注入
- MCP instructions 注入
- memory prompt 注入
- scratchpad 说明注入
- function result clearing 提示注入
- brief / proactive / token budget 等 feature-gated section 注入
这类成熟 Agent 产品的 prompt 往往不是静态字符串,而是一个 **system prompt assembly architecture**
## 3.2 `getSystemPrompt()` 不是文本,而是编排器
`getSystemPrompt()` 里最核心的结构,是先构造静态部分,再加上动态部分。你可以把它理解成:
### 静态前缀(更适合 cache
- `getSimpleIntroSection()`
- `getSimpleSystemSection()`
- `getSimpleDoingTasksSection()`
- `getActionsSection()`
- `getUsingYourToolsSection()`
- `getSimpleToneAndStyleSection()`
- `getOutputEfficiencySection()`
### 动态后缀(按会话条件注入)
- session guidance
- memory
- env info
- language
- output style
- mcp instructions
- scratchpad
- function result clearing
- summarize tool results
- token budget
- brief
这个设计非常值钱,因为它不是“把能想到的都写进 system prompt”而是把 prompt 当作可编排运行时资源来管理。
---
# 4. Prompt 全量提取与模块级拆解
## 4.1 身份与基础定位
系统提示词会先定义它是 interactive agent并明确它是帮助用户完成软件工程任务的不是普通聊天机器人。
## 4.2 基础系统规范
这里会规定:
- 所有非工具输出都直接给用户看
- 工具运行在 permission mode 下
- 用户拒绝后不能原样重试
- tool result 可能含系统提醒或外部内容
- 需要对 prompt injection 保持警惕
## 4.3 做任务哲学
它非常强调:
- 不要乱加功能
- 不要过度抽象
- 不要瞎重构
- 先读代码再改代码
- 不要轻易创建新文件
- 结果要诚实汇报
这部分是它稳定性的关键来源之一。
---
# 5. Agent Prompt 与 built-in agents 深挖
## 5.1 AgentTool Prompt 的价值
这份 prompt 本质上是在告诉模型:
- 何时该启动 agent
- 何时该 fork 自己
- 何时不该用 AgentTool
- 如何正确地写 subagent prompt
## 5.2 built-in agents 的意义
内建 agents 至少包括:
- General Purpose Agent
- Explore Agent
- Plan Agent
- Verification Agent
这说明它不是一个万能 worker而是通过角色分工来提高稳定性。
## 5.3 Verification Agent 为什么值钱
Verification Agent 的核心不是“再看一眼”,而是主动去验证、去尝试打破实现。它要求 build、tests、type-check、真实命令输出和最终 verdict这对提高任务完成质量非常关键。
---
# 6. Agent 调度链深挖
## 6.1 总体调度链
主链路可以抽象为:
1. 主模型决定调用 Agent 工具
2. AgentTool 解析输入并选择路径
3. 判断 fork / normal / background / remote / worktree
4. 构造 prompt messages 与 system prompt
5. 组装工具池与上下文
6. 调用 `runAgent()`
7. `runAgent()` 再进入 `query()` 主循环
## 6.2 为什么这个调度链重要
因为这说明 agent execution 不是简单“开个新会话”,而是一个完整的 runtime lifecycle。
---
# 7. Skills / Plugins / Hooks / MCP 生态深挖
## 7.1 Skill 的本质
Skill 不是文档,而是可复用的 workflow package。它让系统能把重复流程变成按需注入的 prompt 资产。
## 7.2 Plugin 的本质
Plugin 不是普通脚本扩展,而是 prompt、metadata 和 runtime constraints 的组合包。
## 7.3 Hook 的本质
Hook 是运行时治理层,它可以改输入、给权限建议、阻止继续执行、注入上下文。
## 7.4 MCP 的本质
MCP 不只是工具桥,还能通过 instructions 影响模型如何理解和使用这些工具。
---
# 8. 权限、Hook、工具执行链深挖
成熟 Agent 产品的工具调用并不是模型直接裸调,而是完整的 runtime pipeline
- schema 校验
- validateInput
- pre-tool hooks
- permission decision
- tool call
- telemetry
- post-tool hooks
- failure hooks
这也是它比很多“会调工具的 Agent”更稳定的重要原因。
---
# 9. 为什么 Claude Code 这么强
最核心的原因不是模型更聪明一点,而是这类产品把这些东西系统化了:
- Prompt architecture
- Tool runtime governance
- Permission model
- Agent specialization
- Skill workflow packaging
- Plugin / MCP extensibility
- Context hygiene
- Async/background lifecycle
- Product-level engineering
所以它真正厉害的不是某一句 prompt而是整个 operating model。
---
# 10. 最终结论
> 成熟 Agent 产品真正的价值,不是一段 system prompt而是一个把 prompt architecture、tool runtime、permission model、agent orchestration、skill packaging、plugin system、hooks governance、MCP integration、context hygiene 和 product engineering 统一起来的系统。
这也是为什么它不像一个“会调工具的聊天机器人”,而更像一个真正的 Agent Operating System。
这样做的目的,是为了让后续接入真实模型时,只需要替换 LLM 调用层,而不需要重写整个 Agent 主体。

105
docs/00-product-overview.md Normal file
View File

@@ -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 软件工程操作系统。

View File

@@ -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. 产品经理视角下的总需求句
> 系统提示词层的核心需求,是把产品规则、工具语法、会话状态和环境约束组装成一套可动态生成、可缓存优化、可支持多角色协作的运行时控制平面。

View File

@@ -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、审计与结果回流确保整个过程安全、稳定、可追踪。

View File

@@ -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 负责接入外部工具与说明,三者共同让系统具备持续生长的能力。

View File

@@ -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. 产品经理视角下的总需求句
> 这套产品必须拥有一个分层记忆系统:它既能保存长期偏好与项目背景,又能在上下文预算受限时压缩历史,并在任务恢复、子任务继承和长期协作中重新注入关键事实。

View File

@@ -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 运行时变成用户可发现、可控制、可追踪的操作者体验。

View File

@@ -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 来证明任务是否真正完成。

112
docs/07-architecture-map.md Normal file
View File

@@ -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 在真实工程环境中成为一个可控、可扩展、可追踪的执行系统。

View File

@@ -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 可完整记录轮次

View File

@@ -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 能独立存储

View File

@@ -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. 工具长输出会被预算裁剪

85
docs/11-task-model.md Normal file
View File

@@ -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. 任务可被停止

View File

@@ -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. 任务结束后能清理隔离资源

View File

@@ -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. 会话中断后可恢复

View File

@@ -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 可附带配置元信息

74
docs/15-mvp-scope.md Normal file
View File

@@ -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

View File

@@ -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

97
docs/README.md Normal file
View File

@@ -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`

96
poetry.lock generated Normal file
View File

@@ -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"

20
pyproject.toml Normal file
View File

@@ -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 <no-reply@example.com>"]
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"

3
src/agt/__init__.py Normal file
View File

@@ -0,0 +1,3 @@
from .agent import Agent, Message, Tool, ToolResult
__all__ = ["Agent", "Message", "Tool", "ToolResult"]

485
src/agt/agent.py Normal file
View File

@@ -0,0 +1,485 @@
"""
教学版 Agent 核心文件。
本文件的目标不是追求最复杂、最完整的工程实现,而是:
1. 用尽量清晰的 Python 代码,演示一个 Agent 的核心结构
2. 保持代码易读、易改、易扩展,方便教学与逐步演进
3. 为后续接入真实 LLM、工具系统、Skills、记忆系统提供稳定骨架
4. 在写法上尽量遵循清晰封装、低耦合、易测试的最佳实践
教学约定:
- 所有文档与注释优先使用中文
- 类、函数、重要属性都尽量提供清晰文档
- 优先追求“容易理解”,再追求“功能丰富”
- 允许用最小可用实现来表达结构,但要保留明确扩展点
"""
from __future__ import annotations
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Callable, Iterator, Protocol
@dataclass
class Message:
"""
表示 Agent 运行时中的一条消息。
这是一种教学用的最小消息模型。真实产品中,消息通常会更复杂,
可能还会包含:消息 ID、父子关系、时间戳、工具块、token 使用信息等。
属性:
role:
消息角色。
常见值包括:
- "user":用户消息
- "assistant":模型回复
- "tool_result":工具执行结果
content:
消息正文内容。
这里为了教学简化为纯文本。
meta:
附加元数据。
用于保存额外的结构化信息,例如 turn 编号、chunks、工具名等。
"""
role: str
content: str
meta: dict[str, Any] = field(default_factory=dict)
@dataclass
class ToolResult:
"""
表示工具执行后的结果。
属性:
ok:
表示工具执行是否成功。
content:
工具返回给 Agent 的文本内容。
在真实系统中,这里也可以扩展为结构化内容。
meta:
工具执行附带的元数据。
例如执行耗时、文件路径、命中条数等。
"""
ok: bool
content: str
meta: dict[str, Any] = field(default_factory=dict)
class Tool:
"""
表示一个可注册到 Agent 中的工具。
设计思路:
- 工具本身只关心自己的名字、说明和处理函数
- Agent 只依赖统一的 Tool 接口,而不关心具体工具细节
- 这样可以降低 Agent 与具体工具实现之间的耦合
参数:
name:
工具名称,必须唯一。
description:
工具用途说明。
主要用于教学展示、调试和未来做工具提示词时使用。
handler:
工具执行函数。
输入为字典,输出为 ToolResult。
"""
def __init__(
self,
name: str,
description: str,
handler: Callable[[dict[str, Any]], ToolResult],
):
"""
初始化一个工具对象。
参数:
name:
工具名称。
description:
工具用途描述。
handler:
真正执行工具逻辑的函数。
"""
self.name = name
self.description = description
self.handler = handler
def call(self, payload: dict[str, Any]) -> ToolResult:
"""
执行工具。
参数:
payload:
传给工具的输入参数。
返回:
ToolResult工具执行结果。
"""
return self.handler(payload)
class LLMClient(Protocol):
"""
LLM 客户端协议。
这是一个非常重要的教学设计点:
我们不让 Agent 直接依赖某一个具体模型 SDK
而是先定义一个统一接口。这样以后无论接:
- OpenAI
- Anthropic
- LiteLLM
- 本地模型
- 假模型Fake LLM
都可以复用同一个 Agent 主体。
方法:
stream_text:
输入当前消息列表,返回一个文本分块迭代器。
"""
def stream_text(self, messages: list[Message]) -> Iterator[str]:
"""
基于当前消息列表,流式返回文本分块。
参数:
messages:
当前上下文消息列表。
返回:
一个字符串迭代器,每次 yield 一段文本。
"""
...
class FakeLLMClient:
"""
教学用假模型客户端。
这个类的核心价值是:
- 不依赖任何远程 API
- 方便本地测试
- 能模拟“流式输出”的基本行为
- 未来可以被真实 LLM 客户端直接替换
当前行为非常简单:
- 读取最后一条用户消息
- 拼成一段固定风格的回复
- 再把回复切成多个 chunk 流式返回
"""
def stream_text(self, messages: list[Message]) -> Iterator[str]:
"""
根据消息列表流式生成文本。
参数:
messages:
当前消息列表。
返回:
文本块迭代器。
"""
last_user_message = next(
(m.content for m in reversed(messages) if m.role == "user"),
"",
)
response = f"[fake-llm] 你刚才说的是:{last_user_message}"
for chunk in self._chunk_text(response, size=8):
yield chunk
@staticmethod
def _chunk_text(text: str, size: int = 8) -> Iterator[str]:
"""
将一段文本切成多个小块,用于模拟流式返回。
参数:
text:
要切分的完整文本。
size:
每个文本块的最大长度。
返回:
文本块迭代器。
"""
for i in range(0, len(text), size):
yield text[i : i + size]
class Agent:
"""
教学版 Agent 主类。
这是当前项目中最核心的对象。它负责:
- 保存消息
- 管理工具
- 管理简单记忆
- 调用 LLM
- 运行最小主循环
当前版本是教学最小骨架,因此刻意保持简单。
未来可以在这个类上继续演化:
- 权限系统
- system prompt
- tool calling 协议
- verification agent
- transcript 持久化
- context management
参数:
llm:
可注入的 LLM 客户端。
如果不传,则默认使用 FakeLLMClient。
属性:
messages:
当前 Agent 的消息历史。
tools:
已注册工具表key 为工具名value 为 Tool 实例。
memory:
教学版最小记忆列表。
当前只做演示用途。
max_turns:
最大回合数,避免无限循环。
llm:
当前绑定的 LLM 客户端实现。
"""
def __init__(self, llm: LLMClient | None = None) -> None:
"""
初始化 Agent。
参数:
llm:
可选的 LLM 客户端。
不传时使用默认的 FakeLLMClient。
"""
self.messages: list[Message] = []
self.tools: dict[str, Tool] = {}
self.memory: list[str] = []
self.max_turns: int = 20
self.llm: LLMClient = llm or FakeLLMClient()
def register_tool(self, tool: Tool) -> None:
"""
注册一个工具到 Agent 中。
参数:
tool:
要注册的工具对象。
"""
self.tools[tool.name] = tool
def add_message(self, role: str, content: str, **meta: Any) -> None:
"""
向消息历史中追加一条消息。
参数:
role:
消息角色。
content:
消息文本内容。
**meta:
任意附加元数据。
"""
self.messages.append(Message(role=role, content=content, meta=meta))
def remember(self, text: str) -> None:
"""
向简化记忆列表中加入一条记忆。
参数:
text:
要保存的记忆文本。
"""
self.memory.append(text)
def can_use_tool(self, tool_name: str) -> bool:
"""
判断某个工具当前是否可用。
当前教学版逻辑非常简单:
- 只判断该工具是否已注册
未来可以扩展为:
- 权限判断
- allow / ask / deny
- agent 角色限制
参数:
tool_name:
工具名称。
返回:
bool是否可用。
"""
return tool_name in self.tools
def load_skills(self, skills_dir: str | Path) -> list[str]:
"""
从目录中发现可用 Skills。
当前教学版规则:
- 递归查找 `SKILL.md`
- skill 名称使用其父目录名
参数:
skills_dir:
Skills 根目录路径。
返回:
已发现的 skill 名称列表。
"""
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 call_llm_stream(self) -> Iterator[str]:
"""
调用当前绑定的 LLM 客户端,并返回流式文本块。
这是一个关键的抽象层。
以后接真实模型时,优先改的是 LLMClient 的实现,
而不是 Agent 主循环本身。
返回:
文本块迭代器。
"""
return self.llm.stream_text(self.messages)
def model_step(self) -> dict[str, Any]:
"""
执行一次模型步骤。
当前教学版逻辑:
- 调用 LLM 流式接口
- 收集所有 chunk
- 组装为一条 message 类型结果
在未来版本中,这里可以继续扩展为:
- message
- tool_call
- subagent_call
- verification_request
返回:
一个描述“本轮模型决定”的字典。
"""
chunks = list(self.call_llm_stream())
return {
"type": "message",
"content": "".join(chunks),
"chunks": chunks,
}
def run(self, user_input: str) -> str:
"""
运行 Agent 的最小主循环。
当前版本流程:
1. 记录用户输入
2. 调用 model_step()
3. 如果返回 message则结束
4. 如果未来返回 tool_call则执行工具并继续循环
5. 超过最大轮次则停止
参数:
user_input:
用户输入文本。
返回:
Agent 最终返回给用户的文本。
"""
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,
chunks=step.get("chunks", []),
)
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:
"""
一个最简单的教学示例工具。
作用:
把输入文本原样回显回来。
参数:
payload:
工具输入字典。
约定使用 `text` 字段。
返回:
ToolResult工具执行结果。
"""
text = str(payload.get("text", ""))
return ToolResult(ok=True, content=f"echo: {text}")

47
src/agt/cli.py Normal file
View File

@@ -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())

34
tests/test_agent.py Normal file
View File

@@ -0,0 +1,34 @@
from agt.agent import Agent, FakeLLMClient, Tool, echo_tool
def test_agent_returns_fake_llm_message() -> None:
agent = Agent(llm=FakeLLMClient())
agent.register_tool(Tool("echo", "Echo input text", echo_tool))
result = agent.run("hello")
assert "[fake-llm]" in result
assert "hello" 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"]
def test_fake_llm_streams_multiple_chunks() -> None:
llm = FakeLLMClient()
from agt.agent import Message
chunks = list(llm.stream_text([Message(role="user", content="x")]))
assert len(chunks) >= 2
assert "".join(chunks).startswith("[fake-llm]")

30
tests/test_cli.py Normal file
View File

@@ -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