# 02 · 技术架构设计
> 产品:Kizuna · Agent 通讯录· 版本 v0.2(A2A-ready)· 2026-10-30
> 上游文档:01-PRD · 下游:03-API规范 / 04-端侧Agent服务设计 / 05-DSH插件与管理界面设计
> v0.2 变更:`agents.agent_card` / `tasks.reply_to` 列、`task_results` 只增表;结果回传三分类(进行中/中断/终态,借鉴 A2A)。A2A 兼容为适配层,核心投递引擎保持自有协议。
## 1. 总体架构
```mermaid
flowchart TB
subgraph 来源侧
S1[DSH 会话 Agent]
S2[CLI Agent / Bot]
end
subgraph HUB["Kizuna Hub 中心服务(路由 Agent-A)"]
GW[API 网关
鉴权/限流]
REG[路由字典
Registry]
QQ[(任务队列
SQLite)]
DISP[投递调度器
Dispatcher]
ACKH[回执处理器]
AUD[(投递留痕
audit_logs 只增)]
INST[安装服务
enroll/脚本分发]
ADMIN[管理 API
↔ Web 管理台 / DSH 插件]
end
subgraph 端侧
EP1[Kizuna Box
Linux]
EP2[Kizuna Box
macOS]
EP3[Kizuna Box
Windows]
EP4[Kizuna Box
Android Termux]
AG1[本机 Agent-B]
end
S1 & S2 --> GW
GW --> REG
GW --> QQ
QQ --> DISP
DISP -->|webhook POST + HMAC 签名| EP1 & EP2 & EP3 & EP4
EP1 & EP2 & EP3 & EP4 -->|ACK 回执| ACKH
ACKH --> QQ
ACKH --> AUD
DISP --> AUD
EP1 & EP2 & EP3 & EP4 -->|心跳 60s| GW
EP1 & EP2 & EP3 & EP4 <-.->|本地 API 127.0.0.1| AG1
ADMIN --> QQ & REG & AUD
INST --> REG
```
## 2. 模块职责
| 模块 | 职责 |
| --- | --- |
| API 网关 | 三级鉴权(管理员令牌 / Agent 令牌 / 一次性 enroll 令牌)、限流、错误规范 |
| 路由字典 | Agent 四要素 CRUD、编号唯一性、能力检索、上下线状态(心跳驱动) |
| 任务队列 | 任务包受理、持久化(SQLite WAL)、状态流转、出队删除 |
| 投递调度器 | 1s tick 扫描到期任务 → 读「给谁」→ 查字典 → 按 webhook 投递(并发 32,带抖动) |
| 回执处理器 | 校验回执(2xx + task_id 一致)→ 出队 + 留痕;失败 → 安排下一次重试 |
| 留痕服务 | 唯一写入口(只增不改不删 API),出队时落终态快照 |
| 安装服务 | 签发一次性 enroll 令牌、生成安装提示词、按平台分发安装脚本 |
| 管理 API | 供 Web 管理台与 DSH 插件(Host 半边代理)统一调用 |
## 3. 数据模型(SQLite,WAL 模式)
```sql
-- 路由字典
CREATE TABLE agents (
agent_id TEXT PRIMARY KEY, -- 编号:A-XXXX(自动)或自定义唯一串
name TEXT NOT NULL, -- 名称
capabilities TEXT NOT NULL DEFAULT '', -- 能力声明(自由文本)
capability_tags TEXT NOT NULL DEFAULT '[]',-- 能力标签(JSON 数组,可检索)
webhook_url TEXT NOT NULL, -- 投递地址(http/https)
endpoint_secret TEXT NOT NULL, -- HMAC 签名密钥(投递用)
agent_token TEXT NOT NULL UNIQUE, -- 该 Agent 调 Hub API 的令牌
enabled INTEGER NOT NULL DEFAULT 1,
status TEXT NOT NULL DEFAULT 'unknown', -- online / offline / unknown
last_seen_at TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
-- 任务队列(终态任务不留行,快照进 audit_logs)
CREATE TABLE tasks (
task_id TEXT PRIMARY KEY, -- T-YYYYMMDD-XXXXXX
from_agent TEXT NOT NULL,
to_agent TEXT NOT NULL,
title TEXT,
content TEXT NOT NULL, -- 任务内容主体
format TEXT NOT NULL DEFAULT 'text', -- text | markdown | json
priority TEXT NOT NULL DEFAULT 'normal', -- normal | high
deadline TEXT,
status TEXT NOT NULL DEFAULT 'queued', -- queued/delivering/wait_retry
attempt_count INTEGER NOT NULL DEFAULT 0,
next_action_at TEXT, -- 下次投递/重试时刻(UTC ISO8601)
created_at TEXT NOT NULL
);
CREATE INDEX idx_tasks_next ON tasks(next_action_at);
CREATE INDEX idx_tasks_status ON tasks(status);
-- 逐次尝试明细(在途期间可查;出队时整体快照进留痕)
CREATE TABLE task_attempts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id TEXT NOT NULL,
n INTEGER NOT NULL, -- 第几次尝试(1 起)
attempted_at TEXT NOT NULL,
outcome TEXT NOT NULL, -- ack / timeout / network_error / http_4xx / http_5xx / bad_ack / invalid_signature
http_status INTEGER,
latency_ms INTEGER,
detail TEXT
);
-- 投递留痕:只增不改不删(无 UPDATE/DELETE 通道)
CREATE TABLE audit_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
log_id TEXT UNIQUE NOT NULL, -- AL-YYYYMMDD-XXXXXX
task_id TEXT NOT NULL,
from_agent TEXT NOT NULL,
to_agent TEXT NOT NULL,
enqueued_at TEXT NOT NULL,
dequeued_at TEXT NOT NULL,
outcome TEXT NOT NULL, -- delivered_confirmed / delivery_failed / invalid_target / cancelled
attempt_count INTEGER NOT NULL,
attempts_json TEXT NOT NULL, -- 全部尝试历史快照(JSON)
content_digest TEXT, -- 任务内容 SHA-256 摘要(最小化选项)
note TEXT
);
CREATE INDEX idx_audit_outcome ON audit_logs(outcome, dequeued_at);
-- 一次性注册令牌(安装提示词携带,3 小时有效、用后作废)
CREATE TABLE enroll_tokens (
token TEXT PRIMARY KEY,
agent_draft TEXT NOT NULL, -- 预填的编号/名称/能力声明
created_at TEXT NOT NULL,
expires_at TEXT NOT NULL,
used_at TEXT
);
-- 结果回传(v1.1):签收后的任务生命周期,借鉴 A2A 三分类。只增不改不删。
CREATE TABLE task_results (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id TEXT NOT NULL,
reported_by TEXT NOT NULL, -- 回传方(接收方或 admin)
state TEXT NOT NULL, -- working / input-required / completed / failed / canceled
category TEXT NOT NULL, -- in_progress / interrupted / terminal(三分类)
message TEXT,
result_json TEXT, -- 结果负载(原样 JSON)
content_digest TEXT, -- result 的 SHA-256 摘要
relayed_to TEXT, -- 终态自动回执的信件任务号(未回执为 NULL)
reported_at TEXT NOT NULL
);
CREATE INDEX idx_results_task ON task_results(task_id, id);
```
> v0.2 就地迁移(幂等):`agents` 增加 `agent_card TEXT`(A2A Agent Card,原样 JSON),`tasks` 增加 `reply_to TEXT`(回执信件串联原任务)。v0.1 建库脚本对应语句为 `CREATE TABLE IF NOT EXISTS`,迁移由 Hub 启动时自动完成。
## 4. 任务包状态机
```mermaid
stateDiagram-v2
[*] --> queued: POST /tasks 入队
queued --> delivering: 调度器派发(读「给谁」→ 查 webhook)
queued --> invalid_target: 编号不存在 / webhook 缺失 / 目标已停用
delivering --> delivered: 2xx 且 task_id 一致(有效回执)
delivering --> wait_retry: 失败且剩余重试额度 > 0
wait_retry --> delivering: 到达 next_action_at(+3/10/30/60/120min)
delivering --> failed: 第 6 次(末次)仍失败
queued --> cancelled: 管理员取消
wait_retry --> cancelled: 管理员取消
delivered --> a1: 出队删除 + 留痕(delivered_confirmed)
failed --> a2: 出队删除 + 留痕(delivery_failed)
invalid_target --> a3: 出队删除 + 留痕(invalid_target)
cancelled --> a4: 出队删除 + 留痕(cancelled)
```
> 终态任务从 `tasks` 删除(队列保持干净),完整快照写入 `audit_logs`。设计决策:`invalid_target` 不进重试——目标不存在时重试注定无意义,直接出队留痕,让失败尽早暴露(相对立项陈述的补充规则,已列入 PRD 追溯表)。
### 4.1 结果回传生命周期(v1.1,借鉴 A2A 三分类)
投递签收(`delivered_confirmed`)之后,任务进入**结果回传生命周期**,由接收方驱动、记录于 `task_results`(只增):
```mermaid
stateDiagram-v2
[*] --> delivered_confirmed: 端侧回执 ACK(上表 delivered)
delivered_confirmed --> in_progress: 接收方回传 working(进行中)
in_progress --> interrupted: 回传 input-required(等待输入)
interrupted --> in_progress: 回传 working(恢复处理)
in_progress --> terminal: 回传 completed / failed / canceled
interrupted --> terminal: 回传 completed / failed / canceled
terminal --> [*]: 终态自动生成回执信件(reply_to 串联)→ 发起方按普通任务收信
```
- 分类:**进行中** `working` / **中断** `input-required` / **终态** `completed`·`failed`·`canceled`;
- 进行中与中断仅记录留痕(避免信件风暴);终态默认自动回执发起方(`suppress_reply` 可抑制);
- 回执信件是一条普通 json 任务(`kind:"kizuna.task-result"`),复用全部投递/重试/留痕机制——**Hub 无需第二套通道即成为「带持久队列的 A2A Hub」**。
## 5. 核心时序
### 5.1 正常签收(Happy Path)
```mermaid
sequenceDiagram
participant A as 来源 Agent
participant H as Kizuna Hub(Agent-A)
participant B as Kizuna Box(Agent-B)
A->>H: POST /api/v1/tasks(任务包)
H->>H: 鉴权·校验目标编号·入队(queued)
H-->>A: 202 { task_id, status:"queued" }
H->>H: 调度器取出 → 查字典得 webhook
H->>B: POST {webhook}/kizuna/deliver(HMAC 签名)
B->>B: 验签 → 先落盘(spool 持久化)
B-->>H: 200 {"status":"received","task_id":"T-..."}
H->>H: 出队删除 + 留痕(delivered_confirmed)
Note over A,B: 全程通常 < 3s
```
### 5.2 失败重投(Retry Ladder)
```mermaid
sequenceDiagram
participant H as Kizuna Hub
participant B as Kizuna Box
H->>B: 第 1 次投递(T0)
B--xH: 无响应(超时 10s)
Note over H: T0+3min
H->>B: 第 2 次投递
B--xH: 5xx
Note over H: 上次失败+10min
H->>B: 第 3 次投递(此时端侧已恢复)
B-->>H: 200 回执
H->>H: 出队删除 + 留痕(含 3 次尝试历史)
Note over H,B: 若 6 次全部失败:出队删除 + 留痕(delivery_failed, attempt_count=6)
```
## 6. 投递调度器设计
- **扫描循环**:每 1s `SELECT ... WHERE status IN ('queued','wait_retry') AND next_action_at <= now() LIMIT 64`,取到期任务投递;SQLite 单写者足够。
- **重试梯度**(间隔自上次尝试失败起算,可配置,默认值即立项要求):
| 尝试次序 | 时刻 | 说明 |
| --- | --- | --- |
| 第 1 次 | T0 | 首投 |
| 第 2 次 | T0 + 3min | 重试 1 |
| 第 3 次 | 上次失败 + 10min | 重试 2 |
| 第 4 次 | 上次失败 + 30min | 重试 3 |
| 第 5 次 | 上次失败 + 60min | 重试 4 |
| 第 6 次 | 上次失败 + 120min | 末次;失败即出队 + 留痕 |
累计总跨度约 **3 小时 43 分钟**。
- **投递判定**(详见 03 §3):HTTP 连接超时 5s、整体超时 10s;2xx 且响应体 `task_id` 一致 → `ack`;2xx 但 task_id 不一致 → `bad_ack`(按失败处理);其余按 `http_4xx/http_5xx/timeout/network_error` 记录。
- **并发与抖动**:全局并发 32;对同一目标的投递串行(避免端侧乱序);每次重试附加 ±5s 随机抖动,防止重投风暴。
- **崩溃恢复**:启动时把所有 `delivering` 复位为 `queued`(next_action_at=now),利用幂等的 task_id 保证端侧不产生重复信件(见 §7)。
## 7. 幂等与一致性
| 环节 | 机制 |
| --- | --- |
| 端侧防重 | 以 `task_id` 为主键落盘;重复投递(重试窗口内重发成功)直接幂等返回同一回执,不产生第二条信件 |
| 落盘先行 | 端侧**先持久化、再回 200**;Hub 视角「收到回执 ⇒ 端侧必已落盘」 |
| Hub 出队 | 收到有效回执后:写尝试明细 → 写留痕 → 删 `tasks` 行,同一事务完成 |
| 投递至多一次 vs 至少一次 | 本方案为「至少一次投递 + 端侧幂等去重」,语义上等效「恰好一次信件落箱」 |
## 8. 安全设计
- **三级令牌**:
- 管理员令牌(Admin Token):管理 API 全量权限,Web 管理台/DSH 插件 Host 半边持有;
- Agent 令牌(Agent Token):该 Agent 提交任务包、心跳、查询自己的任务;
- 一次性注册令牌(Enroll Token):安装提示词携带,3 小时有效、用后作废,换取正式 Agent Token。
- **投递签名**:Hub → 端侧带 `X-Kizuna-Timestamp` 与 `X-Kizuna-Signature: sha256=HMAC(endpoint_secret, timestamp + '.' + rawBody)`;端侧校验时间窗 ±300s 防重放(算法与示例代码见 03 §6)。
- **端侧端口分离**:交付端口(收 Hub 投递,必须验签)与本地 API(127.0.0.1 回环,供本机 Agent 读取)分端口监听。
- **传输**:生产环境 Hub 与 webhook 全链路 HTTPS;Hub 强制管理员登录(令牌)。
- **最小化**:留痕默认只存任务内容摘要;管理界面展示任务正文需权限(v1.0 ACL)。
## 9. 技术选型
| 项 | 选择 | 理由 |
| --- | --- | --- |
| 语言/运行时 | Node.js 20 + TypeScript | 与团队现有 DSH 插件/解析器栈一致;端侧可零依赖单文件运行 |
| Hub 框架 | Fastify | 轻量、schema 校验、性能足够 |
| 存储 | SQLite(better-sqlite3,WAL) | 单文件运维最简,单机写吞吐远超需求;HA 演进路径 Postgres(v1.1+) |
| 端侧服务 | 单文件 Node 脚本(仅用 `node:http`/`node:fs`,零 npm 依赖) | 五平台安装脚本最简化;crash 自启交给系统服务管理器 |
| 部署 | 裸机 systemd / Docker Compose(hub + 卷 + Caddy TLS) | 二选一,运维成本最低 |
## 10. 部署与运维
- **Hub 单机部署**:`kizuna-hub` 进程(默认端口 7700)+ `data/kizuna.db`;Docker Compose 样例随仓库提供;Caddy 反代终止 TLS。
- **备份**:每日 `sqlite3 .backup` 快照(含留痕),保留 30 份;留痕另按月导出 JSONL 归档。
- **监控指标**(`GET /metrics`,Prometheus 文本格式,v1.0):
- `kizuna_queue_depth`(队列深度;> 1000 告警)
- `kizuna_deliveries_total{outcome}`(投递结果计数)
- `kizuna_delivery_latency_seconds`(投递→回执时延直方图)
- `kizuna_agents_online`(在线 Agent 数)
- `kizuna_audit_deletions_total{outcome}`(出队留痕计数)
- **日志**:结构化 JSON 日志,按 10MB × 5 轮转;投递与出队留痕事件单独打点。