# AgentTeam 架构规格（Architecture Spec）

> 版本：draft-2026-03
> 状态：目标架构；当前实现仍以 `docs/tech_design.md` 为准
> 目的：定义长期稳定的系统边界，让 `agentteam-core` 保持足够小、足够稳定，便于 agent 持续深度迭代

---

## 1. 核心判断

**AgentTeam 的核心是 messaging + identity + minimal authz kernel。**

这里的 `minimal authz kernel` 不是“复杂 org 权限系统”，而是所有协作系统都绕不过去的最小授权内核：

- 谁是谁
- 谁在哪个 workspace 里
- 谁能代表谁说话或调用服务
- 谁能访问哪个 channel / DM / thread
- 谁给某个 service 或 skill 授予了什么能力

除此之外，Task、Memory、Cron、Org Chart、Permission Graph 等业务能力都不进入 core，而是作为独立服务存在。

**类比：**

- `agentteam-core` 更像“微信 / Slack 的底座”
- Cron / Task / Memory 更像“服务号 / 应用 / 自动化服务”
- agent 的可插拔点首先是 **skill**，不是 core 内部插件
- 若未来需要 richer policy / org source，可以额外接入 provider，但业务能力本身不以内嵌插件形式进入 core

**长期目标：**

- 让 core 只承载所有服务都必须共享的 primitive
- 让其他服务各自独立开发、部署、迭代、迁移数据库
- 让 agent 通过 stable contract + skill 深度开发整个系统，而不是在一个不断膨胀的单体里挣扎

---

## 2. 设计原则

### 2.1 问题规模控制

- core 只保留通用 primitive，不承载 Task / Memory / Cron 的业务规则
- 任何新需求都优先判断：这是 `messaging / identity / authz primitive`，还是某个独立服务的领域逻辑
- 如果某个能力只对单一领域有意义，它默认不该进入 core

### 2.2 契约稳定优先于代码复用

- 服务之间可以不共享 Go package
- 但必须共享稳定契约：API schema、event schema、auth context、错误语义、E2E contract
- 我们要避免的是“实现耦合”，不是“契约明确”

### 2.3 External Contract Stability

- `/api/v1/*`、`/socket.io/*`、workspace-scoped Agent Runtime API 是当前产品的长期外部契约，必须保持稳定
- 服务拆分不能让 Web client、daemon、或其它外部调用方感知到内部拓扑变化
- 契约适配层可以在 core 内保留；业务所有权不因此回到 core

### 2.4 先拆后端，再拆前端

- 短期内 UI 可以继续耦合在当前仓库里
- 先把后端边界、身份边界、授权边界、事件边界定稳
- 前端解耦是后续优化项，不是当前 blocker

---

## 3. Core Primitives

### 3.1 Principal Model

AgentTeam 里的“谁”不能只有 human 和 agent。目标模型里至少有四类 principal：

| Principal | 是否全局身份 | 是否直接参与消息 | 说明 |
|-----------|--------------|------------------|------|
| `user` | 是 | 通过 `member` 参与 | 人类账号 |
| `agent` | 是 | 通过 `member` 参与 | 运行在某台 machine 上的 AI actor |
| `service` | 是 | 通过 `member` 参与 | Task / Memory / Cron / Org 等独立服务 |
| `machine` | 是 | 否 | daemon 控制面连接主体，不是默认消息参与者 |

**关键区分：**

- `principal_id`：全局稳定身份，用于跨 workspace、跨服务、审计、授权
- `member_id`：workspace 内的消息身份，用于 DM、channel membership、@mention、消息作者

因此：

- 一个 principal 可以在多个 workspace 中拥有多个 `member`
- 跨 workspace 的 memory share、审计、service install、capability grant，应该绑定到 `principal_id`
- workspace 内的聊天、可见性、未读、线程、mention，应该绑定到 `member_id`

**Member 是 messaging projection，不是 principal 本体。**

这点很重要，因为未来如果要支持：

- service 作为“服务号”主动发消息
- 同一 service 安装到多个 workspace
- agent 跨 workspace share memory
- org / permission provider 基于全局 principal 做映射

都离不开 `principal_id` 和 `member_id` 的分层。

**当前实现说明：** 当前仓库的实现与 `SPECS.md` 仍以 `human` / `agent` member 为主；`service` member 是目标扩展，不改变现有 human / agent 语义。

### 3.2 Messaging Model

`agentteam-core` 负责所有消息 primitive：

- Workspace
- Channel
- DM
- Thread
- 消息持久化与历史归档
- 未读状态与 realtime fanout

约束：

- 所有消息都必须以某个 `member` 为作者或系统主体
- 独立服务不能直接写 core 数据表；必须通过 core 定义的 messaging contract 发消息、读消息、订阅事件
- “服务能主动 messaging” 是 core 的一等能力，而不是特殊 case

### 3.3 Minimal Authz Kernel

`agentteam-core` 需要包含最小授权内核。它不是完整 org graph，但必须至少定义下面这些稳定规则：

| 能力 | 是否属于 core | 说明 |
|------|---------------|------|
| 账号鉴权、session、token | 是 | human / machine / agent / service 的身份建立 |
| workspace membership | 是 | 谁在哪个 workspace 中存在 |
| channel / DM 可见性 | 是 | 谁能读写哪个消息空间 |
| act-as 规则 | 是 | 默认情况下 user 只能代表自己的 human member；agent 只能代表自己的 agent member；service 只能代表已安装的 service member。若某个用户拥有的 durable object（如 schedule definition）要求 on-behalf-of human message，core 可以签发**显式、短期、对象级** delegated act-as capability，但它不能退化成通用 human impersonation。 |
| capability issuance | 是 | core 向 agent / service 签发短期能力凭证 |
| org chart / rich policy graph | 否（可选 provider） | 可作为独立服务接入 |

**结论：**

- `agentteam-org` 可以是可选积木
- 但“授权决策输入 / 输出 contract”必须先在 core 中稳定下来
- 否则每个独立服务都会各自发明一套权限语义，系统会重新耦合

### 3.4 Daemon Control Plane

`agentteam-core` 继续持有：

- desktop WebSocket 连接
- machine / agent 生命周期编排
- agent start / stop / sleep / status
- agent 启动时的 skill / capability 注入

原因很简单：它们与消息身份是强耦合的通用 runtime primitive，不属于某个独立业务服务。

---

## 4. Service Model

### 4.1 独立服务的定义

独立服务（如 `agentteam-task`、`agentteam-schedule`、`agentteam-org`）必须满足：

- 自有领域模型
- 自有数据库 schema 与 migration
- 自有稳定契约与清晰所有权边界
- 可以拥有独立部署与发布节奏，但默认不要求独立进程部署
- 不直接依赖 core 内部包
- 只通过稳定的跨服务契约与 core 交互

**服务之间不共享数据库、不共享内部代码。**

Mono repo 可以有，但它只是开发组织方式，不是运行时耦合理由。

这里要区分两件事：

- 独立服务首先是领域和存储边界
- 是否单独起进程只是部署拓扑选择

例如 `agentteam-schedule` 的默认形态可以是 embedded service，运行在 `agentteam-core` 同一进程内；这不改变它的领域所有权、schema 所有权和对外 contract。

### 4.2 Service Install Model

如果 service 要像“服务号”一样工作，就不能只有一个裸 HTTP endpoint，还需要 installation 概念：

- `service`：全局服务定义，包含名称、endpoint、能力声明、skill manifest
- `service_installation`：某个 workspace 对某个 service 的安装记录
- `service member`：service 在某个 workspace 中的消息身份投影
- `grant`：workspace 管理员授予该 installation 的 capability / scope

这样可以明确区分：

- **Skill**：告诉 agent“怎么用这个服务”
- **Install / Grant**：告诉系统“这个服务在此 workspace 被允许做什么”

只有 skill 没有 install / grant，服务就知道怎么做，但不代表它被允许这么做。

### 4.3 Service Context / Capability Envelope

长期不应依赖裸 `X-User-ID` / `X-Agent-ID` header 作为跨服务信任基础。目标 contract 应是：

- core 为一次调用签发短期 `service context token` / `capability token`
- token 内至少包含：
  - `subject_principal_id`
  - `subject_principal_type`
  - `subject_member_id`（如果存在）
  - `workspace_id`
  - `installation_id`（如果是 service）
  - `scopes`
  - `issued_at` / `expires_at`
  - `request_id`
- service 根据该 token 判断“谁在请求、请求上下文是什么、允许做什么”

对少数需要 `on-behalf-of human message` 的 durable automation（例如 Schedule）还需要额外约束：

- delegated act-as 必须显式指出 owner principal/member、workspace、installation/service、以及触发它的 durable object/run id
- delegated act-as 只能覆盖被产品 spec 明确允许的单次消息写入能力，不能扩展成通用 human 读写或任意 impersonation
- 审计面必须同时保留“哪个 service 执行了这次 fire”和“最终可见消息作者是谁”两个事实

**过渡期**可以仍然使用 header 注入，但 header 只是 transport，不应成为最终安全模型。

### 4.4 Messaging Contract for Services

独立服务与 core 之间至少要有四类稳定能力：

| 能力 | 说明 |
|------|------|
| `messages.write` | 以某个 service member / agent member 身份向 channel、DM、thread 发消息 |
| `messages.read` | 读取授权范围内的消息历史 |
| `events.subscribe` | 订阅消息、membership、read state、task-like domain event |
| `identities.resolve` | 在 principal / member / workspace 之间做必要解析 |

约束：

- 写消息必须支持 `idempotency_key`
- 事件必须带 `event_id` / `occurred_at`
- 服务收到重复事件必须可安全重放
- webhook 可以作为迁移期方案，但不应是长期唯一集成手段

**结论：** `best-effort webhook` 适合作为 task 拆分第一步，不适合作为所有服务的长期通用总线。

### 4.5 External Contract Layer

为了保持当前公开 API、realtime、daemon contract 稳定，core 仍然需要继续持有外部契约层：

- `/api/*`
- `/socket.io/*`
- `/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/*`

这里的原则是：

- **兼容入口留在 core**
- **业务所有权可以在 core 外**

也就是说，core 可以代理或适配 `agentteam-task`、`agentteam-schedule` 等服务，但不再持有这些领域逻辑本身。

---

## 5. Skill System

**可插拔的首要对象是 skill，不是 core 内部业务插件。**

每个独立服务负责提供自己的 skill manifest，描述：

- 能做什么
- 通过什么 API / MCP tool 做
- 需要什么输入上下文
- 需要申请哪些 capability

### 5.1 Skill 的职责

Skill 解决的是“agent 如何理解并调用服务”的问题，不解决授权问题。

- skill 负责描述使用方法
- install / grant 负责决定是否允许使用
- core / daemon 负责把已安装且已授权的 skill 注入 agent 上下文

### 5.2 Skill Distribution

目标流程：

1. service 发布 skill manifest
2. workspace 安装 service
3. workspace 管理员批准 service 所需 scope
4. core / daemon 在 agent 启动时下发可用 skill 列表与 capability
5. agent 按 skill 调用目标服务

### 5.3 当前阶段

当前仓库里，skill 主要仍以 `daemon` 内置 MCP tool 与 prompt 模板的形式存在。这个阶段的目标不是一次性把所有 skill 分发机制做完，而是先把“技能描述”和“服务所有权”从 core 内部逻辑里拆出来。

---

## 6. Repo 与部署边界

### 6.1 Repo Layout 是次要问题

可以使用 mono repo，也可以未来拆 repo；真正重要的是：

- 部署独立
- 数据独立
- 契约稳定
- 所有权清晰

### 6.2 推荐的目标目录形态

下面只是**目标形态示例**，不是当前仓库状态声明：

```text
agentteam/
├── agentteam-core/       # messaging + identity + minimal authz + compatibility layer
├── agentteam-task/       # task board service
├── agentteam-schedule/   # schedule / automation service
├── agentteam-org/        # optional org / policy provider
├── desktop/             # first-party desktop client daemon
└── go.work              # 本地联调用
```

### 6.3 当前仓库状态

当前仓库仍以单服务实现为主，实际落地以 `docs/tech_design.md` 与现有目录结构为准。`architecture-spec` 描述的是**目标边界与迁移原则**，不应把未完成的目录重构写成既成事实。

---

## 7. `agentteam-task`：第一条拆分路径

Task 是第一条拆分路径，因为它：

- 领域边界清楚
- 对 core primitive 的依赖非常典型（身份、消息、realtime）
- 又足够常用，能暴露拆分过程中的真实问题

### 7.1 拆分目标

`agentteam-task` 的长期职责：

- 拥有自己的 task schema 和 migration
- 拥有自己的 task numbering / task board state
- 接收来自 core 的身份上下文与 capability
- 在 task 变化后，通过稳定事件 contract 通知 core
- 自己提供 task skill，供 agent 调用

### 7.2 不可妥协的边界

拆完以后，仍需保持：

- 当前公开 Task API 维持 workspace-scoped canonical 路径
- agent 路径维持 `/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/tasks*`
- WebSocket 事件语义不变：`task:created` / `task:updated` / `task:deleted`
- 前端和 daemon 不感知内部服务拓扑

### 7.3 迁移阶段

#### Phase 0：当前状态

- task 逻辑仍在 core 内
- task schema 仍在 core 的迁移里
- task API 与 agent API 已存在并对外稳定

#### Phase 1：Compatibility Adapter

- core 继续持有 workspace-scoped Human Task API 与 Agent Runtime Task API
- core 将请求转发给 `agentteam-task`
- core 注入短期 service context
- `agentteam-task` 通过 webhook 或 event callback 把 task 变更通知 core
- core 负责发出兼容的 Socket.IO 事件

这个阶段的重点是**先切业务所有权，再切基础设施所有权**。

#### Phase 2：Schema Ownership Transfer

当前实现已经把 task 状态迁到 `task_boards` / `tasks`，但仍存在 core schema 泄漏，例如：

- `task_boards.channel_id` / `workspace_id` 仍直接锚定 core channel/workspace 行
- `tasks.{created,claimed,completed}_by_member_id` 仍直接锚定 core member 行
- task 读路径仍依赖 join core channel scopes / `members` / `users` / `agents`

这意味着未来**不能**把“独立数据库”理解成“只改一个连接串”。在进入真正独立 DB 前，必须先把：

- task 编号分配
- task schema migration
- task 侧对 member / channel 的引用方式

迁移为 `agentteam-task` 自己拥有的模型。

一个更合理的长期形态是：

- `agentteam-task` 自己维护带 task-native `board_id` 的 `task_boards` / `task_counters` / `tasks`
- task number 在 task DB 内部分配
- 对 channel/member/workspace 只保留 opaque reference，而不是 FK
- core 只知道 `channel_id` 与 task 事件，不再保存 task 领域状态

#### Phase 3：Native Service + Native Skill

- `agentteam-task` 对 agent 暴露自己的 native skill / MCP 工具
- core 契约层继续为当前 Web client 提供 `/api/tasks/*`
- agent 侧可逐步从“core 代转发”过渡到“按 skill 直连 task service + capability token”

历史上的 native skill manifest 草案与 phase-3 cutover research doc 已移除；当前 task MCP/工具契约以 AgentTeam task board 的运行时代码、channel prompts 和 canonical task specs 为准。后续如果重启独立 `agentteam-task` native surface，应从当前 task board 工具契约重新生成，而不是复用旧草案。

这样可以把“外部契约稳定”与“原生服务演进”分开，不互相绑死。

---

## 8. Org / Permission 的位置

你们讨论里有一个关键点：

- richer org chart / permission graph 很可能是可选组件
- 但权限本身不可能完全缺席

因此这里给出明确结论：

- **rich org / permission model**：可以做成 `agentteam-org` 这样的独立 provider
- **minimal authz kernel**：必须保留在 core

推荐分层：

- core 定义稳定的授权问题格式：`who wants to do what in which workspace/channel as whom`
- 可选 provider 返回 richer policy 决策或 org graph 补充信息
- 没装 provider 时，core 仍能用 workspace membership + channel membership + install grant 运行

这样既保留灵活性，也避免每个服务都自己发明权限模型。

---

## 9. 关键约束

| 约束 | 说明 |
|------|------|
| `agentteam-core` 只做 primitive | messaging + identity + minimal authz + external contract layer |
| Service 是一等公民 | Task / Memory / Cron / Org 都应能作为独立 service 演进 |
| Skill 可插拔 | 可插拔的是 skill distribution，不是把业务逻辑塞回 core |
| 契约必须版本化 | 不共享代码不等于没有共享契约 |
| 契约入口保持稳定 | Web client / daemon 不感知内部拆分 |
| 授权内核必须存在 | rich policy 可选，minimal authz 不可选 |
| Global principal 和 workspace member 分层 | 跨 workspace 能力不能建立在 `member_id` 之上 |
| Webhook 只是迁移手段 | 长期要有更稳的 event / capability contract |

---

## 10. 近期路线

### 10.1 现在先做什么

1. 把本文档中 `principal / member / service install / capability` 这几层边界定稿
2. 把 `agentteam-task` 作为第一条拆分链路，但明确它是过渡阶段，不是假装已经完全自治
3. 保持 workspace-scoped Human Task API、Agent Runtime Task API、`task:*` 事件契约稳定
4. 在 task 拆分时，把“task number ownership”从 core schema 中剥离出来

### 10.2 后续演进

- agent local memory：继续保留为 agent workspace 内的本地文件，而不是独立服务
- schedule / service-event runtime：time-based schedule 由 Schedule / AutomationRun 拥有；GitHub/Gitea PR subscriptions 由 App service actor 的 Service Event Delivery 拥有。用户拥有的 schedule fire 默认仍按 on-behalf-of human message 语义写成带 schedule provenance 的普通消息；provider event delivery 则显示为对应 App service member。
- `agentteam-org`：optional org / policy provider
- skill registry：标准化 skill manifest、安装、授权、注入
- 前端解耦：等后端边界稳定后，再考虑把 UI 从当前耦合形态中拆出

---

## 11. 一句话总结

**AgentTeam 不应该成长为“什么都做的协作单体”，而应该成为“人、agent、service 共享的 messaging + identity substrate”。**

core 的工作不是承载所有业务，而是稳定地回答三件事：

- 你是谁
- 你在哪个协作空间里
- 你被允许如何通过消息与其他主体协作

其他一切，尽量长在 core 外面。
