【从0到1构建一个ClaudeAgent】规划与协调-TodoWrite

这段代码引入了一个非常关键的概念:"自我反思与状态管理"

之前的 Agent 只是单纯的"听指令 -> 干活",容易干着干着就忘了初衷,或者在复杂的任务中迷失方向。TodoManager 就像是给 Agent 装了一个"记事本"和"监工"。

Java 实现代码

java 复制代码
public class AgentWithTodo {
    private static final Path WORKDIR = Paths.get(System.getProperty("user.dir"));
    
    // --- 1. 状态管理:TodoManager ---
    
    // 任务状态枚举
    public enum TaskStatus {
        PENDING("pending"), IN_PROGRESS("in_progress"), COMPLETED("completed");
        public final String label;
        TaskStatus(String label) { this.label = label; }
        public static TaskStatus fromLabel(String s) {
            for (TaskStatus ts : values()) if (ts.label.equals(s)) return ts;
            return PENDING;
        }
    }

    // 任务实体
    public static class TodoItem {
        public String id;
        public String text;
        public TaskStatus status;
        public TodoItem(String id, String text, String status) {
            this.id = id; this.text = text; this.status = TaskStatus.fromLabel(status);
        }
    }

    // 管理器类
    public static class TodoManager {
        private List<TodoItem> items = new ArrayList<>();

        public String update(List<Map<String, Object>> newItems) throws Exception {
            if (newItems.size() > 20) throw new Exception("Max 20 todos allowed");
            List<TodoItem> validated = new ArrayList<>();
            int inProgressCount = 0;

            for (int i = 0; i < newItems.size(); i++) {
                Map<String, Object> item = newItems.get(i);
                String text = (String) item.getOrDefault("text", "");
                String statusStr = (String) item.getOrDefault("status", "pending");
                String id = String.valueOf(item.getOrDefault("id", String.valueOf(i + 1)));

                if (text.trim().isEmpty()) throw new Exception("Item " + id + ": text required");
                
                TaskStatus status = TaskStatus.fromLabel(statusStr.toLowerCase());
                if (status == TaskStatus.IN_PROGRESS) inProgressCount++;

                validated.add(new TodoItem(id, text.trim(), status.label));
            }

            if (inProgressCount > 1) throw new Exception("Only one task can be in_progress at a time");
            this.items = validated;
            return render();
        }

        public String render() {
            if (items.isEmpty()) return "No todos.";
            StringBuilder sb = new StringBuilder();
            for (TodoItem item : items) {
                String marker = item.status == TaskStatus.PENDING ? "[ ]" :
                                item.status == TaskStatus.IN_PROGRESS ? "[>]" : "[x]";
                sb.append(String.format("%s #%s: %s%n", marker, item.id, item.text));
            }
            long done = items.stream().filter(i -> i.status == TaskStatus.COMPLETED).count();
            sb.append(String.format("%n(%d/%d completed)", done, items.size()));
            return sb.toString();
        }
    }

    private static final TodoManager TODO_MANAGER = new TodoManager();

    // --- 2. 工具定义与分发 ---
    public enum ToolType {
        BASH("bash"), READ_FILE("read_file"), WRITE_FILE("write_file"), 
        EDIT_FILE("edit_file"), TODO("todo");  // 新增 todo 工具
        public final String name;
        ToolType(String name) { this.name = name; }
    }

    private static final Map<String, ToolExecutor> TOOL_HANDLERS = new HashMap<>();

    static {
        // ... 省略已有的工具注册
        
        // 注册 Todo 工具
        TOOL_HANDLERS.put(ToolType.TODO.name, args -> {
            @SuppressWarnings("unchecked")
            List<Map<String, Object>> items = (List<Map<String, Object>>) args.get("items");
            return TODO_MANAGER.update(items);
        });
    }

    // --- 3. 核心循环 ---
    public static void agentLoop(List<Map<String, Object>> messages) {
        int roundsSinceTodo = 0;  // 新增:跟踪轮数

        while (true) {
            // ... 省略相同的 LLM 调用、消息追加、停止检查逻辑
            
            // 3. 执行工具
            List<Map<String, Object>> toolResults = new ArrayList<>();
            List<Map<String, Object>> content = (List<Map<String, Object>>) response.get("content");
            boolean usedTodo = false;  // 新增:标记是否使用了 todo 工具

            for (Map<String, Object> block : content) {
                if ("tool_use".equals(block.get("type"))) {
                    // ... 省略相同的工具调用逻辑
                    String toolName = (String) block.get("name");
                    // ... 执行工具
                    
                    if (toolName.equals("todo")) usedTodo = true;  // 标记 todo 使用
                }
            }

            // 4. 监工逻辑 (Nag Reminder)
            roundsSinceTodo = usedTodo ? 0 : roundsSinceTodo + 1;
            
            if (roundsSinceTodo >= 3) {  // 关键:超过3轮没更新就提醒
                Map<String, Object> nag = new HashMap<>();
                nag.put("type", "text");
                nag.put("text", "<reminder>Update your todos.</reminder>");
                toolResults.add(0, nag); // 插入到结果列表最前面
                System.out.println(">>> 监工提醒:更新待办列表!");
            }

            // 5. 回传结果
            // ... 省略相同的回传逻辑
        }
    }

    // --- 4. 工具实现 (简化版) ---
    // ... 省略已有的工具实现
}

状态管理:TodoManager 类

为Agent引入长期记忆和工作进度追踪能力,让Agent能"记住"自己的任务列表和工作状态。

java 复制代码
// 任务状态枚举
public enum TaskStatus {
    PENDING("pending"),
    IN_PROGRESS("in_progress"),
    COMPLETED("completed");
    // 状态枚举:明确定义三种状态
    // 状态驱动:Agent根据状态决定下一步操作
}
java 复制代码
// 任务实体 - 数据结构
public static class TodoItem {
    public String id;          // 唯一标识
    public String text;        // 任务描述
    public TaskStatus status;  // 状态
    // 结构化的任务表示
    // 为LLM提供清晰的上下文
}
java 复制代码
// TodoManager - 核心状态管理
public class TodoManager {
    private List<TodoItem> items = new ArrayList<>();  // 状态存储

    public String update(List<Map<String, Object>> newItems) throws Exception {
        if (newItems.size() > 20) throw new Exception("Max 20 todos allowed");
        // 业务规则1:限制任务数量,防止滥用
        
        int inProgressCount = 0;
        List<TodoItem> validated = new ArrayList<>();

        for (int i = 0; i < newItems.size(); i++) {
            Map<String, Object> item = newItems.get(i);
            String text = (String) item.getOrDefault("text", "");
            String statusStr = (String) item.getOrDefault("status", "pending");
            String id = String.valueOf(item.getOrDefault("id", String.valueOf(i + 1)));
            
            if (text.trim().isEmpty()) throw new Exception("Item " + id + ": text required");
            // 业务规则2:任务文本必填
            
            TaskStatus status = TaskStatus.fromLabel(statusStr.toLowerCase());
            if (status == TaskStatus.IN_PROGRESS) inProgressCount++;
            // 业务规则3:跟踪进行中任务数量
        }

        if (inProgressCount > 1) throw new Exception("Only one task can be in_progress at a time");
        // 业务规则4:一次只能进行一个任务,聚焦执行
        
        this.items = validated;  // 原子性更新
        return render();  // 返回可视化表示
    }

    public String render() {
        if (items.isEmpty()) return "No todos.";
        StringBuilder sb = new StringBuilder();
        for (TodoItem item : items) {
            String marker = item.status == TaskStatus.PENDING ? "[ ]" :
                           item.status == TaskStatus.IN_PROGRESS ? "[>]" : "[x]";
            sb.append(String.format("%s #%s: %s%n", marker, item.id, item.text));
            // 可视化格式:[ ] 待办, [>] 进行中, [x] 已完成
        }
        
        long done = items.stream().filter(i -> i.status == TaskStatus.COMPLETED).count();
        sb.append(String.format("%n(%d/%d completed)", done, items.size()));
        // 进度统计:为LLM提供进度反馈
        return sb.toString();
    }
}
  • 状态持久化:Agent有了"记忆",不再是完全无状态的
  • 结构化表示:用面向对象的方式管理任务状态
  • 业务约束:通过校验规则确保状态一致性
  • 可视化输出:为LLM提供人类可读的进度展示

Todo工具集成

java 复制代码
// 在工具枚举中新增
TODO("todo");  // 扩展工具集,添加状态管理工具

// 注册Todo工具实现
TOOL_HANDLERS.put(ToolType.TODO.name, args -> {
    @SuppressWarnings("unchecked")
    List<Map<String, Object>> items = (List<Map<String, Object>>) args.get("items");
    return TODO_MANAGER.update(items);
    // 状态更新工具:让LLM能操作任务状态
    // 接受LLM传入的任务列表,更新内部状态
});
  • 状态操作作为工具:将状态管理抽象为工具调用
  • 双向通信:LLM可以通过工具更新状态,也能获取状态
  • 统一接口:与其他工具使用相同的调用模式

监工逻辑(Nag Reminder)

java 复制代码
// 在agentLoop中新增
int roundsSinceTodo = 0;  // 计数器:记录多少轮没使用todo工具
boolean usedTodo = false; // 标记当前轮是否使用了todo

// 执行工具时记录
if (toolName.equals("todo")) usedTodo = true;

// 每轮结束后的监工检查
roundsSinceTodo = usedTodo ? 0 : roundsSinceTodo + 1;  // 重置或递增

if (roundsSinceTodo >= 3) {  // 如果超过3轮没更新待办
    Map<String, Object> nag = new HashMap<>();
    nag.put("type", "text");
    nag.put("text", "<reminder>Update your todos.</reminder>");
    toolResults.add(0, nag); // 插入到结果列表最前面
    
    System.out.println(">>> 监工提醒:更新待办列表!");
    // 强制提醒:防止LLM忘记更新状态
}
  • 防遗忘机制:LLM可能会忘记更新状态,需要外部提醒
  • 渐进式提醒:容忍短期遗忘,超过阈值再干预
  • 结构化提示 :使用特殊标签<reminder>,让LLM识别这是系统提示
  • 优先级:插入到结果列表最前面,确保LLM先看到

架构演进与价值

从 AgentWithTools 到 AgentWithTodo 的升级

维度 AgentWithTools AgentWithTodo
状态管理 无状态 有状态(TodoManager)
进度追踪 不支持 支持任务进度管理
长期记忆 不支持 支持任务列表记忆
监督机制 有监工提醒
任务管理 工具级 项目级
相关推荐
Yeh2020582 小时前
maven
java·maven
色空大师2 小时前
【java打包方式详解】
java·开发语言·部署·打包·启动脚本·jar包分离
人道领域2 小时前
2026年Java后端热点全景解析:从LTS革新到云原生跃迁
java·开发语言
鱼鳞_2 小时前
Java学习笔记_Day26(不可变集合)
java·笔记·学习
zhaoyufei1332 小时前
RK3566 EDP屏幕背光闪修改pwm
android·java
清心歌2 小时前
HashMap实现原理及扩容机制
java
一只大袋鼠2 小时前
数据库连接池从入门到精通(下):Druid 连接池使用与工具类封装
java·数据库·连接池
禹中一只鱼2 小时前
【IDEA 出现 `IDE error occurred`】
java·ide·spring boot·intellij-idea
西凉的悲伤2 小时前
Guava类库——Lists.partition() 高效分批处理列表数据
java·guava