包含从项目创建至今的全部代码首次入库: 核心框架: - TaskQueue(SQLite,6 种状态,原子 dequeue) - Executor(asyncio 并发,semaphore 限流,agent_role 路由) - Scheduler(APScheduler,三队列独立调度) - Watchdog(超时检测 + 优先级防饥饿) - Scanner(项目扫描,README/TODO/CLAUDE.md 提取) Agent 体系(20+ 角色): - 项目交付组:architect/developer/tester/productizer - 基础技术组:base-architect/validator/hw/os/kernel/lowlevel/system-tester - 算法组:algo-antishake/position/nav - 洞察组:planner/vision-analyst/media-producer - 市场组:market-pm/sport/elder/safety - 组织层:boss/group-leader/senior-dev/ops - 平台扩展:nrf-dev/esp32-dev - 三专项调研:rtp-researcher/net-researcher/kernel-analyzer 工具层: - ProjectMemory(goals/facts/history SQLite) - VersionPipeline(并行版本流水线 + 反幻觉门控) - EventBus(Redis Streams,5 consumer groups) - DailyReport(append-only 日报 + Boss 决策视图) - AgentScorer(4 维评分:完成度/质量/自主性/协作) - DeviceAgent(SSH rsync + 板端执行) - EnvCollector(本机+SSH 环境采集) - ArchVersion(快照/优化/promote/rollback) Dashboard(React + Vite + Tailwind): - 看板/项目/配置/洞察/日报/Visual 六标签页 - WebSocket 实时更新 - Markdown 渲染(react-markdown + remark-gfm) 测试:314 passed Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
34 KiB
Project Generalization & Memory Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: 消除 agent 提示词中的平台硬编码,建立分层项目记忆(SQLite facts/history + 向量后端接口),使 agents 每次处理同一项目时能读取并利用历史记忆。
Architecture: ProjectMemory 扩展两张新表(project_facts / project_history);MemoryBackend Protocol 预留向量后端接口;DeveloperAgent 和 ArchitectAgent 移除 Rockchip 硬编码,改为动态读取记忆;Executor 在任务完成后自动写入 history;Architect 通过 [记忆] 标签写入 facts。
Tech Stack: Python 3.10, SQLite, dataclasses, Protocol (typing), pytest
Task 1: 扩展 ProjectMemory — facts + history 表
Files:
- Modify:
src/rockchip_agents/tools/project_memory.py - Test:
tests/test_project_memory.py
背景: 当前 ProjectMemory 只有 project_goals 一张表。需要新增:
project_facts:key-value 事实知识库,任意 agent 可写(无角色限制)project_history:任务完成记录,executor 自动写入
Step 1: 写失败测试
在 tests/test_project_memory.py 末尾追加:
def test_set_and_get_fact(tmp_path):
mem = _mem(tmp_path)
mem.set_fact("yolo", "test_cmd", "pytest tests/", role="developer")
facts = mem.get_facts("yolo")
assert facts["test_cmd"] == "pytest tests/"
def test_set_fact_overwrites_existing(tmp_path):
mem = _mem(tmp_path)
mem.set_fact("yolo", "lang", "Python", role="developer")
mem.set_fact("yolo", "lang", "Python/C++", role="architect")
assert mem.get_facts("yolo")["lang"] == "Python/C++"
def test_get_facts_returns_empty_when_absent(tmp_path):
mem = _mem(tmp_path)
assert mem.get_facts("nonexistent") == {}
def test_add_and_get_history(tmp_path):
mem = _mem(tmp_path)
mem.add_history("yolo", "修复零检出", "fix_bug", "done", "已修复推理配置")
history = mem.get_recent_history("yolo")
assert len(history) == 1
assert history[0]["task_title"] == "修复零检出"
assert history[0]["status"] == "done"
def test_history_limit(tmp_path):
mem = _mem(tmp_path)
for i in range(8):
mem.add_history("yolo", f"任务{i}", "code_review", "done", f"摘要{i}")
history = mem.get_recent_history("yolo", limit=5)
assert len(history) == 5
# 最新的在前
assert history[0]["task_title"] == "任务7"
def test_context_includes_facts_and_history(tmp_path):
mem = _mem(tmp_path)
mem.set_goal("yolo", "实时目标检测", role="productizer")
mem.set_fact("yolo", "lang", "Python", role="developer")
mem.add_history("yolo", "修复零检出", "fix_bug", "done", "摘要")
ctx = mem.get_context_for_prompt("yolo")
assert "项目长期目标" in ctx
assert "lang" in ctx
assert "Python" in ctx
assert "近期任务" in ctx
assert "修复零检出" in ctx
Step 2: 运行验证测试失败
cd /data/rockchip/agents && source venv/bin/activate
pytest tests/test_project_memory.py::test_set_and_get_fact -v
预期: FAIL — AttributeError: 'ProjectMemory' object has no attribute 'set_fact'
Step 3: 实现新方法
完整替换 src/rockchip_agents/tools/project_memory.py:
from __future__ import annotations
import sqlite3
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Optional, Protocol, runtime_checkable
DEFAULT_DB_PATH = Path(__file__).parent.parent.parent.parent / "data" / "project_memory.db"
WRITE_ROLES: frozenset[str] = frozenset({"productizer", "architect"})
@runtime_checkable
class MemoryBackend(Protocol):
"""Layer-2 vector backend interface. Implement to enable semantic search."""
def search(self, query: str, project: str, top_k: int = 5) -> list[str]: ...
def upsert(self, project: str, key: str, text: str) -> None: ...
@dataclass
class ProjectGoal:
project: str
goal_text: str
updated_by: str
updated_at: str
class ProjectMemory:
"""Persistent project knowledge store.
Layer 1 — SQLite: goals, facts (key-value), task history.
Layer 2 — Optional MemoryBackend: vector semantic search (future).
Facts can be written by any agent; goals only by WRITE_ROLES.
"""
def __init__(
self,
db_path: Path | None = None,
vector_backend: Optional[MemoryBackend] = None,
) -> None:
self._db_path = db_path or DEFAULT_DB_PATH
self._db_path.parent.mkdir(parents=True, exist_ok=True)
self._vector = vector_backend
self._init_db()
# ── goals (write-restricted) ──────────────────────────────────────────────
def get_goal(self, project: str) -> Optional[ProjectGoal]:
conn = self._conn()
try:
row = conn.execute(
"SELECT project, goal_text, updated_by, updated_at "
"FROM project_goals WHERE project = ?",
(project,),
).fetchone()
return ProjectGoal(**dict(row)) if row else None
finally:
conn.close()
def set_goal(self, project: str, goal_text: str, role: str) -> None:
if role not in WRITE_ROLES:
raise PermissionError(f"角色 {role!r} 无权修改项目目标(需要 {WRITE_ROLES})")
conn = self._conn()
try:
conn.execute(
"INSERT OR REPLACE INTO project_goals "
"(project, goal_text, updated_by, updated_at) VALUES (?, ?, ?, ?)",
(project, goal_text, role, datetime.now().isoformat()),
)
conn.commit()
finally:
conn.close()
def list_goals(self) -> dict[str, ProjectGoal]:
conn = self._conn()
try:
rows = conn.execute(
"SELECT project, goal_text, updated_by, updated_at FROM project_goals"
).fetchall()
return {r["project"]: ProjectGoal(**dict(r)) for r in rows}
finally:
conn.close()
# ── facts (open write) ────────────────────────────────────────────────────
def set_fact(self, project: str, key: str, value: str, role: str) -> None:
"""Store a project fact. Any agent role may write."""
conn = self._conn()
try:
conn.execute(
"INSERT OR REPLACE INTO project_facts "
"(project, key, value, updated_by, updated_at) VALUES (?, ?, ?, ?, ?)",
(project, key, value, role, datetime.now().isoformat()),
)
conn.commit()
if self._vector is not None:
self._vector.upsert(project, key, f"{key}: {value}")
finally:
conn.close()
def get_facts(self, project: str) -> dict[str, str]:
conn = self._conn()
try:
rows = conn.execute(
"SELECT key, value FROM project_facts WHERE project = ? ORDER BY key",
(project,),
).fetchall()
return {r["key"]: r["value"] for r in rows}
finally:
conn.close()
# ── history (auto-written by executor) ───────────────────────────────────
def add_history(
self,
project: str,
task_title: str,
task_type: str,
status: str,
summary: str,
) -> None:
conn = self._conn()
try:
conn.execute(
"INSERT INTO project_history "
"(project, task_title, task_type, status, summary, created_at) "
"VALUES (?, ?, ?, ?, ?, ?)",
(
project,
task_title[:200],
task_type,
status,
summary[:500],
datetime.now().isoformat(),
),
)
conn.commit()
finally:
conn.close()
def get_recent_history(self, project: str, limit: int = 5) -> list[dict]:
conn = self._conn()
try:
rows = conn.execute(
"SELECT task_title, task_type, status, summary, created_at "
"FROM project_history WHERE project = ? "
"ORDER BY id DESC LIMIT ?",
(project, limit),
).fetchall()
return [dict(r) for r in rows]
finally:
conn.close()
# ── composite context builder ─────────────────────────────────────────────
def get_context_for_prompt(self, project: str) -> str:
"""Build a context block to inject into agent system prompts.
Includes goal, facts, and recent task history. If a vector backend
is configured, appends semantic search results too.
"""
parts: list[str] = []
goal = self.get_goal(project)
if goal:
parts.append(
f"【项目长期目标(由 {goal.updated_by} 设定,{goal.updated_at[:10]})】\n"
f"{goal.goal_text}"
)
facts = self.get_facts(project)
if facts:
facts_lines = "\n".join(f" {k}: {v}" for k, v in facts.items())
parts.append(f"【项目知识库】\n{facts_lines}")
history = self.get_recent_history(project)
if history:
hist_lines = "\n".join(
f" [{h['status']}] {h['task_title'][:60]}" for h in history
)
parts.append(f"【近期任务记录(最近 {len(history)} 条)】\n{hist_lines}")
if self._vector is not None:
extra = self._vector.search(project, project, top_k=3)
if extra:
parts.append("【语义检索相关记忆】\n" + "\n".join(f" {e}" for e in extra))
return "\n\n".join(parts) + "\n" if parts else ""
# ── db helpers ────────────────────────────────────────────────────────────
def _init_db(self) -> None:
conn = self._conn()
try:
conn.executescript("""
CREATE TABLE IF NOT EXISTS project_goals (
project TEXT PRIMARY KEY,
goal_text TEXT NOT NULL,
updated_by TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS project_facts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project TEXT NOT NULL,
key TEXT NOT NULL,
value TEXT NOT NULL,
updated_by TEXT NOT NULL,
updated_at TEXT NOT NULL,
UNIQUE(project, key)
);
CREATE TABLE IF NOT EXISTS project_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project TEXT NOT NULL,
task_title TEXT NOT NULL,
task_type TEXT NOT NULL DEFAULT '',
status TEXT NOT NULL,
summary TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL
);
""")
conn.commit()
finally:
conn.close()
def _conn(self) -> sqlite3.Connection:
conn = sqlite3.connect(str(self._db_path))
conn.row_factory = sqlite3.Row
return conn
Step 4: 运行所有新测试
pytest tests/test_project_memory.py -v
预期: 全部 PASS(原有 10 个 + 新增 6 个 = 16 个)
Step 5: 运行全量测试确认无回归
pytest tests/ -q
预期: 73 passed(数量与之前一致)
Task 2: DeveloperAgent 去硬编码,注入记忆
Files:
- Modify:
src/rockchip_agents/agents/developer.py - Test:
tests/test_developer.py
背景: _build_context() 在 line 95 硬编码了 "Rockchip RKNN NPU 推理项目";且 DeveloperAgent 没有读取 ProjectMemory。修改后,context 从项目的 description 字段和 ProjectMemory 动态构建。
Step 1: 写失败测试
读取 tests/test_developer.py 找到现有测试的结构,然后追加:
def test_developer_context_uses_description_not_hardcoded(tmp_path):
"""_build_context must not mention 'Rockchip RKNN' when description differs."""
from rockchip_agents.config import AgentsConfig, ProjectConfig, SchedulerConfig
from rockchip_agents.config import ClaudeConfig, FeishuConfig
from rockchip_agents.agents.developer import DeveloperAgent
from rockchip_agents.core.queue import Task
cfg = AgentsConfig(
projects={"myproj": ProjectConfig(
path=tmp_path, mode="report",
description="通用 Web 后端服务"
)},
scheduler=SchedulerConfig(),
claude=ClaudeConfig(model="claude-sonnet-4-6"),
feishu=FeishuConfig(),
devices={},
)
agent = DeveloperAgent(cfg)
task = Task(project="myproj", type="fix_bug", title="修复登录",
context="", priority=2, mode="report")
ctx = agent._build_context(task)
assert "通用 Web 后端服务" in ctx
assert "Rockchip RKNN" not in ctx
assert "myproj" in ctx
def test_developer_context_includes_memory(tmp_path):
"""_build_context includes ProjectMemory context when available."""
from rockchip_agents.config import AgentsConfig, ProjectConfig, SchedulerConfig
from rockchip_agents.config import ClaudeConfig, FeishuConfig
from rockchip_agents.agents.developer import DeveloperAgent
from rockchip_agents.tools.project_memory import ProjectMemory
from rockchip_agents.core.queue import Task
mem = ProjectMemory(db_path=tmp_path / "mem.db")
mem.set_goal("myproj", "稳定高性能后端", role="productizer")
mem.set_fact("myproj", "test_cmd", "pytest tests/", role="developer")
cfg = AgentsConfig(
projects={"myproj": ProjectConfig(path=tmp_path, mode="auto",
description="通用 Web 后端服务")},
scheduler=SchedulerConfig(),
claude=ClaudeConfig(model="claude-sonnet-4-6"),
feishu=FeishuConfig(),
devices={},
)
agent = DeveloperAgent(cfg, memory=mem)
task = Task(project="myproj", type="fix_bug", title="修复登录",
context="", priority=2, mode="auto")
ctx = agent._build_context(task)
assert "稳定高性能后端" in ctx
assert "test_cmd" in ctx
Step 2: 验证测试失败
pytest tests/test_developer.py::test_developer_context_uses_description_not_hardcoded -v
预期: FAIL — assert "Rockchip RKNN" not in ctx 失败(因为还没改)
Step 3: 修改 DeveloperAgent
完整替换 src/rockchip_agents/agents/developer.py:
from __future__ import annotations
import json
import logging
import os
import subprocess
from dataclasses import dataclass
from typing import Optional
from rockchip_agents.config import AgentsConfig
from rockchip_agents.core.queue import Task
from rockchip_agents.tools.project_memory import ProjectMemory
logger = logging.getLogger(__name__)
TIMEOUT = 300
@dataclass
class AgentResult:
status: str # done | failed
summary: str
def _parse_cli_result(r: subprocess.CompletedProcess) -> AgentResult:
"""解析 claude --output-format json 的输出。"""
if r.returncode != 0:
return AgentResult(status="failed", summary=r.stderr or r.stdout or "未知错误")
try:
data = json.loads(r.stdout)
summary = data.get("result", r.stdout)
is_error = data.get("is_error", False)
return AgentResult(status="failed" if is_error else "done", summary=summary)
except (json.JSONDecodeError, KeyError):
return AgentResult(status="done", summary=r.stdout)
class DeveloperAgent:
def __init__(
self,
config: AgentsConfig,
memory: Optional[ProjectMemory] = None,
) -> None:
self._config = config
self._memory = memory or ProjectMemory()
def run(self, task: Task) -> AgentResult:
proj_cfg = self._config.projects.get(task.project)
proj_path = str(proj_cfg.path) if proj_cfg else None
cmd = self._build_cmd(task)
env = self._build_env()
try:
r = subprocess.run(
cmd,
capture_output=True, text=True,
timeout=TIMEOUT,
cwd=proj_path,
env=env,
)
return _parse_cli_result(r)
except subprocess.TimeoutExpired:
return AgentResult(status="failed", summary=f"执行超时({TIMEOUT}s)")
except Exception as e:
logger.error("DeveloperAgent 执行失败: %s", e)
return AgentResult(status="failed", summary=str(e))
def _build_cmd(self, task: Task) -> list[str]:
cmd = [
"claude", "--print",
"--output-format", "json",
"--no-session-persistence",
"--model", self._config.claude.model,
"--append-system-prompt", self._build_context(task),
]
if task.mode == "report":
cmd += ["--allowedTools", "Read,Glob,Grep"]
elif task.mode == "confirm":
cmd += ["--allowedTools", "Read,Glob,Grep,Bash"]
else: # auto
cmd.append("--dangerously-skip-permissions")
cmd.append(self._build_prompt(task))
return cmd
def _build_env(self) -> dict:
env = os.environ.copy()
if self._config.claude.api_key:
env["ANTHROPIC_API_KEY"] = self._config.claude.api_key
env.pop("CLAUDECODE", None)
env.pop("CLAUDE_CODE_ENTRYPOINT", None)
return env
def _build_context(self, task: Task) -> str:
proj_cfg = self._config.projects.get(task.project)
proj_path = str(proj_cfg.path) if proj_cfg else ""
proj_desc = (proj_cfg.description if proj_cfg and proj_cfg.description
else task.project)
mode_desc = {
"auto": "直接修复文件并运行测试验证。",
"confirm": "只分析并给出修改方案摘要,不实际写文件。",
"report": "只读取和分析,输出详细报告,不做任何修改。",
}.get(task.mode, "")
memory_ctx = self._memory.get_context_for_prompt(task.project)
return (
f"你是 {task.project} 项目的 Developer Agent。\n"
f"项目路径: {proj_path}。\n"
f"项目描述: {proj_desc}。\n"
f"当前模式: {task.mode} — {mode_desc}\n"
f"{memory_ctx}"
f"请用中文回复,保持简洁,聚焦问题。"
)
def _build_prompt(self, task: Task) -> str:
return (
f"任务类型: {task.type}\n"
f"任务标题: {task.title}\n"
f"上下文:\n{task.context}\n\n"
f"请分析并处理这个任务。"
)
Step 4: 运行新测试
pytest tests/test_developer.py -v
预期: 全部 PASS
Step 5: 全量测试
pytest tests/ -q
预期: 所有测试通过
Task 3: ArchitectAgent 去硬编码 + [记忆] 标签支持
Files:
- Modify:
src/rockchip_agents/agents/architect.py - Test:
tests/test_architect.py
背景: ARCHITECT_SYSTEM 在 line 21-32 硬编码了 "Rockchip AI 平台"、"RKNN NPU 推理项目" 以及 YOLO/embedding/MediaPipe 等具体集成示例。需要改成通用架构师角色。同时新增 [记忆] 标签解析,让 architect 可以写入项目 facts。
Step 1: 写失败测试
在 tests/test_architect.py 末尾追加:
def test_architect_parses_memory_tag_and_writes_fact(tmp_path):
"""[记忆] tag in report should call memory.set_fact for each key=value pair."""
from unittest.mock import MagicMock, patch
from rockchip_agents.agents.architect import ArchitectAgent
from rockchip_agents.tools.project_memory import ProjectMemory
from rockchip_agents.core.queue import TaskQueue
cfg = _make_cfg(tmp_path)
mem = ProjectMemory(db_path=tmp_path / "mem.db")
queue = TaskQueue(db_path=tmp_path / "q.db")
agent = ArchitectAgent(cfg, queue=queue, memory=mem)
report = (
"架构分析完成。\n"
"[记忆] yolo: test_cmd=pytest tests/, main_lang=Python\n"
"[优化] yolo: 优化推理管道\n"
)
agent._extract_and_enqueue(report, requester="test")
facts = mem.get_facts("yolo")
assert facts.get("test_cmd") == "pytest tests/"
assert facts.get("main_lang") == "Python"
def test_architect_system_prompt_is_generic():
"""ARCHITECT_SYSTEM must not contain hardcoded project names."""
from rockchip_agents.agents.architect import ARCHITECT_SYSTEM
for hardcoded in ["Rockchip", "RKNN", "YOLO", "MediaPipe", "embedding"]:
assert hardcoded not in ARCHITECT_SYSTEM, (
f"ARCHITECT_SYSTEM 不应包含硬编码词语: {hardcoded!r}"
)
Step 2: 验证测试失败
pytest tests/test_architect.py::test_architect_system_prompt_is_generic -v
预期: FAIL — assert "Rockchip" not in ARCHITECT_SYSTEM 失败
Step 3: 修改 architect.py
替换 ARCHITECT_SYSTEM 常量(line 21-32):
ARCHITECT_SYSTEM = """你是一位技术架构师,负责分析多个软件项目,识别整合机会,提出架构改进建议。
分析维度:
- 功能互补性:哪些项目的能力可以组合,产生新价值
- 技术共性:是否有可复用的基础设施或数据管道
- 产品可能性:整合后能解决什么实际问题
请用中文输出结构化分析报告,末尾按格式列出任务(每行一条):
- [预研] 任务名称 ← 跨项目探索、新功能研究
- [优化] <项目名>: 优化描述 ← 对特定项目的架构改进(项目名须在上面列出的项目中)
- [目标] <项目名>: 目标描述 ← 更新项目长期目标(仅当需要修订时)
- [记忆] <项目名>: key=value, key2=value2 ← 记录项目关键技术事实
"""
在 _extract_and_enqueue() 方法末尾([目标] 解析块之后)追加 [记忆] 解析:
# [记忆] <project>: key=value, key2=value2 → update project facts
for m in re.finditer(r"\[记忆\]\s+([\w-]+)\s*[::]\s*(.+)", report_text):
project = m.group(1).strip()
kv_text = m.group(2).strip()
if project not in self._config.projects:
logger.warning("记忆更新项目 %r 不在配置中,跳过", project)
continue
for kv in kv_text.split(","):
kv = kv.strip()
if "=" in kv:
key, _, value = kv.partition("=")
key = key.strip()[:50]
value = value.strip()[:200]
if key:
try:
self._memory.set_fact(project, key, value, "architect")
logger.info("项目事实已记录 [%s] %s=%s", project, key, value)
except Exception as e:
logger.warning("[%s] 写入项目事实失败: %s", project, e)
Step 4: 运行新测试
pytest tests/test_architect.py -v
预期: 全部 PASS
Step 5: 全量测试
pytest tests/ -q
预期: 所有测试通过
Task 4: Executor 自动写任务历史 + 传递 ProjectMemory 给 agents
Files:
- Modify:
src/rockchip_agents/core/executor.py - Test:
tests/test_executor.py
背景: 每次任务完成(done 或 failed)后,应把结果写入 project_history,让下一轮 agent 处理同一项目时看到历史记录。同时 _make_agent() 需要接受 memory 参数,将同一 ProjectMemory 实例传递给所有 agents。
Step 1: 写失败测试
在 tests/test_executor.py 末尾追加(或找到该文件后追加):
def test_executor_writes_history_after_task(tmp_path):
"""Executor must write task result to ProjectMemory history after completion."""
import asyncio
from unittest.mock import patch, MagicMock
from rockchip_agents.core.executor import Executor
from rockchip_agents.core.queue import TaskQueue, Task
from rockchip_agents.tools.project_memory import ProjectMemory
from rockchip_agents.agents.developer import AgentResult
mem = ProjectMemory(db_path=tmp_path / "mem.db")
queue = TaskQueue(db_path=tmp_path / "q.db")
queue.enqueue(Task(project="yolo", type="fix_bug", title="修复零检出",
context="", priority=2, mode="report", agent_role="developer"))
cfg = _make_cfg(tmp_path) # reuse helper from existing tests
executor = Executor(cfg, queue=queue, memory=mem)
mock_result = AgentResult(status="done", summary="已修复")
with patch("rockchip_agents.agents.developer.DeveloperAgent.run",
return_value=mock_result):
asyncio.run(executor.run_next())
history = mem.get_recent_history("yolo")
assert len(history) == 1
assert history[0]["status"] == "done"
assert history[0]["task_title"] == "修复零检出"
Step 2: 验证测试失败
pytest tests/test_executor.py::test_executor_writes_history_after_task -v
预期: FAIL — Executor.__init__ 不接受 memory 参数,或 history 为空
Step 3: 修改 executor.py
完整替换 src/rockchip_agents/core/executor.py:
from __future__ import annotations
import asyncio
import logging
from typing import Optional
from rockchip_agents.agents.developer import DeveloperAgent
from rockchip_agents.config import AgentsConfig
from rockchip_agents.core.queue import TaskQueue, Task
from rockchip_agents.tools.feishu import FeishuNotifier
from rockchip_agents.tools.project_memory import ProjectMemory
logger = logging.getLogger(__name__)
def _make_agent(config: AgentsConfig, task: Task, memory: ProjectMemory):
"""根据 agent_role 路由到对应 Agent,传入共享 memory 实例。"""
role = task.agent_role
if role == "tester":
from rockchip_agents.agents.tester import TesterAgent
return TesterAgent(config)
if role == "productizer":
from rockchip_agents.agents.productizer import ProductizerAgent
return ProductizerAgent(config, memory=memory)
if role == "architect":
from rockchip_agents.agents.architect import ArchitectAgent
return ArchitectAgent(config, memory=memory)
return DeveloperAgent(config, memory=memory)
class Executor:
def __init__(
self,
config: AgentsConfig,
queue: Optional[TaskQueue] = None,
memory: Optional[ProjectMemory] = None,
) -> None:
self._config = config
self._queue = queue or TaskQueue()
self._memory = memory or ProjectMemory()
self._feishu = FeishuNotifier(
webhook_url=config.feishu.webhook_url,
bot_token=config.feishu.bot_token,
)
self._semaphore = asyncio.Semaphore(config.scheduler.max_concurrent)
async def run_next(self) -> bool:
task = self._queue.dequeue()
if not task:
logger.debug("任务队列为空")
return False
async with self._semaphore:
await self._run_task(task)
return True
async def run_all_pending(self) -> int:
tasks_to_run: list[Task] = []
while True:
task = self._queue.dequeue()
if not task:
break
tasks_to_run.append(task)
async def _run_with_sem(t: Task) -> None:
async with self._semaphore:
await self._run_task(t)
if tasks_to_run:
await asyncio.gather(
*[_run_with_sem(t) for t in tasks_to_run],
return_exceptions=True,
)
return len(tasks_to_run)
async def _run_task(self, task: Task) -> None:
logger.info(
"开始执行任务 [%s] %s (role=%s, mode=%s)",
task.project, task.title, task.agent_role, task.mode,
)
try:
agent = _make_agent(self._config, task, self._memory)
result = await asyncio.get_running_loop().run_in_executor(
None, agent.run, task
)
if task.mode == "confirm" and result.status == "done":
callback_url = f"http://localhost:{self._config.feishu.callback_port}"
self._feishu.send_confirm_card(
task_id=task.id,
project=task.project,
task_title=task.title,
diff_summary=result.summary,
callback_url=callback_url,
)
self._queue.mark_waiting_confirm(task.id, diff=result.summary)
logger.info("任务等待确认 [%s] %s", task.project, task.title)
else:
self._queue.mark_done(task.id, result.summary)
self._feishu.send_task_result(
project=task.project, task_title=task.title,
status=result.status, summary=result.summary,
)
logger.info("任务完成 [%s] %s", task.project, task.title)
# Write history so future agents can learn from this run
try:
self._memory.add_history(
project=task.project,
task_title=task.title,
task_type=task.type,
status=result.status,
summary=result.summary[:300],
)
except Exception as hist_err:
logger.warning("写入任务历史失败: %s", hist_err)
except Exception as e:
logger.error("任务失败 [%s] %s: %s", task.project, task.title, e)
self._queue.mark_failed(task.id, str(e))
try:
self._memory.add_history(
project=task.project,
task_title=task.title,
task_type=task.type,
status="failed",
summary=str(e)[:300],
)
except Exception:
pass
Step 4: 运行新测试
pytest tests/test_executor.py -v
预期: 全部 PASS
Step 5: 全量测试
pytest tests/ -q
预期: 所有测试通过(原 73 个 + 新增若干 = N passed)
Task 5: 新增 CLI 命令 + 更新文档
Files:
- Modify:
scripts/run.py - Modify:
DEVELOP.md
背景: 新增 facts 命令,方便查看各项目的知识库事实。同时更新 DEVELOP.md 里的记忆架构说明。
Step 1: 在 scripts/run.py 中新增 facts 子命令
在 sub.add_parser("goals", ...) 行之后追加:
sub.add_parser("facts", help="查看所有项目的知识库事实(由 agents 自动记录)")
p_sf = sub.add_parser("set-fact", help="手动写入项目事实")
p_sf.add_argument("project", help="项目名")
p_sf.add_argument("key", help="事实键名,如 test_cmd")
p_sf.add_argument("value", help="事实值")
在 elif args.cmd == "goals": 块之后追加:
elif args.cmd == "facts":
from rockchip_agents.tools.project_memory import ProjectMemory
mem = ProjectMemory()
from rockchip_agents.config import load_config
cfg = load_config()
any_found = False
for proj_name in sorted(cfg.projects):
facts = mem.get_facts(proj_name)
if facts:
any_found = True
print(f"\n[{proj_name}]")
for k, v in facts.items():
print(f" {k}: {v}")
if not any_found:
print("暂无项目事实记录(运行 architect 任务后自动写入)")
elif args.cmd == "set-fact":
from rockchip_agents.tools.project_memory import ProjectMemory
mem = ProjectMemory()
mem.set_fact(args.project, args.key, args.value, role="developer")
print(f"已写入: [{args.project}] {args.key} = {args.value}")
Step 2: 手动验证命令可用
source venv/bin/activate
python scripts/run.py facts
python scripts/run.py set-fact memory-analyze test_cmd "cd test_cases && python test_runner.py"
python scripts/run.py facts
预期: 正确打印 memory-analyze 的 test_cmd 事实
Step 3: 更新 DEVELOP.md 记忆章节
在 DEVELOP.md 中找到"项目记忆"相关章节(或在末尾追加),写入以下内容:
## 项目记忆系统
ProjectMemory 采用分层设计:
| 层 | 存储 | 读写规则 |
|----|------|---------|
| Layer 1a — goals | SQLite project_goals | 只有 productizer/architect 可写 |
| Layer 1b — facts | SQLite project_facts | 任意 agent 可写;architect 通过 [记忆] 标签写入 |
| Layer 1c — history | SQLite project_history | executor 自动写入,每次任务完成后记录 |
| Layer 2 — vector | MemoryBackend Protocol | 可选;实现接口后自动启用语义检索 |
### Agent 写入记忆的方式
- **Developer**: 任务执行后由 executor 自动写 history
- **Architect**: 分析报告中添加 `[记忆] <项目名>: key=value` 标签写 facts
- **Productizer**: 评估后自动调用 `set_goal()` 更新项目目标
### 查看项目记忆
```bash
python scripts/run.py goals # 项目长期目标
python scripts/run.py facts # 项目知识库事实
接入向量后端(Layer 2)
实现 MemoryBackend Protocol,传入 ProjectMemory(vector_backend=your_backend):
class MyVectorBackend:
def search(self, query: str, project: str, top_k: int = 5) -> list[str]: ...
def upsert(self, project: str, key: str, text: str) -> None: ...
**Step 4: 全量测试最终确认**
```bash
pytest tests/ -q
预期: 所有测试通过,0 warnings