Imported from Windpicker-owo/Purrcept-Runtime (
AGENTS.md). Install upstream withnpx skills add Windpicker-owo/Purrcept-Runtime. Copyright stays with the author.
AGENTS.md
本文件面向在 purrcept_runtime 仓库中工作的 Codex、代码 Agent 与人工维护者。开始修改前
必须先阅读本文件、README.md 和与目标模块对应的 docs/ 文档。
1. 项目定位
purrcept_runtime 是 purrcept_core 之上的世界导向运行时,不是另一个 Agent Core,也
不是具体聊天机器人产品。
职责分层:
purrcept_core:模型无关的 Conversation、历史、Prompt、Tool 协议和事务化 Agent Flow。purrcept_litellm:LiteLLM 传输适配与 Provider 归一化。purrcept_runtime:世界、实体、插件、Capability、行动执行、事件、实体调度,以及可选的应用装配层。- 具体应用插件:手机、消息、浏览器、文件、日历、游戏世界、MCP Adapter 等。
除非任务明确要求并给出兼容计划,不要修改相邻的 Core 或 LiteLLM 仓库来规避 Runtime 的 设计问题。
2. 不可破坏的核心不变量
下面的规则优先于局部实现便利。任何架构变更都必须说明对这些不变量的影响。
- Entity 与 Controller 分离。
Entity只保存世界身份和状态;LLM、人类、脚本与回放 都是可替换 Controller,不得让世界对象继承或持有 Core Agent 实现。 - 所有世界副作用必须经过 Capability Gateway。 Action Engine、Controller、插件事件 处理器不得绕过 Gateway 直接调用作为模型行动的 Provider。
- 行动 Worker 不得获得真实插件对象。 Worker 只拿到 JSON Bootstrap 和 IPC 代理; 密钥、网络客户端、数据库连接与 Service Registry 留在宿主进程。
- 每次 Action 冻结世界修订与 Registry Epoch。 执行中插件热更新或世界变化不能悄悄 改写该 Action 已生成的绑定集合。
- 插件卸载先退休、后等待租约、再释放资源。 不得先关闭 Provider 客户端再等待在途 Action,否则会制造随机中断和部分状态。
- 模型可见内容必须可重建。 动态 World Reminder、模型轮次、Action 和 Capability 调用 应具有可关联的事件上下文。
- 外部副作用不承诺全局回滚。 失败结果必须保留调用轨迹和
committed_effects,不得把 “后续失败”伪装成“前面没有发生”。 - 不可逆操作必须可被策略和审批拦截。 新 Capability 必须准确声明 effect、permission、 idempotency、concurrency 和 timeout。
- 插件注册必须可逆。 Capability、服务、订阅、定时器和外部客户端均应进入
PluginContext的生命周期栈。 - Event Bus 与执行 Pipeline 分离。 Event 表示已经发生的事实;权限、审批、重试、 Provider 调用和结果规范化属于 Gateway Pipeline,不得依赖订阅顺序实现授权。
- 同一 Entity 的 Controller 串行运行。 不要通过绕开
EntityScheduler引入同一实体的 并发模型轮次;Controller 自身也应保留锁作为第二道边界。邮箱按 tick 周期排空:等到当前 间隔后再取走当时已排队的全部刺激,合成一次模型轮次;间隔内以及本轮进行中新到的刺激等 下一个 tick。休息中使用更长间隔。 - CodeExecutor 是可替换安全边界。 默认子进程执行器只是基线实现,核心协议不得绑定
到
multiprocessing或特定操作系统。 - JSON 边界必须严格。 跨事件、Worker、Capability 和持久化边界只传输受支持的 JSON 值;不要把任意 Python 对象塞进 metadata 或 payload。
- Runtime Kernel 保持小而稳定。 具体设备、App、平台 SDK 和业务策略进入插件,不得 因单一应用需求扩张核心包。
3. 目录职责
src/purrcept_runtime/
├── action/ # Action Scope、源码校验、执行器、Worker、play_action Tool
├── app/ # 应用装配:TOML 配置、factory 加载、控制台、CLI
├── capabilities/ # Spec、Provider、Registry、Policy、Ledger、Gateway
├── controller/ # Entity Controller,当前含 Core LLMController
├── events/ # EventEnvelope、Store、Bus、Publisher
├── kernel/ # 生命周期、服务注册与插件管理
├── prompts/ # 唯一模型可见 PromptTemplate / PromptLibrary
├── scheduling/ # Stimulus、Mailbox、EntityScheduler
├── world/ # 不可变世界值、版本化 World、PerceptionProjector
├── errors.py # 稳定错误层级
├── runtime.py # 默认单进程宿主组装
└── values.py # JSON 冻结、解冻和大小边界
依赖方向应大致保持:
values / errors / ids
↓
events / kernel / world
↓
prompts
↓
capabilities
↓
action
↓
controller / scheduling
↓
runtime
↓
app
prompts 不得 import runtime 或 app。配置发现发生在 app/,挂载、租约和卸载仍由
PluginManager 执行。不要在 import 时扫描插件。
避免底层模块导入顶层 Runtime。kernel.__init__ 不重新导出插件 API 是有意为之,用于
避免 events ↔ kernel.plugins 循环导入。
4. 代码与注释规范
- 代码、标识符和稳定错误码使用英文;面向维护者的注释和文档字符串使用中文。
- 每个模块开头写清楚职责、边界以及“不负责什么”。
- 公共类、协议、方法必须有中文文档字符串;私有函数在语义不明显时补充原因说明。
- 注释解释“为什么这样做”和不变量,不要逐行复述代码。
- 复杂模块使用空行和小节保持阅读节奏,不要把验证、状态转换和 I/O 堆成连续代码块。
- 公开类型优先使用不可变
dataclass(frozen=True, slots=True)。 - 公共容器返回只读快照,避免泄露内部可变字典或列表。
- 所有输入边界做明确类型和值校验,不依赖模糊的
AttributeError。 - 稳定数据边界使用
Protocol;不要为了共享几行逻辑建立深继承树。 - 不使用裸
except:。捕获BaseException只用于必须处理取消/清理的生命周期边界,并在 完成清理后重新抛出或汇总。 - 不隐藏自动重试。若以后加入重试,必须有显式策略、事件、幂等边界和测试。
- 不新增进程全局可变 Registry、单例或隐式插件发现。
- 行宽目标 100;Python 最低版本 3.11;严格类型检查目标由
pyproject.toml定义。
5. Capability 开发规则
新增 Capability 时必须逐项决定:
id:稳定、点分、与 Provider 实现解耦,例如messages.send。description:说明可观察行为,不使用模糊的“do something”。模型看见的是能力卡片,不是源码。usage/example(可选):何时用、不要用它来做什么,以及一行合法调用。permission:需要单独授权时给出稳定权限名。effect:在pure、world_read、world_write、external_read、external_write、irreversible中准确选择。concurrency:并行、Actor 串行、目标串行或全局排他。idempotency:是否需要幂等键;外部写入应优先提供业务侧幂等。requires_approval:不能仅依赖描述文字提醒模型。timeout_seconds:外部 I/O 应给出合理单调用超时。- 输入/输出:只使用可稳定 JSON 化的结构。
函数式 Provider 应使用完整类型标注:
@capability(
"messages.send",
description="通过当前账号发出一条文本消息。",
usage="你决定现在对某人说话时用。不要用它代替心里想。",
example='await me.phone.messages.send(recipient="Alice", text="今晚有空吗?")',
permission="messages.send",
effect=EffectKind.EXTERNAL_WRITE,
concurrency=ConcurrencyMode.ACTOR_SERIAL,
idempotency=IdempotencyMode.REQUIRED,
timeout_seconds=20,
)
async def send_message(
recipient: str,
text: str,
context: CapabilityContext,
) -> CapabilityOutcome: ...
需要发布领域事实时返回 CapabilityOutcome(events=(EmittedEvent(...),));不要在 Provider
成功前直接发布“已经完成”的领域事件。
6. 世界与 Perception 规则
WorldNode.id是稳定身份,不等同于展示名称。kind使用点分类型,例如device.phone、app.messaging。namespace是模型 SDK 路径,不是 Python 包路径。- App 推荐作为独立
WorldObject,通过parent_id安装到设备,而不是写成设备对象内部类。 - 所有权、父子关系和可见性由世界状态表达,不写死在 Capability Provider 中。
PerceptionProjector只决定 Actor 能看见什么和路径如何投影,不执行权限授权;最终调用仍 必须由 Gateway 再次检查。- 修改世界状态时继续采用 copy-on-write 修订,不要把旧 Snapshot 变成动态视图。
- 插件创建世界节点时必须登记对应清理回调;若节点会被其他插件引用,需要先设计明确的 所有权和卸载协议,不能粗暴级联删除不属于自己的状态。
7. 插件规则
PluginManifest.id是公开 Python 标识符;版本使用 PEP 440;依赖使用 specifier。mount()内只通过PluginContext暴露 Runtime 扩展点。- 外部客户端创建后立即登记
context.on_cleanup(...),再继续后续注册,缩小失败泄漏窗口。 - 注册顺序应让逆序清理满足依赖关系:先创建底层资源,再注册依赖它的 Capability/订阅。
- 不在模块导入阶段连接网络、创建线程或注册全局回调。
- 不依赖插件登记顺序;只依赖 Manifest 拓扑。
- 插件挂载失败必须可安全重试。
- 不在卸载回调中偷偷重新注册自身或其他插件。
- 若新增插件级扩展点,优先使用显式
ServiceKey[T],不要通过字符串属性探测对象。
8. Action 与安全规则
允许的实现
- 宿主侧只做源码预检、Scope 构造和 IPC 调度。
- 真正的
exec仅允许存在于隔离 Worker 实现中。 - Worker 重复 AST 校验,不能把宿主预检当作安全边界。
- Worker 的
world模块必须是虚拟模块,禁止导入真实插件包。 - 每次 IPC 调用按冻结绑定解析,再进入 Gateway。
- 超时、取消和 Worker 崩溃必须转换为结构化
ActionResult。
禁止的捷径
- 在宿主进程直接
exec(action_code)。 - 仅删除几个 builtin 后声称得到安全沙箱。
- 把
ServiceRegistry、环境变量、文件系统或网络客户端传入 Worker。 - 允许模型导入任意已安装 Python 包。
- 通过私有属性、反射或对象图访问逃逸限制。
- 把整个 Action 宣称为数据库式原子事务。
- 因为代码运行失败而丢弃此前已经成功的调用轨迹。
更改 AST 白名单、内建集合、模块白名单、IPC 协议或资源限制时,必须增加安全回归测试,
并同步更新 docs/action-security.md。
9. 事件规则
- 事件名使用点分层级,例如
capability.call.started。 EventPublisher必须保持“先 Store、后 Bus”的顺序。- 领域模块发布事件时携带尽可能完整的
EventContext:world_id、actor_id、turn_id、action_id、call_id、correlation_id、causation_id。 - Event handler 失败不会回滚已存事件;调用方需要明确处理这一语义。
- 处理器内的因果子发布允许重入并采用深度优先顺序;不要依赖并发子任务形成未声明的事件顺序。
- 高频性能遥测未来应使用独立 Telemetry 接缝,不要把所有 Span 明细塞进领域 EventStore。
- 不使用 Event Bus 的监听器优先级实现关键控制流。
10. Core 集成规则
LLMController使用 CoreConversation作为唯一历史与工具循环来源。- 能力用法、参数和示例通过
WorldPromptCompiler进入系统指令;绑定和契约未变时文本 必须稳定。动态 Reminder 只列本轮能力路径与世界状态,两者都不能写入 message 历史。<world_view>含墙钟与修订,必须使用ReminderPlacement.TAIL。Reminder 在 Chat Completions 上是 user 消息,不得发成system:那是SystemInstruction的角色。 - 模型可见工具默认保持单一
play_action;新增直接 Tool 需要架构层理由和兼容说明。 - Core Tool handler 只调用
ActionEngine,不复制 Gateway 逻辑。 - Core Conversation 的事务语义必须保留:失败轮次不能提交成成功历史。
- Provider 特有配置留在模型后端/
provider_options。Kernel 不依赖 LiteLLM 类型;装配层 绑定purrcept_litellm,并从purrcept.toml的[model]构造 Backend。
11. 测试要求
任何行为变更至少覆盖最接近的层级;跨层变更必须补端到端测试。
基础测试矩阵
- 值边界:递归冻结、解冻、非法 JSON、大小限制。
- World:修订稳定、可见性、引用完整性、级联行为、路径冲突。
- Capability:参数 Schema、Context 注入、策略、审批、幂等、并发、超时、领域事件。
- Registry:Epoch、精确快照、Owner 退休、租约等待。
- Plugin:依赖拓扑、版本冲突、循环、挂载失败回滚、重新启动、反向卸载。
- Action:AST 拒绝、Worker 崩溃、超时、并发调用、远端错误、结果大小、标准输出、部分提交。
- Scheduler:同 Entity 串行、按批排空邮箱、不同 Entity 可并发、背压、关闭 drain/cancel。
- Core:动态 Reminder、唯一 Tool、自动工具循环和最终历史提交。
提交前命令
python -m compileall -q src tests examples
PYTHONPATH=src:../purrcept_core/src python -m pytest -q
# 安装开发依赖后:
uv run ruff check .
uv run ruff format --check .
uv run pyright
uv run pytest -q
uv run python -m build
不要为了让测试通过而放宽核心校验,除非同时给出新的安全模型和迁移说明。
12. 兼容与版本策略
- 公共 API 由各模块
__all__和顶层purrcept_runtime.__init__共同定义。 - 新增公共能力时同步更新导出、README、相关 docs 和测试。
- 修改事件名、字段、错误层级、Capability ID、World 序列化或 Action IPC 属于协议变化。
0.x阶段仍应提供迁移说明;不要无声改变语义。- Core/LiteLLM 版本范围必须与实际测试版本一致。
- 插件 API 变更优先通过新增字段和默认值演进,避免要求所有插件同步修改。
13. 建议工作流程
Git 提交标题与正文统一使用中文,技术名称、代码标识符和命令可以保留原文。
- 阅读相关模块、测试和文档,先确认边界而不是立即加抽象。
- 写出要保护的不变量和失败情形。
- 先补或调整测试,再实现最小闭环。
- 在低层模块修复问题,不在上层堆补丁绕过。
- 运行局部测试,再运行完整测试与 compileall。
- 同步 README、docs、类型导出和变更记录。
- 检查是否引入不可逆副作用、隐式全局状态、循环依赖或无法卸载资源。
14. 完成检查清单
- 行为位于正确模块,没有扩大 Runtime Kernel。
- 没有绕过 Capability Gateway。
- 没有把真实插件对象或密钥暴露给 Worker。
- 插件资源都能逆序清理,失败路径也覆盖。
- Action 的 Snapshot、Epoch、租约和部分提交语义未被破坏。
- 新数据可 JSON 化并在边界冻结。
- 新公共 API 有中文文档字符串和导出。
- 新事件包含必要关联字段。
- 测试覆盖成功、失败、取消/超时与清理。
- README、docs、示例和 AGENTS.md 已同步。
-
compileall与完整测试通过。
