Merge pull request #3 from xmindlab-heicode/feature/v6-benchmark-v2

Agent Swarm v6:基准 v2.1、主控 Agent、对等回复与客户端指南
This commit is contained in:
Fasthei
2026-06-09 17:12:33 +08:00
committed by GitHub
41 changed files with 2692 additions and 20 deletions
+56
View File
@@ -0,0 +1,56 @@
name: CI
on:
push:
branches: ["**"]
pull_request:
branches: ["**"]
jobs:
guardrails:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Defense in depth: fail if secrets or heavy/generated dirs were ever committed.
- name: Block secrets & node_modules
run: |
if git ls-files | grep -E '(^|/)\.env($|\.)|(^|/)secrets/|\.pem$|\.key$|\.p12$|\.pfx$|(^|/)id_rsa$|(^|/)id_ed25519$'; then
echo "::error::Secret-like files are tracked — remove them and rotate any exposed credential."; exit 1
fi
if git ls-files | grep -E '(^|/)node_modules/'; then
echo "::error::node_modules is tracked — it must be gitignored."; exit 1
fi
- name: Required standards files present
run: |
for f in CLAUDE.md PROJECT_STANDARD.md README.md; do
test -f "$f" || { echo "::error::Missing required file: $f"; exit 1; }
done
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r orchestrator/requirements.txt -r agent/requirements.txt
# Hermetic: in-memory store, planner forced offline by the tests — no model key needed.
- name: Manager contract test
env: { REDIS_FAKE: "1" }
run: python scripts/test-runtime-contract.py
- name: Workflow mechanism smoke test
env: { REDIS_FAKE: "1" }
run: python scripts/test-merge-smoke.py
- name: End-to-end workflow test
env: { REDIS_FAKE: "1" }
run: python scripts/test-workflow-e2e.py
+1 -1
View File
@@ -1,6 +1,6 @@
# CLAUDE.md — HeiCode Swarm(执行面 / 运行时)
本仓库(`agent_swarm_v5`,对应 **HeiCode-Swarm**)是蜂群执行面 / 回调 / Swarm Runtime。整体介绍见 [README.md](README.md),工程标准见 [PROJECT_STANDARD.md](PROJECT_STANDARD.md),交付说明见 [docs/DELIVERY.md](docs/DELIVERY.md)。
本仓库(`agent_swarm_v6`,对应 **HeiCode-Swarm**)是蜂群执行面 / 回调 / Swarm Runtime。整体介绍见 [README.md](README.md),工程标准见 [PROJECT_STANDARD.md](PROJECT_STANDARD.md),交付说明见 [docs/DELIVERY.md](docs/DELIVERY.md)。
## 改动前必读
1. 先读本 `CLAUDE.md` 与本仓 `PROJECT_STANDARD.md`。
+123
View File
@@ -0,0 +1,123 @@
# 客户端接入指南(Client Guide)
本文说明**客户端如何与 Agent Swarm 运行时交互**。系统总览见 [README.md](README.md);接口契约见 [docs/integration/runtime-contract.md](docs/integration/runtime-contract.md);事件结构见 [docs/integration/event-schema.md](docs/integration/event-schema.md)。
> 架构说明:生产链路中,终端用户通过 **Heicode Manager / 桌面客户端** 提交需求,由 Manager 调用本 Swarm 运行时(见 `heicode-swarm-deferred` 裁定)。「客户端」在本文指**调用 Swarm 运行时 REST API 的一方**(Manager、联调脚本或开发者)。Swarm 不在对话回路,通过**带签名回调**回报状态。
---
## 1. 两个交互面
1. **REST API(客户端 → Swarm)**:创建部署、查询状态/任务/日志/事件/指标、审批、停止。
2. **回调事件流(Swarm → 客户端/Manager)**:生命周期事件经带 HMAC 签名的回调推送(见 §6)。
WebSocket `/ws/{agent_id}` 仅供 **Agent 执行单元** 接入,**不是客户端接口**。
---
## 2. 鉴权
- 客户端调用带 `Authorization: Bearer <token>`,Swarm 校验 `AGENT_RUNTIME_SERVICE_TOKEN`(兼容 `AGNET_RUNTIME_SERVICE_TOKEN`)。
- 未配置令牌时为**非安全开发模式**(不校验,仅本地)。
- 常用请求头:`X-Correlation-ID`(贯穿追踪)、`X-Idempotency-Key`(创建幂等)。
- 响应信封:成功 `{ "success": true, "data": {...} }`;失败 `{ "success": false, "error": {code,message,request_id} }`。
---
## 3. 生命周期:创建 → 观察 → (审批)→ 停止
### 3.1 创建部署
`POST /api/swarms`(别名 `/api/agent/swarm/deployments`、`/api/agnet/deployments`)
```bash
curl -s -X POST http://<host>:8000/api/swarms \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Correlation-ID: corr-123" \
-H "X-Idempotency-Key: idem-123" \
-d '{
"mode": "swarm",
"requirement": { "objective": "实现 add(a,b) 并补充测试与说明" },
"callback": { "url": "https://your-manager/api/agent/callbacks/runtime-events", "subscribed_events": [] },
"metadata": { "manager_deployment_id": "dep-1" }
}'
```
必填:`requirement.objective`(或 `orchestration_plan.objective`)、`callback.url`、`metadata.manager_deployment_id`。
响应 `data`:`deployment_id`、`runtime_deployment_id`、`swarm_id`(=workflow_id)、`manager_deployment_id`、`mode`、`status`、`created`。**请保存 `deployment_id`** 用于后续查询。
### 3.2 观察
| 接口 | 用途 |
|---|---|
| `GET /api/swarms/{deployment_id}` | 部署状态概要 |
| `GET /api/swarms/{deployment_id}/workflow` | 工作流/阶段视图(phases、agents、tokens、tools、artifacts、summary) |
| `GET /api/swarms/{deployment_id}/tasks` | 任务 DAG(task_id、agent_role、status、depends_on…) |
| `GET /api/swarms/{deployment_id}/logs`(`/events`) | 事件流,分页 `?limit=&cursor=`(断线后用 cursor 回补) |
| `GET /api/swarms/{deployment_id}/metrics` | 任务/Agent/预算指标 |
| `GET /api/swarms/{deployment_id}/diagnostics` | 失败、回调尝试、审批诊断 |
状态机:`waiting_approval → running → (blocked ⇄ running) → completed | failed | stopped`。
### 3.3 审批(高危操作)
当部署进入 `waiting_approval` 并推送 `approval.requested` 时,客户端经 Manager 审批后回执:
```bash
curl -s -X POST http://<host>:8000/api/swarms/<deployment_id>/approvals/<approval_id> \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "decision": "approved" }' # 或 "rejected",可带 credential_ref / reason
```
### 3.4 停止
```bash
curl -s -X POST http://<host>:8000/api/swarms/<deployment_id>/stop \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "reason": "client requested stop" }'
```
---
## 4. 健康检查
- `GET /` → `{status, service}`;`GET /health` → 含 Redis 与连接数;`GET /metrics` → Prometheus。
---
## 5. 启用完整工作流
默认仅执行 Manager 提供的编排;要启用「分解 → 评审/重做 → 汇总」主控工作流,运行 Swarm 时设置(默认关闭):
- `ENABLE_PLANNER_FALLBACK=1`:无 Manager 分工时由主控 Agent 自动分解。
- `ENABLE_REVIEW_LOOP=1` / `MAX_REVIEW_CYCLES=2`:主控评审与重做循环 + 结果汇总。
- 模型走 OpenAI 兼容网关(`OPENAI_API_KEY`/`OPENAI_API_BASE`/`OPENAI_MODEL`,经 `secret_ref` 注入)。
---
## 6. 回调事件流(Swarm → 客户端/Manager)
Swarm 向 `callback.url` POST 事件,**带 HMAC 签名**(客户端须验签):
- 头:`X-Agent-Timestamp`(ms)、`X-Agent-Signature`、`X-Agent-Event-Id`、`X-Correlation-ID`(+ `X-Agnet-` 兼容别名)。
- 签名:`sha256=hex(HMAC_SHA256(secret, "{timestamp}.{event_id}.{raw_body}"))`,secret = `AGENT_CALLBACK_SIGNING_SECRET`,时间容差 300s。
- 幂等:按 `X-Agent-Event-Id` → body `event_id` → `idempotency_key` 去重。
- 事件类型与必填字段:见 [event-schema.md](docs/integration/event-schema.md)(`deployment.status_changed`、`task.*`、`handoff.*`、`approval.requested`、`artifact.created`、`timeline.updated`、`budget.alert`…)。
客户端可只消费 REST(轮询 `/workflow`+`/logs?cursor=`),或同时接收回调。
---
## 7. 本地联调(无需密钥)
```bash
# 1) 编排器(内存存储 + 工作流开关)
set "REDIS_FAKE=1" & set "ENABLE_PLANNER_FALLBACK=1" & set "ENABLE_REVIEW_LOOP=1"
python -m uvicorn orchestrator.main:app --host 0.0.0.0 --port 8000
# 2) 无密钥桩 Agent(联调用)
set "ORCHESTRATOR_URL=ws://localhost:8000" & set "AGENT_ID=stub-1" & set "AGENT_CAPABILITIES=python,code_generation,testing,pytest,technical-writing,general"
python scripts/stub_agent.py
# 3) 用 §3.1 的 curl 提交需求,再用 §3.2 观察
```
---
## 8. 说明与边界
- **桌面/前端 UI**:当前桌面客户端为单 Agent A2A 直连模型;**蜂群图 / 协作时间线**的前端事件 API 尚未冻结(见 [frontend-event-api.md](docs/integration/frontend-event-api.md))。
- **不得在请求/回调/日志中出现明文密钥**;凭据一律 `secret_ref`(`azkv://`)。见 [security-boundary.md](docs/integration/security-boundary.md)。
- 计费在 NewAPI(模型网关)侧按请求计量,Swarm 仅上报用量;见 [usage-billing-schema.md](docs/integration/usage-billing-schema.md)。
+15 -1
View File
@@ -1,9 +1,23 @@
# Agent Swarm(HeiCode Swarm)
一个多智能体「蜂群」系统:用户提出需求后,由主控逻辑自动将其分解为多个子任务,分发给擅长不同领域的专家 Agent 并行完成;专家之间可就重叠领域相互协作;产出汇总后由主控评审是否达标,未达标则退回重做,循环直至生成满意的最终回答。
一个多智能体「蜂群」系统:用户提出需求后,由**主控 Agent(Master Agent,`orchestrator/master_agent.py`)**自动将其分解为多个子任务,分发给擅长不同领域的专家 Agent 并行完成;专家之间可就重叠领域相互协作;产出汇总后由主控 Agent 评审是否达标,未达标则退回重做,循环直至生成满意的最终回答。主控 Agent 负责「分解 / 评审决策 / 汇总」的认知决策,编排器负责执行其决策(派发、重开任务、持久化、事件)。
> ⚠️ **能力边界(主链路接入状态)**
> 本仓当前是一个**可运行的多 Agent 工作流运行系统**,**尚未**作为 Heicode 主链路的正式 Runtime Backend 接入。下表区分能力状态;契约见 [docs/integration/](docs/integration/),量化标准见 [docs/benchmark/](docs/benchmark/)。
>
> | 能力 | 状态 |
> |---|---|
> | 任务分解(规划回退)、能力路由派发、专家执行(OpenAI 兼容)、评审/重做循环、结果汇总、peer 协作消息路由、WebSocket Agent 协议、Redis 持久化、Prometheus 指标 | ✅ 已实现(仓内可运行) |
> | Manager↔Runtime 生命周期契约(对齐 `heicode-am-contract`)、HMAC 签名回调事件 envelope、稳定的 `deployment_id`/`workflow_id`/`trace_id` | 🟡 待接入(见 `docs/integration/runtime-contract.md`;事件 envelope 与签名待 Manager 对齐) |
> | 统一 usage/计费聚合(归属 NewAPI)、审计/lineage trace、前端事件 API、统一 Agent registry/scheduling、统一 secret/workspace/tool/MCP/tenant 安全边界 | 🟡 待接入(需与 Billing / Audit / Frontend / Infra / Security Team 对齐) |
> | Benchmark 自证(`Benchmark_Agent`、`S_swarm`、`G_E`、`G_E,c`、治理/协作/通信/鲁棒性指标、baseline 对比、telemetry 架构) | 🔴 规划中(标准见 `docs/benchmark/`,采集器尚未落地) |
>
> 在以上「待接入 / 规划中」项目完成并经对应 Team 验收前,本文与各子文档**不得宣称**「已接入主链路」或「已具备完整 Agent Swarm 工程能力」。
## 系统架构
> 说明:下图中 **Heicode Manager → Orchestrator** 为**目标形态**。当前 orchestrator 仅实现了占位的 Manager 面接口与回调骨架,尚未按 `heicode-am-contract` 正式注册为 Runtime Backend(见 `docs/integration/runtime-contract.md`)。
```
用户
│
+54 -12
View File
@@ -74,6 +74,7 @@ class Agent:
MAX_CONCURRENT_TASKS = int(os.getenv("MAX_CONCURRENT_TASKS", "4"))
TASK_TIMEOUT_SECONDS = int(os.getenv("TASK_TIMEOUT_SECONDS", "60"))
PEER_REPLY_TIMEOUT_SECONDS = int(os.getenv("PEER_REPLY_TIMEOUT_SECONDS", "20"))
HEARTBEAT_INTERVAL_SECONDS = 15
def __init__(
@@ -102,6 +103,8 @@ class Agent:
self.peer_waiters: dict[str, asyncio.Future] = {}
# Summary of this agent's most recently completed task, shared when peers consult it.
self.last_summary: Optional[str] = None
# Lazily-created executor used to compose substantive peer replies.
self._peer_executor: Optional[TaskExecutor] = None
self.workspace_git = GitOperations(str(self.workspace_dir), self.agent_id)
@@ -233,28 +236,66 @@ class Agent:
summary = result.get("summary")
return summary.strip() if isinstance(summary, str) and summary.strip() else None
def _get_peer_executor(self) -> TaskExecutor:
# Lazily create a TaskExecutor for peer replies (reuses the model client / workspace).
# Raises if no model key is configured; callers fall back to the cached summary.
if self._peer_executor is None:
self._peer_executor = TaskExecutor(agent_id=self.agent_id, workspace_dir=str(self.workspace_dir))
return self._peer_executor
def _peer_fallback_reply(self) -> dict:
# Cheap, no-LLM reply used when the model is unavailable or errors.
shared = (
f"My latest result: {self.last_summary}. "
if self.last_summary
else "No completed result yet. "
)
content = (
f"From {self.agent_id} (capabilities: {', '.join(self.capabilities)}). {shared}"
"Treat implementation artifacts as the source of truth for behavior and exception semantics."
)
return {"content": content, "stance": "info", "evidence": self.last_summary or "", "refs": []}
async def _build_peer_reply(self, query: str, task_id: Optional[str]) -> dict:
# Produce a substantive, query-scoped reply grounded in this agent's own work.
# Falls back to the cached summary if there is no query, no model key, or the call fails.
if not query:
return self._peer_fallback_reply()
try:
executor = self._get_peer_executor()
except Exception as e:
logger.warning(f"peer reply executor unavailable ({e}); using cached summary")
return self._peer_fallback_reply()
try:
return await asyncio.wait_for(
executor.peer_reply(
query=query,
capabilities=self.capabilities,
last_summary=self.last_summary,
),
timeout=self.PEER_REPLY_TIMEOUT_SECONDS,
)
except Exception as e:
logger.warning(f"peer reply LLM failed ({e}); using cached summary")
return self._peer_fallback_reply()
async def answer_peer_query(self, message: dict):
# Respond to an inbound peer query, sharing what this agent has actually done so far.
# Respond to an inbound peer query with a substantive, grounded reply (LLM, with fallback).
requester = message.get("from_agent_id") or message.get("agent_id")
correlation_id = message.get("correlation_id")
if not requester or not correlation_id:
return
shared = (
f"My latest result: {self.last_summary}"
if self.last_summary
else "I have no completed result to share yet."
)
reply_content = (
f"From {self.agent_id} (capabilities: {', '.join(self.capabilities)}). {shared} "
"Treat implementation artifacts as the source of truth for behavior and exception semantics."
)
reply = await self._build_peer_reply((message.get("content") or "").strip(), message.get("task_id"))
try:
await self.safe_send({
"type": "peer_message",
"agent_id": self.agent_id,
"target_agent_id": requester,
"task_id": message.get("task_id"),
"content": reply_content,
"content": reply.get("content", ""),
"stance": reply.get("stance"),
"evidence": reply.get("evidence"),
"refs": reply.get("refs"),
"correlation_id": correlation_id,
"is_reply": True,
"timestamp": time.time(),
@@ -489,7 +530,8 @@ class Agent:
message.get("task_id"),
message.get("from_agent_id") or message.get("agent_id"),
)
await self.answer_peer_query(message)
# Answer in the background so a slow (LLM) reply doesn't stall the message loop.
asyncio.create_task(self.answer_peer_query(message))
async def handle_message(self, message: dict):
# Route each inbound orchestrator message to the appropriate handler.
+38
View File
@@ -292,6 +292,44 @@ Return ONLY the JSON, no other text."""
self._record_openai_usage(response)
return response.choices[0].message.content or ""
async def peer_reply(self, *, query: str, capabilities: list[str],
last_summary: Optional[str], max_tokens: Optional[int] = None) -> dict:
"""Compose a substantive, grounded reply to a peer agent's query (one bounded LLM call).
Returns {stance, content, evidence, refs}. `content` is what the requesting agent reads.
Bounded by PEER_CONSULT_MAX_TOKENS (default 500).
"""
if max_tokens is None:
max_tokens = int(os.getenv("PEER_CONSULT_MAX_TOKENS", "500"))
workspace_files = self._summarize_workspace()
workspace_context = self._collect_workspace_context(max_files=8)
prompt = f"""You are a specialist agent being consulted by a peer in a collaborative swarm.
Your capabilities: {', '.join(capabilities)}
Your latest completed work (summary): {last_summary or 'none'}
A peer asks:
{query}
Your workspace files:
{json.dumps(workspace_files, indent=2)}
Relevant workspace file contents:
{json.dumps(workspace_context, indent=2)}
Answer concisely and concretely, grounded in YOUR actual work/artifacts. Treat implementation
artifacts as the source of truth for behavior and exception semantics; if the peer's assumption
conflicts with your work, say so.
Return ONLY JSON:
{{\"stance\": \"agree|disagree|info\", \"content\": \"<concise answer to the peer>\", \"evidence\": \"<what in your work supports this>\", \"refs\": [\"relative/file/path\"]}}"""
content = await self._complete(prompt, max_tokens=max_tokens)
result = self._parse_json_response(content)
if not result.get("content"):
result["content"] = (content or "").strip()[:1000]
result.setdefault("stance", "info")
result.setdefault("evidence", "")
result.setdefault("refs", [])
return result
def _empty_usage(self, context: Optional[dict] = None) -> dict:
plan = ((context or {}).get("orchestration_plan") or {})
billing = plan.get("billing_context") or {}
+22
View File
@@ -0,0 +1,22 @@
# benchmark/ —— 蜂群基准(骨架)
对应 [`docs/benchmark/`](../docs/benchmark/) 的标准实现入口。依据 **Agent 蜂群指标量化与标准 v2.0**。
## 实现状态
| 模块 | 内容 | 状态 |
|---|---|---|
| `metrics.py` | v2.0 `SwarmMetrics`(15 字段)+ **纯公式**(`pheromone`/`heuristic`/`p_decision`/`reward`/`completion`/`collaboration`/`communication`/`cost`/`robustness`/`governance`/`swarm_score`/`emergence_gain`/`swarm_cost`/`cost_normalized_gain`/`benchmark_agent`)+ v2.0 推荐权重常量 | ✅ 已实现、可单测(`test-benchmark-metrics.py`) |
| `collectors/` | base `SwarmMetricsCollector` + `SwarmRunMetricsCollector`(从真实 run 计算 `s_completion`/`s_collaboration`/`s_cost`/`s_robustness`;其余标记 NaN + `coverage=False`) | 🟡 部分落地(4/15 字段真实可算) |
| `baselines/` | Single / Chain / Sub-Agent / Strong 基线运行器 | 🔴 未落地 |
| `replay/` | 执行回放 | 🔴 未落地 |
| `leaderboard/` | 排行榜聚合 | 🔴 未落地 |
> 公式可计算 ≠ 能自证。**数据采集(collectors)与基线(baselines)未落地前,不得宣称已具备
> Agent Swarm 量化自证能力**(不能证明 `Swarm > Single/Chain/Sub-Agent/Strong`,也无法输出完整 `Benchmark_Agent`)。
## 待落地(按依赖顺序)
1. `collectors/`:接入事件流 / Prometheus / OTel / 任务与用量(telemetry-architecture)。
2. `baselines/`:四类基线 + 统一任务集(emergence-evaluation / baseline-comparison)。
3. `replay/`、`leaderboard/`。
4. 权重 v2.0 已给定(`λ`/`w*`/`γ*`,Σλ=1.0);`α/β/ρ/N_agent/ε` 用 `THETA_DEFAULTS` 推荐初值,调优口径待定。
+8
View File
@@ -0,0 +1,8 @@
"""Agent Swarm benchmark package.
实现状态(见 docs/benchmark/swarm-benchmark-protocol.md §6):
- metrics.py:指标公式(纯函数,已实现、可测)+ SwarmMetrics 数据类。
- collectors/ baselines/ replay/ leaderboard/:骨架,数据采集与基线运行**未落地**。
在采集器与基线运行器落地前,不得宣称本仓具备 Agent Swarm 量化自证能力。
"""
+14
View File
@@ -0,0 +1,14 @@
"""Benchmark baselines — Single / Chain / Sub-Agent / Strong(v2.0 §9.1)。
两部分:
- **比较评估器(已落地)**:`comparison.py` 定义共享记录 `BenchmarkRunRecord` 与 `compare`/`evaluate`,
在给定 swarm 与 baseline 记录时计算 `G_E` / `G_E,c`(纯函数、可单测)。
- **基线运行器(未落地,🔴)**:实际运行各基线系统、在统一任务集上产出 `BenchmarkRunRecord` 的 runner,
以及对应的 Quality 插桩(TestPass/CodeReview/UserAcceptance),尚未实现。
即:一旦 swarm 与 sub-agent 程序按 `BenchmarkRunRecord` 产出记录,比较即可计算。
记录的产生(尤其 Quality)见 docs/benchmark/baseline-record-schema.md。
"""
from .comparison import ( # noqa: F401
BenchmarkRunRecord, SYSTEMS, quality, cost_efficiency, unified_metrics, compare, evaluate,
)
+123
View File
@@ -0,0 +1,123 @@
"""Baseline comparison adapter — Agent 蜂群指标量化与标准 v2.0.
Defines the shared `BenchmarkRunRecord` that BOTH the swarm and each baseline (Single / Chain /
Sub-Agent / Strong) emit per benchmark run, plus the evaluator that computes G_E and G_E,c from
two records. Pure/deterministic given records — the records themselves must be PRODUCED by the
respective programs (instrumentation, esp. Quality, is pending; see
docs/benchmark/baseline-record-schema.md). No record → no comparison (we never fabricate scores).
"""
from __future__ import annotations
from dataclasses import dataclass
from ..metrics import (
quality_score, completion_score, speed_score, cost_score, robustness_score,
emergence_gain, swarm_cost, cost_normalized_gain, BASE_COEFFICIENTS,
)
SYSTEMS = {"swarm", "single", "chain", "sub", "strong"}
@dataclass
class BenchmarkRunRecord:
"""Unified per-run record (v2.0 §9 collection). Both swarm and baselines emit this."""
system: str # one of SYSTEMS
scenario: str # coding | refactoring | architecture | devops | bugfix
task_set_id: str # identifies the shared task set (fairness: same id across systems)
n_agent: int # agent count used by this system
completed_tasks: int
total_tasks: int
test_pass_rate: float # 0..100 (Quality input)
code_review_score: float # 0..100 (Quality input)
user_acceptance: float # 0..100 (Quality input)
budget_usd: float # planned cost
actual_cost_usd: float # measured model cost
model_tokens: int
target_time_s: float # planned/target time
actual_time_s: float # measured time
recovered_failures: int
total_failures: int
@classmethod
def from_dict(cls, data: dict) -> "BenchmarkRunRecord":
missing = [f for f in cls.__dataclass_fields__ if f not in data]
if missing:
raise ValueError(f"benchmark record missing required fields: {missing}")
if data["system"] not in SYSTEMS:
raise ValueError(f"unknown system '{data['system']}' (expected one of {sorted(SYSTEMS)})")
return cls(**{f: data[f] for f in cls.__dataclass_fields__})
# --- derived quantities (from a single record) ---
def quality(rec: BenchmarkRunRecord) -> float:
"""Q = 0.4·TestPass + 0.3·CodeReview + 0.3·UserAcceptance (v2.0 §4)."""
return quality_score(rec.test_pass_rate, rec.code_review_score, rec.user_acceptance)
def cost_efficiency(rec: BenchmarkRunRecord) -> float:
"""CostEfficiency = 100·Budget/ActualCost (v2.0 §4 E_cost口径)."""
return cost_score(rec.budget_usd, rec.actual_cost_usd)
def unified_metrics(rec: BenchmarkRunRecord) -> dict:
"""The protocol's unified collection (v2.0 §9): Completion / Quality / Cost / Time / Robustness."""
return {
"completion": completion_score(rec.completed_tasks, rec.total_tasks),
"quality": quality(rec),
"cost_efficiency": cost_efficiency(rec),
"speed": speed_score(rec.target_time_s, rec.actual_time_s),
"robustness": robustness_score(rec.recovered_failures, rec.total_failures),
}
# --- comparison (swarm vs one baseline) ---
def compare(swarm_rec: BenchmarkRunRecord, base_rec: BenchmarkRunRecord, *,
symmetric_cost: bool = True) -> dict:
"""Compute G_E and G_E,c for the swarm against one baseline record.
G_E = Q_swarm − Q_base
G_E,c = (Q_swarm / C_swarm) − (Q_base / C_base), C_swarm = N_agent·(CostEfficiency/100+0.5)
`symmetric_cost`:
- True (DEFAULT — Benchmark Owner ratified correction, 2026-06-09): `C_base` uses the SAME
formula as C_swarm with the baseline's own N_agent + CostEfficiency → both terms are
quality-per-cost (comparable, non-degenerate).
- False: literal v2.0 text — `C_base = 1.0` (scale-degenerate; kept for reference only).
See docs/benchmark/cost-normalized-gain.md and OWNER-NOTE-cost-normalized-gain.md.
"""
if swarm_rec.system != "swarm":
raise ValueError("swarm_rec.system must be 'swarm'")
if swarm_rec.task_set_id != base_rec.task_set_id:
raise ValueError("records must share the same task_set_id (fair comparison)")
q_swarm, q_base = quality(swarm_rec), quality(base_rec)
g_e = emergence_gain(q_swarm, q_base)
c_swarm = swarm_cost(swarm_rec.n_agent, cost_efficiency(swarm_rec))
c_base = swarm_cost(base_rec.n_agent, cost_efficiency(base_rec)) if symmetric_cost else 1.0
g_e_c = cost_normalized_gain(q_swarm, c_swarm, q_base, c_base=c_base)
return {
"base_system": base_rec.system,
"base_coefficient": BASE_COEFFICIENTS.get(base_rec.system),
"cost_mode": "symmetric" if symmetric_cost else "literal_v2.0",
"q_swarm": q_swarm,
"q_base": q_base,
"c_swarm": c_swarm,
"c_base": c_base,
"g_e": g_e,
"g_e_cost": g_e_c,
"raw_gain_positive": g_e > 0,
"cost_normalized_positive": g_e_c > 0,
}
def evaluate(swarm_rec: BenchmarkRunRecord, baseline_recs: list[BenchmarkRunRecord], *,
symmetric_cost: bool = True) -> dict:
"""Compare the swarm against all baselines. swarm_valid requires beating ALL on G_E and G_E,c."""
results = [compare(swarm_rec, b, symmetric_cost=symmetric_cost) for b in baseline_recs]
swarm_valid = bool(results) and all(r["raw_gain_positive"] and r["cost_normalized_positive"] for r in results)
return {
"scenario": swarm_rec.scenario,
"cost_mode": "symmetric" if symmetric_cost else "literal_v2.0",
"comparisons": results,
"swarm_valid": swarm_valid,
}
+20
View File
@@ -0,0 +1,20 @@
"""Benchmark collectors — 把运行时信号采集成 SwarmMetrics。
状态:**部分落地**。base `SwarmMetricsCollector.collect()` 故意抛 NotImplementedError(避免占位冒充完成,
组织规则 #9);具体实现 `run_collector.SwarmRunMetricsCollector` 从真实 run 计算
`completion`/`collaboration`/`cost`/`robustness`,其余指标返回 NaN 并在 `coverage` 标记 False。
未落地部分(gain/communication/p_decision/reward + 基线)见 docs/benchmark/telemetry-architecture.md 与 baseline-comparison.md。
"""
from __future__ import annotations
from ..metrics import SwarmMetrics
class SwarmMetricsCollector:
"""标准 §9 采集接口。子类应从运行时数据源装配 SwarmMetrics。"""
async def collect(self) -> SwarmMetrics:
raise NotImplementedError(
"数据采集未落地:需接入 telemetry-architecture 的数据源后,"
"用 benchmark.metrics 的公式装配 SwarmMetrics。"
)
+137
View File
@@ -0,0 +1,137 @@
"""SwarmRunMetricsCollector — compute SwarmMetrics from a real swarm run.
Wires benchmark.metrics formulas to a run's tasks + event stream (orchestrator state).
Only metrics with real data sources are computed; the rest are returned as NaN and flagged
in `coverage` (False) — we do NOT fake a 0/100 score for uncollected metrics (rule #9).
Uncollected today (need work flagged in docs/benchmark/swarm-metrics-schema.md):
- gain → needs baselines (emergence-evaluation)
- communication → needs agent message telemetry (not counted yet)
- p_decision → needs τ/η decision scoring (not implemented)
- reward → needs weights (standard gives no numeric w*)
- governance → only derivable from approvals; NaN when a run has no governed ops
"""
from __future__ import annotations
import json
import math
from collections import Counter
from . import SwarmMetricsCollector
from ..metrics import (
SwarmMetrics, completion_score, collaboration_score, cost_score, robustness_score,
governance_score,
)
def _task_cost(task) -> float:
"""Extract model_cost_usd from a task's stored result, 0.0 if absent."""
result = getattr(task, "result", None)
if not result:
return 0.0
try:
data = json.loads(result) if isinstance(result, str) else result
return float((data.get("usage") or {}).get("model_cost_usd") or 0.0)
except Exception:
return 0.0
class SwarmRunMetricsCollector(SwarmMetricsCollector):
"""Collect SwarmMetrics for one swarm run (by swarm_id)."""
def __init__(self, swarm_id: str):
self.swarm_id = swarm_id
self.coverage: dict[str, bool] = {}
async def collect(self) -> SwarmMetrics:
# Lazy import: the collector reads live orchestrator state.
from orchestrator.swarm_runtime import swarm_runtime
from orchestrator.task_queue import task_queue, TaskStatus
run = await swarm_runtime.get_run(self.swarm_id)
if not run:
raise ValueError(f"run not found: {self.swarm_id}")
tasks = [t for t in [await task_queue.get_task(tid) for tid in run.task_ids] if t]
events = (await swarm_runtime.list_events(self.swarm_id, limit=10000)).get("events", [])
event_types = Counter(e.get("event_type") for e in events)
def status(t):
return t.status.value if hasattr(t.status, "value") else t.status
# --- s_completion (real) ---
total = len(tasks)
completed = sum(1 for t in tasks if status(t) == TaskStatus.COMPLETED.value)
s_completion = completion_score(completed, total)
self.coverage["s_completion"] = total > 0
# --- s_collaboration (real) ---
req = event_types.get("handoff.requested", 0)
comp = event_types.get("handoff.completed", 0)
handoff_success = (100.0 * comp / req) if req else 100.0
dep_tasks = [t for t in tasks if t.depends_on]
done_ids = {t.task_id for t in tasks if status(t) == TaskStatus.COMPLETED.value}
resolved = [t for t in dep_tasks if all(d in done_ids for d in t.depends_on)]
dep_resolution = (100.0 * len(resolved) / len(dep_tasks)) if dep_tasks else 100.0
per_agent = Counter(t.assigned_agent_id for t in tasks if t.assigned_agent_id)
if per_agent:
counts = list(per_agent.values())
workload_balance = 100.0 * (min(counts) / max(counts))
else:
workload_balance = 100.0
s_collaboration = collaboration_score(handoff_success, dep_resolution, workload_balance)
self.coverage["s_collaboration"] = total > 0
# --- s_robustness (real) ---
failures = [t for t in tasks if t.retry_count > 0 or status(t) == TaskStatus.FAILED.value]
recovered = [t for t in failures if status(t) == TaskStatus.COMPLETED.value]
s_robustness = robustness_score(len(recovered), len(failures))
self.coverage["s_robustness"] = True
# --- s_cost (real if budget + usage present) ---
plan = (run.request_body or {}).get("orchestration_plan") or {}
budget = plan.get("budget") or {}
max_cost = budget.get("max_cost_usd")
actual = sum(_task_cost(t) for t in tasks)
if isinstance(max_cost, (int, float)) and actual > 0:
s_cost = cost_score(float(max_cost), actual)
self.coverage["s_cost"] = True
else:
s_cost = math.nan
self.coverage["s_cost"] = False
# --- s_governance (real only if the run had governed ops / approvals) ---
approvals = list((run.approvals or {}).values())
if approvals:
compliant = sum(1 for a in approvals if a.get("decision") in ("approved", "rejected"))
s_governance = governance_score(compliant, len(approvals))
self.coverage["s_governance"] = True
else:
s_governance = math.nan
self.coverage["s_governance"] = False
# --- not yet collectable (see docs/benchmark/metric-coverage-gaps.md) ---
# s_gain needs baselines; s_communication needs message telemetry; tau/eta/p_decision need
# the decision-layer signals; reward needs quality/risk/rework inputs; s_swarm/g_e/g_e_cost/
# benchmark depend on the above (any NaN component → NaN aggregate).
for k in ("tau", "eta", "p_decision", "reward", "s_gain", "s_communication",
"s_swarm", "g_e", "g_e_cost", "benchmark"):
self.coverage[k] = False
return SwarmMetrics(
tau=math.nan,
eta=math.nan,
p_decision=math.nan,
reward=math.nan,
s_completion=s_completion,
s_gain=math.nan,
s_collaboration=s_collaboration,
s_communication=math.nan,
s_cost=s_cost,
s_robustness=s_robustness,
s_governance=s_governance,
s_swarm=math.nan,
g_e=math.nan,
g_e_cost=math.nan,
benchmark=math.nan,
)
+6
View File
@@ -0,0 +1,6 @@
"""Benchmark leaderboard — 排行榜聚合与展示字段。
状态:**未落地(骨架)**。展示字段(标准 §11):Benchmark_Agent / S_swarm / G_E / G_E,c /
Reward / Cost Efficiency;场景:Coding / Refactoring / Architecture / DevOps / Bug Fix。
需定义 leaderboard schema 与持久化。
"""
+177
View File
@@ -0,0 +1,177 @@
"""Swarm benchmark metric formulas + SwarmMetrics schema.
Aligned to **Agent 蜂群指标量化与标准 v2.0**. Pure formulas only (given inputs → value, unit-testable);
data collection (turning runtime signals into inputs) is partial — see collectors/run_collector.py
and docs/benchmark/metric-coverage-gaps.md. Recommended weights/hyperparameters from v2.0 are
provided as defaults below.
"""
from __future__ import annotations
from dataclasses import dataclass
# --- v2.0 recommended weights / hyperparameters ---
TAU_WEIGHTS = {"success": 0.25, "quality": 0.20, "acceptance": 0.20,
"cost": 0.10, "time": 0.10, "risk": 0.08, "rollback": 0.07}
ETA_WEIGHTS = {"match": 0.25, "urgency": 0.15, "dependency": 0.15, "resource": 0.15,
"confidence": 0.10, "risk": 0.10, "budget_pressure": 0.10}
REWARD_WEIGHTS = {"s_task": 0.20, "q_quality": 0.20, "v_speed": 0.12, "e_cost": 0.13,
"r_robust": 0.13, "g_gov": 0.10, "p_risk": 0.07, "p_rework": 0.05}
SWARM_WEIGHTS = {"completion": 0.25, "gain": 0.20, "collaboration": 0.15, "communication": 0.10,
"cost": 0.10, "robustness": 0.10, "governance": 0.10}
LAMBDA_WEIGHTS = {"lambda1": 0.30, "lambda2": 0.20, "lambda3": 0.25, "lambda4": 0.15, "lambda5": 0.10} # Σ = 1.0
THETA_DEFAULTS = {"alpha": 1.0, "beta": 2.0, "rho": 0.10, "n_agent": 5, "epsilon": 0.10}
# Q_base reference coefficients (standard §6.1), by baseline type.
# ⚠️ PROVISIONAL / METADATA ONLY: these are assumed constants — the standard gives NO derivation.
# They are reported as `base_coefficient` but used in NO formula (G_E/G_E,c use raw Q_base).
# TODO(benchmark): quantify empirically later, e.g. coefficient = Q_baseline / Q_reference on the
# shared task set (reference = strongest baseline or an oracle/acceptance ceiling). Until then,
# do not wire into scoring. See docs/benchmark/baseline-comparison.md §1.1.
BASE_COEFFICIENTS = {"single": 0.75, "chain": 0.85, "sub": 0.90, "strong": 0.95}
@dataclass
class SwarmMetrics:
"""v2.0 §8.1 collection interface."""
tau: float # 信息素得分
eta: float # 启发式得分
p_decision: float # 决策概率
reward: float # 执行奖励
s_completion: float
s_gain: float
s_collaboration: float
s_communication: float
s_cost: float
s_robustness: float
s_governance: float
s_swarm: float # 蜂群总分
g_e: float # 涌现增益
g_e_cost: float # 成本归一化增益
benchmark: float # 综合评分
def _ratio_pct(numerator: float, denominator: float, *, empty: float = 0.0) -> float:
if not denominator:
return empty
return 100.0 * numerator / denominator
# --- 决策层(v2.0 §3) ---
def pheromone(*, success: float, quality: float, acceptance: float, cost: float, time: float,
risk: float, rollback: float, weights: dict = TAU_WEIGHTS) -> float:
w = weights
return (w["success"] * success + w["quality"] * quality + w["acceptance"] * acceptance
- w["cost"] * cost - w["time"] * time - w["risk"] * risk - w["rollback"] * rollback)
def heuristic(*, match: float, urgency: float, dependency: float, resource: float,
confidence: float, risk: float, budget_pressure: float, weights: dict = ETA_WEIGHTS) -> float:
g = weights
return (g["match"] * match + g["urgency"] * urgency + g["dependency"] * dependency
+ g["resource"] * resource + g["confidence"] * confidence
- g["risk"] * risk - g["budget_pressure"] * budget_pressure)
def action_probability(tau: float, eta: float, alpha: float, beta: float,
candidates: list[tuple[float, float]]) -> float:
# P(s,a,r) = τ^α·η^β / Σ τ_i^α·η_i^β
denom = sum((t ** alpha) * (e ** beta) for t, e in candidates)
if not denom:
return 0.0
return ((tau ** alpha) * (eta ** beta)) / denom
def p_decision(tau: float, eta: float, alpha: float = THETA_DEFAULTS["alpha"],
beta: float = THETA_DEFAULTS["beta"]) -> float:
# v2.0 §3.3: P_decision = τ^α · η^β · 100
return (tau ** alpha) * (eta ** beta) * 100.0
# --- 执行层(v2.0 §4) ---
def quality_score(test_pass_rate: float, code_review_score: float, user_acceptance: float) -> float:
return 0.4 * test_pass_rate + 0.3 * code_review_score + 0.3 * user_acceptance
def speed_score(target_time: float, actual_time: float) -> float:
return _ratio_pct(target_time, actual_time)
def cost_efficiency_score(expected_cost: float, actual_cost: float) -> float:
# Owner ruling (v2.1): E_cost (§4.1) == S_cost (§5.1) == CostEfficiency (§6.2) — one quantity,
# 100×Budget/ActualCost (Budget≡ExpectedCost, ActualUsage≡ActualCost). Same math as cost_score().
return _ratio_pct(expected_cost, actual_cost)
def rework_penalty(rework_count: int, total_tasks: int) -> float:
return _ratio_pct(rework_count, total_tasks)
def reward(*, s_task: float, q_quality: float, v_speed: float, e_cost: float, r_robust: float,
g_gov: float, p_risk: float, p_rework: float, weights: dict = REWARD_WEIGHTS) -> float:
w = weights
return (w["s_task"] * s_task + w["q_quality"] * q_quality + w["v_speed"] * v_speed
+ w["e_cost"] * e_cost + w["r_robust"] * r_robust + w["g_gov"] * g_gov
- w["p_risk"] * p_risk - w["p_rework"] * p_rework)
# --- 蜂群层(v2.0 §5) ---
def completion_score(completed_tasks: int, total_tasks: int) -> float:
return _ratio_pct(completed_tasks, total_tasks)
def collaboration_score(handoff_success_rate: float, dependency_resolution_rate: float,
workload_balance_score: float) -> float:
return (0.5 * handoff_success_rate + 0.3 * dependency_resolution_rate
+ 0.2 * workload_balance_score)
def communication_score(successful_messages: int, total_messages: int) -> float:
return _ratio_pct(successful_messages, total_messages)
def cost_score(budget: float, actual_usage: float) -> float:
return _ratio_pct(budget, actual_usage)
def robustness_score(recovered_failures: int, total_failures: int) -> float:
return _ratio_pct(recovered_failures, total_failures, empty=100.0)
def governance_score(compliant_operations: int, total_operations: int) -> float:
return _ratio_pct(compliant_operations, total_operations, empty=100.0)
def swarm_score(*, completion: float, gain: float, collaboration: float, communication: float,
cost: float, robustness: float, governance: float, weights: dict = SWARM_WEIGHTS) -> float:
w = weights
return (w["completion"] * completion + w["gain"] * gain + w["collaboration"] * collaboration
+ w["communication"] * communication + w["cost"] * cost
+ w["robustness"] * robustness + w["governance"] * governance)
# --- 涌现增益(v2.0 §6) ---
def emergence_gain(q_swarm: float, q_base: float) -> float:
# G_E = Q_swarm − Q_base
return q_swarm - q_base
def swarm_cost(n_agent: int, cost_efficiency: float) -> float:
# v2.0 §6.2: C_swarm = N_agent × (CostEfficiency/100 + 0.5)
return n_agent * (cost_efficiency / 100.0 + 0.5)
def cost_normalized_gain(q_swarm: float, c_swarm: float, q_base: float, c_base: float = 1.0) -> float:
# v2.0 §6.2 (CHANGED from v1 ratio-of-ratios to a DIFFERENCE):
# G_E,c = (Q_swarm / C_swarm) − (Q_base / C_base)
if not c_swarm or not c_base:
raise ValueError("cost_normalized_gain needs non-zero C_swarm / C_base")
return (q_swarm / c_swarm) - (q_base / c_base)
# --- 综合 Benchmark(v2.0 §7) ---
def benchmark_agent(*, s_swarm: float, g_e: float, reward: float, observability: float,
governance: float, weights: dict = LAMBDA_WEIGHTS, validate: bool = True) -> float:
"""Benchmark_Agent = λ1·S_swarm + λ2·G_E + λ3·R + λ4·O + λ5·Gov, with Σλ = 1.0 (v2.0 §7.1)."""
if validate and abs(sum(weights.values()) - 1.0) > 1e-6:
raise ValueError(f"λ weights must sum to 1.0 (got {sum(weights.values())})")
return (weights["lambda1"] * s_swarm + weights["lambda2"] * g_e + weights["lambda3"] * reward
+ weights["lambda4"] * observability + weights["lambda5"] * governance)
+5
View File
@@ -0,0 +1,5 @@
"""Benchmark replay — 回放一次 swarm/benchmark 执行。
状态:**未落地(骨架)**。事件流已按 swarm_id 顺序持久化,可作为回放数据源;
需定义快照格式与回放接口。见 docs/integration/audit-trace-schema.md §4。
"""
@@ -0,0 +1,57 @@
# 指标采集覆盖与缺口根因分析(Metric Coverage Gaps)
> 状态:**现状分析**。说明 `SwarmRunMetricsCollector`(`benchmark/collectors/run_collector.py`)当前能真实计算哪些指标、哪些返回 `NaN + coverage=False`,以及**为什么**。
>
> 配套:[`swarm-metrics-schema.md`](./swarm-metrics-schema.md)、[`telemetry-architecture.md`](./telemetry-architecture.md)、[`emergence-evaluation.md`](./emergence-evaluation.md)、[`baseline-comparison.md`](./baseline-comparison.md)、[`governance-score.md`](./governance-score.md)。
## 1. 一句话根因
本仓最初是**多 Agent 工作流执行器**,不是**被插桩的基准测量目标**。能算出来的指标是「执行过程本就会产生」的副产物;其余指标各自缺少执行器从不需要产生的东西(基线 / 计数器 / 决策机制 / 输入)。**v2.0 已给定全部权重(τ/η/reward/λ)**,但这不改变「输入/机制缺失」的根因。
## 2. 覆盖现状(v2.0 `SwarmMetrics` 共 15 字段,4 个真实可算)
| 字段 | 状态 | 数据来源 / 缺口 |
|---|---|---|
| `s_completion` | ✅ 真实 | 任务状态统计 |
| `s_collaboration` | ✅ 真实 | `handoff.*` 事件 + `depends_on` + `assigned_agent_id` |
| `s_cost` | ✅ 真实 | 每任务 `usage.model_cost_usd` + 请求 `budget.max_cost_usd` |
| `s_robustness` | ✅ 真实 | `retry_count` + 任务状态 |
| `s_governance` | 🟡 部分 | 仅审批可派生;无审批 → NaN |
| `s_gain` | 🔴 NaN | 需基线 |
| `s_communication` | 🔴 NaN | 无消息计数 |
| `tau` / `eta` / `p_decision` | 🔴 NaN | 无 τ/η 决策引擎 |
| `reward` | 🔴 NaN | 权重已给定,**输入**未采集 |
| `s_swarm` / `g_e` / `g_e_cost` / `benchmark` | 🔴 NaN | 依赖上述(任一 NaN → 聚合 NaN) |
> 不可算的指标返回 `NaN` 并在 `coverage` 标记 `False`,**不伪造 0/100 分值**(组织规则 #9)。
## 3. 为什么这 4 个能算
它们都是执行器正常运行的副产物,已存在于 orchestrator 状态:
- `completion` ← 队列本就跟踪任务状态。
- `collaboration` ← 移交事件、依赖、被分配 Agent 都是派发所需。
- `cost` ← 计费归因本就记录每任务 `model_cost_usd`;预算在请求里。
- `robustness` ← 重试逻辑本就维护 `retry_count` 与状态。
## 4. 为什么这 5 个算不出(逐项根因)
| 指标 | 根因(缺什么) | 类别 | 关闭成本 |
|---|---|---|---|
| `gain`(`G_E = Q_swarm − Q_base`) | **本质是对比指标**,单次 swarm run 无法自算;缺 baseline 运行器(Single/Strong/Chain/Sub-Agent)、对比 harness 与数据集 | 缺**基线** | 高(跨团队/基础设施/设计决策) |
| `communication`(`Successful/Total messages`) | peer/WS 消息只**路由**、从不**计数**;无成功/总数计数器,数据流过但未插桩 | 缺**计数器** | 低(加计数器) |
| `governance`(`Compliant/Total ops`) | 有审批机制,但无「受治理/敏感操作 vs 合规」计数;更广的治理面(tool/MCP 权限、allowed_paths 强制)未实现,**没有受治理操作记录可计数**;无审批的 run → 0 操作 → 空集 → NaN | 缺**计数器 + 策略强制点** | 中(计数器 + 部分强制点) |
| `p_decision`(`τ^α·η^β·100`) | v2.0 已给公式,但派发仍是**确定性贪心能力匹配**(`can_agent_run_task`);ACO 决策模型**未实现**——无信息素 `τ`(历史有效性追踪)、无启发式 `η` 评分;没有可测的概率决策 | 缺**整套机制** | 高(新建决策引擎 + 历史库) |
| `reward`(`w₁·S_task + … − w₈·P_rework`) | v2.0 **已给定 `w₁..w₈`**;但**输入**未采集(`Q_quality` 需 TestPass/CodeReview/UserAcceptance、`P_risk`、`P_rework`)→ 仍不可算 | 缺**输入**(权重已定) | 中(质量/CI/风险/返工接入) |
## 5. 关闭路径(按成本排序)
1. **低成本(本仓可做)**:`s_communication`、`s_governance` — 在 peer/WS 发送处与受治理操作处加计数器,按 `swarm_id` 聚合。可把真实可算项从 4 提到 6。
2. **中成本**:`reward` — 权重 v2.0 已定;接入 `Q_quality`(CI/评审/验收)与 risk/rework 输入即可算。
3. **高成本**:
- `p_decision` — 设计并实现 `τ/η/P` 决策引擎与历史有效性追踪(改变派发机制)。
- `gain`(及由其驱动的 `Benchmark_Agent`、`G_E,c`、「Swarm > baselines」验收)— 实现 4 类基线运行器 + 统一数据集 + 对比/显著性,属跨团队与基础设施工作(见 baseline-comparison)。
## 6. 影响
- 在 `gain` 落地前,**无法输出完整 `Benchmark_Agent`,也无法证明 Swarm 优于任一基线** —— 这正是工单「当前更接近多 Agent Workflow Demo」的判断依据。
- `NaN + coverage=False` 是这一缺口的**诚实证据**;不得以占位分值对外宣称已具备量化自证能力。
@@ -0,0 +1,53 @@
# Owner Note:`G_E,c` 成本归一化增益公式退化 —— ✅ 已裁定(对称 C_base)
> 提交:Swarm 仓(HeiCode-Swarm)· 面向:Benchmark Owner(Agent 蜂群指标量化与标准 v2.0 §6.2)
> 状态:**✅ 已裁定(2026-06-09)**。Benchmark Owner 采纳「对称 `C_base`」修正:`C_base` 用基线自身
> `N_agent` 与 `CostEfficiency` 同式计算。已设为 `benchmark/baselines.compare` 的**默认**(`symmetric_cost=True`);
> 字面 v2.0(`C_base=1.0`)仅作参考(`symmetric_cost=False`)。下文保留问题背景与依据。
## 1. 问题
v2.0 §6.2:
```
G_E,c = (Q_swarm / C_swarm) − (Q_base / C_base)
C_swarm = N_agent × (CostEfficiency/100 + 0.5)
C_base = 1.0
```
`C_base` 固定为 `1.0`,而 `C_swarm ≈ O(N_agent)`(数量级 ~数倍)。两项尺度不一致:
- `Q_base / C_base = Q_base`(满量级,如 65.5)
- `Q_swarm / C_swarm`(被压缩,如 85.5/12.5 ≈ 6.84)
→ 基线项恒压倒实验项,**几乎任何多 Agent 蜂群的 `G_E,c` 都为负**,与质量无关。该指标**退化**,无法区分「高性价比蜂群」与「堆 Agent 蜂群」——恰与其设计意图相反。
**复现**(`scripts/test-baseline-comparison.py`):Q_swarm=85.5 > Q_sub=65.5(`G_E=+20`),但字面公式 `G_E,c = 6.84 − 65.5 = −58.7`。
## 2. 根因
`C_base = 1.0` 疑为简化/笔误:它让基线「零成本」,使差值失去「单位成本质量」的可比性。
## 3. 建议修复(推荐:对称成本归一化)
让 `C_base` 与 `C_swarm` 同式计算(用基线自身的 `N_agent` 与 `CostEfficiency`):
```
C_base = N_agent_base × (CostEfficiency_base/100 + 0.5)
```
则两项均为「单位成本质量」,可比、非退化,且保留 v2.0「差值 + 动态成本」的设计意图。
- 复现示例(对称):`C_base = 3×(166.7/100+0.5) = 6.5` → `G_E,c = 6.84 − 10.08 = −3.24`。这是**有意义**的结果:蜂群质量更高,但用 5 个 Agent 的代价不成比例;当质量增益足够大时 `G_E,c` 可转正。
备选:恢复 v1 的**比值形式** `G_E,c = (Q_swarm/C_swarm)/(Q_base/C_base)`(阈值 `>1`),天然尺度无关。
## 4. 本仓现状(已就绪,等裁定)
- `benchmark/metrics.py:cost_normalized_gain` 接受 `c_base` 参数。
- `benchmark/baselines/compare(..., symmetric_cost=False)`:**默认 = 字面 v2.0(C_base=1.0)**;`symmetric_cost=True` = 上述对称修复。
- 由 `scripts/test-baseline-comparison.py` 覆盖两种模式。
- 一旦 Owner 确认,将对称模式设为默认并更新 v2.0 文档与本仓默认值。
## 5. 裁定结果(2026-06-09)
- [x] **采用对称 `C_base`**(默认 `symmetric_cost=True`)。
- [ ] ~~改回比值形式(阈值 >1)~~ 未采用。
- [ ] ~~维持 `C_base=1.0`~~ 仅作参考保留(`symmetric_cost=False`)。
- [x] `CostEfficiency` 口径:统一为 `100·Budget/ActualCost`(= `S_cost` = `E_cost`,Owner 裁定 v2.1)。
- [ ] **参考系数 ×0.75/0.85/0.90/0.95**:Owner 决定**暂留为元数据、暂不接入公式**,**标记待后续实测量化**(如 `系数 = Q_baseline/Q_reference`)。其含义见 `baseline-comparison.md §1.1`。
+67
View File
@@ -0,0 +1,67 @@
# Baseline Comparison(基线对比)
> 状态:**规划中(方法已定义,基线运行器未落地)**。
>
> 依据:**Agent 蜂群指标量化与标准 v2.0 §9.1**。配套:[`emergence-evaluation.md`](./emergence-evaluation.md)、[`cost-normalized-gain.md`](./cost-normalized-gain.md)、[`swarm-benchmark-protocol.md`](./swarm-benchmark-protocol.md)。
## 1. 基线与实验组(标准 §9.1)
| 组 | 系统 | 说明 | 参考系数 |
|---|---|---|---|
| Baseline A | Single Agent | 单 Agent 直接完成(最小基准) | ×0.75 |
| Baseline B | Chain Agent | 串行链式编排(无蜂群协作/评审) | ×0.85 |
| Baseline C | Sub-Agent | 主从结构委派(无对等协作/评审循环) | ×0.90 |
| Baseline D | Strong Agent | 高能力单体(更大模型/更长上下文) | ×0.95 |
| Experimental | **Swarm Agent**(本仓) | 分解→派发→协作→评审/重做→汇总 | — |
### 1.1 参考系数的含义(暂定值 · 待量化 · 暂不接入公式)
> ⚠️ **状态:暂定 / 仅元数据**。×0.75/0.85/0.90/0.95 为人为设定的强度先验,标准**未给计算方法**;**不参与任何公式**(仅在 `compare(...)` 结果中作 `base_coefficient` 上报)。**待后续实测量化**(如 `系数 = Q_baseline/Q_reference`,按场景标定)后再决定是否接入。
参考系数(Single 0.75 < Chain 0.85 < Sub 0.90 < Strong 0.95)表示**各基线的相对能力强度**——把四类基线按「越接近满能力」排序:Single 最弱(约 0.75),Strong 最强(约 0.95)。其用意是:
- **打败越强的基线越有价值**。对 Strong(0.95)取得的质量增益,比对 Single(0.75)取得同样增益更能证明涌现——因为强基线的「上行空间」更小(headroom ≈ `1 − 系数`)。
- 因此它本质是一个**难度 / 可信度因子**:用于在跨基线汇总时给「战胜强基线」的增益更高权重,防止「我们赢了单 Agent」被当作强涌现证据。
可能的数学接入方式(**标准 v2.x 未指定,待裁定**):
- 难度加权增益:`weighted_G_E = (Q_swarm − Q_base) × 系数`(战胜强基线权重更高);或
- headroom 归一化:`G_E_norm = (Q_swarm − Q_base) / (1 − 系数)`(放大对强基线的增益);或
- 期望基线缩放:`Q_base_expected = 系数 × Q_reference`。
**当前实现**:系数仅作为 `compare(...)` 结果里的 `base_coefficient` **元数据上报,不参与任何公式**(`G_E`/`G_E,c` 用原始 `Q_base`)。代码常量见 `benchmark/metrics.py:BASE_COEFFICIENTS`。
## 2. 统一采集指标(标准 §10)
所有组在**同一任务集**上运行,统一采集:`Completion`、`Quality`、`Cost`、`Time`、`Robustness`。
由此得到每组 `Q_*`(质量,口径见 emergence-evaluation §3)与 `C_*`(成本,见 cost-normalized-gain §3)。
## 3. 对比方法
1. **同一任务集 / 同一场景**(Coding / Refactoring / Architecture / DevOps / Bug Fix)。
2. **同等约束**:相同模型网关、相同预算上限口径(避免实验组单独放宽)。
3. 每组多次运行,报告均值与方差。
4. 输出:
- `G_E = Q_swarm − Q_base`(对 A/B/C/D 各算一次)。
- `G_E,c = (Q_swarm/C_swarm)/(Q_base/C_base)`。
- 成立判定:对**全部** A/B/C/D 满足 `G_E > 0` 且 `G_E,c > 1`。
## 4. 公平性约束(防止虚高)
- 实验组不得使用基线没有的额外资源/预算/工具(除「蜂群协作与评审」本身)。
- 成本必须计入**多 Agent + 评审重做**的累计(天然反映在按 `swarm_id` 聚合的 `usage`,见 cost-normalized-gain §3)。
- 报告需同时给出 `G_E` 与 `G_E,c`;只报 `G_E` 不足以验收。
## 5. 实现状态
| 组件 | 位置 | 状态 |
|---|---|---|
| 基线运行器 A–D | `benchmark/baselines/` | 🔴 未实现 |
| 统一任务集 / 数据集 | `test-data/` + `benchmark/baselines/` | 🔴 未实现 |
| 指标采集 | `benchmark/collectors/` | 🔴 未实现 |
| 对比与显著性 | `benchmark/` | 🔴 未实现 |
## 6. 待对齐
- 四类基线的标准实现边界(尤其 Strong / Chain / Sub-Agent)。
- 统一任务集与场景数据集。
- 运行次数、方差/显著性门槛与公平性约束的强制方式。
+71
View File
@@ -0,0 +1,71 @@
# Baseline Run Record Schema(基线运行记录 · 共享契约)
> 状态:**Schema + 比较评估器已落地;记录生产(尤其 Quality)未落地**。
>
> 依据:**Agent 蜂群指标量化与标准 v2.0 §6, §9**。实现:`benchmark/baselines/comparison.py`(`BenchmarkRunRecord` / `compare` / `evaluate`)。配套:[`baseline-comparison.md`](./baseline-comparison.md)、[`emergence-evaluation.md`](./emergence-evaluation.md)、[`cost-normalized-gain.md`](./cost-normalized-gain.md)、[`IMPORTANT-metric-coverage-gaps.md`](./IMPORTANT-metric-coverage-gaps.md)。
## 1. 目的
蜂群验收的核心是 **swarm vs baseline 对比**(尤其 sub-agent,Baseline C ×0.90)。要算 `G_E`/`G_E,c`,**swarm 与每个基线必须在同一任务集、同等约束下,产出同一份记录**。本文定义这份共享记录 `BenchmarkRunRecord`:**两侧程序各自产出**,由本仓评估器统一比较。
## 2. 共享记录 `BenchmarkRunRecord`
| 字段 | 类型 | 含义 |
|---|---|---|
| `system` | str | `swarm` / `single` / `chain` / `sub` / `strong` |
| `scenario` | str | `coding` / `refactoring` / `architecture` / `devops` / `bugfix` |
| `task_set_id` | str | 共享任务集 ID(**同一 id 才可比**) |
| `n_agent` | int | 该系统使用的 Agent 数 |
| `completed_tasks` / `total_tasks` | int | 完成度(Completion) |
| `test_pass_rate` / `code_review_score` / `user_acceptance` | float(0–100) | 质量三输入(Quality) |
| `budget_usd` / `actual_cost_usd` | float | 计划/实测成本(Cost、CostEfficiency) |
| `model_tokens` | int | token 用量 |
| `target_time_s` / `actual_time_s` | float | 计划/实测耗时(Time、Speed) |
| `recovered_failures` / `total_failures` | int | 恢复/总失败(Robustness) |
JSON 示例:
```json
{
"system": "sub", "scenario": "coding", "task_set_id": "coding-set-1", "n_agent": 3,
"completed_tasks": 9, "total_tasks": 10,
"test_pass_rate": 70, "code_review_score": 60, "user_acceptance": 65,
"budget_usd": 10, "actual_cost_usd": 6, "model_tokens": 42000,
"target_time_s": 600, "actual_time_s": 720, "recovered_failures": 1, "total_failures": 2
}
```
## 3. 派生量(评估器,已实现)
- `Q = quality(rec)` = `0.4·TestPass + 0.3·CodeReview + 0.3·UserAcceptance`
- `CostEfficiency = 100·Budget/ActualCost`
- 统一采集 `unified_metrics(rec)`:Completion / Quality / CostEfficiency / Speed / Robustness(v2.0 §9)
- `compare(swarm_rec, base_rec)`(默认 `symmetric_cost=True`,Owner 修正 2026-06-09):
- `G_E = Q_swarm − Q_base`
- `C_swarm = N_agent_swarm·(CostEfficiency_swarm/100 + 0.5)`;`C_base = N_agent_base·(CostEfficiency_base/100 + 0.5)`(对称)
- `G_E,c = (Q_swarm/C_swarm) − (Q_base/C_base)`
- 字面 v2.0(`C_base=1.0`)仅 `symmetric_cost=False` 保留参考;背景见 [`OWNER-NOTE-cost-normalized-gain.md`](./OWNER-NOTE-cost-normalized-gain.md)。
- `evaluate(swarm_rec, [baselines])`:对全部基线比较;`swarm_valid` 要求对每个基线 `G_E>0` 且 `G_E,c>0`。
## 4. 谁产出什么
| 字段族 | swarm 侧现状 | sub-agent 侧需产出 |
|---|---|---|
| Completion (`completed/total`) | ✅ 任务状态 | ✅ 需提供 |
| Cost (`actual_cost_usd`/`model_tokens`)、`n_agent` | ✅ usage 已采集 | ✅ 需提供 |
| Time (`actual_time_s`) | ✅ 时间戳 | ✅ 需提供 |
| Robustness (`recovered/total_failures`) | 🟡 需计数 | ✅ 需提供 |
| **Quality (`test_pass_rate`/`code_review_score`/`user_acceptance`)** | 🔴 **未插桩** | 🔴 **需插桩** |
> **关键阻塞**:Quality 在 **swarm 与 sub-agent 两侧都未插桩**(无 CI/测试通过率、评审分、验收)。没有 Quality 就无 `Q` → 无 `G_E`。这是「记录已可比较、但记录尚不可生产」的根本原因。
## 5. 接入路径
1. 双方各实现一个「跑统一任务集 → 产出 `BenchmarkRunRecord`」的 runner(sub-agent 侧在其程序内;swarm 侧可由 `SwarmRunMetricsCollector` 扩展 + Quality 插桩)。
2. 统一任务集与 `task_set_id`、同等约束(同模型网关、同预算口径)。
3. 把两侧记录交给 `benchmark/baselines/evaluate(...)` → 得 `G_E`/`G_E,c` 与 `swarm_valid`。
## 6. 待对齐
- Quality 三输入的采集口径与 CI/评审/验收来源。
- `CostEfficiency` 口径(`S_cost` vs `E_cost`)与基线参考系数(×0.75/0.85/0.90/0.95)在公式中的确切作用。
- 统一任务集与数据集(各场景)。
+63
View File
@@ -0,0 +1,63 @@
# Cost-Normalized Gain(成本归一化增益)
> 状态:**规划中(公式已对齐,ROI 评估器未落地)**。
>
> 依据:**Agent 蜂群指标量化与标准 v2.0 §6.2**。配套:[`emergence-evaluation.md`](./emergence-evaluation.md)、[`swarm-metrics-schema.md`](./swarm-metrics-schema.md)、[`usage-billing` 用量](../integration/usage-billing-schema.md)。实现:`benchmark/metrics.py` 的 `cost_normalized_gain` / `swarm_cost`。
## 1. 公式(v2.0 + Owner 修正 2026-06-09:对称成本归一化)
```
G_E,c = (Q_swarm / C_swarm) − (Q_base / C_base)
C_swarm = N_agent_swarm × (CostEfficiency_swarm / 100 + 0.5)
C_base = N_agent_base × (CostEfficiency_base / 100 + 0.5)
```
- `Q_*`:质量(同 emergence-evaluation §3)。`CostEfficiency`:成本效率 `100·Budget/ActualCost`(**= §5.1 `S_cost` = §4.1 `E_cost`,同一量**,Owner 裁定 v2.1)。
- 判读:`G_E,c > 0` 表示**单位成本质量**优于基线;`< 0` 表明蜂群以更高的 Agent 成本换取质量、性价比不及基线,应缩减 `N_agent`。
> ✅ **Owner 修正(2026-06-09)**:原始 v2.0 §6.2 取 `C_base = 1.0`,与 `C_swarm ≈ O(N_agent)` 尺度不一致,
> 致使几乎任何多 Agent 蜂群 `G_E,c < 0`(与质量无关,指标退化)。经 Benchmark Owner 裁定,改为**对称归一化**
> (`C_base` 用基线自身 `N_agent` 与 `CostEfficiency` 同式计算),使两项均为单位成本质量、可比。
> 实现为 `benchmark/baselines.compare` 的**默认**(`symmetric_cost=True`);字面 v2.0(`C_base=1.0`)仅 `symmetric_cost=False` 保留参考。背景见 [`OWNER-NOTE-cost-normalized-gain.md`](./OWNER-NOTE-cost-normalized-gain.md)。
## 2. 目的
防止通过**无限扩张 Agent 数量 / Token** 获得虚假涌现增益:`C_swarm` 随 `N_agent` 线性增长,堆 Agent 会拉低 `Q_swarm/C_swarm`。蜂群验收**不只**看 `G_E > 0`,还要 `G_E,c > 0`。
## 3. 成本 C 口径
`C` 建议为单次任务集的总成本,至少包含(数据源见 usage-billing-schema):
```
C = model_cost_usd(所有 task / 所有 review 重做累计)
[+ runtime / 基础设施成本,待接入]
```
| 成本项 | 本仓可采集 |
|---|---|
| `model_cost_usd`(含多 Agent、多 review 重做累计) | 🟡 usage 已采集,按 `swarm_id` 聚合即可 |
| `model_tokens`(token 效率视角) | 🟡 已采集 |
| 基础设施成本(cpu/memory/Pod) | 🔴 未接入账本(见 usage-billing §6) |
> 多 Agent / review 重做的成本**已隐含累计**在按 `swarm_id` 聚合的 `usage` 中,因此 `C_swarm` 天然反映「堆 Agent / 重做」的代价——这正是 `G_E,c` 要惩罚的。
## 4. 衍生视角(Leaderboard)
- **Token 效率**:`Q / total_tokens`。
- **Cost Efficiency**:`Q / model_cost_usd`。
- **Swarm ROI**:`G_E,c` 本身。
## 5. 实现状态
| 组件 | 状态 |
|---|---|
| `usage` 成本聚合(按 swarm_id) | 🟡 数据具备,无聚合器 |
| 成本归一化评估器(G_E,c) | 🔴 未实现 |
| 多 Agent ROI / token 效率 / 成本效率 | 🔴 未实现 |
| 基础设施成本纳入 C | 🔴 未接入 |
## 6. 待对齐
- `C` 是否纳入基础设施成本(与 Billing Team / usage-billing §7 对齐)。
- `Q` 口径与 emergence-evaluation 一致化。
- `CostEfficiency` 的确切口径(`S_cost` vs `E_cost`)与 `G_E,c` 的验收阈值(`>0` 之外是否设场景化下限)。
+68
View File
@@ -0,0 +1,68 @@
# Emergence Evaluation(涌现增益评估)
> 状态:**规划中(公式已对齐,基线运行器/评估器未落地)**。
>
> 依据:**Agent 蜂群指标量化与标准 v2.0 §6.1, §9**。配套:[`baseline-comparison.md`](./baseline-comparison.md)、[`cost-normalized-gain.md`](./cost-normalized-gain.md)、[`swarm-benchmark-protocol.md`](./swarm-benchmark-protocol.md)。
## 1. 原始增益
```
G_E = Q_swarm − Q_base
```
`Q_base` 取自四类基线之一(标准 §6.1,附**参考系数**,由低到高):
| 基准 | 类型 | 参考系数 |
|---|---|---|
| Baseline A | Single Agent | ×0.75 |
| Baseline B | Chain Agent | ×0.85 |
| Baseline C | Sub-Agent | ×0.90 |
| Baseline D | Strong Agent | ×0.95 |
> ⚠️ **参考系数为暂定值(待量化)**:×0.75/0.85/0.90/0.95 是人为设定的强度先验,标准未给计算方法;**当前不参与公式**(`G_E` 用原始 `Q_base`),仅作元数据。后续应实测量化(见 [`baseline-comparison.md §1.1`](./baseline-comparison.md))。代码常量见 `benchmark/metrics.py:BASE_COEFFICIENTS`。
## 2. 成立条件(蜂群是否真正优于基线)
蜂群成立要求对**全部**基线为正增益:
```
Q_swarm > Q_single ∧ Q_swarm > Q_strong ∧ Q_swarm > Q_chain ∧ Q_swarm > Q_sub
```
否则蜂群不成立(仅是「多 Agent 工作流」而非有涌现的蜂群)。
> 仅有原始增益不足以验收:还须经成本归一化(见 [`cost-normalized-gain.md`](./cost-normalized-gain.md)),证明增益非「堆 Agent / Token」虚高。
## 3. Q(质量)口径
`Q` 采用统一质量度量。建议复用标准 §5.3:
```
Q = Q_quality = 0.4·TestPassRate + 0.3·CodeReviewScore + 0.3·UserAcceptance
```
并在每组统一采集 `Completion / Quality / Cost / Time / Robustness`(标准 §10),按场景(Coding / Refactoring / Architecture / DevOps / Bug Fix)分别计算 `G_E`。
> ⚠️ `Q` 的精确口径(是否等于 `Q_quality`,或综合 Completion/Robustness)v2.0 未唯一指定 → 待对齐。
## 4. 评测流程(草案)
1. 固定任务集(按场景)。
2. 在 A/B/C/D 与 Swarm 上**同一任务集**运行,统一采集指标(见 swarm-metrics-schema §0)。
3. 计算每场景 `Q_*`,得 `G_E = Q_swarm − Q_base`。
4. 进入成本归一化(`G_E,c`)。
5. 多次运行取均值并报告方差/显著性。
## 5. 实现状态
| 组件 | 位置(规划) | 状态 |
|---|---|---|
| 基线运行器 A–D | `benchmark/baselines/` | 🔴 未实现 |
| 基线 benchmark 套件 / 数据集 | `benchmark/baselines/` + `test-data/` | 🔴 未实现 |
| 基线对比 | baseline-comparison | 🔴 未实现 |
| 涌现评估器(G_E) | `benchmark/` | 🔴 未实现 |
| 归一化增益评估器(G_E,c) | cost-normalized-gain | 🔴 未实现 |
> 在以上落地前,**不得宣称已验证蜂群涌现能力**(重大能力缺口)。
## 6. 待对齐
- `Q` 的唯一口径。
- 四类基线的标准实现(尤其 Strong Agent / Chain Agent / Sub-Agent 的定义边界)与统一数据集。
- 运行次数、方差/显著性门槛。
+53
View File
@@ -0,0 +1,53 @@
# Governance Score(治理评分)
> 状态:**规划中(公式已对齐,治理计数器未落地)**。
>
> 依据:**Agent 蜂群指标量化与标准 v2.0 §4.1, §5.1**(公式未变)。配套:[`security-boundary`](../integration/security-boundary.md)、[`audit-trace`](../integration/audit-trace-schema.md)、[`swarm-metrics-schema.md`](./swarm-metrics-schema.md)。
## 1. 公式
```
G_gov = CompliantActions / SensitiveActions × 100 # 执行层(奖励 R 中 w₆=0.10)
S_governance = CompliantOperations / TotalOperations × 100 # 蜂群层(S_swarm 中 0.10)
```
`Gov`(综合治理能力,进入 `Benchmark_Agent`)由上述构成。
## 2. 何为「需治理 / 合规」操作
需纳入治理计数的操作类别(对齐 security-boundary):
- **审批门**:高危操作是否走客户端审批(`approval.requested` → 审批回执)。
- **权限**:资源访问是否在 `allowed_actions`/`constraints` 范围内。
- **密钥**:是否仅用 `secret_ref`、无明文泄露。
- **MCP / 工具边界**:工具调用是否在允许集合内(`sk_tool.*`)。
- **敏感操作**:生产部署、DB 写、外部 API 等是否满足策略与 TTL。
- **审计**:是否产生可追溯记录(见 audit-trace)。
「合规」= 上述操作满足策略;`S_governance` = 合规操作数 / 总(敏感/受治理)操作数。
## 3. 本仓可采集状态
| 治理维度 | 现状 |
|---|---|
| 审批门(waiting_approval + approvals 回执) | 🟡 状态机已实现,但未计数「合规/总」 |
| 密钥(`azkv://` 强校验、明文拒绝、脱敏) | 🟡 已强制,但未作为治理计数项 |
| 权限(allowed_actions/constraints 运行时强制) | 🔴 未运行时强制(见 security-boundary §3) |
| MCP / 工具边界 | 🔴 未实现(无工具权限引擎) |
| 敏感操作策略 + TTL 校验 | 🔴 未实现(审批逐项校验待加强) |
| 审计可追溯 | 🟡 事件流可追溯,未量化为治理分 |
> 结论:治理当前仍**偏 README/机制级**,缺少 `CompliantOperations / TotalOperations` 的统一计数与导出 → `S_governance` / `Gov` 暂不可量化输出。
## 4. 实现状态
| 组件 | 状态 |
|---|---|
| 治理操作计数器(governed/compliant/total) | 🔴 未实现 |
| 治理评分导出(S_governance / Gov) | 🔴 未实现 |
| 与 audit 事件的绑定 | 🔴 未实现 |
## 5. 待对齐
- 「敏感 / 受治理操作」的精确清单与判定(Security/Governance Team)。
- 「合规」判定规则(策略命中、审批命中、范围内、TTL 内)。
- 计数与导出口径(按 `swarm_id` 聚合,进入 `SwarmMetrics.governance`)。
@@ -0,0 +1,88 @@
# Swarm Benchmark Protocol
> 状态:**规划中(协议已成文,采集器/基线/回放未落地)**。
>
> 依据:**Agent 蜂群指标量化与标准 v2.0**。本目录其余文档对各分量展开:[`swarm-metrics-schema.md`](./swarm-metrics-schema.md)、[`emergence-evaluation.md`](./emergence-evaluation.md)、[`baseline-comparison.md`](./baseline-comparison.md)、[`cost-normalized-gain.md`](./cost-normalized-gain.md)、[`governance-score.md`](./governance-score.md)、[`telemetry-architecture.md`](./telemetry-architecture.md)。
## 1. 目标
统一蜂群评测体系:支持不同 Agent Framework 横向比较、持续优化与回归测试、Benchmark 排行榜。**最终验收不是「能运行」或「能协作」,而是通过本协议自证**(尤其涌现增益 `G_E` 与成本归一化增益 `G_E,c`)。
## 2. 综合评分 Benchmark_Agent
```
Benchmark_Agent = λ1·S_swarm + λ2·G_E + λ3·R + λ4·O + λ5·Gov
```
| 分量 | 含义 | 展开文档 |
|---|---|---|
| `S_swarm` | 蜂群能力 | swarm-metrics-schema §蜂群层 |
| `G_E` | 涌现增益(vs baseline) | emergence-evaluation |
| `R` | 奖励函数 | swarm-metrics-schema §执行层 |
| `O` | 可观测性 | telemetry-architecture |
| `Gov` | 治理能力 | governance-score |
**λ 权重(v2.0,强制 `Σλᵢ = 1.0`)**:`λ1=0.30`(S_swarm)· `λ2=0.20`(G_E)· `λ3=0.25`(R)· `λ4=0.15`(O)· `λ5=0.10`(Gov)。`benchmark/metrics.py:benchmark_agent` 会校验 `Σλ=1.0`。
`S_swarm` 子权重(v2.0,未变):
```
S_swarm = 0.25·S_completion + 0.20·S_gain + 0.15·S_collaboration
+ 0.10·S_communication + 0.10·S_cost + 0.10·S_robustness + 0.10·S_governance
```
## 3. Benchmark Protocol(基线与实验组)
| 组 | 系统 |
|---|---|
| Baseline A | Single Agent |
| Baseline B | Chain Agent |
| Baseline C | Sub-Agent |
| Baseline D | Strong Agent |
| Experimental | **Swarm Agent**(本仓) |
所有组**统一采集**:`Completion`、`Quality`、`Cost`、`Time`、`Robustness`。
蜂群成立的硬条件:`Swarm > Single`、`Swarm > Chain`、`Swarm > Sub-Agent`、`Swarm > Strong`(见 emergence-evaluation),且经成本归一化后增益非「堆 Agent/Token」虚高(见 cost-normalized-gain)。
## 3.1 超参数向量 Θ(v2.0 §7.2)
```
Θ = [α, β, ρ, N_agent, ε] Θ* = argmax(Benchmark_Agent)
```
推荐初值:`α=1.0`、`β=2.0`、`ρ=0.10`(信息素挥发率)、`N_agent=5`、`ε=0.10`(探索率)。见 `benchmark/metrics.py:THETA_DEFAULTS`。
## 4. 场景(Leaderboard 维度)
Coding · Refactoring · Architecture · DevOps · Bug Fix。
## 5. Leaderboard 展示字段
`Benchmark_Agent`、`S_swarm`、`G_E`、`G_E,c`、`Reward`、`Cost Efficiency`。
## 6. 实现状态(本仓)
| 组件 | 位置(规划) | 状态 |
|---|---|---|
| 指标定义 / schema | `docs/benchmark/*`、`SwarmMetrics` | ✅ 已成文 |
| 采集器 `SwarmMetricsCollector` | `benchmark/collectors/` | 🟡 部分落地(`SwarmRunMetricsCollector` 真实计算 completion/collaboration/cost/robustness;其余 NaN+coverage) |
| 基线运行器 A–D | `benchmark/baselines/` | 🔴 未落地 |
| 回放 replay | `benchmark/replay/` | 🔴 未落地 |
| 排行榜 leaderboard | `benchmark/leaderboard/` | 🔴 未落地 |
| 数据采集架构 | telemetry-architecture | 🔴 未接入(OTel/Prometheus/ClickHouse/ES) |
> 在采集器与基线运行器落地前,**不得宣称本仓已具备 Agent Swarm 量化自证能力**。
## 7. 验收标准(对齐工单)
- [ ] 能输出完整 `Benchmark_Agent`。
- [ ] 能量化 `S_swarm`、`G_E`、`G_E,c`、`Gov`。
- [ ] 能回放一次 benchmark execution。
- [ ] 能证明 `Swarm > Single / Chain / Sub-Agent`。
- [ ] 能证明增益不依赖无限 token/GPU 堆叠(成本归一化)。
## 8. 待对齐
- ~~`λ1…λ5` 数值权重~~ ✅ v2.0 已给定(Σλ=1.0)。
- `Θ*` 调优口径与搜索方法。
- 各基线(Single/Chain/Sub-Agent/Strong)的标准实现与数据集。
- `Quality` 的统一口径(TestPassRate / CodeReviewScore / UserAcceptance 来源)。
- 数据源与平台(telemetry-architecture)。
+103
View File
@@ -0,0 +1,103 @@
# Swarm Metrics Schema(v2.0)
> 状态:**公式已对齐标准 v2.0;采集器部分落地**。
>
> 依据:**Agent 蜂群指标量化与标准 v2.0**(§3–§8)。配套:[`swarm-benchmark-protocol.md`](./swarm-benchmark-protocol.md)、[`metric-coverage-gaps.md`](./metric-coverage-gaps.md)、[`telemetry-architecture.md`](./telemetry-architecture.md)。
>
> 实现:公式见 `benchmark/metrics.py`(纯函数,含 v2.0 推荐权重常量 `TAU_WEIGHTS`/`ETA_WEIGHTS`/`REWARD_WEIGHTS`/`SWARM_WEIGHTS`/`LAMBDA_WEIGHTS`/`THETA_DEFAULTS`);真实采集见 `benchmark/collectors/run_collector.py`(`SwarmRunMetricsCollector`)。
## 0. 采集接口(标准 §8.1)
```python
@dataclass
class SwarmMetrics:
tau: float # 信息素得分
eta: float # 启发式得分
p_decision: float # 决策概率
reward: float # 执行奖励
s_completion: float
s_gain: float
s_collaboration: float
s_communication: float
s_cost: float
s_robustness: float
s_governance: float
s_swarm: float # 蜂群总分
g_e: float # 涌现增益
g_e_cost: float # 成本归一化增益
benchmark: float # 综合评分
```
`SwarmRunMetricsCollector` 真实计算 `s_completion`/`s_collaboration`/`s_cost`/`s_robustness`,其余返回 `NaN` 并在 `coverage` 标记 `False`(不伪造分值)。
## 1. 决策层(标准 §3)
```
τ(s,a,r) = w₁·Success + w₂·Quality + w₃·Acceptance − w₄·Cost − w₅·Time − w₆·Risk − w₇·Rollback
η(s,a,r) = γ₁·Match + γ₂·Urgency + γ₃·Dependency + γ₄·Resource + γ₅·Confidence − γ₆·Risk − γ₇·BudgetPressure
P(s,a,r) = τ^α·η^β / Σ τ_i^α·η_i^β
P_decision = τ^α · η^β · 100 # v2.0 §3.3(变更)
```
**τ 权重(v2.0)**:Success 0.25 · Quality 0.20 · **Acceptance 0.20(提升为一级因子)** · Cost 0.10 · Time 0.10 · Risk 0.08 · Rollback 0.07。
**η 权重(v2.0)**:Match 0.25 · Urgency 0.15 · Dependency 0.15 · Resource 0.15 · **Confidence 0.10(新增)** · Risk 0.10 · BudgetPressure 0.10。
| 因子 | 数据来源(标准) | 本仓可采集 |
|---|---|---|
| τ.Success | Task 完成记录 | ✅ |
| τ.Quality | 测试 / CI/CD | 🔴 无 CI 接入 |
| τ.Acceptance | 人工验收日志 | 🟡 评审 accepted |
| τ.Cost / Time | 资源监控 / Runtime 日志 | 🟡 usage / 时间戳 |
| τ.Risk / Rollback | 安全审计 / Git·部署 | 🔴 |
| η.Match / Dependency | 能力画像 / DAG | ✅ |
| η.Confidence | Agent 自评接口 | 🔴 未实现 |
| η.Urgency / Resource / Risk / BudgetPressure | 优先级 / 授权 / 风险引擎 / 预算 | 🔴 / 🟡 / 🔴 / 🟡 |
| P_decision | 上述综合 | 🔴 无 τ/η 引擎 |
## 2. 执行层(标准 §4)
```
R = w₁·S_task + w₂·Q_quality + w₃·V_speed + w₄·E_cost + w₅·R_robust + w₆·G_gov − w₇·P_risk − w₈·P_rework
S_task = CompletedTasks/TotalTasks×100 Q_quality = 0.4·TestPass + 0.3·CodeReview + 0.3·UserAcceptance
V_speed = 100·TargetTime/ActualTime E_cost = 100·ExpectedCost/ActualCost
R_robust = RecoveredFailures/TotalFailures×100 G_gov = CompliantActions/SensitiveActions×100
P_rework = ReworkCount/TotalTasks×100
```
**R 权重(v2.0,已给定)**:S_task **0.20** · Q_quality **0.20(与完成度对齐)** · V_speed 0.12 · E_cost 0.13 · R_robust 0.13 · G_gov 0.10 · P_risk 0.07 · P_rework 0.05。
| 指标 | 本仓可采集 |
|---|---|
| `S_task` | ✅ |
| `Q_quality` | 🔴 需 TestPass/CodeReview/UserAcceptance(无 CI/评分) |
| `V_speed` / `E_cost` | 🟡 Actual 有;Target/Expected 需基线 |
| `R_robust` | 🟡 重试/恢复未计数 |
| `G_gov` / `P_risk` / `P_rework` | 🔴 无敏感操作/风险/返工计数 |
> v2.0 已给定全部 `w*`/`γ*` 权重,但多数**输入**仍未采集 → `τ`/`η`/`reward`/`p_decision` 暂不可算。
## 3. 蜂群层(标准 §5,权重未变)
```
S_swarm = 0.25·S_completion + 0.20·S_gain + 0.15·S_collaboration
+ 0.10·S_communication + 0.10·S_cost + 0.10·S_robustness + 0.10·S_governance
S_completion = CompletedTasks/TotalTasks×100
S_collaboration= 0.5·HandoffSuccessRate + 0.3·DependencyResolutionRate + 0.2·WorkloadBalanceScore
S_communication= SuccessfulMessages/TotalMessages×100
S_cost = 100·Budget/ActualUsage
S_robustness = RecoveredFailures/TotalFailures×100
S_governance = CompliantOperations/TotalOperations×100
```
| 指标 | 状态 |
|---|---|
| `s_completion` / `s_collaboration` / `s_cost` / `s_robustness` | ✅ 真实可算(采集器) |
| `s_gain` | 🔴 需基线(见 emergence-evaluation) |
| `s_communication` | 🔴 未计数(无消息 telemetry) |
| `s_governance` | 🟡 仅审批可派生 |
| `s_swarm` | 🔴 含 gain/communication NaN → 暂为 NaN |
## 4. 说明与待对齐
- **成本口径统一(v2.1)**:`S_cost`(§3)、`E_cost`(§2)、`CostEfficiency`(见 cost-normalized-gain)为**同一量** `100×Budget/ActualCost`。
- `α/β/ρ/N_agent/ε` 取值:标准 §7.2 给推荐初值(1.0 / 2.0 / 0.10 / 5 / 0.10),调优口径待定。
- `Q_quality` 三项来源、`SuccessfulMessages/TotalMessages`、`Recovered/Total`、`Compliant/Total`、`Rework/Total` 的精确计数定义。
+53
View File
@@ -0,0 +1,53 @@
# Telemetry Architecture(数据采集架构)
> 状态:**规划中(部分信号已发出,统一采集管道未接入)**。
>
> 依据:**Agent 蜂群指标量化与标准 v2.0 §8.2**。配套:[`swarm-metrics-schema.md`](./swarm-metrics-schema.md)、[`event-schema`](../integration/event-schema.md)、[`audit-trace`](../integration/audit-trace-schema.md)。`O`(可观测性)分量来源于本文。
## 1. 数据来源矩阵(标准 §8.2)
| 指标类型 | 数据来源 | 推荐平台 | 本仓现状 |
|---|---|---|---|
| 任务完成 | Task Logs、Handoff Logs | Elasticsearch | ✅ 任务状态 + 事件流(按 `swarm_id`);未汇入 ES |
| 成本 / Token | Runtime Metrics | Prometheus | 🟡 usage 已采集;Prometheus 指标可抓取 |
| 质量 | CI/CD Results、Code Review | 各 CI 平台 | 🔴 未接入 |
| 治理合规 | Audit Logs | OpenTelemetry | 🟡 事件流可作审计源,未独立留存 |
| 通信 | WebSocket Logs | ClickHouse | 🟡 连接事件有日志;消息成功率未计数 |
| 基础设施 | Infrastructure Metrics | Prometheus | 🔴 未接入(cpu/memory/Pod) |
## 3. 本仓已发出的信号
| 信号 | 实现 | 去向 |
|---|---|---|
| Prometheus 指标 | ✅ 编排器 `GET /metrics`;Agent `METRICS_PORT` | 可被 Prometheus 抓取(`k8s/prometheus-config.yaml`) |
| OpenTelemetry tracing | 🟡 `orchestrator/tracing.py`(`OTEL_EXPORTER_OTLP_ENDPOINT`) | 需配置 collector |
| 事件流(lifecycle/DAG/handoff/usage) | ✅ 按 `swarm_id` 持久化 + 回调 | Redis + HM 回调;可导出 ClickHouse/ES(未接) |
| 任务/用量/预算 | ✅ `/tasks`、`/metrics`、`budget.alert` | REST + 事件 |
## 4. 目标采集管道(草案)
```
Agent / Orchestrator
├─ Prometheus ← /metrics(指标:任务、时长、并发、被拒/重复、reconnect…)
├─ OpenTelemetry → OTLP collector → trace 存储(task/agent/handoff span)
└─ 事件流(event-schema) → 导出器 → ClickHouse / Elasticsearch(用于 benchmark 聚合与回放)
↑
SwarmMetricsCollector 读取并产出 SwarmMetrics
```
## 5. 缺口
| 项 | 状态 |
|---|---|
| Prometheus 指标暴露 | ✅ 已有(指标项可再补:消息成功率、恢复率、治理计数) |
| OTel trace 实际导出 | 🟡 模块在,collector/span 覆盖待完善 |
| 事件 → ClickHouse/ES 导出器 | 🔴 未实现 |
| CI/CD、Git、基础设施指标接入 | 🔴 未实现 |
| 统一 `SwarmMetricsCollector` 落地 | 🔴 未实现(见 swarm-metrics-schema §0) |
| 刷新可恢复 / 断线回补(前端) | 🟡 `/logs?cursor=`(见 frontend-event-api) |
## 6. 待对齐
- 选定后端(ClickHouse / ES)与 schema、留存期(与 Audit Team)。
- 指标命名规范与标签(`swarm_id`/`agent_role`/`scenario`)。
- 导出器归属(本仓 exporter vs 平台统一采集)。
@@ -0,0 +1,65 @@
# Agent 能力与调度 Schema(Capability / Registry / Routing)
> 状态:**DRAFT / 待对齐 Infra & Scheduling Team**。
>
> 依据:`heicode-mananger/docs/heicode.md §五`、`docs/integration/heicode-am-contract.md §2`。配套:[`runtime-contract.md`](./runtime-contract.md)、[`security-boundary.md`](./security-boundary.md)。
## 1. 现状
- 当前能力调度为 **swarm 私有实现**:Agent 以 `AGENT_CAPABILITIES` 注册到编排器(Redis 注册表),编排器按「`required_capabilities ⊆ agent.capabilities` + 剩余容量」派发。
- **尚未接入统一 agent registry / capability center / quota / tenant / region·GPU 调度 / provider routing / model policy**。这些归 Infra/Scheduling,未在本仓实现(避免 Runtime Scheduling 双实现)。
## 2. 已实现的能力模型(本仓)
### 2.1 Agent 注册(`AgentMetadata`)
```jsonc
{ "agent_id": "...", "status": "idle|busy|handoff-pending|failed",
"capabilities": ["python","testing", ...], "current_task_id": "...|null",
"last_heartbeat": 0.0 }
```
另:register/heartbeat 上报 `available_slots`(剩余容量),编排器据此避免向满载 Agent 派发。
### 2.2 任务侧能力需求
任务携带 `agent_role` 与 `required_capabilities`;派发用 `can_agent_run_task`(`required ⊆ capabilities`,空需求视为通用可执行)。
### 2.3 能力别名(`handoff_logic.CAPABILITY_ALIASES`)
`code_generation` / `python` / `general` 等扩展为本地等价能力集合,用于移交决策与匹配宽松化。
### 2.4 角色(role)
- Manager/产品口径角色(`heicode.md §五`):`product`、`frontend`、`backend`、`reviewer`、`ops`。
- 本仓规划回退默认角色:`implementation`、`testing`、`documentation`(`agent_role` 字段,注入 `specialist_role`)。
## 3. 角色 ↔ 资源 ↔ env(对齐 AM)
资源授权(Resource Grant,Manager 侧)按角色绑定资源,HM 解析为固定 env 名注入 Agent(`heicode-am-contract §2`):
| 资源类型 | provider | 非密 env | 密钥 env(经 secret_ref) |
|---|---|---|---|
| git | github/gitea/gitlab | `GIT_PROVIDER` `GIT_REPO_URL` `GIT_DEFAULT_BRANCH` | `GIT_TOKEN` |
| database | mysql / postgres | `MYSQL_*` / `POSTGRES_*`(host/port/db/user) | `MYSQL_PASSWORD` / `POSTGRES_PASSWORD` |
| storage | azure blob | `AZURE_BLOB_ACCOUNT_NAME` `AZURE_BLOB_CONTAINER` | `AZURE_BLOB_ACCOUNT_KEY` |
角色/指令经 `AGENT_ROLE_NAME` + `AGENT_INSTRUCTION_TEXT` 注入(AM 约定)。
## 4. 目标统一调度 Schema(待 Infra 定义)
| 维度 | 字段(建议) | 状态 |
|---|---|---|
| Registry | `agent_id`、`capabilities`、`role`、`framework`、`version`、`health` | 🟡 本仓有私有注册表,未接统一中心 |
| Capability center | 能力字典、能力版本、能力↔工具映射 | 🔴 未接入 |
| Quota | `max_concurrent`、`token_quota`、`cost_quota`(按 user/channelId) | 🟡 仅 Agent 侧 `MAX_CONCURRENT_TASKS`/容量 |
| Tenant isolation | —(按标准用 user/channelId,不引入 tenant) | ⛔ 不在本仓 |
| Region / GPU scheduling | `region`、`gpu_class`、`node_pool` | 🔴 未接入(AKS/Infra) |
| Provider routing | `provider`、`model_policy`、`fallback` | 🔴 未接入(NewAPI/网关侧) |
| Model policy | `model_ref`、`profile`、`budget` | 🟡 Agent 用 `OPENAI_MODEL`;统一 policy 未接 |
## 5. 缺口
- 🔴 统一 agent registry / capability center / provider routing / region·GPU 调度:未实现,归 Infra/Scheduling。
- 🟡 配额仅 Agent 并发/容量级;无 user/channelId 维度配额中心。
- ⛔ tenant 维度:按标准不引入。
- ⚠️ 风险:若 Infra 落地统一调度,需收敛本仓私有派发,避免 Runtime Scheduling 双实现。
## 6. 待对齐对象
Infra & Scheduling Team:统一 registry/capability center 字段、quota(按 user/channelId)、region·GPU·provider routing、model policy,以及本仓私有派发与统一调度的收敛路径。
+75
View File
@@ -0,0 +1,75 @@
# 审计与链路追踪 Schema(Audit / Lineage / Trace)
> 状态:**DRAFT / 待对齐 Audit & Compliance Team**。
>
> 依据:`heicode-mananger/docs/heicode.md §五/§九`、`docs/heicode-runtime-auth-newapi-secret-design.md §三`。配套:[`event-schema.md`](./event-schema.md)、[`security-boundary.md`](./security-boundary.md)。
## 1. 原则
- 审计要能回答:**谁、在什么时候、让哪个子 Agent、使用了什么资源、做了什么、结果如何**。
- AGENT.md / resource context 面向模型理解;**permission manifest 面向系统强制执行**;审计以结构化记录为准,不以 Markdown 为准。
- 审计记录**不得含明文密钥**:只记 `secret_ref`(`azkv://`)、审批 `approval_id`、范围与 TTL。
## 2. 追踪域(Lineage)
### 2.1 部署 / 运行链路
`manager_deployment_id ↔ deployment_id ↔ swarm_id(workflow_id) ↔ correlation_id(trace_id)`,四者贯穿一次交付,事件按 `swarm_id` 持久化。
### 2.2 任务链路(已实现)
每个任务携带:`task_id`、`parent_task_id`、`root_task_id`、`child_task_ids`、`depends_on`、`source`(`runtime_bridge` / `planner` / `dynamic_handoff`)、`agent_role`、`assigned_agent_id`、`retry_count`、时间戳。可由此重建任务 DAG 与重做/移交谱系。
### 2.3 执行链路(已实现,基于事件流)
按 `swarm_id` 顺序持久化的事件即执行轨迹:`task.created/claimed/running/heartbeat/blocked/retried/failed/completed`、`handoff.requested/completed`、`artifact.created`、`timeline.updated`、`deployment.status_changed`,每条含 `event_id`、`occurred_at`、`agent_instance_id`、`correlation_id`。
### 2.4 审批链路(已实现)
`run.approvals[approval_id]`:`operation`、`risk_level`、`decision`(approved/rejected)、`credential_ref`、`lease_id`、`reason`、决策时间。客户端审批,运行时仅记录与校验。
### 2.5 资源 / 凭证访问链路
`secret_refs`(`azkv://`,仅引用)、resource grant 的 `allowed_actions`/`constraints`/`status`/`created_by`/`revoked_by`/`created_at`/`revoked_at`(字段定义在 Manager 侧 `heicode.md §五`)。本仓只透传与引用,不落明文。
### 2.6 回调投递链路(已实现)
`run.metadata.callback_attempts`:每次回调的 `event_id`、`event_type`、`url`、`status`、`attempted_at`,用于回调可靠性审计(`/diagnostics` 暴露)。
## 3. 已实现(本仓)
| 能力 | 实现 |
|---|---|
| 事件流持久化 + 分页查询 | `_store_event`(按 `swarm_id` 追加)、`list_events`(`cursor`/`limit`);`/logs`、`/events` 暴露 |
| 任务谱系字段 | `parent/root/child_task_ids`、`depends_on`、`source`、`retry_count` |
| 审批记录 | `run.approvals`、`record_approval_decision` |
| 回调投递审计 | `callback_attempts`、`/diagnostics` |
| 用量归因 | `usage` + `X-Agent-*` 归因头(见 usage-billing) |
| 脱敏 | `_redact_sensitive`(保留 `secret_ref`,脱敏明文密钥) |
## 4. 缺口
| 追踪项 | 状态 |
|---|---|
| Task lineage / execution trace | ✅ 已实现(事件流 + 任务字段) |
| Approval trace | ✅ 已实现 |
| Prompt trace(每次模型输入提示留痕) | 🔴 未实现(仅记 `model_id`/用量,不留 prompt 原文) |
| Model trace(请求/响应、参数、provider) | 🟡 部分(`model_id`/tokens;无完整请求响应留痕) |
| Tool trace(工具调用谱系) | 🔴 未实现(无 SK/MCP 工具计量,`sk_tool.*` 仅在 schema 预留) |
| 统一审计查询字段 / 留存策略 | 🟡 事件可查;标准查询字段与留存期未定义 |
| 交付回放(replay) | 🟡 事件流可顺序回放生命周期;无独立 replay 接口/快照格式 |
## 5. 建议审计记录 Schema(草案,待 Audit Team 冻结)
```jsonc
{
"audit_id": "aud_...",
"occurred_at": "2026-06-08T...Z",
"actor": { "user_id": "...", "channel_id": "..." }, // 谁(按 user/channelId,非 tenant)
"subject": { "agent_role": "...", "agent_instance_id": "...", "task_id": "..." }, // 哪个子 Agent / 任务
"action": "task.completed | approval.decided | resource.accessed | ...",
"resource": { "type": "git|database|storage|model", "ref": "non-secret-id", "secret_ref": "azkv://..." },
"approval_id": "approval_... | null",
"lineage": { "manager_deployment_id": "...", "deployment_id": "...", "swarm_id": "...", "correlation_id": "..." },
"result": "success | failed | blocked",
"redacted": true
}
```
## 6. 待对齐对象
Audit & Compliance Team:prompt/model/tool lineage 是否强制留痕及留存期、统一审计查询字段、交付回放快照格式、日志留存与合规要求;与 `telemetry-architecture`(benchmark 目录)数据源对齐。
+98
View File
@@ -0,0 +1,98 @@
# Swarm 运行时事件 Schema(回调 / 事件流)
> 状态:**已对齐 HM 实现**(依据 `heicode-mananger/heicode/controller/agent_callback.go`,handler `POST /api/agent/callbacks/runtime-events`)。本文是 Swarm → HM 回调与 `GET …/{id}/events` 事件流的统一 schema。
>
> 配套:生命周期与签名见 [`runtime-contract.md`](./runtime-contract.md)。
## 1. 传输与鉴权
- Swarm → HM:`POST {callback.url}`(create 时下发)。
- 鉴权二选一:服务令牌 `X-Agent-Service-Token`(或 `Authorization: Bearer`),或 HMAC 签名。
- **HMAC 签名(已实现,与 HM 一致)**:
- 头:`X-Agent-Timestamp`(Unix 毫秒)、`X-Agent-Signature`、`X-Agent-Event-Id`、`X-Correlation-ID`(均含 `X-Agnet-` 兼容别名)。
- 规范串:`canonical = f"{timestamp}.{event_id}.{raw_body}"`。
- 签名:`X-Agent-Signature = "sha256=" + hex(HMAC_SHA256(secret, canonical))`。
- secret:`AGENT_CALLBACK_SIGNING_SECRET`(兼容 `AGNET_…`,支持 `…_REF` 的 `azkv://` 解析)。
- 时间容差:HM 默认 300s(`AGENT_CALLBACK_SIGNATURE_TOLERANCE_SECONDS`)。
- **幂等去重**:HM 依次按 `X-Agent-Event-Id` 头 → body `event_id` → `idempotency_key`。重复返回 `{inserted:false, idempotent:true, deduplicated:true}`。
## 2. 事件 Envelope
```jsonc
{
"event_id": "evt-...", // 必填;去重主键
"idempotency_key": "...", // 可选;缺省回退 event_id
"event_type": "task.completed", // 必填;取值见 §4
"deployment_id": "runtime-dep-...",// 运行时部署 ID
"swarm_id": "swarm-...", // 工作流 ID(事件按此持久化)
"agent_instance_id": "...", // Agent 实例/角色实例 ID(可选)
"task_id": "...", // 任务相关事件填
"occurred_at": "2026-06-08T...Z", // 事件时间
"correlation_id": "corr-...", // 追踪 ID(X-Correlation-ID)
"source": "heicode-swarm-runtime", // 事件来源标识
"metadata": { }, // 自定义元数据(不得含明文密钥)
"payload": { }, // 事件专属字段(见 §4)
"artifact": { } // 可选;见 §3
}
```
## 3. Artifact 子对象(`artifact.created` 及完成事件可带)
```jsonc
{
"artifact_id": "art_...",
"artifact_type": "code_patch | document | deployment_manifest",
"title": "...",
"summary": "...",
"uri": "git://repo#branch | runtime://...",
"checksum": "<commit_sha>",
"metadata": { "redacted": true, "agent_role": "...", "files_modified": [] }
}
```
## 4. 事件类型注册表(与 HM 一致)
| event_type | payload 必填字段(HM 校验) | 分类 | Swarm 是否发出 |
|---|---|---|---|
| `deployment.status_changed` | `status` | deployment | ✅ |
| `phase.changed` | `stage` 或 `checkpoint` | ordinary_sub | ❌(暂未用) |
| `agent.started` | `agent_role` | ordinary_sub | ❌ |
| `agent.completed` | `agent_role` | ordinary_sub | ❌ |
| `agent.crashed` | `agent_role`, `reason` | ordinary_sub | ❌ |
| `task.created` | `task_id`, `title` | swarm_task_flow | ✅ |
| `task.claimed` | `task_id`, `agent_role` | swarm_task_flow | ✅ |
| `task.running` | `task_id`, `agent_role` | swarm_task_flow | ✅ |
| `task.heartbeat` | `task_id`, `agent_role` | swarm_task_flow | ✅ |
| `task.blocked` | `task_id`, `reason` | swarm_task_flow | ✅ |
| `task.retried` | `task_id`, `attempt` | swarm_task_flow | ✅ |
| `task.released` | `task_id`, `agent_role` | swarm_task_flow | ⚠️ 未发出(见 §6) |
| `task.failed` | `task_id`, `reason` | swarm_task_flow | ✅ |
| `task.completed` | `task_id` | swarm_task_flow | ✅ |
| `handoff.requested` | `task_id`, `from_role`, `to_role` | swarm_task_flow | ✅ |
| `handoff.completed` | `task_id`, `from_role`, `to_role` | swarm_task_flow | ⚠️ 缺 `from_role`/`to_role`(见 §6) |
| `approval.requested` | `approval_id`, `operation`, `risk_level` | approval | ✅ |
| `artifact.created` | `artifact_id` | artifact | ✅ |
| `timeline.updated` | `title` | timeline | ⚠️ 发出 `summary`,缺 `title`(见 §6) |
| `sk_tool.called` | `tool_name`, `tool_invocation_id` | sk | ❌(无 SK 工具) |
| `sk_tool.completed` | `tool_name`, `tool_invocation_id` | sk | ❌ |
| `sk_tool.failed` | `tool_name`, `tool_invocation_id`, `reason` | sk | ❌ |
| `budget.alert` | `threshold_pct` | budget | ⚠️ 发出 `threshold`,缺 `threshold_pct`(见 §6) |
> 说明:评审/重做循环复用 `task.retried` + `timeline.updated`(`Review cycle N`)表达,无单独 review 事件类型;如 HM 前端需要独立 review 事件,列为待对齐项。
## 5. HM 响应
```json
{ "success": true, "event_id": "evt-...", "inserted": true, "idempotent": false, "deduplicated": false, "deployment_id": "dep_..." }
```
## 6. 已知对齐缺口(需在 Swarm 代码修正)
下列为本仓 `orchestrator/` 当前发出字段与 HM 校验的差异,应在对应 emit 处修正后再宣称完全对齐:
1. **`timeline.updated`**:HM 要求 `title`;当前发 `summary`。修正:payload 增加 `title`(可复用 `summary`)。
2. **`budget.alert`**:HM 要求 `threshold_pct`;当前发 `threshold`(如 `0.8`)。修正:增加 `threshold_pct`(百分比,如 `80`)。
3. **`handoff.completed`**:HM 要求 `from_role`/`to_role`;当前缺。修正:在 `finalize_parent_after_child` / handoff 完成处补 `from_role`/`to_role`。
4. **`task.released`**:HM 注册表含此事件;`task_queue.release_task` 当前静默。修正:释放任务时发 `task.released`(`task_id`, `agent_role`)。
> 这些是确定性的小修,建议配 contract test(按 HM 必填字段断言每类事件 payload)。
+58
View File
@@ -0,0 +1,58 @@
# 前端事件 API(Frontend Event API)
> 状态:**DRAFT / 待对齐 Frontend & Product Team**。
>
> 依据:`heicode-mananger/docs/integration/heicode-desktop-client-api.md`、`docs/integration/manager-side-contract-patches.md §6`。配套:[`event-schema.md`](./event-schema.md)、[`runtime-contract.md`](./runtime-contract.md)。
## 1. 现状(重要)
- 当前**桌面端是单 Agent 模型**:用户部署「模板 Agent」,客户端从 HM 拿 `subdomain`+`access_token` 后**直连 Agent**走 A2A:`POST {subdomain}/message/stream`(SSE)/ `message/send`、`GET /.well-known/agent.json`、`/health`。HM 不在回路。
- **HM 尚未定义「蜂群图 / agent 拓扑 / review loop / retry 时间线」的前端事件 API**。蜂群前端契约**归 Swarm + Frontend/Product Team 共同定义**,目前不存在。
- 因此本文档区分:①HM 既有的任务卡片事件通道;②Swarm 现已提供、前端可消费的 REST;③待定义的蜂群前端事件 API。
## 2. HM 既有前端事件通道(任务卡片,非蜂群图)
来自 `manager-side-contract-patches.md §6`:
- `GET /api/user/tasks/{id}/events`(SSE);HM 当前以**轮询兜底**(active 3s、其它状态 15s)。
- 建议 SSE 事件名与载荷:
- `status_changed` → 新状态
- `followup_added` → followup 载荷
- `card_updated` → 完整 `HeicodeTaskCard`(`{goal, scope, generated_artifacts, manager_actions:[{label, deeplink}]}`)
- 这是**任务卡片**视角,不是蜂群 DAG/拓扑视角。
## 3. Swarm 现已提供的可消费 REST(已实现)
前端(经 HM 或直连受控读接口)可消费:
| 接口 | 用途 | 关键字段 |
|---|---|---|
| `GET …/{deployment_id}/workflow` | 工作流/阶段视图 | `status`、`summary`、`agent_count`、`tokens`、`tools`、`elapsed_seconds`、`phases[]`、`artifacts[]` |
| `GET …/{deployment_id}/tasks` | 任务 DAG | 每任务 `task_id`、`agent_role`、`status`、`depends_on`、`parent_task_id`、`root_task_id`、`attempt`、`blocked_reason` |
| `GET …/{deployment_id}/logs`(`/events`) | 事件流 | 事件 envelope(见 event-schema),分页 `cursor`/`limit` |
| `GET …/{deployment_id}/metrics` | 指标 | `tasks_by_status`、`agents_connected`、`average_task_duration_seconds`、`budget` |
| `GET …/{deployment_id}/diagnostics` | 诊断 | 失败任务、回调尝试、审批 |
`phases[]` 形如:`{phase_id, name, status, agents[]}`,`name ∈ {Plan, Dispatch, Execute, Handoff, Review, Deliver}`;`agents[]` 形如 `{agent_id, name, role, status, tokens, tools, elapsed_seconds, artifact_ids}`。
> 注意:当前**仅 REST 拉取**,Swarm 未向前端推送 SSE/WS。前端可轮询 `/workflow` + `/logs`(用 `cursor` 增量)。
## 4. 目标:蜂群前端事件 API(待定义)
为支撑工单要求的 swarm graph / agent topology / task DAG / review loop / retry timeline / token-cost monitor / collaboration timeline,建议(待 Frontend/Product Team 冻结):
| UI 视图 | 数据来源(现有) | 缺口 |
|---|---|---|
| Task DAG / topology | `/tasks`(`depends_on`)+ `/workflow.phases` | 稳定 graph schema、增量更新事件 |
| Review loop / retry timeline | `task.retried` + `timeline.updated`(`Review cycle N`) | 独立 review/retry 事件类型与时间线 schema |
| Collaboration timeline | `handoff.requested/completed` + peer 路由 | peer 消息事件未对前端暴露 |
| Token / cost monitor | `/metrics` + `budget.alert`(usage) | 统一 usage 推送(见 usage-billing §7) |
| 实时推送 | `/logs` 轮询 | 统一 SSE/WS event stream(事件名、断线重连、刷新可恢复) |
建议事件流形态(草案):SSE `GET …/{deployment_id}/stream`,事件名复用 `event-schema` 的 `event_type`,载荷为对应 envelope;前端按 `swarm_id` 渲染、按 `event_id` 去重、断线后用 `/logs?cursor=` 回补。
## 5. 缺口与待对齐
- 🔴 Swarm 未提供前端 SSE/WS 推送(仅 REST 轮询)。
- 🔴 蜂群 graph/timeline/cost 的统一前端 schema 未定义。
- 🟡 review/retry/collaboration 缺独立事件类型(当前借 `task.retried`/`timeline.updated`)。
- 待对齐:Frontend / Product Team 确认蜂群三视图(graph / timeline / cost)入口与事件流形态;与 HM 的 `/api/user/tasks/{id}/events` 通道如何衔接。
+96
View File
@@ -0,0 +1,96 @@
# Manager ↔ Swarm Runtime Contract(Swarm 侧拥有)
> 状态:**草案(Swarm 侧拥有,待 HM 对齐)** · 对齐对象:Heicode Manager Runtime Team
>
> 依据:
> - HM 侧裁定 `heicode-mananger/docs/integration/heicode-swarm-deferred.md`:**HM 当前不实现 swarm runtime**;多 Agent 蜂群归 `agent_swarm` / `HeiCode-Swarm`(本仓);HM 侧待本仓给出正式 `/api/agent/swarm/*` 接口后,在 HM 的 `docs/integration/` 另立契约跟踪。
> - 既有运行时集成范式 `heicode-mananger/docs/integration/heicode-am-contract.md`(单 Agent 模板 Agent,经 AM 启动)。本契约沿用其鉴权、回调、env、路径覆盖等约定。
>
> 本文是 HM 侧 deferred 锚点所要求的「Swarm 侧正式接口」起草。冻结需 Manager Runtime Team 评审。
## 1. 角色与边界
- **HM(Heicode Manager)**:控制面。负责用户输入、资源/权限/计费/审计、模型网关(`/v1/*`)。HM **不**承载蜂群编排。
- **Swarm Runtime(本仓)**:执行面。接收 HM 下发的编排请求,分解→派发→执行→评审→汇总,并通过**带签名回调**回报生命周期事件。
- HM 不在对话/执行回路;Swarm 通过回调把状态推回 HM,HM 也可主动拉取(见 §4)。
## 2. 鉴权(沿用 AM 约定)
- HM → Swarm:`Authorization: Bearer <service_token>`。Swarm 侧校验 `AGENT_RUNTIME_SERVICE_TOKEN`(兼容 `AGNET_RUNTIME_SERVICE_TOKEN`)。未配置时为**非安全开发模式**(仅本地)。
- 统一响应信封:成功 `{ "success": true, "data": {...} }`;失败 `{ "success": false, "error": { "code", "message", "request_id" } }`。
- 常用请求头:`X-Correlation-ID`、`X-Idempotency-Key`、`Authorization`。
## 3. 生命周期接口(已实现 / 待对齐)
Swarm 暴露以下接口(三组别名等价,便于 HM 路径覆盖):
`/api/swarms`、`/api/agent/swarm/deployments`、`/api/agnet/deployments`。
| 动作 | 方法 + 路径 | 状态 | 说明 |
|---|---|---|---|
| **create** | `POST /api/agent/swarm/deployments` | ✅ 已实现 | 创建蜂群部署;幂等键 `X-Idempotency-Key` |
| **status** | `GET …/{deployment_id}` | ✅ 已实现 | 返回部署概要与状态 |
| task graph | `GET …/{deployment_id}/tasks` | ✅ 已实现 | 任务 DAG |
| logs / events | `GET …/{deployment_id}/logs`、`/events` | ✅ 已实现 | 事件流(分页 `cursor`/`limit`) |
| metrics / workflow / diagnostics | `GET …/{deployment_id}/metrics`、`/workflow`、`/diagnostics` | ✅ 已实现 | 指标与编排视图 |
| **cancel** | `POST …/{deployment_id}/stop` | ✅ 已实现 | 停止并取消非终态任务,向 Agent 下发 `cancel_task` |
| **approve** | `POST …/{deployment_id}/approvals/{approval_id}` | ✅ 已实现 | 接收 Manager 审批决定(approved/rejected) |
| **resume** | — | 🟡 待对齐 | 当前仅「审批通过」隐式恢复(approvals);无独立 resume 端点 |
| **retry** | — | 🟡 待对齐 | 任务级重试 / 评审重做为内部机制;无外部 retry 端点 |
### 3.1 create 请求(必填校验)
必填:`orchestration_plan.objective`、`callback.url`、`metadata.manager_deployment_id`。
约束:`mode` 必须为 `swarm`;`billing_context.secret_ref`(若有)必须 `azkv://` 前缀;`resource_grants`/`metadata`/`callback` 不得含明文密钥。
create 响应 `data`:`deployment_id`、`runtime_deployment_id`、`manager_deployment_id`、`swarm_id`、`mode`、`status`、`runtime_execution_status`、`created`。
### 3.2 ID 语义(回应 HM 标记的 `deployment_id ↔ swarm_id` 缺口)
- `deployment_id` / `runtime_deployment_id`:Swarm 侧运行时部署 ID(`runtime-dep-…`)。
- `swarm_id`:工作流 ID(`swarm-…`),作为 `workflow_id`;事件按 `swarm_id` 持久化与查询。
- `manager_deployment_id`:HM 侧部署 ID(请求 `metadata.manager_deployment_id` 透传)。
- `correlation_id`:贯穿一次交付的追踪 ID(`X-Correlation-ID`),作为 `trace_id`。
- 三者映射在 Swarm 持久化中维护:`deployment_id ↔ swarm_id ↔ manager_deployment_id`,`get_run_by_identifier` 支持三者任一查询。
> HM 侧 `heicode-swarm-deferred.md` 记录的「Swarm 仅暴露 `/tasks`、缺 `deployment_id↔swarm_id`」为旧状态;本仓 v5/v6 已实现上述映射与 `/api/agent/swarm/*` 接口,需 HM 复核更新该锚点。
## 4. 状态机
部署状态:`waiting_approval` → `running` →(`blocked` ⇄ `running`)→ 终态 `completed` / `failed` / `stopped`。
| 状态 | 含义 | 进入方式 |
|---|---|---|
| `waiting_approval` | 高危/需审批,等待 Manager 审批 | create 时命中审批条件 |
| `running` | 任务派发与执行中 | 审批通过 / 有在途任务 |
| `blocked` | 任务因移交/依赖阻塞 | 子任务 `blocked_on_handoff` |
| `completed` | 所有任务终态且评审通过 | 全部完成(评审循环可选) |
| `failed` | 存在失败且不可恢复 | 任务终态含 failed |
| `stopped` | Manager 主动停止 | `…/stop` |
每次状态变更通过回调 `deployment.status_changed` 推送(见 §5)。
## 5. 回调(已与 HM 处理器对齐)
Swarm → HM:`POST {callback.url}`(create 时下发,默认 HM 的 `/api/agent/callbacks/runtime-events`)。
**鉴权与签名(与 HM `agent_callback.go` 一致,已实现):**
- 头:`X-Agent-Service-Token`(或 `Authorization: Bearer`)、`X-Agent-Timestamp`(Unix 毫秒)、`X-Agent-Signature`、`X-Agent-Event-Id`、`X-Correlation-ID`(均含 `X-Agnet-` 兼容别名)。
- 签名:`signature = "sha256=" + hex(HMAC_SHA256(secret, f"{timestamp}.{event_id}.{raw_body}"))`。
- secret:`AGENT_CALLBACK_SIGNING_SECRET`(兼容 `AGNET_…`);HM 侧容差默认 300s。
- 幂等:HM 按 `X-Agent-Event-Id` → body `event_id` → `idempotency_key` 去重。
事件 envelope 字段与 event_type 取值见 [`event-schema.md`](./event-schema.md)(与 HM 注册表一致)。
## 6. 路径与超时覆盖(沿用 AM 约定)
HM 侧可用 env 覆盖:`AGENT_RUNTIME_BASE_URL`、`AGENT_RUNTIME_SERVICE_TOKEN`、`AGENT_RUNTIME_AGENT_START_PATH`、`AGENT_RUNTIME_AGENT_PATH`、`AGENT_RUNTIME_AGENT_STOP_PATH`、`AGENT_RUNTIME_START_TIMEOUT_SECONDS`、`AGENT_RUNTIME_CALLBACK_URL`。
## 7. 安全
- `env`/请求体不得含明文密钥;凭据经 `secret_ref`(`azkv://`)注入。详见 [`security-boundary.md`](./security-boundary.md)。
- Swarm 启动接口必须 HTTPS / 私网;回调走 HTTPS。
## 8. 待对齐项(冻结前需 Manager Runtime Team 确认)
- [ ] resume / retry 是否需要独立外部端点,还是沿用 approvals + 内部重做。
- [ ] HM 是否以 AM 同款 `/agents` 生命周期(而非 `/api/agent/swarm/*`)调用 Swarm;若是,需路径映射。
- [ ] `event-schema` 中各 event_type 的必填字段与 HM 注册表逐项核对(见 event-schema.md)。
- [ ] 更新 HM 侧 `heicode-swarm-deferred.md` 锚点,登记本契约。
+85
View File
@@ -0,0 +1,85 @@
# 安全边界(Secret / Workspace / Tool / Approval / Tenant / Sandbox)
> 状态:**部分已实现,部分待接入**(依据 `heicode-mananger/docs/heicode.md §六/七`、`docs/heicode-runtime-auth-newapi-secret-design.md §三/五`、`docs/integration/heicode-am-contract.md §3.1/§4`)。
>
> 配套:[`runtime-contract.md`](./runtime-contract.md)、[`usage-billing-schema.md`](./usage-billing-schema.md)。
## 1. 不可破坏原则
- 密钥/Token/云凭据/SSH 私钥/数据库密码/NewAPI key **不得**进入代码、日志、Markdown、前端响应或 Git。
- 凭据基线 = **Azure Key Vault**;引用一律 `azkv://<vault>/secrets/<name>`,**不向后兼容 `vault://`**。
- Manager DB 与 Swarm 只保存 `secret_ref`,真实凭证由 Secret Broker 写入 Key Vault。
- 子 Agent 不持有长期密钥;只接收角色、资源元数据、AGENT.md 与**短期、最小权限、可审计**凭证。
- 高危操作审批**只在客户端完成**;运行时只校验审批结果,不发起审批。
## 2. Secret 注入边界
| 项 | 标准 | 本仓状态 |
|---|---|---|
| `secret_ref` 前缀 `azkv://` 强校验 | 必须 | ✅ `swarm_runtime.validate_create_request` 校验 `billing_context.secret_ref` 等为 `azkv://` |
| 拒绝明文密钥进入请求 | 必须 | ✅ `_reject_plaintext_secrets`(`metadata`/`resource_grants`/`callback`) |
| 响应/事件/持久化脱敏 | 必须 | ✅ `_redact_sensitive` 将明文密钥键(`password`/`token`/`secret`/`private_key`/`access_key` 及 `*_token`/`*_secret`/`*_password`/`*_key`)脱敏为 `[redacted]`;**保留** `secret_ref`/`credential_ref`/`signing_secret_ref`(安全引用) |
| `.env`/密钥不入库 | 必须 | ✅ `.gitignore` 忽略 `.env`、`secrets/`、`*.pem/key/p12/pfx`、`id_rsa/ed25519` |
| 真实凭证写入 Key Vault(Secret Broker) | Manager 侧 | ⛔ 非本仓(Manager Secret Broker 负责) |
| 短期凭证派生与注入 | 客户端审批后 | 🟡 Swarm 接收 `secret_context`;K8s 派生/注入由 Agent 平台(AM)实现 |
`secret_context`(HM 下发,仅引用与审批结果):
```jsonc
"secret_context": {
"secret_refs": ["azkv://heicode-kv.vault.azure.net/secrets/res_git_1"],
"inject_short_lived_credentials": true,
"approval_id": "approval_123"
}
```
## 3. Workspace 隔离
- 每个任务在**独立按任务工作目录**执行(agent `task_workspace`)。
- 文件写入有**路径越界校验**(`task_executor._resolve_workspace_path`,拒绝绝对路径与逃逸 workspace)。
- Git 操作在仓库根(`repo_root`)执行,限定结果分支。
- 🟡 待接入:跨任务/跨租户的强隔离、只读挂载、allowed_paths 强制(当前由模型提示约束,未做运行时强制)。
## 4. Tool / MCP 权限边界
- 当前 Agent 工具能力 = 工作区内文件读写 + Git;**无** SK/MCP 工具权限引擎。
- 🟡 待接入:统一 tool/MCP permission boundary、allowed/denied 工具策略、敏感工具审批联动(`sk_tool.*` 事件已在 schema 预留)。
## 5. 审批门(Approval Gate)
- 高危操作审批**只在客户端**;Swarm 不发起审批。
- Swarm 实现:create 命中高危(`risk_level=high` 或 `requires_user_approval`)→ 进入 `waiting_approval`,发 `approval.requested`(`approval_id`/`operation`/`risk_level`);客户端经 Manager 审批后回 `POST …/approvals/{approval_id}`(approved/rejected)→ 恢复或阻断。
- 🟡 待接入:审批主体/范围/TTL/`credential_ref`/`lease_id` 的逐项校验与到期失效,需与 Manager 审批链对齐。
## 6. Agent 鉴权与租户隔离
- **Swarm 模型**:Agent 主动出站连编排器 WebSocket(`/ws/{agent_id}`),**不**对公网暴露每 Agent 子域名。AM 单 Agent 模型里的「客户端↔agent 直连 + `AGENT_ACCESS_TOKEN` 本地校验」**不适用于** swarm(无直连回路)。
- 服务间鉴权:HM→Swarm 用 `AGENT_RUNTIME_SERVICE_TOKEN`(Bearer);回调 HMAC 签名。
- 🟡 待接入:多租户运行时隔离(命名空间/网络/配额)由 Agent 平台(AKS Workload Identity)承载,非本仓编排器;归因主轴为 `user.id`/`channelId`(见 `usage-billing-schema.md`),不引入 tenant 概念。
## 7. 外部 API 与传输
- 模型调用统一走 HM `/v1`(OpenAI 兼容),用 HM 现签 `OPENAI_API_KEY`。
- 传输:编排器/Agent 接口与回调走 **HTTPS / 私网**;`env` 含明文密钥时启动接口必须 HTTPS(`heicode-am-contract §4`)。
- 🟡 待接入:统一 external API egress 策略(白名单/出网控制)。
## 8. 执行沙箱
- 当前执行单元为进程 / K8s Pod,隔离强度依赖部署(namespace/资源限额)。
- 🟡 待接入:强化沙箱(seccomp/只读根/网络策略/能力裁剪),由 Infra/Security Team 定义。
## 9. 覆盖与缺口
| 边界 | 状态 |
|---|---|
| `azkv://` `secret_ref` 强校验 / 明文拒绝 / 脱敏 / `.gitignore` | ✅ 已实现(本仓) |
| Workspace 路径越界校验 | ✅ 已实现 |
| 审批状态机(waiting_approval + approvals 回执) | ✅ 已实现(逐项校验待加强) |
| 短期凭证派生注入、Workload Identity | 🟡 AM/K8s 侧 |
| Tool/MCP 权限引擎 | 🔴 未实现 |
| allowed_paths 运行时强制、强隔离 | 🔴 未实现 |
| 强化执行沙箱 | 🔴 未实现 |
| 租户隔离 | ⛔ 不在本仓(归因按 user/channelId) |
## 10. 待对齐对象
Security / Governance Team(tool/MCP 边界、沙箱、审批逐项校验)、Infra Team(Workload Identity、租户隔离、egress 策略)。
+108
View File
@@ -0,0 +1,108 @@
# 用量与计费 Schema(Usage / Billing)
> 状态:**已对齐既有边界**(依据 `heicode-mananger/docs/heicode.md §八`、`docs/heicode-runtime-auth-newapi-secret-design.md §四/五`、`docs/integration/Heicode-Manager-PayPal支付接入与计费关系说明.md §3/§5`)。
>
> 配套:事件载体见 [`event-schema.md`](./event-schema.md)(`budget.alert`);安全见 [`security-boundary.md`](./security-boundary.md)。
## 1. 计费主线原则(不可破坏)
- **NewAPI 是计费账本**:模型调用费用在 HM 模型网关 `/v1/*` 处**按请求计量并扣费**(NewAPI 的 user / token / group / quota / usage)。**Swarm 不是账本**,也不重算扣费。
- **Swarm 的职责 = 用量上报与预算约束**,用于归因、观测与预算告警,不作为扣费依据。
- **归因主轴 = `user.id` / `channelId` / NewAPI user/token/group**,**不是** `tenant`/`project`。`heicode-runtime-auth §六` 明确:当前不引入 tenant/project 作为计费主轴。
- Agent 基础设施成本(CPU/内存/Pod)当前**未形成用户账本**(PayPal 说明 §5)。
> ⚠️ 与工单口径的差异:工单要求「tenant attribution」;按 Manager 标准应改为 **user/channelId 归因**。如未来确需租户账本,需作为独立产品决策,不在本仓暗自添加。
## 2. 计费归因上下文(HM 下发,Swarm 透传)
来自 `heicode-runtime-auth §五`:
```jsonc
"billing_context": {
"provider": "newapi",
"newapi_user_ref": "newapi_user_123",
"newapi_group": "development",
"quota_ref": "newapi_token_or_group_quota_ref"
}
```
- Agent 调模型走 HM `/v1`,用 **HM 为该用户现签的 `OPENAI_API_KEY`(sk-)**,扣费即记在该用户名下;删 agent 时吊销。
- Swarm 的 task_executor 随模型请求带 **`X-Agent-*` / `X-Agnet-*` 归因头**(`manager_deployment_id`、`swarm_id`、`task_id`、`agent_role`、`correlation_id`、`model_id`),供 HM/NewAPI 关联归因。
## 3. 预算约束(orchestration_plan.budget)
来自 PayPal 说明 §5:
| 字段 | 含义 |
|---|---|
| `budget.max_tokens` / `token_limit` | 本次部署 token 上限 |
| `budget.max_cost_usd` | 本次部署美元成本上限 |
| `budget.duration_seconds` / `max_duration_seconds` | 运行时长上限 |
Swarm 在运行中按时长/成本比例发 `budget.alert`(默认 80% 阈值),并在 `metrics` 返回预算占比。**这是约束与告警,不是扣费。**
## 4. 用量上报 Schema
### 4.1 Agent 单任务用量(task 结果 `usage`,已实现)
```jsonc
"usage": {
"model_id": "gpt-4o-mini",
"model_tokens": 0,
"prompt_tokens": 0,
"completion_tokens": 0,
"model_cost_usd": 0.0,
"runtime_seconds": 0.0,
"billing_source": "newapi | unknown"
}
```
### 4.2 运行级用量事件(orchestrator,已实现)
通过 `budget.alert` 事件上报(payload 含):
```jsonc
{
"model_id": "...", "model_tokens": 0, "prompt_tokens": 0, "completion_tokens": 0,
"model_cost_usd": 0.0, "runtime_seconds": 0.0, "billing_source": "...",
"manager_deployment_id": "...", "swarm_id": "...", "task_id": "...",
"agent_role": "...", "correlation_id": "...",
"budget": { "max_tokens": null, "max_cost_usd": null, "consumed_usd": 0.0, "remaining_usd": null }
}
```
### 4.3 目标完整回传形态(对齐 AM,部分待实现)
PayPal 说明 §5 给出的运行时用量回传目标:
```jsonc
{
"deployment_id": "dep_xxx", "agent_instance_id": "agi_backend_001",
"usage": {
"model_tokens": 32000, "model_cost_usd": 4.21, "runtime_seconds": 930,
"cpu_core_seconds": 1200, "memory_mb_seconds": 2048000
},
"billing_source": "newapi", "correlation_id": "corr_xxx"
}
```
## 5. 多 Agent / 评审重做 成本聚合
- 一次 swarm 请求拆成多 task / 多 agent / 多评审轮;**每个 task 的 `usage` 可按 `swarm_id` 聚合**得到运行级合计(观测用途)。
- **评审重做成本**:每轮重做都会重新执行任务并累计 `usage`,因此**已隐含计入**运行级合计;但当前**未单独打标**「review_retry 成本」。
## 6. 字段覆盖与缺口
| 字段 | 状态 |
|---|---|
| `model_tokens` / `prompt_tokens` / `completion_tokens` | ✅ 已采集 |
| `model_cost_usd`、`runtime_seconds`、`billing_source` | ✅ 已采集 |
| 归因:`manager_deployment_id`/`swarm_id`/`task_id`/`agent_role`/`correlation_id` | ✅ 已采集 |
| `reasoning_tokens` / `cache_tokens` | 🔴 未采集(取决于 provider usage 返回) |
| `tool_cost`(工具调用成本) | 🔴 未采集(无 SK 工具计量) |
| `cpu_core_seconds` / `memory_mb_seconds`(基础设施) | 🔴 未采集(K8s 指标未接入账本) |
| `review_retry` 成本单独打标 | 🟡 隐含累计,未单独标注 |
| provider 成本拆分 | 🟡 由 NewAPI/账本侧负责,非本仓 |
| tenant 归因 | ❌ 按标准不使用(见 §1) |
## 7. 待对齐项
- [ ] 运行级用量是否需独立 `usage.report` 事件类型(当前借 `budget.alert` 载体);与 HM/Billing Team 确认(HM 注册表暂无 usage 事件)。
- [ ] `budget.alert` 需补 `threshold_pct`(见 `event-schema.md §6`)。
- [ ] 若需基础设施计费,接入 K8s 用量(cpu/memory)并由 Billing Team 定义资源计价。
- [ ] `review_retry` 成本是否需单独打标,由 Billing Team 决定。
+117
View File
@@ -0,0 +1,117 @@
# Peer Communication Logistics(对等通信机制与深化设计)
> 状态:**现状 + 深化设计**。描述专家 Agent 之间「就重叠领域相互沟通」的消息机制、路由逻辑、当前局限,以及深化方案与分阶段计划。
>
> 相关代码:`agent/main.py`(`request_peer_collaboration` / `handle_peer_message` / `answer_peer_query`)、`orchestrator/main.py`(`peer_message` 路由 / `build_dispatch_context`)、`agent/task_executor.py`(`_execute_subtask` 触发)。相关文档:[`event-schema`](./integration/event-schema.md)、[`frontend-event-api`](./integration/frontend-event-api.md)、[`benchmark/swarm-metrics-schema`](./benchmark/swarm-metrics-schema.md)(`S_communication`)。
---
## 1. 当前实现(mechanics)
### 1.1 消息信封
```jsonc
{
"type": "peer_message",
"agent_id": "发送方",
"target_agent_id": "接收方",
"from_agent_id": "由编排器转发时补盖(接收方据此回复)",
"task_id": "...",
"content": "文本内容",
"correlation_id": "peer-<task>-<rand>", // 关联请求与回复
"is_reply": false, // true = 回复
"timestamp": 0.0
}
```
### 1.2 路由(编排器为 broker)
Agent 出站连编排器 WebSocket,彼此不直连。编排器收到 `peer_message` → 按 `target_agent_id` 转发,并**补盖 `from_agent_id`**(发送方身份)。无 `target_agent_id` 则记告警丢弃。
### 1.3 请求/回复(一问一答)
- 发起方 `request_peer_collaboration(task_id, target_agent_id, content, timeout)`:生成 `correlation_id`,挂一个 `asyncio.Future` 到 `peer_waiters[correlation_id]`,发出 query,`await asyncio.wait_for(waiter, timeout)`。
- 接收方 `handle_peer_message`:若 `correlation_id` 命中自身 waiter(或 `is_reply`)→ 解析为「回复」并 resolve waiter;否则视为「入站查询」→ `answer_peer_query`。
- `answer_peer_query`:**已实现实质性回复**——对查询做一次受限 LLM 推理(`TaskExecutor.peer_reply`,受 `PEER_CONSULT_MAX_TOKENS`,默认 500 约束),结合自身 workspace 产物,返回结构化 `{stance, content, evidence, refs}`;无模型/超时/失败时回退到 `last_summary` 摘要。回复在后台任务中完成,不阻塞消息循环(`PEER_REPLY_TIMEOUT_SECONDS`,默认 20s)。
### 1.4 触发与编排(logistics)
- 派发时 `build_dispatch_context` 向任务上下文注入:`peer_agents`(本次 run 中**已连接**的其它 Agent:`agent_id`/`role`/`capabilities`)与 `dependency_artifacts`(已完成依赖产物)。
- `task_executor._execute_subtask` 仅当 `specialist_role ∈ {testing, documentation}` 且存在 `peer_agents` 且提供了回调时发起咨询;按 `prefer implementation` 排序,取前 `max_peer_consults`(默认 2)个,`peer_timeout_seconds`(默认 10s)。
- 对齐原则(提示词内):实现产物为 API/异常语义的 source of truth;peer 输入与实现冲突时以实现为准并在变更摘要中说明。
---
## 2. 当前局限(为何"浅")
| 局限 | 说明 |
|---|---|
| ~~回复浅~~ ✅ 已深化 | `answer_peer_query` 现做受限 LLM 推理 + 引用 workspace 产物 + 结构化字段(`stance/content/evidence/refs`);无模型时回退摘要(见 §3.3) |
| 触发窄 | 仅 testing/documentation 角色发起;实现角色不主动协作 |
| 单轮 | 一问一答,无多轮/澄清/协商线程 |
| 无广播 | 只能点对点 `target_agent_id`,无按角色/能力的群发或发现 |
| 无遥测 | 消息未计数(成功/失败/总数)→ `S_communication` 不可算(见 metric-coverage-gaps) |
| 弱冲突处理 | 仅"以实现为准"的提示约定,无结构化协商/升级协议 |
| 无前端可见 | peer 消息未作为事件暴露,前端协作时间线无数据 |
---
## 3. 深化设计(target)
### 3.1 消息类型(扩展 `kind`)
`query`(提问)·`reply`(回答)·`clarify`(追问)·`proposal`(提案)·`critique`(质疑)·`broadcast`(群发)·`ack`(确认)。
### 3.2 多轮线程
增加 `conversation_id` + `turn`;`peer_waiters` 升级为按会话的队列,支持 `clarify` 往返与超时续约,受 `max_rounds` 约束。
### 3.3 实质性回复 ✅ 已实现
`answer_peer_query` 已由「回 last_summary」升级为:**针对 query 生成有据回复**(`TaskExecutor.peer_reply` 做一次受限推理,引用自身 workspace 产物),返回结构化 `{stance, content, evidence, refs}`,受 `PEER_CONSULT_MAX_TOKENS`(默认 500)约束;无模型/失败回退摘要;后台任务回复不阻塞消息循环。由 `scripts/test-merge-smoke.py` 覆盖(fallback + 实质回复两条断言)。
### 3.4 寻址与发起
- 点对点(`target_agent_id`)+ **按角色/能力寻址**(编排器按注册表解析)+ 受控**广播**。
- 任何角色均可发起咨询(不限 testing/doc),由预算与策略限制频次。
### 3.5 冲突解决协议
`critique`/`proposal` 往返;默认「实现为 source of truth」;**僵局上交 Master Agent**(`master_agent` 仲裁,纳入评审决策),而非各 Agent 私下定夺。
### 3.6 遥测(接入 benchmark)
编排器对每条 `peer_message` 计数:`total` / `delivered` / `failed`(接收方离线/超时)。
- 产出 `S_communication = SuccessfulMessages / TotalMessages`(见 swarm-metrics-schema)。
- 作为协作事件暴露(见 §4),供前端协作时间线与审计。
### 3.7 预算与可靠性
`max_peer_consults`、`peer_timeout_seconds`、`max_rounds`、`peer_consult_max_tokens`;接收方离线 → 立即回 `failed` 而非干等;`correlation_id` 幂等去重。
---
## 4. 事件与可见性
为满足前端与审计,建议把对等协作纳入事件流(与 [`event-schema`](./integration/event-schema.md) 对齐):
- 新增/复用事件:`collaboration.message`(含 `from`/`to`/`kind`/`conversation_id`/`correlation_id`,内容脱敏)、`collaboration.resolved`/`collaboration.escalated`。
- 前端按 `conversation_id` 渲染协作时间线(见 frontend-event-api §4)。
- HM 注册表暂无 `collaboration.*` 事件 → 列为与 Workflow/Frontend Team 的待对齐项。
---
## 5. 与其它机制的关系
| 机制 | 作用 | 区别 |
|---|---|---|
| `dependency_artifacts` | 顺序依赖:下游看到上游产物 | 单向、派发时注入,非交互 |
| handoff | 把子任务委派给更合适的专家 | 转移所有权,非对等沟通 |
| peer communication | 重叠领域的对等交流 | 双向、运行中、不转移所有权 |
| Master Agent 评审 | 全局裁决是否达标 | 中心化决策,非点对点 |
---
## 6. 分阶段计划
1. **遥测先行(低成本)**:编排器对 `peer_message` 计数 → 点亮 `S_communication`;把消息作为 `collaboration.message` 事件暴露。
2. **实质回复** ✅ 已实现:`answer_peer_query` 改为受限 LLM 推理 + 引用产物(替换 last_summary)。
3. **多轮 + 寻址**:`conversation_id`/`clarify`、角色/能力寻址、广播。
4. **冲突协议 + 升级 Master**:`critique`/`proposal`,僵局上交 `master_agent`。
5. 与 Frontend/Workflow Team 冻结 `collaboration.*` 事件与协作时间线契约。
---
## 7. 待对齐
- `collaboration.*` 事件类型与载荷(Workflow/Frontend Team;当前 HM 注册表未含)。
- 对等咨询的预算口径(`max_rounds`/token)与频次策略(Governance)。
- 实质回复是否计入用量/计费(每次 peer consult 是一次模型调用 → 见 usage-billing §5)。
+26 -4
View File
@@ -24,6 +24,7 @@ from .handoff_manager import handoff_manager, HandoffRequest
from .task_queue import task_queue, TaskStatus
from .swarm_runtime import RuntimeValidationError, swarm_runtime
from .planner import planner
from .master_agent import master_agent
# Configure logging
logging.basicConfig(
@@ -217,7 +218,7 @@ async def maybe_run_review_cycle(run, tasks) -> bool:
return False
results = {t.task_id: {"result": parse_task_result(t) or {}} for t in completed}
verdict = await planner.review(
verdict = await master_agent.review_and_decide(
run.objective,
[task_payload(t) for t in completed],
results,
@@ -382,7 +383,7 @@ async def refresh_swarm_run_status(run):
# Synthesize the specialist results into one coherent, user-facing answer.
completed = [t for t in tasks if t.status == TaskStatus.COMPLETED]
results = {t.task_id: {"result": parse_task_result(t) or {}} for t in completed}
final_summary = await planner.synthesize(run.objective, results)
final_summary = await master_agent.synthesize(run.objective, results)
run.metadata["final_summary"] = final_summary
await swarm_runtime.save_run(run)
@@ -538,7 +539,7 @@ async def build_planner_task_specs(run, body: Dict[str, Any], base_specs: List[D
"""
objective = run.objective or "Complete swarm objective"
base_context = (base_specs[0].get("context") if base_specs else {}) or {}
subtasks = await planner.build_plan(run.swarm_id, objective)
subtasks = await master_agent.plan(run.swarm_id, objective)
prefix = f"{run.swarm_id}-"
@@ -1017,6 +1018,8 @@ async def finalize_parent_after_child(run, child_task, agent_id: str, success: b
**payload,
"child_task_id": child_task.task_id,
"parent_task_id": parent_task.task_id,
"from_role": parent_task.agent_role,
"to_role": child_task.agent_role,
},
)
await swarm_runtime.emit_event(
@@ -1896,6 +1899,10 @@ async def websocket_endpoint(websocket: WebSocket, agent_id: str):
"source_agent_id": handoff.source_agent_id,
"target_agent_id": handoff.target_agent_id,
"task_id": task_id,
# HM requires from_role/to_role; this legacy accept path
# lacks role context, so fall back to agent ids.
"from_role": handoff.source_agent_id,
"to_role": handoff.target_agent_id,
},
)
@@ -2130,7 +2137,22 @@ async def websocket_endpoint(websocket: WebSocket, agent_id: str):
logger.info(
f"Agent {agent_id} rejected task {rejected_task_id} ({reject_reason}); requeuing"
)
await task_queue.release_task(rejected_task_id, agent_id=agent_id)
released = await task_queue.release_task(rejected_task_id, agent_id=agent_id)
if released:
released_task = await task_queue.get_task(rejected_task_id)
released_run = await swarm_runtime.get_run_for_task(rejected_task_id)
if released_task and released_run:
await swarm_runtime.emit_event(
released_run,
"task.released",
task_id=rejected_task_id,
agent_instance_id=agent_id,
payload={
"task_id": rejected_task_id,
"agent_role": released_task.agent_role,
"reason": reject_reason,
},
)
elif message_type == "peer_message":
# Route peer collaboration messages between agents; stamp the sender so the
+69
View File
@@ -0,0 +1,69 @@
"""Master Agent — the decision-making entity of a swarm run.
This makes the "master" a first-class entity rather than scattered orchestrator functions.
The master agent owns the *cognitive* loop:
- plan() : decompose the objective into specialist subtasks
- review_and_decide(): judge whether the combined result is good enough, and which tasks to redo
- synthesize() : compose the specialists' outputs into one user-facing answer
It uses an LLM as its brain (via `planner`, with deterministic fallbacks). The orchestrator
remains the "hands": it executes the master's decisions (dispatch, reopen tasks, persist state,
emit events). This separation keeps decisions in one named entity while leaving runtime
mechanics (and the Manager contract) in the orchestrator.
NOTE: agent→task dispatch is still capability-matched in the orchestrator loop; `select_agent`
below is the seam where the master can later own assignment (LLM-driven), without changing the
loop's contract today.
"""
from __future__ import annotations
import logging
from typing import Any, Dict, List, Optional
from .planner import planner
logger = logging.getLogger(__name__)
class MasterAgent:
"""The master agent: decompose → decide → synthesize for a swarm run."""
def __init__(self, brain=planner):
# `brain` is the LLM-backed planner (build_plan / review / synthesize), swappable for tests.
self.brain = brain
async def plan(self, run_id: str, objective: str) -> List[Dict[str, Any]]:
"""Decompose the user objective into specialist subtasks."""
subtasks = await self.brain.build_plan(run_id, objective)
logger.info(f"[master] planned {len(subtasks)} subtask(s) for run {run_id}")
return subtasks
async def review_and_decide(self, objective: str, tasks: List[Dict[str, Any]],
results: Dict[str, Any]) -> Dict[str, Any]:
"""Decide whether the combined result is good enough.
Returns the master's verdict: {accepted: bool, summary: str, retry_tasks: [task_id,...]}.
The orchestrator acts on it (reopen retry_tasks or finalize).
"""
verdict = await self.brain.review(objective, tasks, results)
logger.info(
f"[master] review decision: accepted={verdict.get('accepted')} "
f"retry={verdict.get('retry_tasks')}"
)
return verdict
async def synthesize(self, objective: str, results: Dict[str, Any]) -> str:
"""Compose the specialists' outputs into one user-facing answer."""
return await self.brain.synthesize(objective, results)
def select_agent(self, task, candidates: List[Any]) -> Optional[Any]:
"""Choose which agent runs a task. Seam for future LLM-driven assignment.
Default: first capability-eligible candidate (capability matching is enforced upstream
by task_queue.get_ready_pending_task), preserving current dispatch behavior.
"""
return candidates[0] if candidates else None
# Singleton master agent for the runtime.
master_agent = MasterAgent()
+9
View File
@@ -427,6 +427,15 @@ class SwarmRuntime:
**redacted_payload,
}
# Conform to HM event contract required fields (heicode agent_callback.go):
# timeline.updated requires `title`; budget.alert requires `threshold_pct`.
if event_type == "timeline.updated" and "title" not in redacted_payload:
redacted_payload["title"] = redacted_payload.get("summary") or "timeline"
if event_type == "budget.alert" and "threshold_pct" not in redacted_payload:
threshold = redacted_payload.get("threshold")
if isinstance(threshold, (int, float)):
redacted_payload["threshold_pct"] = threshold * 100 if threshold <= 1 else threshold
event_id = f"evt_{uuid.uuid4().hex}"
body: Dict[str, Any] = {
"event_id": event_id,
+86
View File
@@ -0,0 +1,86 @@
"""Test the baseline comparison adapter (benchmark/baselines/comparison.py) — v2.0.
Run from agent_swarm_v6: python scripts/test-baseline-comparison.py
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from benchmark.baselines import BenchmarkRunRecord, quality, unified_metrics, compare, evaluate
failures = []
def check(name, cond):
print(("PASS" if cond else "FAIL"), "-", name)
if not cond:
failures.append(name)
swarm = BenchmarkRunRecord(
system="swarm", scenario="coding", task_set_id="coding-set-1", n_agent=5,
completed_tasks=10, total_tasks=10,
test_pass_rate=90, code_review_score=80, user_acceptance=85,
budget_usd=10, actual_cost_usd=5, model_tokens=50000,
target_time_s=600, actual_time_s=500, recovered_failures=1, total_failures=1,
)
sub = BenchmarkRunRecord(
system="sub", scenario="coding", task_set_id="coding-set-1", n_agent=3,
completed_tasks=9, total_tasks=10,
test_pass_rate=70, code_review_score=60, user_acceptance=65,
budget_usd=10, actual_cost_usd=6, model_tokens=42000,
target_time_s=600, actual_time_s=720, recovered_failures=1, total_failures=2,
)
# Quality: 0.4*90+0.3*80+0.3*85 = 85.5 ; 0.4*70+0.3*60+0.3*65 = 65.5
check("Q_swarm = 85.5", quality(swarm) == 85.5)
check("Q_sub = 65.5", quality(sub) == 65.5)
# DEFAULT is now symmetric C_base (Owner-ratified): C_base = n_base*(CostEff_base/100+0.5) = 3*(166.667/100+0.5) = 6.5
r = compare(swarm, sub)
check("default mode is symmetric", r["cost_mode"] == "symmetric" and r["c_base"] == 6.5)
check("G_E = 20.0", r["g_e"] == 20.0)
check("C_swarm = 12.5 (5*(200/100+0.5))", r["c_swarm"] == 12.5)
check("G_E,c symmetric = (85.5/12.5)-(65.5/6.5)", round(r["g_e_cost"], 4) == round((85.5 / 12.5) - (65.5 / 6.5), 4))
check("base_coefficient(sub) = 0.90", r["base_coefficient"] == 0.90)
check("raw gain positive", r["raw_gain_positive"] is True)
check("cost-normalized negative (5 agents not worth it here)", r["cost_normalized_positive"] is False)
ev = evaluate(swarm, [sub])
check("swarm_valid False (fails cost-normalized)", ev["swarm_valid"] is False)
# Literal v2.0 (reference only): C_base = 1.0
rl = compare(swarm, sub, symmetric_cost=False)
check("literal mode label + C_base=1.0", rl["cost_mode"] == "literal_v2.0" and rl["c_base"] == 1.0)
check("literal G_E,c = (85.5/12.5)-65.5", round(rl["g_e_cost"], 4) == round((85.5 / 12.5) - 65.5, 4))
um = unified_metrics(sub)
check("unified_metrics keys", set(um.keys()) == {"completion", "quality", "cost_efficiency", "speed", "robustness"})
check("unified completion 90", um["completion"] == 90.0)
# from_dict validation
ok = BenchmarkRunRecord.from_dict({f: getattr(sub, f) for f in BenchmarkRunRecord.__dataclass_fields__})
check("from_dict round-trips", ok == sub)
try:
BenchmarkRunRecord.from_dict({"system": "sub"})
check("from_dict rejects missing fields", False)
except ValueError:
check("from_dict rejects missing fields", True)
try:
compare(sub, swarm) # first arg must be system='swarm'
check("compare requires swarm record first", False)
except ValueError:
check("compare requires swarm record first", True)
try:
bad = BenchmarkRunRecord.from_dict({f: getattr(sub, f) for f in BenchmarkRunRecord.__dataclass_fields__} | {"task_set_id": "other"})
compare(swarm, bad)
check("compare requires same task_set_id", False)
except ValueError:
check("compare requires same task_set_id", True)
print()
if failures:
print(f"{len(failures)} comparison check(s) FAILED: {failures}")
sys.exit(1)
print("all baseline comparison checks passed (v2.0)")
+95
View File
@@ -0,0 +1,95 @@
"""Test SwarmRunMetricsCollector against a synthetic run with known states.
Hermetic (REDIS_FAKE). Run from agent_swarm_v6: python scripts/test-benchmark-collector.py
"""
import asyncio
import json
import math
import os
import sys
from pathlib import Path
os.environ["REDIS_FAKE"] = "1"
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from orchestrator.redis_client import redis_client
from orchestrator import swarm_runtime as sr_mod
from orchestrator.swarm_runtime import swarm_runtime
from orchestrator.task_queue import task_queue, TaskStatus
from benchmark.collectors.run_collector import SwarmRunMetricsCollector
failures = []
def check(name, cond):
print(("PASS" if cond else "FAIL"), "-", name)
if not cond:
failures.append(name)
async def _noop(self, *a, **k):
return None
async def add_task(run, task_id, *, status, agent, cost=0.0, retry=0, depends_on=None):
t = await task_queue.create_task(task_id=task_id, description=task_id, agent_role=task_id.split("-")[-1],
depends_on=depends_on or [], enqueue=False)
t.status = status
t.assigned_agent_id = agent
t.retry_count = retry
t.result = json.dumps({"usage": {"model_cost_usd": cost}})
await task_queue._save_task(t)
await swarm_runtime.attach_task(run, t.task_id)
return t
async def main():
await redis_client.connect()
sr_mod.SwarmRuntime._post_callback = _noop # no real HTTP
body = {
"mode": "swarm",
"requirement": {"objective": "collector test"},
"orchestration_plan": {"budget": {"max_cost_usd": 10}},
"callback": {"url": "http://127.0.0.1:9/cb", "subscribed_events": []},
"metadata": {"manager_deployment_id": "m-col"},
}
run, _ = await swarm_runtime.get_or_create_run(body=body, idempotency_key=None, correlation_id="c")
await add_task(run, "t-implementation", status=TaskStatus.COMPLETED, agent="A", cost=2.0)
await add_task(run, "t-testing", status=TaskStatus.COMPLETED, agent="B", cost=3.0, retry=1,
depends_on=["t-implementation"])
await add_task(run, "t-documentation", status=TaskStatus.FAILED, agent="A", cost=0.0, retry=3)
await swarm_runtime.emit_event(run, "handoff.requested", payload={"task_id": "t1", "from_role": "implementation", "to_role": "testing"})
await swarm_runtime.emit_event(run, "handoff.completed", payload={"task_id": "t1", "from_role": "implementation", "to_role": "testing"})
collector = SwarmRunMetricsCollector(run.swarm_id)
metrics = await collector.collect()
cov = collector.coverage
# s_completion = 2/3*100
check("s_completion = 66.67", round(metrics.s_completion, 2) == 66.67 and cov["s_completion"])
# s_collaboration = 0.5*100(handoff) + 0.3*100(dep resolved) + 0.2*50(workload A:2,B:1) = 90
check("s_collaboration = 90.0", round(metrics.s_collaboration, 1) == 90.0 and cov["s_collaboration"])
# s_robustness: failures={t-testing(retry),t-documentation(failed)}=2, recovered={t-testing completed}=1 => 50
check("s_robustness = 50.0", round(metrics.s_robustness, 1) == 50.0 and cov["s_robustness"])
# s_cost = 100*budget(10)/actual(5) = 200
check("s_cost = 200.0", round(metrics.s_cost, 1) == 200.0 and cov["s_cost"])
# governance: no approvals -> NaN, coverage False
check("s_governance NaN + coverage False", math.isnan(metrics.s_governance) and cov["s_governance"] is False)
# not-yet-collectable -> NaN + coverage False
check("uncollectable metrics NaN + coverage False",
all(math.isnan(getattr(metrics, k)) and cov[k] is False
for k in ("tau", "eta", "p_decision", "reward", "s_gain", "s_communication",
"s_swarm", "g_e", "g_e_cost", "benchmark")))
print()
if failures:
print(f"{len(failures)} collector check(s) FAILED: {failures}")
sys.exit(1)
print("all benchmark collector checks passed")
if __name__ == "__main__":
asyncio.run(main())
+72
View File
@@ -0,0 +1,72 @@
"""Unit tests for benchmark metric formulas — Agent 蜂群指标量化与标准 v2.0.
Run from agent_swarm_v6: python scripts/test-benchmark-metrics.py
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
import benchmark.metrics as m
failures = []
def check(name, cond):
print(("PASS" if cond else "FAIL"), "-", name)
if not cond:
failures.append(name)
# --- weight tables sum/shape (v2.0) ---
check("Σλ = 1.0", abs(sum(m.LAMBDA_WEIGHTS.values()) - 1.0) < 1e-9)
check("τ weights sum = 1.0", abs(sum(m.TAU_WEIGHTS.values()) - 1.0) < 1e-9)
check("η weights sum = 1.0", abs(sum(m.ETA_WEIGHTS.values()) - 1.0) < 1e-9)
check("reward weights sum = 1.0", abs(sum(m.REWARD_WEIGHTS.values()) - 1.0) < 1e-9)
check("S_swarm weights sum = 1.0", abs(sum(m.SWARM_WEIGHTS.values()) - 1.0) < 1e-9)
check("η includes Confidence (v2.0)", "confidence" in m.ETA_WEIGHTS)
# --- decision layer ---
# pheromone with all=1 except penalties=0 -> sum of positive weights 0.25+0.20+0.20 = 0.65
check("pheromone positive-only", round(m.pheromone(success=1, quality=1, acceptance=1, cost=0, time=0, risk=0, rollback=0), 4) == 0.65)
check("heuristic positive-only", round(m.heuristic(match=1, urgency=1, dependency=1, resource=1, confidence=1, risk=0, budget_pressure=0), 4) == 0.80)
# p_decision = τ^α·η^β·100 ; τ=0.5, η=0.5, α=1, β=2 -> 0.5*0.25*100 = 12.5
check("p_decision = τ^α·η^β·100", round(m.p_decision(0.5, 0.5, 1.0, 2.0), 4) == 12.5)
# action_probability normalizes
ap = m.action_probability(2.0, 2.0, 1.0, 1.0, [(2.0, 2.0), (1.0, 1.0)])
check("action_probability normalized", round(ap, 4) == round(4.0 / 5.0, 4))
# --- execution layer ---
# reward with all components=100, penalties=0 -> sum of positive weights *100 = (0.20+0.20+0.12+0.13+0.13+0.10)*100 = 88
check("reward positive-only = 88.0",
round(m.reward(s_task=100, q_quality=100, v_speed=100, e_cost=100, r_robust=100, g_gov=100, p_risk=0, p_rework=0), 4) == 88.0)
check("quality_score", m.quality_score(100, 100, 100) == 100.0)
# --- emergence + cost-normalized (v2.0 CHANGED to difference + dynamic C_swarm) ---
check("emergence_gain", m.emergence_gain(90, 80) == 10)
# C_swarm = N_agent*(CostEfficiency/100 + 0.5); N=4, CE=50 -> 4*(0.5+0.5)=4.0
check("swarm_cost dynamic", m.swarm_cost(4, 50) == 4.0)
# G_E,c = (Q_swarm/C_swarm) - (Q_base/C_base); (80/4)-(80/1)... use (80/4)-(20/1)=20-20=0
check("cost_normalized_gain is a difference", m.cost_normalized_gain(80, 4.0, 20, 1.0) == 0.0)
# --- benchmark_agent: Σλ enforcement ---
check("benchmark_agent uses default Σλ=1 weights",
round(m.benchmark_agent(s_swarm=10, g_e=0, reward=0, observability=0, governance=0), 4) == round(0.30 * 10, 4))
try:
m.benchmark_agent(s_swarm=1, g_e=1, reward=1, observability=1, governance=1,
weights={"lambda1": 0.5, "lambda2": 0.5, "lambda3": 0.5, "lambda4": 0.5, "lambda5": 0.5})
check("benchmark_agent rejects Σλ != 1.0", False)
except ValueError:
check("benchmark_agent rejects Σλ != 1.0", True)
# --- dataclass shape (v2.0) ---
expected_fields = {"tau", "eta", "p_decision", "reward", "s_completion", "s_gain", "s_collaboration",
"s_communication", "s_cost", "s_robustness", "s_governance", "s_swarm",
"g_e", "g_e_cost", "benchmark"}
check("SwarmMetrics has v2.0 fields", set(m.SwarmMetrics.__dataclass_fields__.keys()) == expected_fields)
print()
if failures:
print(f"{len(failures)} formula check(s) FAILED: {failures}")
sys.exit(1)
print("all benchmark metric formula checks passed (v2.0)")
+137
View File
@@ -0,0 +1,137 @@
"""Manager event-contract test.
Validates that Swarm's emitted callback events conform to the Heicode Manager handler
(`heicode/controller/agent_callback.go`):
- each event_type carries HM's required payload fields,
- the HMAC signing canonical string / headers match HM's verification.
Hermetic: REDIS_FAKE in-memory store; callbacks are captured in-process (not sent).
Run from agent_swarm_v6: python scripts/test-contract-events.py
"""
import asyncio
import hashlib
import hmac
import json
import os
import sys
from pathlib import Path
os.environ["REDIS_FAKE"] = "1"
os.environ["AGENT_CALLBACK_SIGNING_SECRET"] = "test-signing-secret"
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from orchestrator.redis_client import redis_client
from orchestrator import swarm_runtime as sr_mod
from orchestrator.swarm_runtime import swarm_runtime
# HM required payload fields per event_type (agent_callback.go registry).
REQUIRED = {
"deployment.status_changed": ["status"],
"task.created": ["task_id", "title"],
"task.claimed": ["task_id", "agent_role"],
"task.running": ["task_id", "agent_role"],
"task.heartbeat": ["task_id", "agent_role"],
"task.blocked": ["task_id", "reason"],
"task.retried": ["task_id", "attempt"],
"task.released": ["task_id", "agent_role"],
"task.failed": ["task_id", "reason"],
"task.completed": ["task_id"],
"handoff.requested": ["task_id", "from_role", "to_role"],
"handoff.completed": ["task_id", "from_role", "to_role"],
"approval.requested": ["approval_id", "operation", "risk_level"],
"artifact.created": ["artifact_id"],
"timeline.updated": ["title"],
"budget.alert": ["threshold_pct"],
}
SECRET = os.environ["AGENT_CALLBACK_SIGNING_SECRET"]
captured = [] # (raw_body, headers)
failures = []
def check(name, cond):
print(("PASS" if cond else "FAIL"), "-", name)
if not cond:
failures.append(name)
def validate_envelope(raw_body: str, headers: dict) -> None:
env = json.loads(raw_body)
et = env["event_type"]
# required fields
req = REQUIRED.get(et, [])
payload = env.get("payload") or {}
missing = [f for f in req if f not in payload or payload.get(f) in (None, "")]
check(f"{et}: required fields present {req}", not missing)
# HMAC canonical string == ts + "." + event_id + "." + raw_body
ts = headers.get("X-Agent-Timestamp")
sig = headers.get("X-Agent-Signature", "")
eid = headers.get("X-Agent-Event-Id")
expect = "sha256=" + hmac.new(SECRET.encode(), f"{ts}.{eid}.{raw_body}".encode(), hashlib.sha256).hexdigest()
check(f"{et}: HMAC signature matches HM canonical string", sig == expect)
check(f"{et}: event_id header == body event_id", eid == env.get("event_id"))
async def _capture(self, swarm_id, url, raw_body, headers, event_type, event_id):
captured.append((raw_body, headers))
async def emit(run, event_type, **payload):
captured.clear()
await swarm_runtime.emit_event(run, event_type, payload=payload)
await asyncio.sleep(0) # let the create_task'd callback run
assert captured, f"no callback captured for {event_type}"
return captured[-1]
async def main():
await redis_client.connect()
sr_mod.SwarmRuntime._post_callback = _capture # capture instead of HTTP POST
body = {
"mode": "swarm",
"requirement": {"objective": "contract test"},
"callback": {"url": "http://127.0.0.1:9/cb", "subscribed_events": []},
"metadata": {"manager_deployment_id": "m-contract"},
}
run, _ = await swarm_runtime.get_or_create_run(body=body, idempotency_key=None, correlation_id="corr-contract")
# 1) centralized normalization (the 2 emit_event fixes)
raw, hdr = await emit(run, "timeline.updated", summary="did the thing")
check("timeline.updated normalized to include title", json.loads(raw)["payload"].get("title") == "did the thing")
validate_envelope(raw, hdr)
raw, hdr = await emit(run, "budget.alert", threshold=0.8)
check("budget.alert normalized to threshold_pct=80", json.loads(raw)["payload"].get("threshold_pct") == 80.0)
validate_envelope(raw, hdr)
# 2) the handler-built payloads for the other two fixed types
raw, hdr = await emit(run, "handoff.completed", task_id="t1", from_role="implementation", to_role="testing")
validate_envelope(raw, hdr)
raw, hdr = await emit(run, "task.released", task_id="t1", agent_role="testing", reason="at_capacity")
validate_envelope(raw, hdr)
# 3) representative coverage of the common event types
samples = {
"deployment.status_changed": {"status": "running"},
"task.created": {"task_id": "t1", "title": "impl"},
"task.claimed": {"task_id": "t1", "agent_role": "implementation"},
"task.completed": {"task_id": "t1"},
"task.failed": {"task_id": "t1", "reason": "boom"},
"task.retried": {"task_id": "t1", "attempt": 1},
"approval.requested": {"approval_id": "ap1", "operation": "git.write", "risk_level": "high"},
"artifact.created": {"artifact_id": "art1"},
}
for et, pl in samples.items():
raw, hdr = await emit(run, et, **pl)
validate_envelope(raw, hdr)
print()
if failures:
print(f"{len(failures)} contract check(s) FAILED: {failures}")
sys.exit(1)
print("all event-contract checks passed")
if __name__ == "__main__":
asyncio.run(main())
+21 -2
View File
@@ -113,10 +113,11 @@ async def test_agent_peer_routing():
"type": "peer_message",
"from_agent_id": "agent-impl",
"task_id": "t1",
"content": "please advise",
"content": "", # empty -> deterministic fallback reply (no model needed)
"correlation_id": "corr-1",
"is_reply": False,
})
await asyncio.sleep(0.05) # answer_peer_query is scheduled as a background task
check("peer query produces a reply", len(sent) == 1 and sent[0]["is_reply"] is True)
check("peer reply targets requester", sent[0]["target_agent_id"] == "agent-impl")
@@ -239,8 +240,26 @@ async def test_agent_peer_shares_summary():
sent.append(payload)
a.safe_send = fake_send
# Fallback path: no query content -> cached summary (no model needed).
await a.answer_peer_query({"from_agent_id": "agent-test", "correlation_id": "c", "is_reply": False})
check("peer reply shares last summary", "implemented add()" in sent[0]["content"])
check("peer reply falls back to last summary", "implemented add()" in sent[0]["content"])
# Substantive path: a stubbed executor returns a grounded, structured reply.
class _FakeExec:
async def peer_reply(self, *, query, capabilities, last_summary, max_tokens=None):
return {"content": f"grounded answer to: {query}", "stance": "agree",
"evidence": "src", "refs": ["a.py"]}
a._peer_executor = _FakeExec()
sent.clear()
await a.answer_peer_query({
"from_agent_id": "agent-test", "correlation_id": "c2", "task_id": "t",
"content": "does add() raise ValueError?", "is_reply": False,
})
check("substantive peer reply uses executor content",
sent[0]["content"] == "grounded answer to: does add() raise ValueError?"
and sent[0]["stance"] == "agree" and sent[0]["refs"] == ["a.py"])
async def main():