docs: update docs.json and versions.json

This commit is contained in:
abel533 committed 2026-03-25 00:50:17 +08:00
1 parent 2afa4712cb
commit 1ecef9dc3a
1 file changed
+91 -91
+91 -91
View File
@@ -170,7 +170,7 @@
"filename": "S04Subagent.java",
"title": "Subagents",
"subtitle": "Clean Context Per Subtask",
"loc": 88,
"loc": 92,
"tools": [
"task"
],
@@ -188,7 +188,7 @@
{
"name": "SubagentTool",
"startLine": 101,
"endLine": 150
"endLine": 154
}
],
"functions": [
@@ -230,26 +230,26 @@
{
"name": "BashTool",
"signature": "new BashTool(),",
"startLine": 133
"startLine": 135
},
{
"name": "ReadFileTool",
"signature": "new ReadFileTool(),",
"startLine": 134
"startLine": 136
},
{
"name": "WriteFileTool",
"signature": "new WriteFileTool(),",
"startLine": 135
"startLine": 137
},
{
"name": "EditFileTool",
"signature": "new EditFileTool()",
"startLine": 136
"startLine": 138
}
],
"layer": "planning",
"source": "// === S04Subagent.java ===\npackage io.mybatis.learn.s04;\r\n\r\nimport io.mybatis.learn.core.AgentRunner;\r\nimport io.mybatis.learn.core.tools.BashTool;\r\nimport io.mybatis.learn.core.tools.EditFileTool;\r\nimport io.mybatis.learn.core.tools.ReadFileTool;\r\nimport io.mybatis.learn.core.tools.WriteFileTool;\r\nimport org.springframework.ai.chat.client.ChatClient;\r\nimport org.springframework.ai.chat.model.ChatModel;\r\nimport org.springframework.boot.CommandLineRunner;\r\nimport org.springframework.boot.SpringApplication;\r\nimport org.springframework.boot.WebApplicationType;\r\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\r\n\r\n/**\r\n * S04 - 子 Agent:上下文隔离,保护主 Agent 的思维清晰。\r\n * <p>\r\n * 格言: \"大任务拆小, 每个小任务干净的上下文\"\r\n * <p>\r\n * 核心模式:\r\n * <pre>\r\n * Parent agent Subagent\r\n * +------------------+ +------------------+\r\n * | messages=[...] | | messages=[] | ← fresh\r\n * | | dispatch | |\r\n * | tool: task | --------> | ChatClient.call |\r\n * | prompt=\"...\" | | execute tools |\r\n * | | summary | |\r\n * | result = \"...\" | <-------- | return last text |\r\n * +------------------+ +------------------+\r\n * |\r\n * Parent context stays clean.\r\n * Subagent context is discarded.\r\n * </pre>\r\n * <p>\r\n * TIP: 对应 Python {@code agents/s04_subagent.py}。\r\n * Python 版手动创建空的 messages 列表实现隔离。\r\n * Spring AI 通过创建独立的 {@link ChatClient} 实例实现相同效果 —— 更加自然。\r\n */\r\n@SpringBootApplication(scanBasePackages = \"io.mybatis.learn.core\")\r\npublic class S04Subagent implements CommandLineRunner {\r\n\r\n private final ChatClient chatClient;\r\n\r\n public S04Subagent(ChatModel chatModel) {\r\n this.chatClient = ChatClient.builder(chatModel)\r\n .defaultSystem(\"You are a coding agent at \" + System.getProperty(\"user.dir\")\r\n + \". Use the task tool to delegate exploration or subtasks.\")\r\n .defaultTools(\r\n new BashTool(),\r\n new ReadFileTool(),\r\n new WriteFileTool(),\r\n new EditFileTool(),\r\n new SubagentTool(chatModel) // 父 Agent 独有的 task 工具\r\n )\r\n .build();\r\n }\r\n\r\n @Override\r\n public void run(String... args) {\r\n AgentRunner.interactive(\"s04\", userMessage ->\r\n chatClient.prompt()\r\n .user(userMessage)\r\n .call()\r\n .content()\r\n );\r\n }\r\n\r\n public static void main(String[] args) {\r\n SpringApplication app = new SpringApplication(S04Subagent.class);\r\n app.setWebApplicationType(WebApplicationType.NONE);\r\n app.run(args);\r\n }\r\n}\r\n\n\n// === SubagentTool.java ===\npackage io.mybatis.learn.s04;\r\n\r\nimport io.mybatis.learn.core.tools.BashTool;\r\nimport io.mybatis.learn.core.tools.EditFileTool;\r\nimport io.mybatis.learn.core.tools.ReadFileTool;\r\nimport io.mybatis.learn.core.tools.WriteFileTool;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\nimport org.springframework.ai.chat.client.ChatClient;\r\nimport org.springframework.ai.chat.model.ChatModel;\r\nimport org.springframework.ai.tool.annotation.Tool;\r\nimport org.springframework.ai.tool.annotation.ToolParam;\r\n\r\n/**\r\n * 子 Agent 工具 —— 生成具有独立上下文的子 Agent 执行任务。\r\n * <p>\r\n * TIP: 对应 Python {@code agents/s04_subagent.py} 中的 {@code run_subagent(prompt)} 函数。\r\n * Python 版创建 {@code sub_messages = []} 实现上下文隔离,\r\n * Spring AI 通过创建全新的 {@link ChatClient} 实例实现相同效果。\r\n * 子 Agent 获得基础工具但没有 task 工具(防止递归生成)。\r\n * 只有最终文本返回给父 Agent,子 Agent 的对话历史被完全丢弃。\r\n */\r\npublic class SubagentTool {\r\n private static final Logger log = LoggerFactory.getLogger(SubagentTool.class);\r\n\r\n private final ChatModel chatModel;\r\n private final String workDir;\r\n\r\n public SubagentTool(ChatModel chatModel) {\r\n this.chatModel = chatModel;\r\n this.workDir = System.getProperty(\"user.dir\");\r\n }\r\n\r\n /**\r\n * TIP: 对应 Python {@code run_subagent(prompt)}。\r\n * Python 版在独立线程中运行子 Agent 的 while 循环(最多30次迭代)。\r\n * Spring AI 的 ChatClient.call() 内部管理循环,无需手动限制迭代次数。\r\n */\r\n @Tool(description = \"Spawn a subagent with fresh context. \"\rLine truncated
"source": "// === S04Subagent.java ===\npackage io.mybatis.learn.s04;\r\n\r\nimport io.mybatis.learn.core.AgentRunner;\r\nimport io.mybatis.learn.core.tools.BashTool;\r\nimport io.mybatis.learn.core.tools.EditFileTool;\r\nimport io.mybatis.learn.core.tools.ReadFileTool;\r\nimport io.mybatis.learn.core.tools.WriteFileTool;\r\nimport org.springframework.ai.chat.client.ChatClient;\r\nimport org.springframework.ai.chat.model.ChatModel;\r\nimport org.springframework.boot.CommandLineRunner;\r\nimport org.springframework.boot.SpringApplication;\r\nimport org.springframework.boot.WebApplicationType;\r\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\r\n\r\n/**\r\n * S04 - 子 Agent:上下文隔离,保护主 Agent 的思维清晰。\r\n * <p>\r\n * 格言: \"大任务拆小, 每个小任务干净的上下文\"\r\n * <p>\r\n * 核心模式:\r\n * <pre>\r\n * Parent agent Subagent\r\n * +------------------+ +------------------+\r\n * | messages=[...] | | messages=[] | ← fresh\r\n * | | dispatch | |\r\n * | tool: task | --------> | ChatClient.call |\r\n * | prompt=\"...\" | | execute tools |\r\n * | | summary | |\r\n * | result = \"...\" | <-------- | return last text |\r\n * +------------------+ +------------------+\r\n * |\r\n * Parent context stays clean.\r\n * Subagent context is discarded.\r\n * </pre>\r\n * <p>\r\n * TIP: 对应 Python {@code agents/s04_subagent.py}。\r\n * Python 版手动创建空的 messages 列表实现隔离。\r\n * Spring AI 通过创建独立的 {@link ChatClient} 实例实现相同效果 —— 更加自然。\r\n */\r\n@SpringBootApplication(scanBasePackages = \"io.mybatis.learn.core\")\r\npublic class S04Subagent implements CommandLineRunner {\r\n\r\n private final ChatClient chatClient;\r\n\r\n public S04Subagent(ChatModel chatModel) {\r\n this.chatClient = ChatClient.builder(chatModel)\r\n .defaultSystem(\"You are a coding agent at \" + System.getProperty(\"user.dir\")\r\n + \". Use the task tool to delegate exploration or subtasks.\")\r\n .defaultTools(\r\n new BashTool(),\r\n new ReadFileTool(),\r\n new WriteFileTool(),\r\n new EditFileTool(),\r\n new SubagentTool(chatModel) // 父 Agent 独有的 task 工具\r\n )\r\n .build();\r\n }\r\n\r\n @Override\r\n public void run(String... args) {\r\n AgentRunner.interactive(\"s04\", userMessage ->\r\n chatClient.prompt()\r\n .user(userMessage)\r\n .call()\r\n .content()\r\n );\r\n }\r\n\r\n public static void main(String[] args) {\r\n SpringApplication app = new SpringApplication(S04Subagent.class);\r\n app.setWebApplicationType(WebApplicationType.NONE);\r\n app.run(args);\r\n }\r\n}\r\n\n\n// === SubagentTool.java ===\npackage io.mybatis.learn.s04;\r\n\r\nimport io.mybatis.learn.core.tools.BashTool;\r\nimport io.mybatis.learn.core.tools.EditFileTool;\r\nimport io.mybatis.learn.core.tools.ReadFileTool;\r\nimport io.mybatis.learn.core.tools.WriteFileTool;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\nimport org.springframework.ai.chat.client.ChatClient;\r\nimport org.springframework.ai.chat.model.ChatModel;\r\nimport org.springframework.ai.tool.annotation.Tool;\r\nimport org.springframework.ai.tool.annotation.ToolParam;\r\n\r\n/**\r\n * 子 Agent 工具 —— 生成具有独立上下文的子 Agent 执行任务。\r\n * <p>\r\n * TIP: 对应 Python {@code agents/s04_subagent.py} 中的 {@code run_subagent(prompt)} 函数。\r\n * Python 版创建 {@code sub_messages = []} 实现上下文隔离,\r\n * Spring AI 通过创建全新的 {@link ChatClient} 实例实现相同效果。\r\n * 子 Agent 获得基础工具但没有 task 工具(防止递归生成)。\r\n * 只有最终文本返回给父 Agent,子 Agent 的对话历史被完全丢弃。\r\n */\r\npublic class SubagentTool {\r\n private static final Logger log = LoggerFactory.getLogger(SubagentTool.class);\r\n\r\n private final ChatModel chatModel;\r\n private final String workDir;\r\n\r\n public SubagentTool(ChatModel chatModel) {\r\n this.chatModel = chatModel;\r\n this.workDir = System.getProperty(\"user.dir\");\r\n }\r\n\r\n /**\r\n * TIP: 对应 Python {@code run_subagent(prompt)}。\r\n * Python 版在独立线程中运行子 Agent 的 while 循环(最多30次迭代)。\r\n * Spring AI 的 ChatClient.call() 内部管理循环,无需手动限制迭代次数。\r\n */\r\n @Tool(description = \"Spawn a subagent with fresh context. \"\rLine truncated
},
{
"id": "s05",
@@ -468,7 +468,7 @@
"filename": "S07TaskSystem.java",
"title": "Tasks",
"subtitle": "Task Graph + Dependencies",
"loc": 248,
"loc": 266,
"tools": [
"taskCreate",
"taskGet",
@@ -492,7 +492,7 @@
{
"name": "TaskManager",
"startLine": 99,
"endLine": 316
"endLine": 334
}
],
"functions": [
@@ -529,53 +529,53 @@
{
"name": "maxId",
"signature": "private int maxId()",
"startLine": 121
"startLine": 123
},
{
"name": "load",
"signature": "private Map<String, Object> load(int taskId) throws IOException",
"startLine": 134
"startLine": 136
},
{
"name": "save",
"signature": "private void save(Map<String, Object> task) throws IOException",
"startLine": 143
"startLine": 145
},
{
"name": "taskCreate",
"signature": "public String taskCreate(",
"startLine": 149
"startLine": 151
},
{
"name": "taskGet",
"signature": "public String taskGet(@ToolParam(description = \"Task ID\") int taskId)",
"startLine": 173
"startLine": 179
},
{
"name": "taskUpdate",
"signature": "public String taskUpdate(",
"startLine": 192
"startLine": 200
},
{
"name": "clearDependency",
"signature": "private void clearDependency(int completedId)",
"startLine": 253
"startLine": 265
},
{
"name": "taskList",
"signature": "public String taskList()",
"startLine": 275
"startLine": 291
}
],
"layer": "planning",
"source": "// === S07TaskSystem.java ===\npackage io.mybatis.learn.s07;\r\n\r\nimport io.mybatis.learn.core.AgentRunner;\r\nimport io.mybatis.learn.core.tools.BashTool;\r\nimport io.mybatis.learn.core.tools.EditFileTool;\r\nimport io.mybatis.learn.core.tools.ReadFileTool;\r\nimport io.mybatis.learn.core.tools.WriteFileTool;\r\nimport org.springframework.ai.chat.client.ChatClient;\r\nimport org.springframework.ai.chat.model.ChatModel;\r\nimport org.springframework.boot.CommandLineRunner;\r\nimport org.springframework.boot.SpringApplication;\r\nimport org.springframework.boot.WebApplicationType;\r\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\r\n\r\nimport java.nio.file.Path;\r\n\r\n/**\r\n * S07 - 任务系统:大目标拆成小任务,排好序,记在磁盘上。\r\n * <p>\r\n * 格言: \"大目标要拆成小任务, 排好序, 记在磁盘上\"\r\n * <p>\r\n * TIP: 对应 Python {@code agents/s07_task_system.py}。\r\n * 核心洞察: 任务状态持久化在磁盘({@code .tasks/task_*.json}),\r\n * 不受上下文压缩影响 —— 因为它在对话之外。\r\n */\r\n@SpringBootApplication(scanBasePackages = \"io.mybatis.learn.core\")\r\npublic class S07TaskSystem implements CommandLineRunner {\r\n\r\n private final ChatClient chatClient;\r\n\r\n public S07TaskSystem(ChatModel chatModel) {\r\n Path tasksDir = Path.of(System.getProperty(\"user.dir\"), \".tasks\");\r\n TaskManager taskManager = new TaskManager(tasksDir);\r\n\r\n this.chatClient = ChatClient.builder(chatModel)\r\n .defaultSystem(\"You are a coding agent at \" + System.getProperty(\"user.dir\")\r\n + \". Use task tools to plan and track work.\")\r\n .defaultTools(\r\n new BashTool(),\r\n new ReadFileTool(),\r\n new WriteFileTool(),\r\n new EditFileTool(),\r\n taskManager\r\n )\r\n .build();\r\n }\r\n\r\n @Override\r\n public void run(String... args) {\r\n AgentRunner.interactive(\"s07\", userMessage ->\r\n chatClient.prompt()\r\n .user(userMessage)\r\n .call()\r\n .content()\r\n );\r\n }\r\n\r\n public static void main(String[] args) {\r\n SpringApplication app = new SpringApplication(S07TaskSystem.class);\r\n app.setWebApplicationType(WebApplicationType.NONE);\r\n app.run(args);\r\n }\r\n}\r\n\n\n// === TaskManager.java ===\npackage io.mybatis.learn.s07;\r\n\r\nimport com.fasterxml.jackson.core.type.TypeReference;\r\nimport com.fasterxml.jackson.databind.ObjectMapper;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\nimport org.springframework.ai.tool.annotation.Tool;\r\nimport org.springframework.ai.tool.annotation.ToolParam;\r\n\r\nimport java.io.IOException;\r\nimport java.nio.file.Files;\r\nimport java.nio.file.Path;\r\nimport java.util.*;\r\nimport java.util.stream.Stream;\r\n\r\n/**\r\n * 持久化任务管理器 —— 任务状态存储在磁盘上,不受上下文压缩影响。\r\n * <p>\r\n * TIP: 对应 Python {@code agents/s07_task_system.py} 中的 {@code TaskManager} 类。\r\n * Python 使用 {@code json.loads/json.dumps},Java 使用 Jackson {@code ObjectMapper}。\r\n * <pre>\r\n * .tasks/\r\n * task_1.json {\"id\":1, \"subject\":\"...\", \"status\":\"completed\", ...}\r\n * task_2.json {\"id\":2, \"blockedBy\":[1], \"status\":\"pending\", ...}\r\n *\r\n * 依赖解析:\r\n * task 1 (complete) --> task 2 (blocked) --> task 3 (blocked)\r\n * | ^\r\n * +--- completing task 1 removes it from task 2's blockedBy\r\n * </pre>\r\n */\r\npublic class TaskManager {\r\n private static final Logger log = LoggerFactory.getLogger(TaskManager.class);\r\n\r\n private static final ObjectMapper MAPPER = new ObjectMapper();\r\n private static final List<String> VALID_STATUSES = List.of(\"pending\", \"in_progress\", \"completed\");\r\n\r\n private final Path dir;\r\n private int nextId;\r\n\r\n public TaskManager(Path tasksDir) {\r\n this.dir = tasksDir;\r\n try {\r\n Files.createDirectories(dir);\r\n log.debug(\"任务目录已就绪: {}\", dir);\r\n } catch (IOException e) {\r\n log.error(\"创建任务目录失败: {}, error={}\", dir, e.getMessage());\r\n throw new RuntimeException(\"Cannot create tasks directory: \" + e.getMessage(), e);\r\n }\r\n this.nextId = maxId() + 1;\r\n log.info(\"TaskManager 初始化完成,nextId={}, dir={}\", nextId, dir);\r\n }\r\n\r\n private int maxId() {\r\n try (Stream<Path> files = Files.list(dir)) {\r\n return files.filter(f -> f.getFileName().toString().matches(\"task_\\\\d+\\\\.json\"))\r\n Line truncated
"source": "// === S07TaskSystem.java ===\npackage io.mybatis.learn.s07;\r\n\r\nimport io.mybatis.learn.core.AgentRunner;\r\nimport io.mybatis.learn.core.tools.BashTool;\r\nimport io.mybatis.learn.core.tools.EditFileTool;\r\nimport io.mybatis.learn.core.tools.ReadFileTool;\r\nimport io.mybatis.learn.core.tools.WriteFileTool;\r\nimport org.springframework.ai.chat.client.ChatClient;\r\nimport org.springframework.ai.chat.model.ChatModel;\r\nimport org.springframework.boot.CommandLineRunner;\r\nimport org.springframework.boot.SpringApplication;\r\nimport org.springframework.boot.WebApplicationType;\r\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\r\n\r\nimport java.nio.file.Path;\r\n\r\n/**\r\n * S07 - 任务系统:大目标拆成小任务,排好序,记在磁盘上。\r\n * <p>\r\n * 格言: \"大目标要拆成小任务, 排好序, 记在磁盘上\"\r\n * <p>\r\n * TIP: 对应 Python {@code agents/s07_task_system.py}。\r\n * 核心洞察: 任务状态持久化在磁盘({@code .tasks/task_*.json}),\r\n * 不受上下文压缩影响 —— 因为它在对话之外。\r\n */\r\n@SpringBootApplication(scanBasePackages = \"io.mybatis.learn.core\")\r\npublic class S07TaskSystem implements CommandLineRunner {\r\n\r\n private final ChatClient chatClient;\r\n\r\n public S07TaskSystem(ChatModel chatModel) {\r\n Path tasksDir = Path.of(System.getProperty(\"user.dir\"), \".tasks\");\r\n TaskManager taskManager = new TaskManager(tasksDir);\r\n\r\n this.chatClient = ChatClient.builder(chatModel)\r\n .defaultSystem(\"You are a coding agent at \" + System.getProperty(\"user.dir\")\r\n + \". Use task tools to plan and track work.\")\r\n .defaultTools(\r\n new BashTool(),\r\n new ReadFileTool(),\r\n new WriteFileTool(),\r\n new EditFileTool(),\r\n taskManager\r\n )\r\n .build();\r\n }\r\n\r\n @Override\r\n public void run(String... args) {\r\n AgentRunner.interactive(\"s07\", userMessage ->\r\n chatClient.prompt()\r\n .user(userMessage)\r\n .call()\r\n .content()\r\n );\r\n }\r\n\r\n public static void main(String[] args) {\r\n SpringApplication app = new SpringApplication(S07TaskSystem.class);\r\n app.setWebApplicationType(WebApplicationType.NONE);\r\n app.run(args);\r\n }\r\n}\r\n\n\n// === TaskManager.java ===\npackage io.mybatis.learn.s07;\r\n\r\nimport com.fasterxml.jackson.core.type.TypeReference;\r\nimport com.fasterxml.jackson.databind.ObjectMapper;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\nimport org.springframework.ai.tool.annotation.Tool;\r\nimport org.springframework.ai.tool.annotation.ToolParam;\r\n\r\nimport java.io.IOException;\r\nimport java.nio.file.Files;\r\nimport java.nio.file.Path;\r\nimport java.util.*;\r\nimport java.util.stream.Stream;\r\n\r\n/**\r\n * 持久化任务管理器 —— 任务状态存储在磁盘上,不受上下文压缩影响。\r\n * <p>\r\n * TIP: 对应 Python {@code agents/s07_task_system.py} 中的 {@code TaskManager} 类。\r\n * Python 使用 {@code json.loads/json.dumps},Java 使用 Jackson {@code ObjectMapper}。\r\n * <pre>\r\n * .tasks/\r\n * task_1.json {\"id\":1, \"subject\":\"...\", \"status\":\"completed\", ...}\r\n * task_2.json {\"id\":2, \"blockedBy\":[1], \"status\":\"pending\", ...}\r\n *\r\n * 依赖解析:\r\n * task 1 (complete) --> task 2 (blocked) --> task 3 (blocked)\r\n * | ^\r\n * +--- completing task 1 removes it from task 2's blockedBy\r\n * </pre>\r\n */\r\npublic class TaskManager {\r\n private static final Logger log = LoggerFactory.getLogger(TaskManager.class);\r\n\r\n private static final ObjectMapper MAPPER = new ObjectMapper();\r\n private static final List<String> VALID_STATUSES = List.of(\"pending\", \"in_progress\", \"completed\");\r\n\r\n private final Path dir;\r\n private int nextId;\r\n\r\n public TaskManager(Path tasksDir) {\r\n this.dir = tasksDir;\r\n try {\r\n Files.createDirectories(dir);\r\n if (log.isDebugEnabled()) {\r\n System.out.printf(\"🚀 任务目录就绪: %s%n\", dir);\r\n }\r\n } catch (IOException e) {\r\n log.error(\"创建任务目录失败: {}, error={}\", dir, e.getMessage());\r\n throw new RuntimeException(\"Cannot create tasks directory: \" + e.getMessage(), e);\r\n }\r\n this.nextId = maxId() + 1;\r\n log.info(\"TaskManager 初始化完成,nextId={}, dir={}\", nextId, dir);\r\n }\r\n\r\n private int maxId() {\r\n try (Stream<Path> files = Files.list(dir)) {\r\n return files.filter(f -> f.getLine truncated
},
{
"id": "s08",
"filename": "S08BackgroundTasks.java",
"title": "Background Tasks",
"subtitle": "Background Threads + Notifications",
"loc": 171,
"loc": 179,
"tools": [
"backgroundRun",
"checkBackground"
@@ -595,7 +595,7 @@
{
"name": "BackgroundManager",
"startLine": 121,
"endLine": 236
"endLine": 244
}
],
"functions": [
@@ -652,28 +652,28 @@
{
"name": "InputStreamReader",
"signature": "new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8)))",
"startLine": 171
"startLine": 173
},
{
"name": "checkBackground",
"signature": "public String checkBackground(",
"startLine": 204
"startLine": 208
},
{
"name": "drainNotifications",
"signature": "public List<Notification> drainNotifications()",
"startLine": 227
"startLine": 233
}
],
"layer": "concurrency",
"source": "// === S08BackgroundTasks.java ===\npackage io.mybatis.learn.s08;\r\n\r\nimport io.mybatis.learn.core.AgentRunner;\r\nimport io.mybatis.learn.core.tools.BashTool;\r\nimport io.mybatis.learn.core.tools.EditFileTool;\r\nimport io.mybatis.learn.core.tools.ReadFileTool;\r\nimport io.mybatis.learn.core.tools.WriteFileTool;\r\nimport org.springframework.ai.chat.client.ChatClient;\r\nimport org.springframework.ai.chat.model.ChatModel;\r\nimport org.springframework.boot.CommandLineRunner;\r\nimport org.springframework.boot.SpringApplication;\r\nimport org.springframework.boot.WebApplicationType;\r\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\r\n\r\nimport java.util.stream.Collectors;\r\n\r\n/**\r\n * S08 - 后台任务:慢操作丢后台,Agent 继续想下一步。\r\n * <p>\r\n * 格言: \"慢操作丢后台, agent 继续想下一步\"\r\n * <p>\r\n * TIP: 对应 Python {@code agents/s08_background_tasks.py}。\r\n * Python 在每次 LLM 调用前 drain 通知队列并注入 {@code <background-results>}。\r\n * Spring AI 的 ChatClient 管理内部循环,因此改为在每次用户输入时\r\n * drain 通知并注入系统提示。核心概念不变: fire and forget。\r\n */\r\n@SpringBootApplication(scanBasePackages = \"io.mybatis.learn.core\")\r\npublic class S08BackgroundTasks implements CommandLineRunner {\r\n\r\n private final ChatModel chatModel;\r\n\r\n public S08BackgroundTasks(ChatModel chatModel) {\r\n this.chatModel = chatModel;\r\n }\r\n\r\n @Override\r\n public void run(String... args) {\r\n BackgroundManager bgManager = new BackgroundManager();\r\n String workDir = System.getProperty(\"user.dir\");\r\n\r\n AgentRunner.interactive(\"s08\", userMessage -> {\r\n // Drain 后台任务通知(对应 Python 中循环前的 drain_notifications)\r\n var notifs = bgManager.drainNotifications();\r\n String bgContext = \"\";\r\n if (!notifs.isEmpty()) {\r\n String notifText = notifs.stream()\r\n .map(n -> \"[bg:\" + n.taskId() + \"] \" + n.status() + \": \" + n.result())\r\n .collect(Collectors.joining(\"\\n\"));\r\n bgContext = \"\\n\\n<background-results>\\n\" + notifText + \"\\n</background-results>\";\r\n System.out.println(\"[Background tasks completed: \" + notifs.size() + \"]\");\r\n }\r\n\r\n String system = \"You are a coding agent at \" + workDir\r\n + \". Use backgroundRun for long-running commands.\" + bgContext;\r\n\r\n ChatClient chatClient = ChatClient.builder(chatModel)\r\n .defaultSystem(system)\r\n .defaultTools(\r\n new BashTool(),\r\n new ReadFileTool(),\r\n new WriteFileTool(),\r\n new EditFileTool(),\r\n bgManager\r\n )\r\n .build();\r\n\r\n return chatClient.prompt()\r\n .user(userMessage)\r\n .call()\r\n .content();\r\n });\r\n }\r\n\r\n public static void main(String[] args) {\r\n SpringApplication app = new SpringApplication(S08BackgroundTasks.class);\r\n app.setWebApplicationType(WebApplicationType.NONE);\r\n app.run(args);\r\n }\r\n}\r\n\n\n// === BackgroundManager.java ===\npackage io.mybatis.learn.s08;\r\n\r\nimport io.mybatis.learn.core.AgentRunner;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\nimport org.springframework.ai.tool.annotation.Tool;\r\nimport org.springframework.ai.tool.annotation.ToolParam;\r\n\r\nimport java.io.BufferedReader;\r\nimport java.io.InputStreamReader;\r\nimport java.nio.charset.StandardCharsets;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.Map;\r\nimport java.util.UUID;\r\nimport java.util.concurrent.ConcurrentHashMap;\r\nimport java.util.concurrent.CopyOnWriteArrayList;\r\nimport java.util.concurrent.ExecutorService;\r\nimport java.util.concurrent.Executors;\r\nimport java.util.stream.Collectors;\r\n\r\n/**\r\n * 后台任务管理器 —— 慢操作丢后台,Agent 继续想下一步。\r\n * <p>\r\n * TIP: 对应 Python {@code agents/s08_background_tasks.py} 中的 {@code BackgroundManager} 类。\r\n * Python 使用 {@code threading.Thread(daemon=True)},\r\n * Java 使用 {@link ExecutorService} + 虚拟线程(Java 21)。\r\n * <pre>\r\n * Main thread Background thread\r\n * +-----------------+ +-----------------+\r\n * | agent loop | | task executes |\r\n * | ... | | ... |\r\n * | [LLM call] <--+------- | notify(result) |\r\n * | ^drain queue | +-----------------+\r\n * +-----------------+\r\n * </pre>\r\n */\r\npublic cLine truncated
"source": "// === S08BackgroundTasks.java ===\npackage io.mybatis.learn.s08;\r\n\r\nimport io.mybatis.learn.core.AgentRunner;\r\nimport io.mybatis.learn.core.tools.BashTool;\r\nimport io.mybatis.learn.core.tools.EditFileTool;\r\nimport io.mybatis.learn.core.tools.ReadFileTool;\r\nimport io.mybatis.learn.core.tools.WriteFileTool;\r\nimport org.springframework.ai.chat.client.ChatClient;\r\nimport org.springframework.ai.chat.model.ChatModel;\r\nimport org.springframework.boot.CommandLineRunner;\r\nimport org.springframework.boot.SpringApplication;\r\nimport org.springframework.boot.WebApplicationType;\r\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\r\n\r\nimport java.util.stream.Collectors;\r\n\r\n/**\r\n * S08 - 后台任务:慢操作丢后台,Agent 继续想下一步。\r\n * <p>\r\n * 格言: \"慢操作丢后台, agent 继续想下一步\"\r\n * <p>\r\n * TIP: 对应 Python {@code agents/s08_background_tasks.py}。\r\n * Python 在每次 LLM 调用前 drain 通知队列并注入 {@code <background-results>}。\r\n * Spring AI 的 ChatClient 管理内部循环,因此改为在每次用户输入时\r\n * drain 通知并注入系统提示。核心概念不变: fire and forget。\r\n */\r\n@SpringBootApplication(scanBasePackages = \"io.mybatis.learn.core\")\r\npublic class S08BackgroundTasks implements CommandLineRunner {\r\n\r\n private final ChatModel chatModel;\r\n\r\n public S08BackgroundTasks(ChatModel chatModel) {\r\n this.chatModel = chatModel;\r\n }\r\n\r\n @Override\r\n public void run(String... args) {\r\n BackgroundManager bgManager = new BackgroundManager();\r\n String workDir = System.getProperty(\"user.dir\");\r\n\r\n AgentRunner.interactive(\"s08\", userMessage -> {\r\n // Drain 后台任务通知(对应 Python 中循环前的 drain_notifications)\r\n var notifs = bgManager.drainNotifications();\r\n String bgContext = \"\";\r\n if (!notifs.isEmpty()) {\r\n String notifText = notifs.stream()\r\n .map(n -> \"[bg:\" + n.taskId() + \"] \" + n.status() + \": \" + n.result())\r\n .collect(Collectors.joining(\"\\n\"));\r\n bgContext = \"\\n\\n<background-results>\\n\" + notifText + \"\\n</background-results>\";\r\n System.out.println(\"[Background tasks completed: \" + notifs.size() + \"]\");\r\n }\r\n\r\n String system = \"You are a coding agent at \" + workDir\r\n + \". Use backgroundRun for long-running commands.\" + bgContext;\r\n\r\n ChatClient chatClient = ChatClient.builder(chatModel)\r\n .defaultSystem(system)\r\n .defaultTools(\r\n new BashTool(),\r\n new ReadFileTool(),\r\n new WriteFileTool(),\r\n new EditFileTool(),\r\n bgManager\r\n )\r\n .build();\r\n\r\n return chatClient.prompt()\r\n .user(userMessage)\r\n .call()\r\n .content();\r\n });\r\n }\r\n\r\n public static void main(String[] args) {\r\n SpringApplication app = new SpringApplication(S08BackgroundTasks.class);\r\n app.setWebApplicationType(WebApplicationType.NONE);\r\n app.run(args);\r\n }\r\n}\r\n\n\n// === BackgroundManager.java ===\npackage io.mybatis.learn.s08;\r\n\r\nimport io.mybatis.learn.core.AgentRunner;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\nimport org.springframework.ai.tool.annotation.Tool;\r\nimport org.springframework.ai.tool.annotation.ToolParam;\r\n\r\nimport java.io.BufferedReader;\r\nimport java.io.InputStreamReader;\r\nimport java.nio.charset.StandardCharsets;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.Map;\r\nimport java.util.UUID;\r\nimport java.util.concurrent.ConcurrentHashMap;\r\nimport java.util.concurrent.CopyOnWriteArrayList;\r\nimport java.util.concurrent.ExecutorService;\r\nimport java.util.concurrent.Executors;\r\nimport java.util.stream.Collectors;\r\n\r\n/**\r\n * 后台任务管理器 —— 慢操作丢后台,Agent 继续想下一步。\r\n * <p>\r\n * TIP: 对应 Python {@code agents/s08_background_tasks.py} 中的 {@code BackgroundManager} 类。\r\n * Python 使用 {@code threading.Thread(daemon=True)},\r\n * Java 使用 {@link ExecutorService} + 虚拟线程(Java 21)。\r\n * <pre>\r\n * Main thread Background thread\r\n * +-----------------+ +-----------------+\r\n * | agent loop | | task executes |\r\n * | ... | | ... |\r\n * | [LLM call] <--+------- | notify(result) |\r\n * | ^drain queue | +-----------------+\r\n * +-----------------+\r\n * </pre>\r\n */\r\npublic cLine truncated
},
{
"id": "s09",
"filename": "S09AgentTeams.java",
"title": "Agent Teams",
"subtitle": "Teammates + Mailboxes",
"loc": 268,
"loc": 282,
"tools": [
"spawnTeammate",
"listTeammates",
@@ -698,12 +698,12 @@
{
"name": "S09AgentTeams",
"startLine": 35,
"endLine": 169
"endLine": 175
},
{
"name": "TeammateManager",
"startLine": 170,
"endLine": 368
"startLine": 176,
"endLine": 382
}
],
"functions": [
@@ -755,73 +755,73 @@
{
"name": "loadConfig",
"signature": "private Map<String, Object> loadConfig()",
"startLine": 197
"startLine": 203
},
{
"name": "saveConfig",
"signature": "private synchronized void saveConfig()",
"startLine": 210
"startLine": 218
},
{
"name": "findMember",
"signature": "private Map<String, Object> findMember(String name)",
"startLine": 221
"startLine": 231
},
{
"name": "setStatus",
"signature": "protected synchronized void setStatus(String name, String status)",
"startLine": 229
"startLine": 239
},
{
"name": "spawn",
"signature": "public synchronized String spawn(String name, String role, String prompt)",
"startLine": 240
"startLine": 250
},
{
"name": "teammateLoop",
"signature": "protected void teammateLoop(String name, String role, String initialPrompt)",
"startLine": 273
"startLine": 283
},
{
"name": "WriteFileTool",
"signature": "new WriteFileTool(), new EditFileTool(), messageTool)",
"startLine": 285
"startLine": 295
},
{
"name": "listAll",
"signature": "public String listAll()",
"startLine": 317
"startLine": 331
},
{
"name": "memberNames",
"signature": "public List<String> memberNames()",
"startLine": 329
"startLine": 343
},
{
"name": "TeammateMessageTool",
"signature": "public TeammateMessageTool(MessageBus bus, String name)",
"startLine": 346
"startLine": 360
},
{
"name": "sendMessage",
"signature": "public String sendMessage(",
"startLine": 352
"startLine": 366
},
{
"name": "readInbox",
"signature": "public String readInbox()",
"startLine": 359
"startLine": 373
}
],
"layer": "collaboration",
"source": "// === S09AgentTeams.java ===\npackage io.mybatis.learn.s09;\r\n\r\nimport com.fasterxml.jackson.databind.ObjectMapper;\r\nimport io.mybatis.learn.core.AgentRunner;\r\nimport io.mybatis.learn.core.team.MessageBus;\r\nimport io.mybatis.learn.core.tools.*;\r\nimport org.springframework.ai.chat.client.ChatClient;\r\nimport org.springframework.ai.chat.model.ChatModel;\r\nimport org.springframework.ai.tool.annotation.Tool;\r\nimport org.springframework.ai.tool.annotation.ToolParam;\r\nimport org.springframework.beans.factory.annotation.Autowired;\r\nimport org.springframework.boot.CommandLineRunner;\r\nimport org.springframework.boot.SpringApplication;\r\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\r\n\r\nimport java.nio.file.Path;\r\nimport java.util.List;\r\n\r\n/**\r\n * S09 - Agent Teams:持久化命名agent + JSONL收件箱通信\r\n *\r\n * TIPS: 对应Python s09_agent_teams.py。\r\n * Python用 TOOL_HANDLERS 字典 + 手动while循环分发9个工具;\r\n * Java用 ChatClient + @Tool 自动处理,Lead工具封装在 LeadTools 内部类。\r\n *\r\n * 核心概念:\r\n * Subagent (s04): spawn → execute → return summary → destroyed\r\n * Teammate (s09): spawn → work → idle → work → ... → shutdown\r\n *\r\n * 关键差异:Python队友在每次LLM调用前检查收件箱(s09第174行),\r\n * Java队友在每次完整工具链(call())之间检查。这是Spring AI的自然适配。\r\n */\r\n@SpringBootApplication(scanBasePackages = \"io.mybatis.learn.core\")\r\npublic class S09AgentTeams implements CommandLineRunner {\r\n\r\n @Autowired\r\n private ChatModel chatModel;\r\n\r\n @Override\r\n public void run(String... args) throws Exception {\r\n Path workDir = Path.of(System.getProperty(\"user.dir\"));\r\n Path teamDir = workDir.resolve(\".team\");\r\n\r\n MessageBus bus = new MessageBus(teamDir.resolve(\"inbox\"));\r\n TeammateManager team = new TeammateManager(chatModel, bus, teamDir);\r\n LeadTools leadTools = new LeadTools(bus, team);\r\n ObjectMapper mapper = new ObjectMapper();\r\n\r\n String systemPrompt = \"You are a team lead at \" + workDir\r\n + \". Spawn teammates and communicate via inboxes.\";\r\n\r\n ChatClient chatClient = ChatClient.builder(chatModel)\r\n .defaultSystem(systemPrompt)\r\n .defaultTools(new BashTool(), new ReadFileTool(),\r\n new WriteFileTool(), new EditFileTool(), leadTools)\r\n .build();\r\n\r\n AgentRunner.interactive(\"s09\", input -> {\r\n if (\"/team\".equals(input)) return team.listAll();\r\n if (\"/inbox\".equals(input)) {\r\n try {\r\n return mapper.writeValueAsString(bus.readInbox(\"lead\"));\r\n } catch (Exception e) {\r\n return \"Error: \" + e.getMessage();\r\n }\r\n }\r\n\r\n // 每次LLM调用前检查lead收件箱\r\n var inbox = bus.readInbox(\"lead\");\r\n String fullInput = input;\r\n if (!inbox.isEmpty()) {\r\n try {\r\n fullInput = \"<inbox>\" + mapper.writeValueAsString(inbox)\r\n + \"</inbox>\\n\\n\" + input;\r\n } catch (Exception e) { /* ignore */ }\r\n }\r\n\r\n return chatClient.prompt(fullInput).call().content();\r\n });\r\n }\r\n\r\n /**\r\n * Lead专用工具集(5个团队管理工具)\r\n *\r\n * TIPS: 对应Python TOOL_HANDLERS中的5个lead工具(s09第310-319行)。\r\n * Python用lambda + 字典映射;Java用@Tool注解的方法。\r\n */\r\n public static class LeadTools {\r\n private final MessageBus bus;\r\n private final TeammateManager team;\r\n private final ObjectMapper mapper = new ObjectMapper();\r\n\r\n public LeadTools(MessageBus bus, TeammateManager team) {\r\n this.bus = bus;\r\n this.team = team;\r\n }\r\n\r\n @Tool(description = \"Spawn a persistent teammate that runs in its own thread\")\r\n public String spawnTeammate(\r\n @ToolParam(description = \"Teammate name\") String name,\r\n @ToolParam(description = \"Role description\") String role,\r\n @ToolParam(description = \"Initial task prompt\") String prompt) {\r\n return team.spawn(name, role, prompt);\r\n }\r\n\r\n @Tool(description = \"List all teammates with name, role, status\")\r\n public String listTeammates() {\r\n return team.listAll();\r\n }\r\n\r\n @Tool(description = \"Send a message to a teammate's inbox\")\r\n public String sendMessage(\r\n @ToolParam(description = \"Recipient teammate name\") String to,\r\n @ToolParam(deLine truncated
"source": "// === S09AgentTeams.java ===\npackage io.mybatis.learn.s09;\r\n\r\nimport com.fasterxml.jackson.databind.ObjectMapper;\r\nimport io.mybatis.learn.core.AgentRunner;\r\nimport io.mybatis.learn.core.team.MessageBus;\r\nimport io.mybatis.learn.core.tools.*;\r\nimport org.springframework.ai.chat.client.ChatClient;\r\nimport org.springframework.ai.chat.model.ChatModel;\r\nimport org.springframework.ai.tool.annotation.Tool;\r\nimport org.springframework.ai.tool.annotation.ToolParam;\r\nimport org.springframework.beans.factory.annotation.Autowired;\r\nimport org.springframework.boot.CommandLineRunner;\r\nimport org.springframework.boot.SpringApplication;\r\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\r\n\r\nimport java.nio.file.Path;\r\nimport java.util.List;\r\n\r\n/**\r\n * S09 - Agent Teams:持久化命名agent + JSONL收件箱通信\r\n *\r\n * TIPS: 对应Python s09_agent_teams.py。\r\n * Python用 TOOL_HANDLERS 字典 + 手动while循环分发9个工具;\r\n * Java用 ChatClient + @Tool 自动处理,Lead工具封装在 LeadTools 内部类。\r\n *\r\n * 核心概念:\r\n * Subagent (s04): spawn → execute → return summary → destroyed\r\n * Teammate (s09): spawn → work → idle → work → ... → shutdown\r\n *\r\n * 关键差异:Python队友在每次LLM调用前检查收件箱(s09第174行),\r\n * Java队友在每次完整工具链(call())之间检查。这是Spring AI的自然适配。\r\n */\r\n@SpringBootApplication(scanBasePackages = \"io.mybatis.learn.core\")\r\npublic class S09AgentTeams implements CommandLineRunner {\r\n\r\n @Autowired\r\n private ChatModel chatModel;\r\n\r\n @Override\r\n public void run(String... args) throws Exception {\r\n Path workDir = Path.of(System.getProperty(\"user.dir\"));\r\n Path teamDir = workDir.resolve(\".team\");\r\n\r\n MessageBus bus = new MessageBus(teamDir.resolve(\"inbox\"));\r\n TeammateManager team = new TeammateManager(chatModel, bus, teamDir);\r\n LeadTools leadTools = new LeadTools(bus, team);\r\n ObjectMapper mapper = new ObjectMapper();\r\n\r\n String systemPrompt = \"You are a team lead at \" + workDir\r\n + \". Spawn teammates and communicate via inboxes.\";\r\n\r\n ChatClient chatClient = ChatClient.builder(chatModel)\r\n .defaultSystem(systemPrompt)\r\n .defaultTools(new BashTool(), new ReadFileTool(),\r\n new WriteFileTool(), new EditFileTool(), leadTools)\r\n .build();\r\n\r\n AgentRunner.interactive(\"s09\", input -> {\r\n if (\"/team\".equals(input)) return team.listAll();\r\n if (\"/inbox\".equals(input)) {\r\n try {\r\n return mapper.writeValueAsString(bus.readInbox(\"lead\"));\r\n } catch (Exception e) {\r\n return \"Error: \" + e.getMessage();\r\n }\r\n }\r\n\r\n // 每次LLM调用前检查lead收件箱\r\n var inbox = bus.readInbox(\"lead\");\r\n String fullInput = input;\r\n if (!inbox.isEmpty()) {\r\n try {\r\n fullInput = \"<inbox>\" + mapper.writeValueAsString(inbox)\r\n + \"</inbox>\\n\\n\" + input;\r\n } catch (Exception e) { /* ignore */ }\r\n }\r\n\r\n return chatClient.prompt(fullInput).call().content();\r\n });\r\n }\r\n\r\n /**\r\n * Lead专用工具集(5个团队管理工具)\r\n *\r\n * TIPS: 对应Python TOOL_HANDLERS中的5个lead工具(s09第310-319行)。\r\n * Python用lambda + 字典映射;Java用@Tool注解的方法。\r\n */\r\n public static class LeadTools {\r\n private final MessageBus bus;\r\n private final TeammateManager team;\r\n private final ObjectMapper mapper = new ObjectMapper();\r\n\r\n public LeadTools(MessageBus bus, TeammateManager team) {\r\n this.bus = bus;\r\n this.team = team;\r\n }\r\n\r\n @Tool(description = \"Spawn a persistent teammate that runs in its own thread\")\r\n public String spawnTeammate(\r\n @ToolParam(description = \"Teammate name\") String name,\r\n @ToolParam(description = \"Role description\") String role,\r\n @ToolParam(description = \"Initial task prompt\") String prompt) {\r\n return team.spawn(name, role, prompt);\r\n }\r\n\r\n @Tool(description = \"List all teammates with name, role, status\")\r\n public String listTeammates() {\r\n return team.listAll();\r\n }\r\n\r\n @Tool(description = \"Send a message to a teammate's inbox\")\r\n public String sendMessage(\r\n @ToolParam(description = \"Recipient teammate name\") String to,\r\n @ToolParam(deLine truncated
},
{
"id": "s10",
"filename": "S10TeamProtocols.java",
"title": "Team Protocols",
"subtitle": "Shared Communication Rules",
"loc": 303,
"loc": 305,
"tools": [
"shutdownResponse",
"planApproval",
@@ -852,7 +852,7 @@
{
"name": "ProtocolTracker",
"startLine": 337,
"endLine": 425
"endLine": 427
}
],
"functions": [
@@ -989,21 +989,21 @@
{
"name": "respondToShutdown",
"signature": "public String respondToShutdown(String sender, String requestId, boolean approve, String reason)",
"startLine": 379
"startLine": 381
},
{
"name": "submitPlan",
"signature": "public String submitPlan(String sender, String planText)",
"startLine": 395
"startLine": 397
},
{
"name": "reviewPlan",
"signature": "public String reviewPlan(String requestId, boolean approve, String feedback)",
"startLine": 410
"startLine": 412
}
],
"layer": "collaboration",
"source": "// === S10TeamProtocols.java ===\npackage io.mybatis.learn.s10;\r\n\r\nimport com.fasterxml.jackson.databind.ObjectMapper;\r\nimport io.mybatis.learn.core.AgentRunner;\r\nimport io.mybatis.learn.core.team.MessageBus;\r\nimport io.mybatis.learn.core.tools.*;\r\nimport org.springframework.ai.chat.client.ChatClient;\r\nimport org.springframework.ai.chat.model.ChatModel;\r\nimport org.springframework.ai.tool.annotation.Tool;\r\nimport org.springframework.ai.tool.annotation.ToolParam;\r\nimport org.springframework.beans.factory.annotation.Autowired;\r\nimport org.springframework.boot.CommandLineRunner;\r\nimport org.springframework.boot.SpringApplication;\r\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\r\n\r\nimport java.io.IOException;\r\nimport java.nio.file.Files;\r\nimport java.nio.file.Path;\r\nimport java.util.*;\r\nimport java.util.concurrent.ConcurrentHashMap;\r\n\r\n/**\r\n * S10 - Team Protocols:关闭协议 + 计划审批协议\r\n *\r\n * TIPS: 对应Python s10_team_protocols.py。\r\n * 在S09基础上新增3个Lead工具和2个Teammate工具。\r\n *\r\n * 关闭协议FSM: pending → approved | rejected\r\n * Lead调用 shutdown_request → Teammate收到 shutdown_request 消息\r\n * → Teammate调用 shutdown_response → Lead收件箱收到响应\r\n *\r\n * 计划审批FSM: pending → approved | rejected\r\n * Teammate调用 plan_approval → Lead收件箱收到计划\r\n * → Lead调用 plan_approval(review) → Teammate收到审批结果\r\n *\r\n * 关键洞察:\"Same request_id correlation pattern, two domains.\"\r\n */\r\n@SpringBootApplication(scanBasePackages = \"io.mybatis.learn.core\")\r\npublic class S10TeamProtocols implements CommandLineRunner {\r\n\r\n @Autowired\r\n private ChatModel chatModel;\r\n\r\n @Override\r\n public void run(String... args) throws Exception {\r\n Path workDir = Path.of(System.getProperty(\"user.dir\"));\r\n Path teamDir = workDir.resolve(\".team\");\r\n\r\n MessageBus bus = new MessageBus(teamDir.resolve(\"inbox\"));\r\n ProtocolTracker tracker = new ProtocolTracker(bus);\r\n S10TeammateManager team = new S10TeammateManager(chatModel, bus, tracker, teamDir);\r\n S10LeadTools leadTools = new S10LeadTools(bus, team, tracker);\r\n ObjectMapper mapper = new ObjectMapper();\r\n\r\n String systemPrompt = \"You are a team lead at \" + workDir\r\n + \". Manage teammates with shutdown and plan approval protocols.\";\r\n\r\n ChatClient chatClient = ChatClient.builder(chatModel)\r\n .defaultSystem(systemPrompt)\r\n .defaultTools(new BashTool(), new ReadFileTool(),\r\n new WriteFileTool(), new EditFileTool(), leadTools)\r\n .build();\r\n\r\n AgentRunner.interactive(\"s10\", input -> {\r\n if (\"/team\".equals(input)) return team.listAll();\r\n if (\"/inbox\".equals(input)) {\r\n try { return mapper.writeValueAsString(bus.readInbox(\"lead\")); }\r\n catch (Exception e) { return \"Error: \" + e.getMessage(); }\r\n }\r\n\r\n var inbox = bus.readInbox(\"lead\");\r\n String fullInput = input;\r\n if (!inbox.isEmpty()) {\r\n try {\r\n fullInput = \"<inbox>\" + mapper.writeValueAsString(inbox)\r\n + \"</inbox>\\n\\n\" + input;\r\n } catch (Exception e) { /* ignore */ }\r\n }\r\n\r\n return chatClient.prompt(fullInput).call().content();\r\n });\r\n }\r\n\r\n // ---- 队友管理器(增加协议处理) ----\r\n\r\n /**\r\n * TIPS: 对应Python s10第134-290行的TeammateManager。\r\n * 相比S09增加了:\r\n * 1. should_exit标志 - 队友批准关闭后退出循环\r\n * 2. shutdown_response工具 - 更新tracker + 发送响应消息\r\n * 3. plan_approval工具 - 提交计划到lead收件箱\r\n */\r\n static class S10TeammateManager {\r\n private final ChatModel chatModel;\r\n private final MessageBus bus;\r\n private final ProtocolTracker tracker;\r\n private final Path configPath;\r\n private final ObjectMapper mapper = new ObjectMapper();\r\n private Map<String, Object> config;\r\n\r\n S10TeammateManager(ChatModel chatModel, MessageBus bus,\r\n ProtocolTracker tracker, Path teamDir) {\r\n this.chatModel = chatModel;\r\n this.bus = bus;\r\n this.tracker = tracker;\r\n this.configPath = teamDir.resolve(\"config.json\");\r\n try { Files.createDirectories(teamDir); } catch (IOException e) { throw new RuntimeException(e); }\r\n this.config = loadConfig();\r\n }\r\n\r\n @SuppressWarnings(\"unchecked\")\r\n private Map<String, Object> loadConfig() {\r\n Line truncated
"source": "// === S10TeamProtocols.java ===\npackage io.mybatis.learn.s10;\r\n\r\nimport com.fasterxml.jackson.databind.ObjectMapper;\r\nimport io.mybatis.learn.core.AgentRunner;\r\nimport io.mybatis.learn.core.team.MessageBus;\r\nimport io.mybatis.learn.core.tools.*;\r\nimport org.springframework.ai.chat.client.ChatClient;\r\nimport org.springframework.ai.chat.model.ChatModel;\r\nimport org.springframework.ai.tool.annotation.Tool;\r\nimport org.springframework.ai.tool.annotation.ToolParam;\r\nimport org.springframework.beans.factory.annotation.Autowired;\r\nimport org.springframework.boot.CommandLineRunner;\r\nimport org.springframework.boot.SpringApplication;\r\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\r\n\r\nimport java.io.IOException;\r\nimport java.nio.file.Files;\r\nimport java.nio.file.Path;\r\nimport java.util.*;\r\nimport java.util.concurrent.ConcurrentHashMap;\r\n\r\n/**\r\n * S10 - Team Protocols:关闭协议 + 计划审批协议\r\n *\r\n * TIPS: 对应Python s10_team_protocols.py。\r\n * 在S09基础上新增3个Lead工具和2个Teammate工具。\r\n *\r\n * 关闭协议FSM: pending → approved | rejected\r\n * Lead调用 shutdown_request → Teammate收到 shutdown_request 消息\r\n * → Teammate调用 shutdown_response → Lead收件箱收到响应\r\n *\r\n * 计划审批FSM: pending → approved | rejected\r\n * Teammate调用 plan_approval → Lead收件箱收到计划\r\n * → Lead调用 plan_approval(review) → Teammate收到审批结果\r\n *\r\n * 关键洞察:\"Same request_id correlation pattern, two domains.\"\r\n */\r\n@SpringBootApplication(scanBasePackages = \"io.mybatis.learn.core\")\r\npublic class S10TeamProtocols implements CommandLineRunner {\r\n\r\n @Autowired\r\n private ChatModel chatModel;\r\n\r\n @Override\r\n public void run(String... args) throws Exception {\r\n Path workDir = Path.of(System.getProperty(\"user.dir\"));\r\n Path teamDir = workDir.resolve(\".team\");\r\n\r\n MessageBus bus = new MessageBus(teamDir.resolve(\"inbox\"));\r\n ProtocolTracker tracker = new ProtocolTracker(bus);\r\n S10TeammateManager team = new S10TeammateManager(chatModel, bus, tracker, teamDir);\r\n S10LeadTools leadTools = new S10LeadTools(bus, team, tracker);\r\n ObjectMapper mapper = new ObjectMapper();\r\n\r\n String systemPrompt = \"You are a team lead at \" + workDir\r\n + \". Manage teammates with shutdown and plan approval protocols.\";\r\n\r\n ChatClient chatClient = ChatClient.builder(chatModel)\r\n .defaultSystem(systemPrompt)\r\n .defaultTools(new BashTool(), new ReadFileTool(),\r\n new WriteFileTool(), new EditFileTool(), leadTools)\r\n .build();\r\n\r\n AgentRunner.interactive(\"s10\", input -> {\r\n if (\"/team\".equals(input)) return team.listAll();\r\n if (\"/inbox\".equals(input)) {\r\n try { return mapper.writeValueAsString(bus.readInbox(\"lead\")); }\r\n catch (Exception e) { return \"Error: \" + e.getMessage(); }\r\n }\r\n\r\n var inbox = bus.readInbox(\"lead\");\r\n String fullInput = input;\r\n if (!inbox.isEmpty()) {\r\n try {\r\n fullInput = \"<inbox>\" + mapper.writeValueAsString(inbox)\r\n + \"</inbox>\\n\\n\" + input;\r\n } catch (Exception e) { /* ignore */ }\r\n }\r\n\r\n return chatClient.prompt(fullInput).call().content();\r\n });\r\n }\r\n\r\n // ---- 队友管理器(增加协议处理) ----\r\n\r\n /**\r\n * TIPS: 对应Python s10第134-290行的TeammateManager。\r\n * 相比S09增加了:\r\n * 1. should_exit标志 - 队友批准关闭后退出循环\r\n * 2. shutdown_response工具 - 更新tracker + 发送响应消息\r\n * 3. plan_approval工具 - 提交计划到lead收件箱\r\n */\r\n static class S10TeammateManager {\r\n private final ChatModel chatModel;\r\n private final MessageBus bus;\r\n private final ProtocolTracker tracker;\r\n private final Path configPath;\r\n private final ObjectMapper mapper = new ObjectMapper();\r\n private Map<String, Object> config;\r\n\r\n S10TeammateManager(ChatModel chatModel, MessageBus bus,\r\n ProtocolTracker tracker, Path teamDir) {\r\n this.chatModel = chatModel;\r\n this.bus = bus;\r\n this.tracker = tracker;\r\n this.configPath = teamDir.resolve(\"config.json\");\r\n try { Files.createDirectories(teamDir); } catch (IOException e) { throw new RuntimeException(e); }\r\n this.config = loadConfig();\r\n }\r\n\r\n @SuppressWarnings(\"unchecked\")\r\n private Map<String, Object> loadConfig() {\r\n Line truncated
},
{
"id": "s11",
@@ -1142,7 +1142,7 @@
"filename": "S12WorktreeIsolation.java",
"title": "Worktree + Task Isolation",
"subtitle": "Isolate by Directory",
"loc": 634,
"loc": 664,
"tools": [
"taskCreate",
"taskList",
@@ -1177,22 +1177,22 @@
{
"name": "S12WorktreeIsolation",
"startLine": 32,
"endLine": 214
"endLine": 217
},
{
"name": "EventBus",
"startLine": 215,
"endLine": 304
"startLine": 218,
"endLine": 312
},
{
"name": "WorktreeManager",
"startLine": 305,
"endLine": 633
"startLine": 313,
"endLine": 651
},
{
"name": "WorktreeTaskManager",
"startLine": 634,
"endLine": 799
"startLine": 652,
"endLine": 829
}
],
"functions": [
@@ -1289,141 +1289,141 @@
{
"name": "emit",
"signature": "public synchronized void emit(String event, Map<String, Object> task,",
"startLine": 233
"startLine": 238
},
{
"name": "emit",
"signature": "public void emit(String event)",
"startLine": 252
"startLine": 259
},
{
"name": "listRecent",
"signature": "public String listRecent(int limit)",
"startLine": 257
"startLine": 264
},
{
"name": "isGitRepo",
"signature": "private boolean isGitRepo()",
"startLine": 338
"startLine": 348
},
{
"name": "runGit",
"signature": "private String runGit(String... args) throws IOException",
"startLine": 349
"startLine": 359
},
{
"name": "loadIndex",
"signature": "private Map<String, Object> loadIndex() throws IOException",
"startLine": 370
"startLine": 382
},
{
"name": "saveIndex",
"signature": "private void saveIndex(Map<String, Object> index) throws IOException",
"startLine": 374
"startLine": 386
},
{
"name": "findWorktree",
"signature": "private Map<String, Object> findWorktree(String name) throws IOException",
"startLine": 379
"startLine": 391
},
{
"name": "validateName",
"signature": "private void validateName(String name)",
"startLine": 387
"startLine": 399
},
{
"name": "create",
"signature": "public String create(String name, Integer taskId, String baseRef)",
"startLine": 397
"startLine": 409
},
{
"name": "listAll",
"signature": "public String listAll()",
"startLine": 447
"startLine": 459
},
{
"name": "status",
"signature": "public String status(String name)",
"startLine": 469
"startLine": 483
},
{
"name": "run",
"signature": "public String run(String name, String command)",
"startLine": 491
"startLine": 507
},
{
"name": "remove",
"signature": "public String remove(String name, boolean force, boolean completeTask)",
"startLine": 531
"startLine": 549
},
{
"name": "keep",
"signature": "public String keep(String name)",
"startLine": 580
"startLine": 598
},
{
"name": "isGitAvailable",
"signature": "public boolean isGitAvailable() { return gitAvailable; }",
"startLine": 611
"startLine": 629
},
{
"name": "maxId",
"signature": "private int maxId()",
"startLine": 651
"startLine": 669
},
{
"name": "taskPath",
"signature": "private Path taskPath(int taskId)",
"startLine": 662
"startLine": 680
},
{
"name": "load",
"signature": "private Map<String, Object> load(int taskId) throws IOException",
"startLine": 667
"startLine": 685
},
{
"name": "save",
"signature": "private void save(Map<String, Object> task) throws IOException",
"startLine": 673
"startLine": 691
},
{
"name": "exists",
"signature": "public boolean exists(int taskId)",
"startLine": 678
"startLine": 696
},
{
"name": "create",
"signature": "public synchronized String create(String subject, String description)",
"startLine": 682
"startLine": 700
},
{
"name": "get",
"signature": "public String get(int taskId)",
"startLine": 705
"startLine": 727
},
{
"name": "update",
"signature": "public String update(int taskId, String status, String owner)",
"startLine": 713
"startLine": 735
},
{
"name": "bindWorktree",
"signature": "public String bindWorktree(int taskId, String worktree, String owner)",
"startLine": 736
"startLine": 762
},
{
"name": "unbindWorktree",
"signature": "public String unbindWorktree(int taskId)",
"startLine": 752
"startLine": 780
},
{
"name": "listAll",
"signature": "public String listAll()",
"startLine": 767
"startLine": 797
}
],
"layer": "collaboration",
"source": "// === S12WorktreeIsolation.java ===\npackage io.mybatis.learn.s12;\r\n\r\nimport io.mybatis.learn.core.AgentRunner;\r\nimport io.mybatis.learn.core.tools.*;\r\nimport org.springframework.ai.chat.client.ChatClient;\r\nimport org.springframework.ai.chat.model.ChatModel;\r\nimport org.springframework.ai.tool.annotation.Tool;\r\nimport org.springframework.ai.tool.annotation.ToolParam;\r\nimport org.springframework.beans.factory.annotation.Autowired;\r\nimport org.springframework.boot.CommandLineRunner;\r\nimport org.springframework.boot.SpringApplication;\r\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\r\n\r\nimport java.nio.file.Path;\r\n\r\n/**\r\n * S12 - Worktree + Task Isolation:目录级隔离的并行任务执行\r\n *\r\n * TIPS: 对应Python s12_worktree_task_isolation.py。\r\n * 与S09-S11的团队通信路线不同,S12走「目录隔离」路线:\r\n * 任务(控制平面)+ Worktree(执行平面)。\r\n *\r\n * S09-S11: 多个agent → 消息队列通信 → 协议协调\r\n * S12: 单个agent → 多个worktree → 按目录隔离 → 按task ID协调\r\n *\r\n * 17个工具:base(4) + task(5) + worktree(8)\r\n *\r\n * 关键洞察:\"Isolate by directory, coordinate by task ID.\"\r\n */\r\n@SpringBootApplication(scanBasePackages = \"io.mybatis.learn.core\")\r\npublic class S12WorktreeIsolation implements CommandLineRunner {\r\n\r\n @Autowired\r\n private ChatModel chatModel;\r\n\r\n @Override\r\n public void run(String... args) throws Exception {\r\n Path workDir = Path.of(System.getProperty(\"user.dir\"));\r\n\r\n // 检测git仓库根目录\r\n Path repoRoot = detectRepoRoot(workDir);\r\n if (repoRoot == null) repoRoot = workDir;\r\n\r\n WorktreeTaskManager tasks = new WorktreeTaskManager(repoRoot.resolve(\".tasks\"));\r\n EventBus events = new EventBus(repoRoot.resolve(\".worktrees\").resolve(\"events.jsonl\"));\r\n WorktreeManager worktrees = new WorktreeManager(repoRoot, tasks, events);\r\n\r\n TaskTools taskTools = new TaskTools(tasks);\r\n WorktreeTools wtTools = new WorktreeTools(worktrees, events);\r\n\r\n String systemPrompt = \"You are a coding agent at \" + workDir + \". \"\r\n + \"Use task + worktree tools for multi-task work. \"\r\n + \"For parallel or risky changes: create tasks, allocate worktree lanes, \"\r\n + \"run commands in those lanes, then choose keep/remove for closeout. \"\r\n + \"Use worktree_events when you need lifecycle visibility.\";\r\n\r\n ChatClient chatClient = ChatClient.builder(chatModel)\r\n .defaultSystem(systemPrompt)\r\n .defaultTools(new BashTool(), new ReadFileTool(),\r\n new WriteFileTool(), new EditFileTool(),\r\n taskTools, wtTools)\r\n .build();\r\n\r\n System.out.println(\"Repo root for s12: \" + repoRoot);\r\n if (!worktrees.isGitAvailable()) {\r\n System.out.println(\"Note: Not in a git repo. worktree_* tools will return errors.\");\r\n }\r\n\r\n AgentRunner.interactive(\"s12\", input ->\r\n chatClient.prompt(input).call().content());\r\n }\r\n\r\n private static Path detectRepoRoot(Path cwd) {\r\n try {\r\n ProcessBuilder pb = new ProcessBuilder(\"git\", \"rev-parse\", \"--show-toplevel\");\r\n pb.directory(cwd.toFile());\r\n pb.redirectErrorStream(true);\r\n Process p = pb.start();\r\n String out = new String(p.getInputStream().readAllBytes()).trim();\r\n if (p.waitFor() == 0 && !out.isEmpty()) {\r\n Path root = Path.of(out);\r\n return root.toFile().exists() ? root : null;\r\n }\r\n } catch (Exception e) { /* ignore */ }\r\n return null;\r\n }\r\n\r\n // ---- 任务工具集(5个) ----\r\n\r\n public static class TaskTools {\r\n private final WorktreeTaskManager tasks;\r\n\r\n public TaskTools(WorktreeTaskManager tasks) { this.tasks = tasks; }\r\n\r\n @Tool(description = \"Create a new task on the shared task board\")\r\n public String taskCreate(\r\n @ToolParam(description = \"Task subject\") String subject,\r\n @ToolParam(description = \"Task description\") String description) {\r\n return tasks.create(subject, description);\r\n }\r\n\r\n @Tool(description = \"List all tasks with status, owner, and worktree binding\")\r\n public String taskList() { return tasks.listAll(); }\r\n\r\n @Tool(description = \"Get task details by ID\")\r\n public String taskGet(@ToolParam(description = \"Task ID\") int taskId) {\r\n return tasks.get(taskId);\r\n }\r\n\r\n @Tool(description = \"Update task status or owner\")\r\n public StLine truncated
"source": "// === S12WorktreeIsolation.java ===\npackage io.mybatis.learn.s12;\r\n\r\nimport io.mybatis.learn.core.AgentRunner;\r\nimport io.mybatis.learn.core.tools.*;\r\nimport org.springframework.ai.chat.client.ChatClient;\r\nimport org.springframework.ai.chat.model.ChatModel;\r\nimport org.springframework.ai.tool.annotation.Tool;\r\nimport org.springframework.ai.tool.annotation.ToolParam;\r\nimport org.springframework.beans.factory.annotation.Autowired;\r\nimport org.springframework.boot.CommandLineRunner;\r\nimport org.springframework.boot.SpringApplication;\r\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\r\n\r\nimport java.nio.file.Path;\r\n\r\n/**\r\n * S12 - Worktree + Task Isolation:目录级隔离的并行任务执行\r\n *\r\n * TIPS: 对应Python s12_worktree_task_isolation.py。\r\n * 与S09-S11的团队通信路线不同,S12走「目录隔离」路线:\r\n * 任务(控制平面)+ Worktree(执行平面)。\r\n *\r\n * S09-S11: 多个agent → 消息队列通信 → 协议协调\r\n * S12: 单个agent → 多个worktree → 按目录隔离 → 按task ID协调\r\n *\r\n * 17个工具:base(4) + task(5) + worktree(8)\r\n *\r\n * 关键洞察:\"Isolate by directory, coordinate by task ID.\"\r\n */\r\n@SpringBootApplication(scanBasePackages = \"io.mybatis.learn.core\")\r\npublic class S12WorktreeIsolation implements CommandLineRunner {\r\n\r\n @Autowired\r\n private ChatModel chatModel;\r\n\r\n @Override\r\n public void run(String... args) throws Exception {\r\n Path workDir = Path.of(System.getProperty(\"user.dir\"));\r\n\r\n // 检测git仓库根目录\r\n Path repoRoot = detectRepoRoot(workDir);\r\n if (repoRoot == null) repoRoot = workDir;\r\n\r\n WorktreeTaskManager tasks = new WorktreeTaskManager(repoRoot.resolve(\".tasks\"));\r\n EventBus events = new EventBus(repoRoot.resolve(\".worktrees\").resolve(\"events.jsonl\"));\r\n WorktreeManager worktrees = new WorktreeManager(repoRoot, tasks, events);\r\n\r\n TaskTools taskTools = new TaskTools(tasks);\r\n WorktreeTools wtTools = new WorktreeTools(worktrees, events);\r\n\r\n String systemPrompt = \"You are a coding agent at \" + workDir + \". \"\r\n + \"Use task + worktree tools for multi-task work. \"\r\n + \"For parallel or risky changes: create tasks, allocate worktree lanes, \"\r\n + \"run commands in those lanes, then choose keep/remove for closeout. \"\r\n + \"Use worktree_events when you need lifecycle visibility.\";\r\n\r\n ChatClient chatClient = ChatClient.builder(chatModel)\r\n .defaultSystem(systemPrompt)\r\n .defaultTools(new BashTool(), new ReadFileTool(),\r\n new WriteFileTool(), new EditFileTool(),\r\n taskTools, wtTools)\r\n .build();\r\n\r\n System.out.println(\"Repo root for s12: \" + repoRoot);\r\n if (!worktrees.isGitAvailable()) {\r\n System.out.println(\"Note: Not in a git repo. worktree_* tools will return errors.\");\r\n }\r\n\r\n AgentRunner.interactive(\"s12\", input ->\r\n chatClient.prompt(input).call().content());\r\n }\r\n\r\n private static Path detectRepoRoot(Path cwd) {\r\n try {\r\n ProcessBuilder pb = new ProcessBuilder(\"git\", \"rev-parse\", \"--show-toplevel\");\r\n pb.directory(cwd.toFile());\r\n pb.redirectErrorStream(true);\r\n Process p = pb.start();\r\n String out = new String(p.getInputStream().readAllBytes()).trim();\r\n if (p.waitFor() == 0 && !out.isEmpty()) {\r\n Path root = Path.of(out);\r\n return root.toFile().exists() ? root : null;\r\n }\r\n } catch (Exception e) { /* ignore */ }\r\n return null;\r\n }\r\n\r\n // ---- 任务工具集(5个) ----\r\n\r\n public static class TaskTools {\r\n private final WorktreeTaskManager tasks;\r\n\r\n public TaskTools(WorktreeTaskManager tasks) { this.tasks = tasks; }\r\n\r\n @Tool(description = \"Create a new task on the shared task board\")\r\n public String taskCreate(\r\n @ToolParam(description = \"Task subject\") String subject,\r\n @ToolParam(description = \"Task description\") String description) {\r\n return tasks.create(subject, description);\r\n }\r\n\r\n @Tool(description = \"List all tasks with status, owner, and worktree binding\")\r\n public String taskList() { return tasks.listAll(); }\r\n\r\n @Tool(description = \"Get task details by ID\")\r\n public String taskGet(@ToolParam(description = \"Task ID\") int taskId) {\r\n return tasks.get(taskId);\r\n }\r\n\r\n @Tool(description = \"Update task status or owner\")\r\n public StLine truncated
},
{
"id": "full",
@@ -1676,7 +1676,7 @@
"newTools": [
"task"
],
"locDelta": -28
"locDelta": -24
},
{
"from": "s04",
@@ -1696,7 +1696,7 @@
"newTools": [
"loadSkill"
],
"locDelta": 38
"locDelta": 34
},
{
"from": "s05",
@@ -1747,7 +1747,7 @@
"taskUpdate",
"taskList"
],
"locDelta": 80
"locDelta": 98
},
{
"from": "s07",
@@ -1769,7 +1769,7 @@
"backgroundRun",
"checkBackground"
],
"locDelta": -77
"locDelta": -87
},
{
"from": "s08",
@@ -1806,7 +1806,7 @@
"sendMessage",
"readInbox"
],
"locDelta": 97
"locDelta": 103
},
{
"from": "s09",
@@ -1836,7 +1836,7 @@
"shutdownResponse",
"planApproval"
],
"locDelta": 35
"locDelta": 23
},
{
"from": "s10",
@@ -1864,7 +1864,7 @@
"main",
"main"
],
"locDelta": 7
"locDelta": 5
},
{
"from": "s11",
@@ -1930,7 +1930,7 @@
"worktreeKeep",
"worktreeEvents"
],
"locDelta": 324
"locDelta": 354
}
]
}