agent-fleet/openspec/changes/agent-fleet-platform/design.md
Zer4tul aabd52ed52 init: OpenSpec project scaffolding with proposal, design, specs, tasks
- 7 capabilities: agent-registry, task-router, receipt-protocol,
  forgejo-integration, matrix-chatops, agent-adapter, orchestrator-core
- Tech stack: Rust + axum + zigbuild (single binary)
- Forgejo as task source of truth
- Matrix as real-time ChatOps layer
- Adapter pattern for multi-agent type support
2026-05-11 14:37:43 +08:00

9.6 KiB
Raw Blame History

Context

当前有多台机器WSL2 workstation、MacBook、可能的云服务器运行不同类型的 AI Agent

  • OpenClawJeeves orchestrator + 多个 sub-agent
  • Claude Code:本地 CLI 编码
  • Codex CLIOpenAI 编码
  • Hermes Agent:其他类型
  • ACP-compatible agentsDevin 等

现状Agent 之间无统一协同机制,任务靠人工传递,进度靠人工追踪,没有结构化的完成验证。

已有基础设施:

  • Matrix0x08.org homeserver 已运行
  • Forgejo:需要部署(建议自建,或用 Gitea 替代)
  • 各机器可以互相网络通信

Goals / Non-Goals

Goals:

  • 建立统一的 Agent 编排平台,支持任意类型 Agent 加入
  • Forgejo Issue/PR 作为任务事实来源,人类和 Agent 共用同一界面
  • Matrix 作为实时协同层,提供 ChatOps 和通知
  • 结构化 receipt 协议,不信任模型口头声明
  • 最小化对现有 Agent 的侵入性adapter 模式)
  • 可扩展:新 Agent 类型只需实现 adapter 即可接入

Non-Goals:

  • 不做 Agent 自身的改进Agent 内部行为不在 scope 内)
  • 不做 IDE 集成VS Code / JetBrains 插件)
  • 不做 LLM provider 管理(使用各 Agent 自己的 provider 配置)
  • 不做前端 Web UIPhase 1 用 Forgejo + Matrix 作为 UI
  • 不替换 cc-connect / cc-telegram-bridge它们可以作为 adapter 接入)

Decisions

Decision 1: Forgejo而非 Gitea作为 Git 平台

选择: Forgejo

理由:

  • Forgejo 是 Gitea 的社区 fork治理更开放
  • 内置 CI/CDForgejo Actions未来可扩展
  • API 兼容 Gitea迁移成本低
  • 支持 F3 (Friendly Forge Format),数据可移植

替代方案:

  • Gitea更成熟但治理有争议公司化
  • GitHub不方便自建网络可达性差
  • GitLab太重资源消耗大

Decision 2: Orchestrator 用 Rust + zigbuild

选择: Rust + cargo-zigbuild交叉编译

理由:

  • 内存安全:无 GC、无 data race、无 null pointer
  • 单二进制部署zigbuild 交叉编译,无 libc 兼容性问题
  • 性能:原生性能,适合长运行服务
  • 类型系统:强类型 + algebraic data types适合状态机建模
  • 生态reqwestHTTP、matrix-sdkMatrix、rusqliteSQLite、serdeJSON/TOML
  • 交叉编译cargo-zigbuild 一行命令产出 Linux/macOS/Windows 二进制

替代方案:

  • TypeScript (Node.js):开发快但需要运行时,非单二进制
  • Go单二进制但内存安全不如 Rust交叉编译有 libc 问题

Decision 3: Agent-Orchestrator 通信用 HTTP APIaxum

选择: Agent 通过 HTTP API 与 Orchestrator 通信Orchestrator 端用 axum 框架

理由:

  • 跨语言、跨平台:任何 Agent 都能发 HTTP 请求
  • 防火墙友好
  • 与 Forgejo webhook 统一协议栈
  • axum 是 Rust 生态最成熟的 async HTTP 框架

替代方案:

  • WebSocket实时性好但复杂度高重连逻辑麻烦
  • gRPC性能好但 Agent 端实现门槛高
  • Message QueueNATS/Redis增加基础设施依赖

Decision 4: Event sourcing 作为审计基础rusqlite

选择: 所有状态变更作为不可变事件 append 到 SQLite通过 rusqlite 访问

理由:

  • 完整审计追踪
  • 可重放
  • 轻量,不需要外部数据库,适合单二进制打包
  • rusqlite 是 Rust 生态最成熟的 SQLite 绑定
  • 适合中小规模 Agent 舰队(< 100 agents

替代方案:

  • PostgreSQL强大但引入运维复杂度破坏单二进制部署
  • JSON 文件:简单但无查询能力
  • 只记录当前状态:无法审计

Decision 5: Adapter 模式接入多类型 Agent

选择: 定义统一 adapter interface各 Agent 实现自己的 adapter

理由:

  • 最小侵入Agent 不需要改内部逻辑
  • 可扩展:新 Agent 类型只需写 adapter
  • 隔离adapter 故障不影响 Orchestrator

adapter 执行方式:

  • Claude Code adapterspawn claude -p 子进程
  • Codex adapterspawn codex exec 子进程
  • OpenClaw adapter调用 gateway HTTP API
  • ACP adapter通过 ACP 协议 WebSocket
  • Shell adapter执行任意 shell 命令(最通用)

Decision 6: 配置用 TOML

选择: TOML与 cc-connect 一致)

理由:

  • 人类可读性好
  • 类型比 YAML 更明确
  • cc-connect 已验证

Risks / Trade-offs

Risk: Agent 执行环境差异

不同机器上的 Agent 运行环境OS、依赖、权限不同可能导致同一任务在不同 Agent 上行为不一致。 → Mitigation: adapter 负责环境检查任务描述尽量环境无关receipt 验证在 Forgejo 侧(与执行环境解耦)。

Risk: Orchestrator 单点故障

Orchestrator 挂了,整个舰队停摆。 → Mitigation: Phase 1 接受单点Orchestrator 做到无状态(状态在 SQLite + Forgejo快速重启恢复。

Risk: Forgejo webhook 延迟或丢失

网络抖动可能导致 webhook 丢失。 → Mitigation: Orchestrator 定期轮询 Forgejo 做对账reconciliation弥补 webhook 丢失。

Risk: Agent 心跳风暴

大量 Agent 同时发心跳可能导致 Orchestrator 负载过高。 → Mitigation: 心跳间隔可配置(默认 60sOrchestrator 做好限流。

Trade-off: 简单性 vs 实时性

选择 HTTP 而非 WebSocket/MQ 意味着实时性稍差。 → 对于任务编排场景秒级延迟完全可接受。Matrix 通知提供实时感。

Architecture Overview

┌─────────────┐     webhook      ┌──────────────────┐
│   Forgejo    │ ───────────────→ │   Orchestrator    │
│  (Git+Issue) │ ←─── API ────── │   (Node.js)       │
└─────────────┘                  │                   │
                                 │  - Agent Registry │
┌─────────────┐     HTTP API     │  - Task Router    │
│   Matrix     │ ←──────────────→│  - Event Log      │
│  (ChatOps)   │                  │  - Receipt Validator│
└─────────────┘                  └────────┬──────────┘
                                          │ HTTP API
                      ┌───────────────────┼───────────────────┐
                      │                   │                   │
               ┌──────▼──────┐     ┌──────▼──────┐     ┌──────▼──────┐
               │  Adapter A  │     │  Adapter B  │     │  Adapter C  │
               │ Claude Code │     │  Codex CLI  │     │  OpenClaw   │
               │  (host-01)  │     │  (host-02)  │     │  (host-03)  │
               └─────────────┘     └─────────────┘     └─────────────┘

Data Model (Core)

interface Agent {
  agent_id: string;
  agent_type: "openclaw" | "claude-code" | "codex-cli" | "hermes" | "acp" | "shell";
  hostname: string;
  capabilities: string[];
  max_concurrency: number;
  current_tasks: number;
  status: "online" | "offline" | "draining";
  last_heartbeat_at: string; // ISO 8601
  registered_at: string;
  metadata: Record<string, string>;
}

interface Task {
  task_id: string;
  source: string; // "forgejo:<repo>#<issue>"
  type: string;   // from label: "code", "review", "test", "deploy", "research"
  priority: "low" | "normal" | "high" | "urgent";
  status: TaskStatus;
  assigned_agent_id?: string;
  requirements: string; // Issue body
  labels: string[];
  created_at: string;
  assigned_at?: string;
  started_at?: string;
  completed_at?: string;
  retry_count: number;
  max_retries: number;
  timeout_seconds: number;
}

type TaskStatus = "created" | "assigned" | "running" | "completed" | "failed" | "agent_lost" | "cancelled";

interface Receipt {
  task_id: string;
  agent_id: string;
  status: "completed" | "failed" | "partial";
  duration_seconds: number;
  summary: string;
  artifacts: Artifact[];
  error?: string;
}

interface Artifact {
  type: "pr" | "commit" | "file" | "comment" | "url";
  url?: string;
  path?: string;
  description?: string;
}

interface TaskEvent {
  event_id: string;
  task_id: string;
  event_type: string; // "created", "assigned", "started", "receipt_submitted", "completed", "failed", ...
  agent_id?: string;
  timestamp: string;
  payload: Record<string, unknown>;
}

Migration Plan

  1. 部署 Forgejo:自建实例,配置 webhook
  2. 部署 OrchestratorNode.js 进程,连接 Forgejo + Matrix
  3. 实现 Claude Code adapter:第一个 adapter验证端到端流程
  4. 实现 Codex adapter:第二个 adapter
  5. 实现 OpenClaw adapter:第三个 adapter可选 Phase 2
  6. 逐步迁移现有任务到 Forgejo Issue

Rollback

  • Orchestrator 可随时停止,不影响 Forgejo 和 Matrix
  • Agent 可独立运行(只是没有编排)
  • 所有状态在 Forgejo Issue + SQLite可手动接管

Open Questions

  1. Forgejo 部署位置 已部署在 arm0.0x08.org域名 git.0x08.org
  2. Orchestrator 部署位置TBDarm0 / WSL2 / 独立机器)
  3. Agent 工作目录策略:每个任务 clone 新 worktree 还是共享 workspace
  4. 权限模型Issue comment 中的 /assign 命令是否需要权限控制?
  5. cc-connect 集成 放 Phase 2