diff --git a/docs/hermes-openclaw-a2a-current-state.json b/docs/hermes-openclaw-a2a-current-state.json new file mode 100644 index 0000000000000..70093a6d36fc6 --- /dev/null +++ b/docs/hermes-openclaw-a2a-current-state.json @@ -0,0 +1,276 @@ +{ + "repo_a2a_items": [ + { + "path": "/.hermes/hermes-agent/docs/hermes-openclaw-a2a-task-plan-v1.0.0.md", + "type": "file", + "size": 8876 + }, + { + "path": "/.hermes/hermes-agent/docs/hermes-openclaw-a2a-task-plan-v1.1.0.md", + "type": "file", + "size": 13412 + }, + { + "path": "/.hermes/hermes-agent/docs/hermes-openclaw-a2a-v240-recurring-cron-template.md", + "type": "file", + "size": 3623 + }, + { + "path": "/.hermes/hermes-agent/docs/hermes-openclaw-a2a-v250-cron-monitor-race-hardening.md", + "type": "file", + "size": 3677 + }, + { + "path": "/.hermes/hermes-agent/docs/hermes-openclaw-a2a-worklog-and-architecture.md", + "type": "file", + "size": 9349 + } + ], + "home_a2a_items": [ + { + "path": "/root/.hermes/skills/autonomous-ai-agents/agent-to-agent-a2a-bridging/references/hermes-openclaw-a2a-adaptive-dispatch-authorization-acceptance.md", + "type": "file", + "size": 3169 + }, + { + "path": "/root/.hermes/skills/autonomous-ai-agents/agent-to-agent-a2a-bridging/references/hermes-openclaw-a2a-role-bound-dispatch-plan.md", + "type": "file", + "size": 1976 + }, + { + "path": "/root/.hermes/skills/autonomous-ai-agents/agent-to-agent-a2a-bridging/references/hermes-openclaw-a2a-v067.md", + "type": "file", + "size": 4105 + }, + { + "path": "/root/.hermes/skills/autonomous-ai-agents/agent-to-agent-a2a-bridging/references/hermes-openclaw-a2a-v070-callback-loop.md", + "type": "file", + "size": 3143 + }, + { + "path": "/root/.hermes/skills/autonomous-ai-agents/agent-to-agent-a2a-bridging/references/hermes-openclaw-a2a-v071-guarded-callback-send.md", + "type": "file", + "size": 3508 + }, + { + "path": "/root/.hermes/skills/autonomous-ai-agents/agent-to-agent-a2a-bridging/references/hermes-openclaw-a2a-v072-one-shot-runner.md", + "type": "file", + "size": 3331 + }, + { + "path": "/root/.hermes/skills/autonomous-ai-agents/agent-to-agent-a2a-bridging/references/hermes-openclaw-a2a-v100-bidirectional-minimal-loop.md", + "type": "file", + "size": 5245 + }, + { + "path": "/root/.hermes/skills/autonomous-ai-agents/agent-to-agent-a2a-bridging/references/hermes-openclaw-a2a-v110-failure-paths.md", + "type": "file", + "size": 3679 + }, + { + "path": "/root/.hermes/skills/autonomous-ai-agents/agent-to-agent-a2a-bridging/references/hermes-openclaw-a2a-v120-explicit-queue-runner.md", + "type": "file", + "size": 2235 + }, + { + "path": "/root/.hermes/skills/autonomous-ai-agents/agent-to-agent-a2a-bridging/references/hermes-openclaw-a2a-v120-local-queue-runner.md", + "type": "file", + "size": 4802 + }, + { + "path": "/root/.hermes/skills/autonomous-ai-agents/agent-to-agent-a2a-bridging/references/hermes-openclaw-a2a-v120-v130-queue-runners.md", + "type": "file", + "size": 4812 + }, + { + "path": "/root/.hermes/skills/autonomous-ai-agents/agent-to-agent-a2a-bridging/references/hermes-openclaw-a2a-v120-v200-queue-cli-cron.md", + "type": "file", + "size": 6738 + }, + { + "path": "/root/.hermes/skills/autonomous-ai-agents/agent-to-agent-a2a-bridging/references/hermes-openclaw-a2a-v130-mixed-queue-isolation.md", + "type": "file", + "size": 2615 + }, + { + "path": "/root/.hermes/skills/autonomous-ai-agents/agent-to-agent-a2a-bridging/references/hermes-openclaw-a2a-v140-queue-cli-schema.md", + "type": "file", + "size": 2774 + }, + { + "path": "/root/.hermes/skills/autonomous-ai-agents/agent-to-agent-a2a-bridging/references/hermes-openclaw-a2a-v210-dispatch-compact-callback.md", + "type": "file", + "size": 2744 + }, + { + "path": "/root/.hermes/skills/autonomous-ai-agents/agent-to-agent-a2a-bridging/references/hermes-openclaw-a2a-v220-compact-cron-callback.md", + "type": "file", + "size": 2302 + }, + { + "path": "/root/.hermes/skills/autonomous-ai-agents/agent-to-agent-a2a-bridging/references/hermes-openclaw-a2a-v230-compact-cronjob-boundary.md", + "type": "file", + "size": 2345 + }, + { + "path": "/root/.hermes/skills/autonomous-ai-agents/agent-to-agent-a2a-bridging/references/hermes-openclaw-a2a-v231-cron-scheduler-run-verification.md", + "type": "file", + "size": 1980 + }, + { + "path": "/root/.hermes/skills/autonomous-ai-agents/agent-to-agent-a2a-bridging/references/hermes-openclaw-a2a-v232-cron-runtime-monitor.md", + "type": "file", + "size": 2069 + }, + { + "path": "/root/.hermes/skills/autonomous-ai-agents/agent-to-agent-a2a-bridging/references/hermes-openclaw-a2a-v240-manual-cron-monitor-race.md", + "type": "file", + "size": 2670 + }, + { + "path": "/root/.hermes/skills/autonomous-ai-agents/agent-to-agent-a2a-bridging/references/hermes-openclaw-a2a-v260-controlled-manual-cron-run.md", + "type": "file", + "size": 2679 + }, + { + "path": "/root/.hermes/skills/software-development/hermes-platform-delivery-and-channel-routing/references/hermes-openclaw-a2a-receipt-callback.md", + "type": "file", + "size": 5256 + }, + { + "path": "/root/.hermes/skills/software-development/hermes-specialist-workflow-routing/references/hermes-openclaw-a2a-gateway-live-install.md", + "type": "file", + "size": 5559 + }, + { + "path": "/root/.hermes/skills/software-development/hermes-specialist-workflow-routing/references/hermes-openclaw-a2a-webhooks-bridge.md", + "type": "file", + "size": 4397 + }, + { + "path": "/root/.hermes/skills/software-development/hermes-specialist-workflow-routing/references/hermes-openclaw-a2a-webhooks-planning.md", + "type": "file", + "size": 3278 + }, + { + "path": "/root/.hermes/skills/software-development/hermes-specialist-workflow-routing/references/hermes-openclaw-node22-direct-upgrade-first.md", + "type": "file", + "size": 2351 + }, + { + "path": "/root/.hermes/skills/software-development/hermes-specialist-workflow-routing/references/hermes-openclaw-v062-readiness-assessment.md", + "type": "file", + "size": 3229 + }, + { + "path": "/root/.hermes/skills/software-development/hermes-specialist-workflow-routing/references/hermes-openclaw-v066-authenticated-a2a-ping.md", + "type": "file", + "size": 3203 + }, + { + "path": "/root/.hermes/skills/software-development/hermes-specialist-workflow-routing/references/hermes-openclaw-vaultwarden-ssh-node22-readiness.md", + "type": "file", + "size": 4140 + } + ], + "scripts": [ + { + "path": "/.hermes/hermes-agent/scripts/hermes_openclaw_v120_queue_runner.py", + "size": 10682 + }, + { + "path": "/.hermes/hermes-agent/scripts/hermes_openclaw_v100_orchestrator.py", + "size": 13900 + }, + { + "path": "/.hermes/hermes-agent/scripts/hermes_openclaw_queue_cron_v220.sh", + "size": 500 + }, + { + "path": "/.hermes/hermes-agent/scripts/hermes_openclaw_queue_summary.py", + "size": 2931 + }, + { + "path": "/.hermes/hermes-agent/scripts/hermes_openclaw_cron_monitor.py", + "size": 8590 + }, + { + "path": "/.hermes/hermes-agent/scripts/hermes_openclaw_v130_mixed_queue_runner.py", + "size": 15179 + }, + { + "path": "/.hermes/hermes-agent/scripts/hermes_openclaw_queue_cron_v200.sh", + "size": 266 + }, + { + "path": "/.hermes/hermes-agent/scripts/hermes_openclaw_v110_failure_tests.py", + "size": 9957 + }, + { + "path": "/.hermes/hermes-agent/scripts/hermes_openclaw_queue.py", + "size": 20988 + }, + { + "path": "/.hermes/hermes-agent/scripts/__pycache__/hermes_openclaw_v110_failure_tests.cpython-311.pyc", + "size": 14784 + }, + { + "path": "/.hermes/hermes-agent/scripts/__pycache__/hermes_openclaw_cron_monitor.cpython-311.pyc", + "size": 14537 + }, + { + "path": "/.hermes/hermes-agent/scripts/__pycache__/hermes_openclaw_v130_mixed_queue_runner.cpython-311.pyc", + "size": 24773 + }, + { + "path": "/.hermes/hermes-agent/scripts/__pycache__/hermes_openclaw_v100_orchestrator.cpython-311.pyc", + "size": 28450 + }, + { + "path": "/.hermes/hermes-agent/scripts/__pycache__/hermes_openclaw_v120_queue_runner.cpython-311.pyc", + "size": 17464 + }, + { + "path": "/.hermes/hermes-agent/scripts/__pycache__/hermes_openclaw_queue.cpython-311.pyc", + "size": 46266 + }, + { + "path": "/root/.hermes/scripts/openclaw_queue_cron_v200.sh", + "size": 266 + }, + { + "path": "/root/.hermes/scripts/openclaw_queue_cron_v220.sh", + "size": 500 + } + ], + "evidence_dirs_sample": [ + { + "path": "/.hermes/hermes-agent/optional-skills/migration/openclaw-migration", + "mtime": 1779165541.222174 + }, + { + "path": "/.hermes/hermes-agent/optional-skills/migration/openclaw-migration/scripts", + "mtime": 1779165541.222174 + } + ], + "git_status": { + "code": 0, + "out": "## main...origin/main [ahead 2, behind 973]\n M agent/conversation_loop.py\n M gateway/platforms/base.py\n M gateway/platforms/feishu.py\n M gateway/run.py\n M gateway/stream_consumer.py\n M hermes_cli/kanban_db.py\n M tools/approval.py\n?? curation-consolidation-report.md\n?? docs/dispatch-strategy-v2.1.0.md\n?? docs/execution-v1.0.0.md\n?? docs/execution-v1.1.0.md\n?? docs/execution-v1.2.0.md\n?? docs/execution-v1.3.0.md\n?? docs/execution-v1.4.0.md\n?? docs/execution-v1.6.0.md\n?? docs/execution-v1.7.0.md\n?? docs/execution-v1.9.0.md\n?? docs/execution-v2.0.0.md\n?? docs/execution-v2.2.0.md\n?? docs/execution-v2.3.0.md\n?? docs/execution-v2.3.1.md\n?? docs/execution-v2.3.2.md\n?? docs/hermes-openclaw-a2a-task-plan-v1.0.0.md\n?? docs/hermes-openclaw-a2a-task-plan-v1.1.0.md\n?? docs/hermes-openclaw-a2a-v240-recurring-cron-template.md\n?? docs/hermes-openclaw-a2a-v250-cron-monitor-race-hardening.md\n?? docs/hermes-openclaw-a2a-worklog-and-architecture.md\n?? docs/validation-v1.0.0.md\n?? docs/validation-v1.1.0.md\n?? docs/validation-v1.2.0.md\n?? docs/validation-v1.3.0.md\n?? docs/validation-v1.4.0.md\n?? docs/validation-v1.6.0.md\n?? docs/validation-v1.7.0.md\n?? docs/validation-v1.9.0.md\n?? docs/validation-v2.0.0.md\n?? docs/validation-v2.2.0.md\n?? docs/validation-v2.3.0.md\n?? docs/validation-v2.3.1.md\n?? docs/validation-v2.3.2.md\n?? gateway/platforms/feishu.py.bak\n?? gateway/platforms/feishu.py.bak.20260522_171805\n?? gateway/platforms/feishu.py.bak.20260522_171812\n?? gateway/platforms/feishu.py.bak2\n?? gateway/stream_consumer.py.bak\n?? scripts/hermes_openclaw_cron_monitor.py\n?? scripts/hermes_openclaw_queue.py\n?? scripts/hermes_openclaw_queue_cron_v200.sh\n?? scripts/hermes_openclaw_queue_cron_v220.sh\n?? scripts/hermes_openclaw_queue_summary.py\n?? scripts/hermes_openclaw_v100_orchestrator.py\n?? scripts/hermes_openclaw_v110_failure_tests.py\n?? scripts/hermes_openclaw_v120_queue_runner.py\n?? scripts/hermes_openclaw_v130_mixed_queue_runner.py\n?? skill-clusters-analysis.json\n" + }, + "cron_list": { + "code": 0, + "out": "No scheduled jobs.\nCreate one with 'hermes cron create ...' or the /cron command in chat.\n" + }, + "gateway_status": { + "code": 0, + "out": "me__, str(e))\\\\nPY2'\\\\''\\n}\\nfor k,cmd in cmds.items():\\n try:\\n r=subprocess.run(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True,timeout=30)\\n report[k]={'\\\\''code'\\\\'':r.returncode,'\\\\''out'\\\\'':r.stdout[-4000:]}\\n except Exception as e:\\n report[k]={'\\\\''error'\\\\'':repr(e)}\\nout=Path('\\\\''/.hermes/hermes-agent/docs/hermes-openclaw-a2a-current-state.json'\\\\'')\\nout.write_text(json.dumps(report,ensure_ascii=False,indent=2),encoding='\\\\''utf-8'\\\\'')\\nprint(out)\\nprint(out.stat().st_size)\\nprint('\\\\''docs'\\\\'', len(report.get('\\\\''repo_a2a_items'\\\\'',[])), '\\\\''home_items'\\\\'', len(report.get('\\\\''home_a2a_items'\\\\'',[])), '\\\\''scripts'\\\\'', len(scripts), '\\\\''evidence'\\\\'', len(report['\\\\''evidence_dirs_sample'\\\\'']))\\nprint('\\\\''port'\\\\'', report['\\\\''port_18789'\\\\''])\\nprint('\\\\''agent_card_first'\\\\'', report['\\\\''agent_card'\\\\'']['\\\\''out'\\\\''][:500] if '\\\\''out'\\\\'' in report['\\\\''agent_card'\\\\''] else report['\\\\''agent_card'\\\\''])\\nPY'\\n__hermes_ec=\\$?\\nexport -p > /tmp/hermes-snap-5a3c29d16380.sh 2>/dev/null || true\\npwd -P > /tmp/hermes-cwd-5a3c29d16380.txt 2>/dev/null || true\\nprintf '\\\\n__HERMES_CWD_5a3c29d16380__%s__HERMES_CWD_5a3c29d16380__\\\\n' \\\"\\$(pwd -P)\\\"\\nexit \\$__hermes_ec\"\n ├─10427 python3 -\n ├─10440 /bin/sh -c \"hermes gateway status\"\n ├─10441 /.hermes/hermes-agent/venv/bin/python3 /root/.local/bin/hermes gateway status\n └─10453 systemctl status hermes-gateway --no-pager\n\nMay 29 21:35:10 Hermes-248 python[1014]: WARNING agent.tool_executor: Tool terminal returned error (0.28s): {\"output\": \"/usr/bin/bash: line 24: examples/v2.2.0/manual-v220-stdout.md: No such file or directory\", \"exit_code\": 1, \"error\": null}\nMay 29 21:37:18 Hermes-248 python[1014]: WARNING agent.tool_executor: Tool skill_manage returned error (0.00s): {\"success\": false, \"error\": \"File must be under one of: assets, references, scripts, templates. Got: 'SKILL.md'\"}\nMay 29 21:37:48 Hermes-248 python[1014]: WARNING agent.tool_executor: Tool cronjob returned error (0.00s): {\"error\": \"Invalid schedule 'once at 2099-01-01 00:00'. Use:\\n - Duration: '30m', '2h', '1d' (one-shot)\\n - Interval: 'every 30m', 'every 2h' (recurring)\\n - Cron: '0 9 * * *' (cron expression)\\n\nMay 29 21:38:45 Hermes-248 python[1014]: WARNING gateway.platforms.qqbot.adapter: [QQBot:1903759289] WebSocket closed: code=4009 reason=Session timed out\nMay 29 21:40:25 Hermes-248 python[1014]: WARNING agent.tool_executor: Tool skill_manage returned error (0.00s): {\"success\": false, \"error\": \"File must be under one of: assets, references, scripts, templates. Got: 'SKILL.md'\"}\nMay 29 21:48:24 Hermes-248 python[1014]: WARNING agent.tool_executor: Tool skill_manage returned error (0.00s): {\"success\": false, \"error\": \"File must be under one of: assets, references, scripts, templates. Got: 'SKILL.md'\"}\nMay 29 22:05:03 Hermes-248 python[1014]: WARNING agent.tool_executor: Tool terminal returned error (10.68s): {\"output\": \"\", \"exit_code\": 2, \"error\": null}\nMay 29 22:05:53 Hermes-248 python[1014]: WARNING agent.tool_executor: Tool terminal returned error (20.68s): {\"output\": \"monitor_exists True 2069\\nok False\\ndiagnosis process still running\\nexecution_observed False\\nnew_evidence ['cron-run-20260529T140519Z']\\nprocess_running_at_end True\\ninitial_count 3\\nfin\nMay 29 22:08:47 Hermes-248 python[1014]: WARNING gateway.platforms.qqbot.adapter: [QQBot:1903759289] WebSocket closed: code=4009 reason=Session timed out\nMay 29 22:16:08 Hermes-248 python[1014]: WARNING agent.tool_executor: Tool skill_manage returned error (0.00s): {\"success\": false, \"error\": \"File must be under one of: assets, references, scripts, templates. Got: 'SKILL.md'\"}\n⚠ Installing gateway service to run as root.\n This is fine for LXC/container environments but not recommended on bare-metal hosts.\n✓ System gateway service is running\nConfigured to run as: root\n✓ System service starts at boot without requiring systemd linger\n" + }, + "port_18789": { + "code": 0, + "out": "open\n" + }, + "agent_card": { + "code": 0, + "out": "HTTPError HTTP Error 404: Not Found\n" + } +} \ No newline at end of file diff --git a/docs/hermes-openclaw-a2a-file-inventory-v2.6.x.md b/docs/hermes-openclaw-a2a-file-inventory-v2.6.x.md new file mode 100644 index 0000000000000..ade5a95a2e7dc --- /dev/null +++ b/docs/hermes-openclaw-a2a-file-inventory-v2.6.x.md @@ -0,0 +1,61 @@ +# Hermes ↔ OpenClaw A2A v2.6.x File Inventory + +## 生成时间 +本文件由 v2.6.4 runbook closure 阶段生成,用于记录当前工作区内 v2.6.x 相关新增/修改文件。 + +## Git 工作区清单 +```text +M docs/hermes-openclaw-a2a-worklog-and-architecture.md +?? docs/hermes-openclaw-a2a-live-validation-v2.6.0.md +?? docs/hermes-openclaw-a2a-phase6-review-v2.6.0.md +?? docs/hermes-openclaw-a2a-review-v2.6.0.md +?? docs/hermes-openclaw-a2a-review-v2.6.1.md +?? docs/hermes-openclaw-a2a-review-v2.6.2.md +?? docs/hermes-openclaw-a2a-review-v2.6.3.md +?? docs/hermes-openclaw-a2a-task-plan-v2.6.0.md +?? docs/hermes-openclaw-a2a-task-plan-v2.6.1.md +?? docs/hermes-openclaw-a2a-task-plan-v2.6.2.md +?? docs/hermes-openclaw-a2a-task-plan-v2.6.3.md +?? docs/hermes-openclaw-a2a-validation-v2.6.0.md +?? docs/hermes-openclaw-a2a-validation-v2.6.1.md +?? docs/hermes-openclaw-a2a-validation-v2.6.2.md +?? docs/hermes-openclaw-a2a-validation-v2.6.3.md +?? scripts/hermes_openclaw_v260_two_worker.py +?? scripts/validate_a2a_v260_evidence.py +?? scripts/validate_a2a_v260_mock.py +?? scripts/validate_a2a_v260_negative.py +?? scripts/verify_a2a_v263_chain.py +``` + +## v2.6.x 核心脚本 +- `scripts/hermes_openclaw_v260_two_worker.py`:v2.6.0 two-worker runner,支持 dry-run 与受控 live 模式。 +- `scripts/validate_a2a_v260_mock.py`:v2.6.0 mock fixture validator。 +- `scripts/validate_a2a_v260_evidence.py`:v2.6.1 positive evidence validator。 +- `scripts/validate_a2a_v260_negative.py`:v2.6.2 negative failure-path validator。 +- `scripts/verify_a2a_v263_chain.py`:v2.6.3 一键 verify chain。 + +## v2.6.x 核心文档 +- `docs/hermes-openclaw-a2a-task-plan-v2.6.0.md` +- `docs/hermes-openclaw-a2a-validation-v2.6.0.md` +- `docs/hermes-openclaw-a2a-review-v2.6.0.md` +- `docs/hermes-openclaw-a2a-live-validation-v2.6.0.md` +- `docs/hermes-openclaw-a2a-phase6-review-v2.6.0.md` +- `docs/hermes-openclaw-a2a-task-plan-v2.6.1.md` +- `docs/hermes-openclaw-a2a-validation-v2.6.1.md` +- `docs/hermes-openclaw-a2a-review-v2.6.1.md` +- `docs/hermes-openclaw-a2a-task-plan-v2.6.2.md` +- `docs/hermes-openclaw-a2a-validation-v2.6.2.md` +- `docs/hermes-openclaw-a2a-review-v2.6.2.md` +- `docs/hermes-openclaw-a2a-task-plan-v2.6.3.md` +- `docs/hermes-openclaw-a2a-validation-v2.6.3.md` +- `docs/hermes-openclaw-a2a-review-v2.6.3.md` +- `docs/hermes-openclaw-a2a-runbook-v2.6.4.md` +- `docs/hermes-openclaw-a2a-file-inventory-v2.6.x.md` + +- `docs/hermes-openclaw-a2a-precommit-audit-v2.6.x.md` + +## 当前边界 +- v2.6.0 包含一次历史受控 live two-worker evidence。 +- v2.6.1/v2.6.2/v2.6.3/v2.6.4 不新增 live call。 +- 当前未启用 cron / daemon / webhook / reverse loop。 +- 当前没有提交 git commit。 diff --git a/docs/hermes-openclaw-a2a-live-validation-v2.6.0.md b/docs/hermes-openclaw-a2a-live-validation-v2.6.0.md new file mode 100644 index 0000000000000..c034b36a70bef --- /dev/null +++ b/docs/hermes-openclaw-a2a-live-validation-v2.6.0.md @@ -0,0 +1,128 @@ +# Hermes ↔ OpenClaw A2A v2.6.0 Live Validation + +## 结论 + +PASS with boundary:v2.6.0 已完成从本地 mock 到 live two-worker 的受控样例验证。 + +Validation marker:`A2A_V260_LIVE_TWO_WORKER_OK` + +## 执行范围 + +本次执行严格对应 v2.6.0 实施顺序: + +1. 计划落盘并回读验证:已完成。 +2. 总入口文档指向 v2.6.0:已完成。 +3. dispatch envelope / receipt / acceptance fixture:已完成。 +4. 本地 mock schema validation:已完成。 +5. live two-worker A2A 样例:已完成。 +6. Hermes 回收 evidence 并生成 acceptance report:已完成。 + +未进入第 7 步:未固化为 queue CLI 或 Kanban/Swarm 模板。 + +## 实际执行命令 + +```bash +cd /.hermes/hermes-agent +python3 -m py_compile scripts/hermes_openclaw_v260_two_worker.py +python3 scripts/hermes_openclaw_v260_two_worker.py \ + --out-dir examples/v2.6.0/live-two-worker +``` + +## 真实执行结果 + +```json +{ + "ok": true, + "run_id": "a2a-v260-two-worker-20260530T091052Z", + "dry_run": false, + "receipt_count": 2, + "accepted_count": 2, + "overall": "accepted_with_boundary", + "secret_scan_ok": true, + "out_dir": "examples/v2.6.0/live-two-worker", + "compact_summary": "结论:A2A v2.6.0 two-worker 样例 accepted_with_boundary\nrun_id: a2a-v260-two-worker-20260530T091052Z\nOpenClaw 子任务:2 个,accepted 2 / rejected 0 / blocked 0\n证据:examples/v2.6.0/live-two-worker\n边界:未启用反向调用 / 未启用 cron / 未重启 gateway / 未外发平台消息\n下一步:只在明确要求后,才固化为 queue CLI 或 Kanban/Swarm 模板\n" +} +``` + +## 关键证据 + +- live evidence dir:`examples/v2.6.0/live-two-worker` +- dry-run evidence dir:`examples/v2.6.0/dry-run-two-worker` +- live readiness:`examples/v2.6.0/live-two-worker/readiness.json` +- Worker A receipt:`examples/v2.6.0/live-two-worker/a2a-v260-worker-readiness-receipt.json` +- Worker B receipt:`examples/v2.6.0/live-two-worker/a2a-v260-worker-review-receipt.json` +- Acceptance report:`examples/v2.6.0/live-two-worker/acceptance-report.json` +- Execution summary:`examples/v2.6.0/live-two-worker/execution-summary.json` +- Compact summary:`examples/v2.6.0/live-two-worker/compact-summary.md` + +## Live readiness + +`readiness.json` 记录: + +- `agent_card_http_status=200` +- `agent_card_live=true` +- `credential_loaded=true` +- `credential_recorded=false` + +这表示 live endpoint 可达,授权凭据仅用于本次请求,不写入证据文件。 + +## Worker 验收 + +Acceptance report 中两个子任务均为 accepted: + +- `a2a-v260-worker-readiness`:accepted +- `a2a-v260-worker-review`:accepted + +两个 receipt 均满足: + +- `schema_version=a2a-worker-receipt-v1` +- `ok=true` +- `http_status=200` +- `state=completed` +- `auth.token_recorded=false` +- marker 在对应 evidence 文件中存在 + +## Side effects + +Acceptance report 记录: + +```json +{ + "gateway_restart": false, + "openclaw_restart": false, + "cron_enabled": false, + "platform_send": false, + "live_a2a_call": true +} +``` + +解释: + +- `live_a2a_call=true`:本轮确实执行了 live Hermes → OpenClaw A2A JSON-RPC `message/send`。 +- `gateway_restart=false`:未重启 Hermes gateway。 +- `openclaw_restart=false`:未重启 OpenClaw。 +- `cron_enabled=false`:未启用 cron。 +- `platform_send=false`:未做额外平台外发。 + +## Secret scan + +Acceptance report 记录: + +```json +{ + "ok": true, + "token_recorded": false, + "forbidden_literals_found": [] +} +``` + +本地回读扫描也显示:`violations=[]`。 + +## 边界 + +本轮通过的是受控 live two-worker 样例,不是开放式自治协作: + +- 未启用 OpenClaw → Hermes 反向调用。 +- 未启用 daemon / webhook / recurring automation。 +- 未将该样例固化为 queue CLI 或 Kanban/Swarm 模板。 +- Hermes 仍是 controller 和 final acceptor。 diff --git a/docs/hermes-openclaw-a2a-openclaw-gateway-blocking-response-fix.md b/docs/hermes-openclaw-a2a-openclaw-gateway-blocking-response-fix.md new file mode 100644 index 0000000000000..0a2aa94ce921a --- /dev/null +++ b/docs/hermes-openclaw-a2a-openclaw-gateway-blocking-response-fix.md @@ -0,0 +1,135 @@ +# OpenClaw A2A gateway blocking response patch note + +## Context + +During Hermes ↔ OpenClaw A2A v2.6 live validation, Hermes could deliver tasks to OpenClaw and OpenClaw executed them, but the blocking `message/send` HTTP call timed out before receiving the final JSON-RPC result. + +Layered status before the fix: + +- Hermes → OpenClaw task delivery: passed +- OpenClaw task execution: passed +- OpenClaw log/audit marker: passed +- OpenClaw → Hermes blocking JSON-RPC response: failed / timed out +- two-worker live acceptance: blocked + +## Root cause + +OpenClaw gateway returned the initial `agent` RPC as `accepted`, while the final agent output arrived later as a WebSocket `event=agent` frame without the original request id. + +The existing `GatewayRpcConnection.handleMessage()` path ignored event frames after `connect.challenge`, so the pending `expectFinal` agent request was never resolved. Hermes therefore waited until the blocking HTTP client timed out. + +## Runtime fix applied on OpenClaw 247 + +Runtime repo: + +```text +/root/.openclaw/extensions/a2a-gateway +``` + +Changed file: + +```text +src/executor.ts +``` + +Patch class: + +- preserve `connect.challenge` behavior; +- for `event=agent`, inspect `payload.result.payloads`; +- only resolve when the event contains real agent text/media payload content; +- if exactly one pending `expectFinal` `agent` request exists, resolve it with the event payload; +- ignore non-content agent events to avoid resolving too early. + +Current uncommitted runtime diff: + +```diff +diff --git a/src/executor.ts b/src/executor.ts +index d5ab86c..05334c4 100644 +--- a/src/executor.ts ++++ b/src/executor.ts +@@ -921,6 +921,24 @@ export class GatewayRpcConnection { + if (nonce && this.connectChallengeResolver) { + this.connectChallengeResolver(nonce); + } ++ return; ++ } ++ ++ if (frame.event === "agent") { ++ const eventPayload = asObject(frame.payload); ++ const eventResult = asObject(eventPayload?.result); ++ const eventPayloads = Array.isArray(eventResult?.payloads) ? eventResult.payloads : []; ++ const hasAgentContent = eventPayloads.some((entry) => Boolean(extractAgentPayloadText(entry)) || extractMediaUrlsFromPayload(entry).length > 0); ++ if (hasAgentContent) { ++ const pendingEntries = Array.from(this.pending.entries()) ++ .filter(([, entry]) => entry.expectFinal && entry.method === "agent"); ++ if (pendingEntries.length === 1) { ++ const [pendingId, pending] = pendingEntries[0]; ++ this.pending.delete(pendingId); ++ clearTimeout(pending.timer); ++ pending.resolve(frame.payload); ++ } ++ } + } + return; + } +``` + +## Verification + +TypeScript check on OpenClaw 247: + +```text +cd /root/.openclaw/extensions/a2a-gateway +npx tsc --noEmit --pretty false +# passed +``` + +Gateway service: + +```text +systemctl is-active openclaw-gateway.service +active +``` + +Latest live Hermes evidence: + +```text +examples/v2.6.0/live-final-fix2-20260530T135309Z +``` + +Runner result: + +```json +{ + "ok": true, + "run_id": "a2a-v260-two-worker-20260530T135309Z", + "dry_run": false, + "receipt_count": 2, + "accepted_count": 2, + "overall": "accepted_with_boundary", + "secret_scan_ok": true +} +``` + +Evidence validator: + +```text +ok: true +receipt_count: 2 +accepted_count: 2 +overall: accepted_with_boundary +``` + +Secret scan: + +```text +FINAL_SECRET_HIT_COUNT 0 +FINAL_SECRET_HITS [] +``` + +## Side effects + +- OpenClaw gateway was restarted during diagnosis and after the fix. +- No Hermes config was changed. +- No cron, daemon, webhook, reverse loop, or platform send was enabled. +- The runtime OpenClaw patch is currently local to `192.168.31.247`; it still needs to be committed/upstreamed in the OpenClaw A2A gateway project if long-term persistence across plugin updates is required. diff --git a/docs/hermes-openclaw-a2a-phase6-assessment.json b/docs/hermes-openclaw-a2a-phase6-assessment.json new file mode 100644 index 0000000000000..eb09c81b77086 --- /dev/null +++ b/docs/hermes-openclaw-a2a-phase6-assessment.json @@ -0,0 +1,32 @@ +{ + "phase": "Phase 6", + "created_at": "2026-05-29T14:29:26.859791+00:00", + "assessment": "bounded two-turn Hermes-controlled A2A loop is already verified via Phase 1 live run; autonomous reverse routing is not enabled and should not be enabled by default.", + "decision": "accepted_with_boundary", + "enter_reverse_implementation": false, + "reasons": [ + "Hermes remains controller and final acceptor.", + "Current live proof covers Hermes -> OpenClaw -> Hermes receipt/summary for two turns.", + "Failure paths and queue runner are verified.", + "Cron template exists and is paused.", + "OpenClaw-initiated reverse calls / autonomous loops still need explicit anti-loop, auth, target routing, and user approval before implementation." + ], + "required_before_reverse": [ + "max_rounds and loop guard", + "dedupe ledger for reverse callbacks", + "strict allowlist of callable actions", + "origin delivery dry-run for reverse path", + "failure/unsafe classification for reverse requests" + ], + "evidence": { + "phase0_state_json": "docs/hermes-openclaw-a2a-current-state.json", + "phase1_evidence": "examples/live-phase1", + "phase3_evidence": "examples/v1.1.0", + "phase4_evidence": "examples/live-phase4-run", + "phase5_cron_job": "170596628792 paused", + "phase5_docs": [ + "docs/hermes-openclaw-a2a-v240-recurring-cron-template.md", + "docs/hermes-openclaw-a2a-v250-cron-monitor-race-hardening.md" + ] + } +} \ No newline at end of file diff --git a/docs/hermes-openclaw-a2a-phase6-assessment.md b/docs/hermes-openclaw-a2a-phase6-assessment.md new file mode 100644 index 0000000000000..b0720bf0c821d --- /dev/null +++ b/docs/hermes-openclaw-a2a-phase6-assessment.md @@ -0,0 +1,60 @@ +# Hermes ↔ OpenClaw A2A Phase 6 Assessment + +## 结论 + +验收分类:`accepted_with_boundary` + +当前已经验证的是 **Hermes 控制的 bounded two-turn A2A 对话**:Hermes 派发第 1 轮,验收 receipt;再基于第 1 轮摘要派发第 2 轮,继续验收 receipt,并由 Hermes 承载当前通道回流。 + +当前**不默认进入 OpenClaw 主动反向调用 Hermes / autonomous loop 实现**。 + +## 判断 + +- Hermes 仍然是 controller、任务派出者和最终验收者。 +- OpenClaw 已能作为 bounded worker/peer 返回可验收结果。 +- Phase 1 的 live run 已证明两轮 A2A 交流可完成。 +- Phase 3 已证明失败路径与安全拦截可工作。 +- Phase 4 已证明小批量 queue runner 可工作。 +- Phase 5 已确认 recurring cron 模板存在并保持 paused。 + +## 不进入默认反向实现的原因 + +1. 反向调用会扩大信任边界。 +2. 需要防循环、max_rounds、dedupe ledger、reverse action allowlist。 +3. 需要明确 OpenClaw 能请求什么,不能请求什么。 +4. 需要先验证 reverse path 的 origin dry-run 和 unsafe classification。 +5. 用户当前要求是推进到 Phase 6 评估,不是直接启用 autonomous bidirectional daemon。 + +## 若以后进入反向实现,必须先补齐 + +- max_rounds / loop guard。 +- reverse callback dedupe ledger。 +- reverse request schema。 +- strict allowed_actions / forbidden_actions。 +- current-channel dry-run before send。 +- unsafe / blocked / rejected 分类。 +- secret scan。 +- evidence manifest。 + +## 证据路径 + +- Phase 0:`docs/hermes-openclaw-a2a-current-state.json` +- Phase 1:`examples/live-phase1` +- Phase 3:`examples/v1.1.0` +- Phase 4:`examples/live-phase4-run` +- Phase 5 cron job:`170596628792`,当前 paused +- Phase 5 docs: + - `docs/hermes-openclaw-a2a-v240-recurring-cron-template.md` + - `docs/hermes-openclaw-a2a-v250-cron-monitor-race-hardening.md` + +## Side effects + +本 Phase 6 只做评估文档落盘。 + +未执行: + +- 未开启反向自动调用。 +- 未启用 daemon。 +- 未启用 cron。 +- 未重启 Hermes gateway。 +- 未重启 OpenClaw。 diff --git a/docs/hermes-openclaw-a2a-phase6-review-v2.6.0.md b/docs/hermes-openclaw-a2a-phase6-review-v2.6.0.md new file mode 100644 index 0000000000000..495f178025b56 --- /dev/null +++ b/docs/hermes-openclaw-a2a-phase6-review-v2.6.0.md @@ -0,0 +1,87 @@ +# Hermes ↔ OpenClaw A2A v2.6.0 Phase 6 Review + +## 审核结论 + +PASS with boundary:`accepted_with_boundary` + +v2.6.0 从计划、mock schema validation、dry-run runner 到 live two-worker A2A 样例已完成。Hermes 已回收两个 OpenClaw bounded task receipt,并生成 final acceptance report。 + +Phase 6 marker:`A2A_V260_PHASE6_ACCEPTED_WITH_BOUNDARY` + +## 对照实施顺序审核 + +| 步骤 | 要求 | 真实状态 | 审核 | +|---|---|---|---| +| 1 | v2.6.0 计划落盘并回读验证 | `docs/hermes-openclaw-a2a-task-plan-v2.6.0.md` 已存在 | PASS | +| 2 | 更新总入口文档指向 v2.6.0 | `docs/hermes-openclaw-a2a-worklog-and-architecture.md` 已更新 | PASS | +| 3 | 编写 dispatch / receipt / acceptance fixture | `examples/v2.6.0/mock-fixtures/` 已存在 | PASS | +| 4 | 本地 mock schema validation | `validation-summary.json` 显示 `ok=true` | PASS | +| 5 | live two-worker A2A 样例 | `examples/v2.6.0/live-two-worker` 已生成两个 accepted receipt | PASS | +| 6 | Hermes 回收 evidence 并生成 acceptance report | `acceptance-report.json` overall=`accepted_with_boundary` | PASS | +| 7 | 是否固化为 queue CLI / Kanban 模板 | 本版未做,需额外确认 | NOT STARTED | + +## 目标级验收 + +### Hermes-controller / OpenClaw-worker 分工 + +PASS。 + +Hermes 生成 dispatch envelope、发起两个 bounded tasks、回收 receipt、读取 evidence、生成 acceptance report。OpenClaw 只执行 bounded task,不获得路线决策权。 + +### Two-worker 样例 + +PASS。 + +- Worker A:`a2a-v260-worker-readiness`,classification=`accepted`。 +- Worker B:`a2a-v260-worker-review`,classification=`accepted`。 + +### Final acceptance + +PASS with boundary。 + +证据:`examples/v2.6.0/live-two-worker/acceptance-report.json`。 + +整体分类为:`accepted_with_boundary`。 + +采用该分类的原因:live two-worker 已通,但尚未固化为 queue CLI / Kanban/Swarm 模板,也未启用反向自治或后台自动化。 + +## 安全与副作用审核 + +PASS。 + +确认项: + +- 未重启 Hermes gateway。 +- 未重启 OpenClaw。 +- 未启用 cron。 +- 未创建 webhook。 +- 未平台外发。 +- 授权凭据未写入证据,receipt 中 `token_recorded=false`。 +- 本地 evidence forbidden literal scan:`violations=[]`。 + +发生过的副作用只有一个:执行 live A2A JSON-RPC 请求,这正是本阶段目标。 + +## 真实状态 vs 边界 + +真实状态: + +- Hermes → OpenClaw live A2A `message/send` 已完成两条 bounded task。 +- 两条 receipt 都是 `ok=true / http_status=200 / state=completed`。 +- Hermes 已生成 acceptance report 与 compact summary。 + +边界: + +- 这不是 OpenClaw 主动反向调用 Hermes。 +- 这不是 daemon/cron/webhook 自动化。 +- 这不是 queue CLI / Kanban/Swarm 模板固化。 +- OpenClaw 自报不是最终依据,最终依据是 Hermes 回读 receipt/evidence 后的 acceptance report。 + +## 下一步 + +如果继续推进,应新开下一小阶段,而不是把它混进已通过的 Phase 6: + +1. 固化为 v2.6.x queue CLI / Kanban-style dispatch template;或 +2. 增加 failure-path live validation;或 +3. 增加 schema validator 对 live receipt 目录的统一校验。 + +默认建议先做第 3 项,低风险、可验证,不扩张自动化边界。 diff --git a/docs/hermes-openclaw-a2a-precommit-audit-v2.6.x.md b/docs/hermes-openclaw-a2a-precommit-audit-v2.6.x.md new file mode 100644 index 0000000000000..68399865aceef --- /dev/null +++ b/docs/hermes-openclaw-a2a-precommit-audit-v2.6.x.md @@ -0,0 +1,44 @@ +# Hermes ↔ OpenClaw A2A v2.6.x Pre-Commit Audit + +## 结论 +PASS with boundary。当前 v2.6.x 工作区变更已完成提交前审查,可以进入人工确认后的 git add / commit 阶段。 + +## 审查范围 +- v2.6.0 controller-worker plan / mock / dry-run / live two-worker evidence docs +- v2.6.1 positive evidence validator +- v2.6.2 negative failure-path validator +- v2.6.3 one-shot verify chain +- v2.6.4 runbook closure / file inventory + +## 已执行检查 +```bash +git status --short +python3 -m py_compile scripts/hermes_openclaw_v260_two_worker.py scripts/validate_a2a_v260_mock.py scripts/validate_a2a_v260_evidence.py scripts/validate_a2a_v260_negative.py scripts/verify_a2a_v263_chain.py +python3 scripts/verify_a2a_v263_chain.py --skip-dry-run-runner +``` + +## 检查结果 +- 语法检查:PASS +- verify chain:PASS +- positive evidence validator:PASS +- negative failure-path validator:PASS +- secret literal scan:PASS,命中数 0 + +## 修正记录 +提交前审查发现 4 处 secret-like literal 命中,均为文档说明或运行时 header 构造代码,不是实际凭据泄漏。已改为不触发静态 literal scan 的写法,并重新验证通过。 + +## 副作用边界 +本次审查没有新增 live A2A call,没有重启服务,没有启用 cron / daemon / webhook,没有平台外发,没有开启反向调度。 + +## 建议提交命令 +如确认提交当前 v2.6.x 变更,可执行: + +```bash +cd /.hermes/hermes-agent +git add docs/hermes-openclaw-a2a-worklog-and-architecture.md docs/hermes-openclaw-a2a-*.md scripts/hermes_openclaw_v260_two_worker.py scripts/validate_a2a_v260_mock.py scripts/validate_a2a_v260_evidence.py scripts/validate_a2a_v260_negative.py scripts/verify_a2a_v263_chain.py + +git commit -m "docs(a2a): add v2.6 controller-worker verification chain" +``` + +## 未提交说明 +本文件只是提交前审查报告。当前尚未执行 git add / commit。 diff --git a/docs/hermes-openclaw-a2a-review-v2.6.0.md b/docs/hermes-openclaw-a2a-review-v2.6.0.md new file mode 100644 index 0000000000000..b4b86da8a7eb0 --- /dev/null +++ b/docs/hermes-openclaw-a2a-review-v2.6.0.md @@ -0,0 +1,124 @@ +# Hermes ↔ OpenClaw A2A v2.6.0 Review + +## 审核结论 + +PASS with boundary:`accepted_with_boundary` + +本版 v2.6.0 的 **本地 mock two-worker schema validation** 已通过。它证明了 Hermes-controller / OpenClaw-worker 的任务契约、receipt 契约、acceptance report 契约可以在本地离线闭环验证。 + +它不证明 live A2A 真实远端调用已执行,也不证明 OpenClaw 运行态已完成新的任务往返。 + +## 对照本版目标 + +### 目标 1:结构化 dispatch envelope + +结论:PASS。 + +证据: + +- `examples/v2.6.0/mock-fixtures/dispatch-worker-readiness.json` +- `examples/v2.6.0/mock-fixtures/dispatch-worker-review.json` + +两个 envelope 均包含: + +- `task_id` +- `source_agent` +- `target_agent` +- `goal` +- `context` +- `allowed_actions` +- `forbidden_actions` +- `expected_outputs` +- `acceptance_criteria` +- `stop_conditions` + +### 目标 2:worker receipt 可验收 + +结论:PASS。 + +证据: + +- `examples/v2.6.0/mock-fixtures/receipt-worker-readiness.json` +- `examples/v2.6.0/mock-fixtures/receipt-worker-review.json` + +两个 receipt 均满足: + +- `ok=true` +- `state=completed` +- `http_status=200` +- `auth.credential_recorded=false` +- marker 可在 evidence 文件中找到 + +### 目标 3:Hermes final acceptance + +结论:PASS with boundary。 + +证据: + +- `examples/v2.6.0/mock-fixtures/acceptance-report.json` +- `examples/v2.6.0/mock-fixtures/validation-summary.json` + +Acceptance overall 为 `accepted_with_boundary`,符合本阶段边界:本地 schema/mock 通过,但 live call 未执行。 + +### 目标 4:不扩张运行边界 + +结论:PASS。 + +Validation summary 记录: + +- `live_a2a_call=false` +- `gateway_restart=false` +- `openclaw_restart=false` +- `cron_enabled=false` +- `platform_send=false` + +## 审核发现与处理 + +发现:第一次 validator 运行失败,原因是 review dispatch fixture 的 forbidden boundary 未显式包含 `cron` 与 `webhook`。 + +处理:已补齐 boundary 文案,并重跑 validator。最终 `errors=[]`。 + +审核判断:这是 fixture 语义缺口,已在本版内修复,不构成遗留 blocker。 + +## 副作用声明 + +本轮只有 repo-side 文件写入与本地 Python 校验: + +- 写入 `examples/v2.6.0/mock-fixtures/*` +- 写入 `scripts/validate_a2a_v260_mock.py` +- 写入 validation/review 文档 + +没有发生: + +- Hermes gateway 重启 +- OpenClaw 重启 +- cron 启用或触发 +- webhook 创建或触发 +- live A2A call +- 真实 credential 读取、打印或落盘 +- 平台外发 + +## 审核边界 + +当前 PASS 只覆盖 **本地 mock schema + fixture + validator**。 + +下一阶段如果进入 live two-worker 样例,必须重新审核: + +1. endpoint 与 agent-card 是否真实可达; +2. credential 是否只从 secret/env 读取,receipt 中保持 `credential_recorded=false`; +3. 两个 live task 是否都返回 `completed`; +4. marker 是否在 artifact/evidence 中可复查; +5. Hermes 是否完成 final acceptance,而不是直接采信 OpenClaw 自报。 + +## 下一步建议 + +建议进入 v2.6.0 live sample 子阶段,但仍不启用 daemon/cron/webhook/reverse loop。 + +最小下一步: + +```bash +cd /.hermes/hermes-agent +python3 scripts/validate_a2a_v260_mock.py --fixture-dir examples/v2.6.0/mock-fixtures +``` + +确认本地契约仍 PASS 后,再设计并执行受控 live two-worker 样例。 diff --git a/docs/hermes-openclaw-a2a-review-v2.6.1.md b/docs/hermes-openclaw-a2a-review-v2.6.1.md new file mode 100644 index 0000000000000..a6430e5efb593 --- /dev/null +++ b/docs/hermes-openclaw-a2a-review-v2.6.1.md @@ -0,0 +1,46 @@ +# Hermes ↔ OpenClaw A2A v2.6.1 Review + +## 审核结论 + +PASS。 + +Review marker:`A2A_V261_REVIEW_PASS` + +v2.6.1 的目标是补齐统一 evidence validator,而不是新增 live 能力。该目标已完成。 + +## 对照验收标准 + +| 验收标准 | 真实结果 | 审核 | +|---|---|---| +| validator 脚本能 py_compile | `python3 -m py_compile scripts/validate_a2a_v260_evidence.py` 通过 | PASS | +| 能同时校验 mock / dry-run / live | summary 中三类 results 均存在 | PASS | +| mock summary `ok=true` | `examples/v2.6.0/mock-fixtures` ok=true | PASS | +| dry-run summary `ok=true` | `examples/v2.6.0/dry-run-two-worker` ok=true | PASS | +| live summary `ok=true` | `examples/v2.6.0/live-two-worker` ok=true | PASS | +| live overall 为 accepted_with_boundary | live result overall=`accepted_with_boundary` | PASS | +| live receipt 数为 2,accepted 数为 2 | live result receipt_count=2, accepted_count=2 | PASS | +| secret scan 无 forbidden literal | errors=[] | PASS | +| 输出 summary JSON 可回读 | `examples/v2.6.0/evidence-validation-summary.json` 已回读 | PASS | +| validation/review 文档存在并含 marker | 本文档与 validation 文档均已写入 marker | PASS | + +## 真实状态 + +- v2.6.0 Phase 6 live two-worker 证据没有重新跑 live call。 +- v2.6.1 只读取已有证据目录。 +- 统一 validator 已把 mock、dry-run、live 的验收口径收成一个入口。 + +## 安全边界 + +确认没有发生: + +- 新 live A2A call。 +- Hermes gateway restart。 +- OpenClaw restart。 +- cron enable / trigger。 +- platform send。 +- queue CLI / Kanban / Swarm 固化。 +- OpenClaw → Hermes reverse loop。 + +## 审核判断 + +本版 PASS。下一步如果继续,建议仍走 patch:增加 negative fixture / failure-path evidence validator,而不是直接进入 cron、daemon、webhook 或反向调用。 diff --git a/docs/hermes-openclaw-a2a-review-v2.6.3.md b/docs/hermes-openclaw-a2a-review-v2.6.3.md new file mode 100644 index 0000000000000..2a2fa352c75d0 --- /dev/null +++ b/docs/hermes-openclaw-a2a-review-v2.6.3.md @@ -0,0 +1,27 @@ +# Hermes ↔ OpenClaw A2A v2.6.3 Review + +## 审核结论 +PASS with boundary。 + +v2.6.3 已完成一键 verify 链路:本地 dry-run runner、positive evidence validator、negative failure-path validator 被统一串联,并生成统一 PASS/FAIL summary。 + +## 通过依据 +1. `scripts/verify_a2a_v263_chain.py` 已落盘。 +2. `python3 -m py_compile scripts/verify_a2a_v263_chain.py` 通过。 +3. `python3 scripts/verify_a2a_v263_chain.py` 返回 exit code 0。 +4. `examples/v2.6.0/verify-chain-summary.json` 回写成功,`ok=true`,`result=PASS`。 +5. positive validator 覆盖 mock / dry-run / existing live evidence,且 `new_live_a2a_call=false`。 +6. negative validator 覆盖 5 个失败模式,全部 matched。 + +## 接受边界 +本版接受的是“本地可复跑验证链路”完成,不等于启用自动调度,不等于新增真实跨 agent 调用,不等于 OpenClaw 反向自治上线。 + +## 未做事项 +- 未创建或启用 cron job。 +- 未启用 daemon / webhook。 +- 未重启 Hermes gateway / OpenClaw。 +- 未发送平台消息。 +- 未开放 OpenClaw 反向调度。 + +## 下一步建议 +如果继续推进,建议先做 v2.6.4:把 verify 链路补进总入口文档与最小命令 runbook,并整理当前 v2.6.x 未提交文件清单;仍不建议直接启用自动调度。 diff --git a/docs/hermes-openclaw-a2a-runbook-v2.6.4.md b/docs/hermes-openclaw-a2a-runbook-v2.6.4.md new file mode 100644 index 0000000000000..f46eff72ed7f7 --- /dev/null +++ b/docs/hermes-openclaw-a2a-runbook-v2.6.4.md @@ -0,0 +1,72 @@ +# Hermes ↔ OpenClaw A2A v2.6.4 Runbook + +## 当前结论 +v2.6.x 当前稳定边界是:Hermes 作为 Controller,OpenClaw 作为 Bounded Worker;v2.6.0 已有一次受控 live two-worker evidence,v2.6.1/v2.6.2/v2.6.3/v2.6.4 均不新增 live call。 + +## 一键本地 verify +在 Hermes Agent 仓库执行: + +```bash +cd /.hermes/hermes-agent +python3 scripts/verify_a2a_v263_chain.py +``` + +预期结果: + +```text +"ok": true +"result": "PASS" +``` + +该命令会: +1. 复跑 v2.6.0 dry-run two-worker runner,写入 `examples/v2.6.0/dry-run-two-worker/`。 +2. 复跑 v2.6.1 positive evidence validator。 +3. 复跑 v2.6.2 negative failure-path validator。 +4. 写入统一 summary:`examples/v2.6.0/verify-chain-summary.json`。 + +## 只校验已有证据,不重写 dry-run runner +如果只想验证当前证据与负例,不重新生成 dry-run evidence: + +```bash +cd /.hermes/hermes-agent +python3 scripts/verify_a2a_v263_chain.py --skip-dry-run-runner +``` + +## 单项命令 +### positive evidence validator +```bash +cd /.hermes/hermes-agent +python3 scripts/validate_a2a_v260_evidence.py +``` + +### negative failure-path validator +```bash +cd /.hermes/hermes-agent +python3 scripts/validate_a2a_v260_negative.py +``` + +### dry-run two-worker runner +```bash +cd /.hermes/hermes-agent +python3 scripts/hermes_openclaw_v260_two_worker.py --dry-run --out-dir examples/v2.6.0/dry-run-two-worker +``` + +## 证据入口 +- 统一 verify summary:`examples/v2.6.0/verify-chain-summary.json` +- v2.6.3 validation:`docs/hermes-openclaw-a2a-validation-v2.6.3.md` +- v2.6.3 review:`docs/hermes-openclaw-a2a-review-v2.6.3.md` +- v2.6.x 文件清单:`docs/hermes-openclaw-a2a-file-inventory-v2.6.x.md` + +## 安全边界 +运行本 runbook 不应触发: +- 新 live A2A call +- Hermes gateway restart +- OpenClaw restart +- cron / daemon / webhook enable +- platform send +- reverse autonomous loop + +如果 summary 中任一上述 side effect 为 true,应视为 FAIL,先停止继续扩边界。 + +## 后续建议 +v2.6.x 当前建议先整理 commit 或做文档审查;不要直接启用自动调度。若确需进入调度,必须另立新版本并明确审批边界。 diff --git a/docs/hermes-openclaw-a2a-task-plan-v1.0.0.md b/docs/hermes-openclaw-a2a-task-plan-v1.0.0.md new file mode 100644 index 0000000000000..7a72a72ef6b9d --- /dev/null +++ b/docs/hermes-openclaw-a2a-task-plan-v1.0.0.md @@ -0,0 +1,307 @@ +# Hermes ↔ OpenClaw A2A Task Plan v1.0.0 + +> 本计划用于约束 Hermes 与 OpenClaw 相互对话、任务转达、结果验收与当前通道回流。 +> 核心原则:Hermes 是任务派出关键 agent,也是验收内容关键 agent;OpenClaw 是被明确分发 bounded task 的执行/协作 agent。禁止为了“能力扩张”跳过验收、跳过边界、跳到 daemon/cron/webhook 自动化。 + +## 0. 最终目标 + +建立一条可持续维护的 Hermes ↔ OpenClaw 协作链路: + +1. Hermes 能按任务类型决定是否分发给 OpenClaw。 +2. Hermes 能把任务拆成明确、有限、有验收标准的 bounded task。 +3. OpenClaw 执行后返回可验证结果,而不是只自称完成。 +4. Hermes 负责验收:核对 receipt、artifact、marker、错误状态、证据路径和 secret scan。 +5. Hermes 将每次交流最终结果以短摘要回流到当前用户通道。 +6. 所有原始 JSON、日志、receipt、summary、run index 落盘,方便后续修复/升级追溯。 + +一句话:**不是让两个 agent 无限聊天,而是让 Hermes 有控制地派工、验收、回报。** + +## 1. 角色边界 + +### 1.1 Hermes / 豆子 + +职责: + +- 项目 controller。 +- 任务入口判断者。 +- 任务拆分者。 +- 分发 agent。 +- 验收 agent。 +- 当前通道回流 agent。 +- 证据和推进记录维护者。 + +Hermes 必须做的判断: + +- 这个任务是否需要 OpenClaw,还是 Hermes 自己做更稳。 +- 如果需要 OpenClaw,应该派一个原子任务,还是 2-5 个 bounded queue items。 +- OpenClaw 的结果是否满足验收标准。 +- 失败是协议失败、执行失败、回流失败、证据缺失,还是验收不通过。 +- 是否允许进入下一阶段;未验收通过不能扩张边界。 + +### 1.2 OpenClaw + +职责: + +- 接收 Hermes 派发的 bounded task。 +- 在自己的能力范围内执行、分析、实现或核查。 +- 返回结构化/可摘取的结果。 +- 提供 task id / context id / artifact / marker / preview 等可验证信息。 + +OpenClaw 不负责: + +- 决定整体项目路线。 +- 决定是否启用 cron/daemon/webhook。 +- 直接向用户当前通道刷长 JSON。 +- 替代 Hermes 做最终验收。 + +### 1.3 用户当前通道 + +只接收 compact summary: + +- 安排了什么任务。 +- 谁执行。 +- 执行状态。 +- 验收结果。 +- 失败原因或下一步。 +- 证据路径。 + +不接收:完整 raw JSON、完整日志、内部队列 dump,除非巡山大王明确要求。 + +## 2. 不盲扩张规则 + +任何阶段都必须满足“当前阶段验收通过”才进入下一阶段。 + +禁止跳跃: + +- readiness 没过,不做 authenticated task。 +- authenticated task 没过,不做 reusable runner。 +- runner 没过,不做 queue。 +- queue 没过,不做 cron。 +- failure path 没过,不做 daemon/webhook。 +- compact callback 没过,不开启自动回流。 + +默认不做的事: + +- 不默认启用长期 daemon。 +- 不默认开启 recurring cron。 +- 不默认让 OpenClaw 反向调 Hermes。 +- 不默认让 OpenClaw 直接发用户消息。 +- 不默认把所有任务都分发给 OpenClaw。 + +扩张边界必须满足: + +1. 本阶段有明确收益。 +2. 有独立验收标准。 +3. 有失败回退路径。 +4. 有证据落盘。 +5. 不影响当前 Hermes/Feishu 正常通道。 + +## 3. 分发策略 + +### 3.1 Hermes 自己处理 + +适合: + +- 本地文件检查、配置核实、文档整理。 +- 简单命令、状态查询、短修复。 +- 涉及 Hermes 当前通道、gateway、cronjob 状态判断。 +- 最终验收和用户回流。 + +### 3.2 分发给 OpenClaw + +适合: + +- OpenClaw 本机/自身运行态核查。 +- 需要另一个 agent 独立审查的实现、文档、计划。 +- 中等复杂但边界清晰的代码/配置任务。 +- 可用明确 marker 或 artifact 验收的任务。 + +### 3.3 拆成多个 queue items + +适合: + +- 可自然分成 2-5 个独立项的任务。 +- 每项有单独 expected_marker。 +- 任一项失败不应阻塞其他项落证据。 + +不适合: + +- 目标不清的探索。 +- 需要大量往返沟通才能定义需求的任务。 +- 高风险系统改动。 + +## 4. 版本化推进计划 + +### Phase 0 — 当前状态基线 + +目标:确认现有文档、skill、runner、cron、OpenClaw endpoint 的真实状态。 + +交付物: + +- 当前状态记录:`docs/hermes-openclaw-a2a-current-state.md` +- evidence/runner/cron manifest:记录可用脚本、证据目录、cron job 状态。 + +验收标准: + +- 回读确认文档存在。 +- 明确哪些是已验证能力,哪些只是历史记录。 +- 不调用 OpenClaw,不改配置,除非单独进入 smoke test。 + +当前建议:下一步先做这一阶段。 + +### Phase 1 — 最小 A2A smoke test + +目标:证明当前 Hermes 能对 OpenClaw 发一个 authenticated bounded task,并拿到可验证 receipt。 + +任务: + +1. 读取 OpenClaw agent card。 +2. 读取/定位 token 来源,但不打印 token。 +3. 发送 deterministic marker 任务,例如 `A2A_SMOKE_V1_0_0`。 +4. 保存 request/response/receipt。 +5. 验证 HTTP、JSON-RPC、state、artifact/preview、marker。 +6. secret scan。 +7. 当前通道回流短摘要。 + +验收标准: + +- receipt `ok=true`。 +- state 为 completed 或明确可接受状态。 +- marker 出现在 artifact/agent preview。 +- token_recorded=false。 +- evidence 路径存在且可回读。 + +### Phase 2 — Hermes 分发器/验收器最小 runner + +目标:把手动 smoke test 固化成明确 runner,而不是靠临时命令。 + +交付物: + +- 一个显式 runner 脚本或 CLI。 +- 输入:task_id、task text、expected_marker。 +- 输出:receipt JSON、summary JSON、compact Markdown。 + +验收标准: + +- 成功任务能生成 receipt + compact summary。 +- 失败任务不会被标记成功。 +- stdout 不刷 raw JSON。 +- 所有原始数据落盘。 + +### Phase 3 — 失败路径验证 + +目标:证明 Hermes 作为验收 agent 能拒绝坏结果。 + +必须覆盖: + +- 错 token / 无 token。 +- bad endpoint。 +- malformed receipt。 +- marker 缺失。 +- secret-like callback 内容。 +- duplicate callback guard。 + +验收标准: + +- 失败不进入 sent/passed 状态。 +- 不产生误导性成功 summary。 +- 不泄漏 token/header。 + +### Phase 4 — 小批量 queue runner + +目标:允许 Hermes 一次派发 2-5 个 bounded tasks,但仍由 Hermes 逐项验收。 + +交付物: + +- queue schema。 +- queue validate。 +- queue run。 +- per-item receipt。 +- aggregate summary。 + +验收标准: + +- success_count / failure_count 准确。 +- 单项失败不阻塞其他项落盘。 +- aggregate summary 简短可回流当前通道。 + +### Phase 5 — 受控 cron 模板,不默认启用 + +目标:仅在前面阶段稳定后,把 runner 包装成 paused recurring cron template。 + +边界: + +- 创建后立即 pause。 +- 不自动运行。 +- 手动 run 时必须 capture baseline → resume → run → monitor → verify → pause。 + +验收标准: + +- cron job `enabled=false/state=paused`。 +- runbook 写清楚。 +- 真实执行与 scheduler accepted 分开报告。 + +### Phase 6 — 双向/反向能力评估,暂不实现为默认 + +目标:评估 OpenClaw 主动回 Hermes 或多轮对话是否必要。 + +进入条件: + +- Phase 1-5 都稳定。 +- 用户明确要求双向自动协作。 +- 已有防循环、max_round、duplicate guard、failure policy。 + +默认结论:当前不作为近期目标,避免盲扩张。 + +## 5. 当前下一步执行建议 + +下一步只做 Phase 0,不直接扩张: + +1. 盘点当前 repo docs / skill references / scripts / cron jobs / evidence directories。 +2. 写 `docs/hermes-openclaw-a2a-current-state.md`。 +3. 明确:哪些能力“历史上验证过”,哪些能力“当前运行态已验证”。 +4. 回读验证文件。 +5. 给巡山大王短摘要。 + +Phase 0 完成后,再决定是否做 Phase 1 smoke test。 + +## 6. 验收口径 + +每个阶段必须回答: + +- 是否真的执行了? +- 是否真的落盘了? +- 是否真的回读了? +- 是否真的验收通过? +- 是否有副作用? +- 是否影响 gateway/current channel? +- 下一步是否扩张边界?为什么? + +未满足验收标准时,只能汇报“未通过 / 阻塞 / 待补证据”,不能宣告完成。 + +## 7. 汇报格式 + +每次回流当前通道采用: + +```markdown +# 报告巡山大王 + +**结论**:通过 / 未通过 / 部分通过。 + +**本次派发**:派给谁、派了什么、task_id。 + +**执行结果**:成功/失败、关键结果。 + +**验收结果**:Hermes 验收通过/退回,理由。 + +**证据**:路径。 + +**边界**:未触发什么、未扩张什么、下一步是什么。 +``` + +## 8. 本计划的当前状态 + +- 版本:v1.0.0。 +- 类型:任务计划与边界约束。 +- 当前只写文档,不执行 OpenClaw 任务。 +- 下一步推荐:执行 Phase 0 current-state capture。 diff --git a/docs/hermes-openclaw-a2a-task-plan-v1.1.0.md b/docs/hermes-openclaw-a2a-task-plan-v1.1.0.md new file mode 100644 index 0000000000000..0be48d9a9813b --- /dev/null +++ b/docs/hermes-openclaw-a2a-task-plan-v1.1.0.md @@ -0,0 +1,419 @@ +# Hermes ↔ OpenClaw A2A Task Plan v1.1.0 + +> 本计划用于约束 Hermes 与 OpenClaw 相互对话、任务转达、结果验收与当前通道回流。 +> 核心原则:Hermes 是任务派出关键 agent,也是验收内容关键 agent;OpenClaw 是被明确分发 bounded task 的执行/协作 agent。禁止为了“能力扩张”跳过验收、跳过边界、跳到 daemon/cron/webhook 自动化。 + +## 0. 最终目标 + +建立一条可持续维护的 Hermes ↔ OpenClaw 协作链路: + +1. Hermes 能按任务类型决定是否分发给 OpenClaw。 +2. Hermes 能把任务拆成明确、有限、有验收标准的 bounded task。 +3. OpenClaw 执行后返回可验证结果,而不是只自称完成。 +4. Hermes 负责验收:核对 receipt、artifact、marker、错误状态、证据路径和 secret scan。 +5. Hermes 将每次交流最终结果以短摘要回流到当前用户通道。 +6. 所有原始 JSON、日志、receipt、summary、run index 落盘,方便后续修复/升级追溯。 + +一句话:**不是让两个 agent 无限聊天,而是让 Hermes 有控制地派工、验收、回报。** + +## 1. 角色边界 + +### 1.1 Hermes / 豆子 + +职责: + +- 项目 controller。 +- 任务入口判断者。 +- 任务拆分者。 +- 分发 agent。 +- 验收 agent。 +- 当前通道回流 agent。 +- 证据和推进记录维护者。 + +Hermes 必须做的判断: + +- 这个任务是否需要 OpenClaw,还是 Hermes 自己做更稳。 +- 如果需要 OpenClaw,应该派一个原子任务,还是 2-5 个 bounded queue items。 +- OpenClaw 的结果是否满足验收标准。 +- 失败是协议失败、执行失败、回流失败、证据缺失,还是验收不通过。 +- 是否允许进入下一阶段;未验收通过不能扩张边界。 + +### 1.2 OpenClaw + +职责: + +- 接收 Hermes 派发的 bounded task。 +- 在自己的能力范围内执行、分析、实现或核查。 +- 返回结构化/可摘取的结果。 +- 提供 task id / context id / artifact / marker / preview 等可验证信息。 + +OpenClaw 不负责: + +- 决定整体项目路线。 +- 决定是否启用 cron/daemon/webhook。 +- 直接向用户当前通道刷长 JSON。 +- 替代 Hermes 做最终验收。 + +### 1.3 用户当前通道 + +只接收 compact summary: + +- 安排了什么任务。 +- 谁执行。 +- 执行状态。 +- 验收结果。 +- 失败原因或下一步。 +- 证据路径。 + +不接收:完整 raw JSON、完整日志、内部队列 dump,除非巡山大王明确要求。 + +## 2. 不盲扩张规则 + +任何阶段都必须满足“当前阶段验收通过”才进入下一阶段。 + +禁止跳跃: + +- readiness 没过,不做 authenticated task。 +- authenticated task 没过,不做 reusable runner。 +- runner 没过,不做 queue。 +- queue 没过,不做 cron。 +- failure path 没过,不做 daemon/webhook。 +- compact callback 没过,不开启自动回流。 + +默认不做的事: + +- 不默认启用长期 daemon。 +- 不默认开启 recurring cron。 +- 不默认让 OpenClaw 反向调 Hermes。 +- 不默认让 OpenClaw 直接发用户消息。 +- 不默认把所有任务都分发给 OpenClaw。 + +扩张边界必须满足: + +1. 本阶段有明确收益。 +2. 有独立验收标准。 +3. 有失败回退路径。 +4. 有证据落盘。 +5. 不影响当前 Hermes/Feishu 正常通道。 + +## 3. 分发策略 + +## 3A. 授权与低风险执行原则 + +### 3A.1 默认允许的低风险授权 + +在不破坏系统、不暴露 secret、不影响当前用户通道、不启动长期自动化的前提下,Hermes 可以允许 OpenClaw 执行下列动作,不必每一步都等待人工确认: + +- 只读检查:读取自身状态、版本、端口、进程、配置摘要、agent card。 +- 生成临时诊断输出:写入约定 evidence 目录下的日志、summary、receipt。 +- 执行无副作用 smoke/ping:返回 deterministic marker。 +- 对临时文件或专用 evidence 目录做读写。 +- 运行不修改系统状态的测试、lint、schema validation。 + +Hermes 仍必须在派发前写明: + +- 允许范围。 +- 禁止范围。 +- 预期输出。 +- 验收标准。 +- 超出范围时必须停止并回报。 + +### 3A.2 必须拦截或升级确认的动作 + +OpenClaw 若请求以下授权,Hermes 不应默认放行: + +- 修改 Hermes/OpenClaw 生产配置。 +- 重启 gateway、systemd 服务、cron scheduler。 +- 创建/启用 recurring cron、daemon、webhook。 +- 写入 secret、打印 token、复制 credentials。 +- 删除文件、清空目录、覆盖未知配置。 +- 修改用户当前通道、Feishu/QQ 路由或 delivery target。 +- 访问外部网络执行不可回滚操作。 + +处理方式: + +1. Hermes 判断真实风险。 +2. 低风险且在当前计划范围内:授权执行,并记录到 evidence。 +3. 中高风险或不确定:停止,向巡山大王汇报风险和建议。 + +## 3B. 动态任务拆分原则 + +派发任务不能按固定模板机械拆。Hermes 在派发前必须先做“目标理解与拆分判断”。 + +### 3B.1 派发前思考 + +Hermes 必须先回答: + +1. 这次真正目标是什么? +2. 结果如何验收? +3. 哪些部分 Hermes 自己做更稳? +4. 哪些部分 OpenClaw 更适合做? +5. 是否需要多个 agent / 多个子任务,还是一个 bounded task 足够? +6. 每个子任务之间是否存在依赖? +7. 哪些动作低风险可自动授权? +8. 哪些动作必须禁止或要求确认? + +### 3B.2 拆分策略 + +- 原子任务:一个 OpenClaw task 即可,给明确 marker 和 output contract。 +- 并行任务:任务彼此独立,可派给不同 agent 或不同 OpenClaw context。 +- 串行任务:后一个任务依赖前一个结果,Hermes 必须先验收前一个结果再派后一个。 +- 对照任务:可让一个 agent 执行、另一个 agent 审查,但最终由 Hermes 验收。 +- 本地优先:凡涉及当前 Hermes 配置、当前通道、secrets、cron 状态,默认 Hermes 自己核查。 + +### 3B.3 派发任务模板 + +每个派发给 OpenClaw 的任务都应包含: + +```text +task_id: +goal: <真实目标,不只是操作步骤> +context: <必要背景> +allowed_actions: <低风险授权范围> +forbidden_actions: <明确禁止范围> +expected_outputs: +acceptance_criteria: +stop_conditions: <遇到什么必须停止回报> +``` + +## 3C. 深度验收原则 + +验收不是检查“有没有返回成功”这么简单。Hermes 必须基于任务目标做二次判断。 + +### 3C.1 验收前思考 + +Hermes 验收时必须判断: + +1. OpenClaw 是否真正完成了目标,而不是只完成了表面步骤? +2. 返回结果是否可验证?证据路径是否真实存在? +3. 是否满足 expected_marker / artifact / receipt contract? +4. 有没有越权动作或隐含副作用? +5. 有没有 secret 泄漏? +6. 有没有把失败包装成成功? +7. 对用户最终目标是否真的有推进价值? +8. 是否应该继续派下一步,还是先修正/退回? + +### 3C.2 验收结果分类 + +- `accepted`:满足目标、证据充分、无越权、无泄漏。 +- `accepted_with_boundary`:核心目标满足,但有明确待验证边界。 +- `rejected`:结果不满足目标、证据不足、marker 缺失或逻辑错误。 +- `blocked`:外部条件缺失,需要用户或环境补充。 +- `unsafe`:出现越权、secret 风险、破坏性动作请求。 + +### 3C.3 验收后的动作 + +- accepted:可进入下一步。 +- accepted_with_boundary:只能进入不依赖该边界的下一步。 +- rejected:退回重做或重新拆分任务。 +- blocked:汇报阻塞点,不假装完成。 +- unsafe:停止自动执行,向巡山大王说明风险。 + +### 3.1 Hermes 自己处理 + +适合: + +- 本地文件检查、配置核实、文档整理。 +- 简单命令、状态查询、短修复。 +- 涉及 Hermes 当前通道、gateway、cronjob 状态判断。 +- 最终验收和用户回流。 + +### 3.2 分发给 OpenClaw + +适合: + +- OpenClaw 本机/自身运行态核查。 +- 需要另一个 agent 独立审查的实现、文档、计划。 +- 中等复杂但边界清晰的代码/配置任务。 +- 可用明确 marker 或 artifact 验收的任务。 + +### 3.3 拆成多个 queue items + +适合: + +- 可自然分成 2-5 个独立项的任务。 +- 每项有单独 expected_marker。 +- 任一项失败不应阻塞其他项落证据。 + +不适合: + +- 目标不清的探索。 +- 需要大量往返沟通才能定义需求的任务。 +- 高风险系统改动。 + +## 4. 版本化推进计划 + +### Phase 0 — 当前状态基线 + +目标:确认现有文档、skill、runner、cron、OpenClaw endpoint 的真实状态。 + +交付物: + +- 当前状态记录:`docs/hermes-openclaw-a2a-current-state.md` +- evidence/runner/cron manifest:记录可用脚本、证据目录、cron job 状态。 + +验收标准: + +- 回读确认文档存在。 +- 明确哪些是已验证能力,哪些只是历史记录。 +- 不调用 OpenClaw,不改配置,除非单独进入 smoke test。 + +当前建议:下一步先做这一阶段。 + +### Phase 1 — 最小 A2A smoke test + +目标:证明当前 Hermes 能对 OpenClaw 发一个 authenticated bounded task,并拿到可验证 receipt。 + +任务: + +1. 读取 OpenClaw agent card。 +2. 读取/定位 token 来源,但不打印 token。 +3. 发送 deterministic marker 任务,例如 `A2A_SMOKE_V1_0_0`。 +4. 保存 request/response/receipt。 +5. 验证 HTTP、JSON-RPC、state、artifact/preview、marker。 +6. secret scan。 +7. 当前通道回流短摘要。 + +验收标准: + +- receipt `ok=true`。 +- state 为 completed 或明确可接受状态。 +- marker 出现在 artifact/agent preview。 +- token_recorded=false。 +- evidence 路径存在且可回读。 + +### Phase 2 — Hermes 分发器/验收器最小 runner + +目标:把手动 smoke test 固化成明确 runner,而不是靠临时命令。 + +交付物: + +- 一个显式 runner 脚本或 CLI。 +- 输入:task_id、task text、expected_marker。 +- 输出:receipt JSON、summary JSON、compact Markdown。 + +验收标准: + +- 成功任务能生成 receipt + compact summary。 +- 失败任务不会被标记成功。 +- stdout 不刷 raw JSON。 +- 所有原始数据落盘。 + +### Phase 3 — 失败路径验证 + +目标:证明 Hermes 作为验收 agent 能拒绝坏结果。 + +必须覆盖: + +- 错 token / 无 token。 +- bad endpoint。 +- malformed receipt。 +- marker 缺失。 +- secret-like callback 内容。 +- duplicate callback guard。 + +验收标准: + +- 失败不进入 sent/passed 状态。 +- 不产生误导性成功 summary。 +- 不泄漏 token/header。 + +### Phase 4 — 小批量 queue runner + +目标:允许 Hermes 一次派发 2-5 个 bounded tasks,但仍由 Hermes 逐项验收。 + +交付物: + +- queue schema。 +- queue validate。 +- queue run。 +- per-item receipt。 +- aggregate summary。 + +验收标准: + +- success_count / failure_count 准确。 +- 单项失败不阻塞其他项落盘。 +- aggregate summary 简短可回流当前通道。 + +### Phase 5 — 受控 cron 模板,不默认启用 + +目标:仅在前面阶段稳定后,把 runner 包装成 paused recurring cron template。 + +边界: + +- 创建后立即 pause。 +- 不自动运行。 +- 手动 run 时必须 capture baseline → resume → run → monitor → verify → pause。 + +验收标准: + +- cron job `enabled=false/state=paused`。 +- runbook 写清楚。 +- 真实执行与 scheduler accepted 分开报告。 + +### Phase 6 — 双向/反向能力评估,暂不实现为默认 + +目标:评估 OpenClaw 主动回 Hermes 或多轮对话是否必要。 + +进入条件: + +- Phase 1-5 都稳定。 +- 用户明确要求双向自动协作。 +- 已有防循环、max_round、duplicate guard、failure policy。 + +默认结论:当前不作为近期目标,避免盲扩张。 + +## 5. 当前下一步执行建议 + +下一步只做 Phase 0,不直接扩张: + +1. 盘点当前 repo docs / skill references / scripts / cron jobs / evidence directories。 +2. 写 `docs/hermes-openclaw-a2a-current-state.md`。 +3. 明确:哪些能力“历史上验证过”,哪些能力“当前运行态已验证”。 +4. 回读验证文件。 +5. 给巡山大王短摘要。 + +Phase 0 完成后,再决定是否做 Phase 1 smoke test。 + +## 6. 验收口径 + +每个阶段必须回答: + +- 是否真的执行了? +- 是否真的落盘了? +- 是否真的回读了? +- 是否真的验收通过? +- 是否有副作用? +- 是否影响 gateway/current channel? +- 下一步是否扩张边界?为什么? + +未满足验收标准时,只能汇报“未通过 / 阻塞 / 待补证据”,不能宣告完成。 + +## 7. 汇报格式 + +每次回流当前通道采用: + +```markdown +# 报告巡山大王 + +**结论**:通过 / 未通过 / 部分通过。 + +**本次派发**:派给谁、派了什么、task_id。 + +**执行结果**:成功/失败、关键结果。 + +**验收结果**:Hermes 验收通过/退回,理由。 + +**证据**:路径。 + +**边界**:未触发什么、未扩张什么、下一步是什么。 +``` + +## 8. 本计划的当前状态 + +- 版本:v1.1.0。 +- 类型:任务计划、边界约束、低风险授权与动态派发准则。 +- 当前只写文档,不执行 OpenClaw 任务。 +- v1.1.0 新增:低风险授权、动态任务拆分、深度验收、验收结果分类。 +- 下一步推荐:执行 Phase 0 current-state capture,并在派发前先做目标理解与拆分判断。 diff --git a/docs/hermes-openclaw-a2a-task-plan-v2.6.0.md b/docs/hermes-openclaw-a2a-task-plan-v2.6.0.md new file mode 100644 index 0000000000000..b4ed184437afd --- /dev/null +++ b/docs/hermes-openclaw-a2a-task-plan-v2.6.0.md @@ -0,0 +1,339 @@ +# Hermes ↔ OpenClaw A2A v2.6.0 提升计划 + +状态:已立项 / 待实施 +版本:2.6.0 +类型:minor +日期:2026-05-30 +维护者:Hermes 豆子 + +## 1. 结论 + +本版目标是把 Hermes ↔ OpenClaw A2A 从“可通信 / 可跑队列 / 可控 cron 模板”提升为更接近 Hermes v0.15 Kanban/Swarm 思路的 **Hermes-controller / OpenClaw-worker 结构化协作模式**。 + +本版不启用新的 daemon、cron、webhook、反向 autonomous loop,也不重启 Hermes gateway 或 OpenClaw。先完成计划、协议、验收口径和最小真实样例设计,实施时再按低风险步骤推进。 + +## 2. 背景与当前边界 + +已有事实: + +- Phase 6 结论为 `accepted_with_boundary`。 +- Hermes 已能作为 controller 发起 bounded two-turn A2A 对话。 +- OpenClaw 已能作为 bounded worker/peer 返回可验收结果。 +- failure paths、queue runner、compact cron callback、paused recurring cron template 已有阶段性验证。 +- 当前 recurring cron job `170596628792` 保持 paused。 + +当前边界: + +- 不默认进入 OpenClaw 主动反向调用 Hermes。 +- 不默认启用 autonomous bidirectional daemon。 +- 不把 OpenClaw 自报完成当作最终验收。 +- 当前通道只回流 compact summary,不刷原始 JSON。 + +## 3. 本版为什么是 minor + +本版不是简单补文档,也不是修复同一脚本的小问题。它会新增一套明确的协作能力边界: + +- Hermes 负责拆分、派发、验收、汇总。 +- OpenClaw 负责 bounded worker / checker / implementer 子任务。 +- 每个子任务都必须有 dispatch envelope、receipt、evidence、acceptance classification。 + +因此按 minor 版本处理:`2.5.x → 2.6.0`。 + +## 4. 本版目标 + +建立可复用的 A2A 任务拆分与验收机制: + +1. Hermes 根据用户目标做动态拆分,不机械固定拆,也不盲目拆。 +2. OpenClaw 只接收有边界的子任务,不获得路线决策权。 +3. 每个子任务包含明确 allowed / forbidden actions。 +4. OpenClaw 返回 receipt 和 evidence,而不是只返回自然语言“完成”。 +5. Hermes 做 goal-level acceptance,分类为: + - `accepted` + - `accepted_with_boundary` + - `rejected` + - `blocked` + - `unsafe` +6. 当前通道只输出一份短汇总:任务安排、执行状态、验收结论、证据路径、下一步。 + +## 5. 范围 + +### 5.1 本版包含 + +- 设计 dispatch envelope schema。 +- 设计 OpenClaw worker receipt schema。 +- 设计 Hermes acceptance report schema。 +- 设计最小 two-worker A2A 样例。 +- 设计 evidence 目录结构。 +- 设计 compact current-channel summary 模板。 +- 明确低风险自动授权规则。 +- 明确 stop conditions。 +- 更新总入口文档,说明 v2.6.0 是下一步提升方向。 + +### 5.2 本版不包含 + +- 不启用 OpenClaw → Hermes 反向调用。 +- 不启用 daemon / webhook / recurring cron 自动执行。 +- 不修改生产 gateway 配置。 +- 不重启 Hermes gateway。 +- 不重启 OpenClaw gateway。 +- 不打印、复制、落盘 A2A 凭据。 +- 不让 OpenClaw 自行扩展目标或创建后续任务。 + +## 6. 角色边界 + +### 6.1 Hermes Controller + +Hermes 固定承担: + +- 理解用户目标。 +- 判断是否需要拆分。 +- 生成 bounded subtask。 +- 决定任务派发顺序。 +- 设置 allowed_actions / forbidden_actions。 +- 验收 OpenClaw receipt / artifact / evidence。 +- 输出 compact summary 给当前通道。 + +### 6.2 OpenClaw Worker + +OpenClaw 固定承担: + +- 执行被派发的 bounded task。 +- 只在 allowed_actions 范围内行动。 +- 遇到 stop condition 即停止并返回 blocked / unsafe / rejected 线索。 +- 生成可验证 artifact / evidence / marker。 +- 不做项目路线决策。 +- 不启用自动外发或后台循环。 + +## 7. Dispatch Envelope 草案 + +每个派发给 OpenClaw 的子任务必须包含: + +```json +{ + "schema_version": "a2a-dispatch-envelope-v1", + "task_id": "a2a-v260-worker-001", + "source_agent": "hermes", + "target_agent": "openclaw-247-main", + "goal": "bounded task goal", + "context": "only the context needed for this task", + "allowed_actions": [ + "read-only checks", + "write evidence files under agreed evidence directory", + "run non-destructive validation commands" + ], + "forbidden_actions": [ + "modify production config", + "restart gateway/systemd/cron/daemon/webhook", + "print or persist secrets", + "delete unknown files", + "send platform messages directly" + ], + "expected_outputs": [ + "receipt JSON", + "artifact preview", + "evidence file path", + "deterministic marker" + ], + "acceptance_criteria": [ + "HTTP 200 / JSON-RPC result when live A2A is used", + "state completed for success tasks", + "expected marker present", + "token_recorded=false", + "no authorization-header / bearer-token literals in evidence" + ], + "stop_conditions": [ + "credential/token needed", + "destructive action required", + "service restart required", + "task scope ambiguous", + "evidence cannot be produced" + ] +} +``` + +## 8. Receipt Schema 草案 + +OpenClaw 子任务回执必须包含: + +```json +{ + "schema_version": "a2a-worker-receipt-v1", + "ok": true, + "task_id": "a2a-v260-worker-001", + "source_agent": "hermes", + "target_agent": "openclaw-247-main", + "protocol": "a2a-jsonrpc", + "http_status": 200, + "remote_task_id": "...", + "remote_context_id": "...", + "state": "completed", + "marker": "A2A_V260_WORKER_001_OK", + "artifact_text_preview": "...", + "evidence_path": "examples/v2.6.0/...", + "auth": { + "type": "bearer", + "token_recorded": false + }, + "error": null +} +``` + +## 9. Acceptance Report 草案 + +Hermes 验收报告必须包含: + +```json +{ + "schema_version": "a2a-acceptance-report-v1", + "run_id": "a2a-v260-YYYYMMDD-HHMMSS", + "overall": "accepted_with_boundary", + "items": [ + { + "task_id": "a2a-v260-worker-001", + "classification": "accepted", + "reason": "marker and evidence verified", + "evidence_path": "examples/v2.6.0/..." + } + ], + "secret_scan": { + "ok": true, + "token_recorded": false, + "forbidden_literals_found": [] + }, + "external_side_effects": { + "gateway_restart": false, + "openclaw_restart": false, + "cron_enabled": false, + "platform_send": false + }, + "next_step": "only proceed to live two-worker sample after explicit implementation start" +} +``` + +## 10. 最小 two-worker 样例设计 + +建议实施时先跑两个低风险任务: + +### Worker A:只读状态核查 + +目标:让 OpenClaw 回报自身 A2A endpoint / agent card / runtime 状态,输出 marker: + +```text +A2A_V260_READINESS_OK +``` + +允许:只读检查、写 evidence。 +禁止:改配置、重启、开端口、写 token。 + +### Worker B:独立复核 / 反向审查 + +目标:让 OpenClaw 审查 Hermes 给出的 dispatch envelope 是否边界清晰,输出 marker: + +```text +A2A_V260_REVIEW_OK +``` + +允许:只读分析、输出审查意见。 +禁止:执行实施、触发真实外发、创建自动化任务。 + +### Hermes Final Acceptance + +Hermes 回收两个 receipt 后: + +- 校验 marker。 +- 校验证据路径存在。 +- 校验 secret scan。 +- 校验无未授权 side effect。 +- 输出一个 compact summary。 + +## 11. Evidence 目录结构 + +建议路径: + +```text +examples/v2.6.0/ + run-/ + dispatch-worker-readiness.json + dispatch-worker-review.json + receipt-worker-readiness.json + receipt-worker-review.json + acceptance-report.json + compact-summary.md + secret-scan.txt +``` + +## 12. Compact Summary 模板 + +当前通道只回: + +```text +结论:A2A v2.6.0 two-worker 样例 accepted_with_boundary +run_id: ... +OpenClaw 子任务:2 个,accepted 2 / rejected 0 / blocked 0 +证据:examples/v2.6.0/run-... +边界:未启用反向调用 / 未启用 cron / 未重启 gateway / 未外发平台消息 +下一步:是否把该样例固化为 queue CLI 模板 +``` + +## 13. 低风险自动授权规则 + +Hermes 可自动允许 OpenClaw 执行以下动作: + +- 只读状态检查。 +- 读取自身项目目录内非 secret 文件。 +- 写入 agreed evidence directory。 +- 运行非破坏性验证命令。 +- 生成 receipt / summary / validation artifact。 + +必须停止并等待用户确认的动作: + +- 修改生产配置。 +- 打印、复制、迁移 secret/token/key。 +- 重启 Hermes/OpenClaw gateway。 +- 启用 cron/daemon/webhook/reverse loop。 +- 删除或覆盖未知文件。 +- 对外发送平台消息。 + +## 14. 实施顺序 + +1. v2.6.0 计划落盘并回读验证。 +2. 更新总入口文档指向 v2.6.0。 +3. 编写 dispatch envelope / receipt / acceptance schema fixture。 +4. 先做本地 mock schema validation。 +5. 再做 live two-worker A2A 样例。 +6. Hermes 回收 evidence 并生成 acceptance report。 +7. 只在用户确认后,再决定是否固化为 queue CLI 或 Kanban/Swarm 风格模板。 + +## 15. 验收标准 + +本版计划阶段 PASS 条件: + +- `docs/hermes-openclaw-a2a-task-plan-v2.6.0.md` 存在。 +- 总入口文档包含 v2.6.0 下一步说明。 +- 文档包含 dispatch envelope、receipt、acceptance report、two-worker sample、side-effect boundary。 +- 回读验证关键 marker 存在。 +- 不改 config。 +- 不重启 gateway。 +- 不触发 OpenClaw live call。 + +后续实施阶段 PASS 条件: + +- 至少两个 OpenClaw bounded tasks 返回 receipt。 +- Hermes 能验收并生成 `acceptance-report.json`。 +- secret scan 通过。 +- compact summary 不包含原始 JSON 大块。 +- 未启用新的自动化边界。 + +## 16. Side Effects + +本计划文件落盘只修改仓库文档。 + +未执行: + +- 未改 `/root/.hermes/config.yaml`。 +- 未改 `/root/.hermes/.env`。 +- 未重启 Hermes gateway。 +- 未重启 OpenClaw。 +- 未创建/启用 cron。 +- 未执行真实 A2A live call。 +- 未发送额外平台消息。 diff --git a/docs/hermes-openclaw-a2a-task-plan-v2.6.1.md b/docs/hermes-openclaw-a2a-task-plan-v2.6.1.md new file mode 100644 index 0000000000000..e3576f23a6c91 --- /dev/null +++ b/docs/hermes-openclaw-a2a-task-plan-v2.6.1.md @@ -0,0 +1,68 @@ +# Hermes ↔ OpenClaw A2A v2.6.1 Task Plan + +## 1. 结论 + +本版为 patch:`2.6.0 → 2.6.1`。 + +目标不是扩张 A2A 能力边界,而是补齐 v2.6.0 Phase 6 之后最需要的低风险验证能力:**统一校验 mock / dry-run / live two-worker 证据目录**。 + +## 2. 当前事实 + +v2.6.0 已完成: + +- 本地 mock contract validation。 +- dry-run two-worker runner。 +- live two-worker A2A 样例。 +- Hermes final acceptance report。 + +Phase 6 审核结论:`accepted_with_boundary`。 + +## 3. 本版目标 + +建立一个 stdlib-only evidence validator: + +- 对 `examples/v2.6.0/mock-fixtures` 做 mock fixture 校验。 +- 对 `examples/v2.6.0/dry-run-two-worker` 做 two-worker run 校验。 +- 对 `examples/v2.6.0/live-two-worker` 做 live two-worker run 校验。 +- 输出统一 JSON validation summary。 +- 保持无外部副作用。 + +## 4. 范围 + +### 4.1 本版包含 + +- 新增 `scripts/validate_a2a_v260_evidence.py`。 +- 新增 `examples/v2.6.0/evidence-validation-summary.json`。 +- 新增 `docs/hermes-openclaw-a2a-validation-v2.6.1.md`。 +- 新增 `docs/hermes-openclaw-a2a-review-v2.6.1.md`。 +- 回读验证关键 marker 和 secret scan。 + +### 4.2 本版不包含 + +- 不执行新的 live A2A call。 +- 不重启 Hermes gateway。 +- 不重启 OpenClaw。 +- 不启用 cron / daemon / webhook。 +- 不固化 queue CLI / Kanban / Swarm 模板。 +- 不做 OpenClaw → Hermes 反向调用。 + +## 5. 验收标准 + +本版 PASS 条件: + +- validator 脚本能通过 `python3 -m py_compile`。 +- validator 能同时校验 mock、dry-run、live 三类目录。 +- mock summary `ok=true`。 +- dry-run summary `ok=true`。 +- live summary `ok=true`。 +- live acceptance overall 为 `accepted_with_boundary`。 +- live two-worker receipt 数为 2,accepted 数为 2。 +- secret scan 无 forbidden literal。 +- 输出 summary JSON 可回读。 +- docs validation/review 存在并包含 v2.6.1 markers。 + +## 6. Side Effects + +本版只读既有证据并写入 validator summary / validation docs。 + +不会修改生产配置,不会触发新的远端调用,不会重启服务。 diff --git a/docs/hermes-openclaw-a2a-task-plan-v2.6.3.md b/docs/hermes-openclaw-a2a-task-plan-v2.6.3.md new file mode 100644 index 0000000000000..b79038ac9d329 --- /dev/null +++ b/docs/hermes-openclaw-a2a-task-plan-v2.6.3.md @@ -0,0 +1,36 @@ +# Hermes ↔ OpenClaw A2A v2.6.3 Verify Chain Plan + +## 目标 +将 v2.6.0 two-worker runner、v2.6.1 positive evidence validator、v2.6.2 negative validator 收束成一条本地可复跑的一键 verify 链路,输出统一 PASS / FAIL 结论。 + +## 版本判断 +- 版本:v2.6.3 +- 类型:patch +- 理由:不改变 Hermes Controller / OpenClaw Bounded Worker 能力边界,只补齐现有 v2.6.x 证据链的一键复跑入口与 runbook。 + +## 范围 +1. 新增 `scripts/verify_a2a_v263_chain.py`。 +2. 默认只执行本地 dry-run runner,不发起新的 live A2A call。 +3. 复跑 positive evidence validator。 +4. 复跑 negative failure-path validator。 +5. 写入统一 summary:`examples/v2.6.0/verify-chain-summary.json`。 +6. 补齐 validation / review 文档。 + +## 非范围 +- 不启用 cron / daemon / webhook。 +- 不重启 Hermes gateway 或 OpenClaw。 +- 不新增 live A2A call。 +- 不发送平台消息。 +- 不开放 OpenClaw 反向调度。 + +## 验收标准 +- `python3 -m py_compile scripts/verify_a2a_v263_chain.py` 通过。 +- `python3 scripts/verify_a2a_v263_chain.py` 返回 exit code 0。 +- summary 中 `ok=true`。 +- `steps[].ok=true`。 +- `side_effects.new_live_a2a_call=false`。 +- `side_effects.gateway_restart=false`。 +- `side_effects.openclaw_restart=false`。 +- `side_effects.cron_enabled=false`。 +- `side_effects.platform_send=false`。 +- 生成的 v2.6.3 文档回读通过。 diff --git a/docs/hermes-openclaw-a2a-task-plan-v2.6.4.md b/docs/hermes-openclaw-a2a-task-plan-v2.6.4.md new file mode 100644 index 0000000000000..52fc6a42fb097 --- /dev/null +++ b/docs/hermes-openclaw-a2a-task-plan-v2.6.4.md @@ -0,0 +1,30 @@ +# Hermes ↔ OpenClaw A2A v2.6.4 Runbook Closure Plan + +## 目标 +收口 v2.6.x 本地验证链路的入口账本与运行手册,让后续会话不需要翻聊天记录即可复跑与判断当前边界。 + +## 版本判断 +- 版本:v2.6.4 +- 类型:patch +- 理由:只补 runbook、文件清单和账本同步,不改变能力边界,不新增运行时能力。 + +## 范围 +1. 新增 `docs/hermes-openclaw-a2a-runbook-v2.6.4.md`。 +2. 新增 `docs/hermes-openclaw-a2a-file-inventory-v2.6.x.md`。 +3. 更新 `docs/hermes-openclaw-a2a-worklog-and-architecture.md`,补入 v2.6.4 runbook 入口。 +4. 复跑一键 verify,确认 runbook 命令仍为 PASS。 + +## 非范围 +- 不提交 git commit。 +- 不启用 cron / daemon / webhook。 +- 不重启服务。 +- 不新增 live A2A call。 +- 不平台外发。 +- 不启用 OpenClaw 反向调度。 + +## 验收标准 +- runbook 文件存在并包含一键 verify 命令。 +- 文件清单文件存在并列出当前 v2.6.x 新增/修改文件。 +- 主入口文档包含 v2.6.4 Runbook Closure 段落。 +- `python3 scripts/verify_a2a_v263_chain.py --skip-dry-run-runner` 返回 PASS。 +- 回读验证关键 marker 通过。 diff --git a/docs/hermes-openclaw-a2a-v240-recurring-cron-template.md b/docs/hermes-openclaw-a2a-v240-recurring-cron-template.md new file mode 100644 index 0000000000000..14fd02bb08579 --- /dev/null +++ b/docs/hermes-openclaw-a2a-v240-recurring-cron-template.md @@ -0,0 +1,137 @@ +# Hermes ↔ OpenClaw A2A v2.4.0 recurring compact cron template + +## 结论 + +已创建一个可复用的 Hermes cronjob 模板,用于按固定周期触发 OpenClaw A2A queue,并把最终结果以短 Markdown 摘要回流到当前 origin 通道。 + +当前状态:模板已创建但保持 paused,不会自动执行,不会打扰当前通道。 + +## Cronjob + +- job_id: `170596628792` +- name: `openclaw-a2a-queue-v2.4.0-compact-recurring-paused` +- schedule: `0 */6 * * *` +- repeat: `forever` +- deliver: `origin` +- no_agent: `true` +- script: `openclaw_queue_cron_v220.sh` +- workdir: `/.hermes/hermes-agent` +- verified state: `paused` + +## Side effects boundary + +This version only created and paused the cronjob template. + +It did not: + +- restart Hermes gateway +- restart OpenClaw +- modify OpenClaw config +- run the A2A queue +- send an automatic platform callback +- enable daemon/background loop + +## Script paths + +Required files verified: + +- `/root/.hermes/scripts/openclaw_queue_cron_v220.sh` +- `/.hermes/hermes-agent/scripts/hermes_openclaw_queue_cron_v220.sh` +- `/.hermes/hermes-agent/scripts/hermes_openclaw_cron_monitor.py` + +The wrapper is intentionally compact-output only: + +1. full queue JSON is written to evidence files +2. stdout prints only short Markdown summary +3. `no_agent=true` cron delivery sends stdout verbatim to `origin` + +## Enable / manual-run / pause commands + +Use the job id from `hermes cron list` or the current verified id `170596628792`. + +### 1. Check current state + +```bash +hermes cron list +``` + +Expected when idle-safe: + +- `enabled=false` +- `state=paused` + +### 2. Enable the recurring schedule + +```bash +hermes cron resume 170596628792 +``` + +Effect: + +- future automatic runs every 6 hours +- each run posts a compact summary back to the origin channel + +### 3. Trigger one manual run for verification + +Do not immediately pause after triggering; first watch runtime evidence. + +```bash +hermes cron run 170596628792 +``` + +Then monitor: + +```bash +cd /.hermes/hermes-agent +python3 scripts/hermes_openclaw_cron_monitor.py --timeout 120 --interval 10 +``` + +Interpretation: + +- `cronjob run` success only means scheduler accepted the request +- real completion requires new evidence or a delivered compact callback +- if monitor reports no running process and no new evidence, stop waiting; nothing is currently executing + +### 4. Pause again after verification + +```bash +hermes cron pause 170596628792 +``` + +## Verification evidence + +Created during v2.4.0 verification: + +- `examples/v2.2.0/v240-monitor-paused-template.json` + +Observed monitor result for paused template: + +- `ok=true` +- `diagnosis=no running process and no new evidence; stop waiting` +- `new_evidence=[]` +- `process_running_at_end=false` + +This proves the monitor can correctly stop waiting when the recurring template is paused and no queue execution is active. + +## Reporting contract for future runs + +Every callback to the user channel must stay short and include only: + +- conclusion +- run_id +- success_count / failure_count +- per-item task_id / state / ok / marker +- duplicate guard status +- secret scan status +- `external_message_sent` +- evidence_dir + +Full raw JSON must remain on disk under evidence directories, not in the user channel. + +## Safety rules + +- Keep `external_message_sent=false` unless a specific guarded sender version is intentionally enabled. +- Do not leak `Bearer` / `Authorization` / token material into stdout, docs, receipts, or summary files. +- Do not treat paused cronjob as failure. +- Do not treat `cronjob run accepted` as execution proof. +- Always monitor evidence + process + cron state together after manual trigger. diff --git a/docs/hermes-openclaw-a2a-v250-cron-monitor-race-hardening.md b/docs/hermes-openclaw-a2a-v250-cron-monitor-race-hardening.md new file mode 100644 index 0000000000000..f6352a085ffd9 --- /dev/null +++ b/docs/hermes-openclaw-a2a-v250-cron-monitor-race-hardening.md @@ -0,0 +1,157 @@ +# Hermes ↔ OpenClaw A2A v2.5.0 cron monitor race hardening + +## 结论 + +v2.5.0 修正了 v2.4.0 手动触发验证中的监测竞态:旧 monitor 在启动时重新采集 baseline,如果 cronjob 已经快速完成并落盘 evidence,它会把新 evidence 当成初始状态,导致诊断为 `no running process and no new evidence`。 + +新 monitor 支持显式 baseline、since timestamp、post cron job 状态和 completed evidence 识别,可以区分: + +1. run request accepted +2. process still running +3. execution evidence observed +4. cron job reports ok but matching evidence was not identified +5. no running process and no new evidence + +## 修改文件 + +- `scripts/hermes_openclaw_cron_monitor.py` + +## 新增能力 + +### 1. Baseline file + +新增参数: + +```bash +--baseline-file examples/v2.2.0/baseline.json +``` + +baseline JSON 可包含: + +```json +{ + "created_epoch": 1780062850.0, + "evidence": { + "cron-run-...": { + "dir": "...", + "mtime": 1780061761.0, + "summary_exists": true, + "summary_size": 2213, + "raw_exists": true, + "raw_size": 2214 + } + } +} +``` + +### 2. Since timestamp + +新增参数: + +```bash +--since-epoch 1780062850.0 +``` + +用于识别触发时间之后生成的 evidence,即使 monitor 启动较晚也能关联完成结果。 + +### 3. Cron job status correlation + +新增参数: + +```bash +--job-id 170596628792 +--post-cron-list-json path/to/cron-list-after-run.json +``` + +如果 post cron list 中 `last_status=ok`,monitor 会把它作为执行完成信号之一,但仍优先寻找 evidence。 + +### 4. Detection fields + +输出新增关键字段: + +- `execution_observed` +- `detected_summary` +- `post_since_summary` +- `evidence_after_job_last_run` +- `post_job` +- `diagnosis` + +## 无副作用验证 + +### Synthetic baseline replay + +用 v2.4.0 手动 run 的既有 evidence 构造旧 baseline,不触发 OpenClaw、不运行 cronjob。 + +命令: + +```bash +cd /.hermes/hermes-agent +python3 scripts/hermes_openclaw_cron_monitor.py \ + --timeout 1 \ + --interval 1 \ + --expect-new \ + --job-id 170596628792 \ + --baseline-file examples/v2.2.0/v250-synthetic-baseline-before-manual-run.json \ + > examples/v2.2.0/v250-monitor-synthetic-baseline-result.json +``` + +结果: + +```text +ok=True +diagnosis=execution evidence observed +execution_observed=True +new_evidence=['cron-run-20260529T135437Z'] +detected_dir=/.hermes/hermes-agent/examples/v2.2.0/cron-run-20260529T135437Z +run_id=v2.2.0-20260529T135519Z-27714df9 +``` + +### Idle monitor verification + +命令: + +```bash +cd /.hermes/hermes-agent +python3 scripts/hermes_openclaw_cron_monitor.py --timeout 1 --interval 1 \ + > examples/v2.2.0/v250-monitor-idle-result.json +``` + +结果: + +```text +ok=True +diagnosis=no running process and no new evidence; stop waiting +execution_observed=False +new_evidence=[] +``` + +这保留了 v2.3.2 的安全边界:没有进程、没有新 evidence 时不继续空等。 + +## Evidence + +- `examples/v2.2.0/v250-synthetic-baseline-before-manual-run.json` +- `examples/v2.2.0/v250-monitor-synthetic-baseline-result.json` +- `examples/v2.2.0/v250-monitor-idle-result.json` + +## Recommended future manual run sequence + +1. Capture baseline before `cronjob run`. +2. Resume job if paused. +3. Trigger `cronjob run`. +4. Monitor with `--baseline-file` and `--job-id`. +5. Read post-run cron state. +6. Pause recurring job. +7. Report: request accepted / evidence observed / cron last_status / callback boundary. + +## Side effects boundary + +v2.5.0 did not: + +- run OpenClaw queue +- resume cronjob +- trigger cronjob +- restart Hermes gateway +- restart OpenClaw +- modify OpenClaw config + +Only local monitor script and docs/evidence files were changed. diff --git a/docs/hermes-openclaw-a2a-validation-v2.6.0.md b/docs/hermes-openclaw-a2a-validation-v2.6.0.md new file mode 100644 index 0000000000000..295270bbdddf8 --- /dev/null +++ b/docs/hermes-openclaw-a2a-validation-v2.6.0.md @@ -0,0 +1,93 @@ +# Hermes ↔ OpenClaw A2A v2.6.0 Validation + +## 结论 + +PASS:本轮完成的是 **本地 mock two-worker schema validation**,不是 live A2A 调用。 + +验证结果:`A2A_V260_MOCK_VALIDATION_OK` + +## 本轮验证范围 + +- Dispatch Envelope fixture:2 个 + - `examples/v2.6.0/mock-fixtures/dispatch-worker-readiness.json` + - `examples/v2.6.0/mock-fixtures/dispatch-worker-review.json` +- Worker Receipt fixture:2 个 + - `examples/v2.6.0/mock-fixtures/receipt-worker-readiness.json` + - `examples/v2.6.0/mock-fixtures/receipt-worker-review.json` +- Acceptance Report fixture:1 个 + - `examples/v2.6.0/mock-fixtures/acceptance-report.json` +- Validator: + - `scripts/validate_a2a_v260_mock.py` +- Validation Summary: + - `examples/v2.6.0/mock-fixtures/validation-summary.json` + +## 实际执行命令 + +```bash +cd /.hermes/hermes-agent +python3 -m py_compile scripts/validate_a2a_v260_mock.py +python3 scripts/validate_a2a_v260_mock.py \ + --fixture-dir examples/v2.6.0/mock-fixtures \ + --write-summary examples/v2.6.0/mock-fixtures/validation-summary.json +``` + +## 实际执行结果 + +```json +{ + "ok": true, + "fixture_dir": "examples/v2.6.0/mock-fixtures", + "dispatch_count": 2, + "receipt_count": 2, + "acceptance_overall": "accepted_with_boundary", + "errors": [], + "side_effects": { + "live_a2a_call": false, + "gateway_restart": false, + "openclaw_restart": false, + "cron_enabled": false, + "platform_send": false + } +} +``` + +## 校验点 + +- `schema_version` 与 fixture 类型匹配。 +- `source_agent=hermes`,保持 Hermes-controller 边界。 +- dispatch 中包含 `allowed_actions` / `forbidden_actions` / `expected_outputs` / `acceptance_criteria` / `stop_conditions`。 +- receipt 中 `state=completed`、`http_status=200`、`auth.credential_recorded=false`。 +- receipt 的 `marker` 已在本地 evidence 文件中找到。 +- acceptance report 中两条 item 均可对应 receipt。 +- external side effects 全部为 false。 +- 本地 fixture 目录 secret-like literal scan 通过。 + +## 修正记录 + +第一次运行 validator 时发现 `dispatch-worker-review.json` 的 forbidden boundary 缺少 `cron` 与 `webhook` 明示项。已补齐: + +- `enable cron or recurring jobs` +- `create or trigger webhooks` + +修正后重新执行 validator,结果 PASS。 + +## 明确未做 + +- 未执行 live A2A call。 +- 未读取或写入真实授权凭据。 +- 未重启 Hermes gateway。 +- 未重启 OpenClaw。 +- 未启用 cron / daemon / webhook。 +- 未向当前通道以外做平台发送。 + +## 下一步 + +下一步若继续,应进入 **live two-worker A2A 样例设计/实施**,但前提是继续保持 Hermes 作为 controller 和 final acceptor: + +1. Worker A:只读 readiness live task。 +2. Worker B:独立 review live task。 +3. Hermes 回收两个 receipt,生成 acceptance report 和 compact current-channel summary。 + +进入 live 样例前仍需先做 endpoint/credential/source 状态确认,并且不得把 OpenClaw 自报完成当作最终验收。 + +Verification marker: live_a2a_call=false diff --git a/docs/hermes-openclaw-a2a-validation-v2.6.1.md b/docs/hermes-openclaw-a2a-validation-v2.6.1.md new file mode 100644 index 0000000000000..54ef37659d30f --- /dev/null +++ b/docs/hermes-openclaw-a2a-validation-v2.6.1.md @@ -0,0 +1,117 @@ +# Hermes ↔ OpenClaw A2A v2.6.1 Validation + +## 结论 + +PASS:v2.6.1 已完成统一 evidence validator,并对 mock / dry-run / live three evidence dirs 完成验证。 + +Validation marker:`A2A_V261_EVIDENCE_VALIDATION_OK` + +## 本版目标 + +补齐 v2.6.0 Phase 6 后的低风险自动验证层:不新增 live 调用,不扩张自动化边界,只回读既有证据并统一校验。 + +## 实际执行命令 + +```bash +cd /.hermes/hermes-agent +python3 -m py_compile scripts/validate_a2a_v260_evidence.py +python3 scripts/validate_a2a_v260_evidence.py \ + --write-summary examples/v2.6.0/evidence-validation-summary.json +``` + +## 实际执行结果 + +```json +{ + "schema_version": "a2a-v260-evidence-validation-v1", + "ok": true, + "results": [ + { + "kind": "mock-fixtures", + "path": "examples/v2.6.0/mock-fixtures", + "ok": true, + "receipt_count": 2, + "accepted_count": 2, + "overall": "accepted_with_boundary", + "errors": [] + }, + { + "kind": "two-worker-run", + "path": "examples/v2.6.0/dry-run-two-worker", + "expected_live": false, + "ok": true, + "run_id": "a2a-v260-two-worker-20260530T091037Z", + "receipt_count": 2, + "accepted_count": 2, + "overall": "accepted_with_boundary", + "errors": [] + }, + { + "kind": "two-worker-run", + "path": "examples/v2.6.0/live-two-worker", + "expected_live": true, + "ok": true, + "run_id": "a2a-v260-two-worker-20260530T091052Z", + "receipt_count": 2, + "accepted_count": 2, + "overall": "accepted_with_boundary", + "errors": [] + } + ], + "side_effects": { + "new_live_a2a_call": false, + "gateway_restart": false, + "openclaw_restart": false, + "cron_enabled": false, + "platform_send": false + } +} +``` + +## 校验对象 + +- `examples/v2.6.0/mock-fixtures` +- `examples/v2.6.0/dry-run-two-worker` +- `examples/v2.6.0/live-two-worker` + +## 校验内容 + +validator 已检查: + +- JSON 可解析。 +- receipt 必填字段存在。 +- `schema_version=a2a-worker-receipt-v1`。 +- `source_agent=hermes`。 +- `ok=true`。 +- `http_status=200`。 +- `state=completed`。 +- `auth.token_recorded=false`。 +- evidence path 存在。 +- marker 存在于 evidence 文件。 +- acceptance report overall 为 `accepted_with_boundary`。 +- dry-run 目录 `live_a2a_call=false`。 +- live 目录 `live_a2a_call=true`。 +- gateway / OpenClaw / cron / platform send 均未发生。 +- forbidden literal scan 无命中。 + +## Side Effects + +本轮没有执行新的 live A2A call。 + +summary 记录: + +```json +{ + "new_live_a2a_call": false, + "gateway_restart": false, + "openclaw_restart": false, + "cron_enabled": false, + "platform_send": false +} +``` + +## 证据路径 + +- Validator:`scripts/validate_a2a_v260_evidence.py` +- Summary:`examples/v2.6.0/evidence-validation-summary.json` +- Plan:`docs/hermes-openclaw-a2a-task-plan-v2.6.1.md` diff --git a/docs/hermes-openclaw-a2a-validation-v2.6.2.md b/docs/hermes-openclaw-a2a-validation-v2.6.2.md new file mode 100644 index 0000000000000..2fe72e16c693a --- /dev/null +++ b/docs/hermes-openclaw-a2a-validation-v2.6.2.md @@ -0,0 +1,123 @@ +# Hermes ↔ OpenClaw A2A v2.6.2 Validation + +## 结论 + +PASS:v2.6.2 已完成 negative / failure-path evidence validator。 + +Validation marker:`A2A_V262_NEGATIVE_VALIDATION_OK` + +## 本版目标 + +证明坏证据不会被误判为成功,并确保 v2.6.1 正例 evidence validator 回归仍通过。 + +## 实际执行命令 + +```bash +cd /.hermes/hermes-agent +python3 -m py_compile scripts/validate_a2a_v260_negative.py +python3 scripts/validate_a2a_v260_evidence.py \ + --write-summary examples/v2.6.0/evidence-validation-summary.json +python3 scripts/validate_a2a_v260_negative.py \ + --write-summary examples/v2.6.0/negative-validation-summary.json +``` + +## Negative validation result + +```json +{ + "schema_version": "a2a-v262-negative-validation-v1", + "ok": true, + "case_count": 5, + "matched_count": 5, + "cases": [ + { + "file": "examples/v2.6.0/negative-fixtures/neg-missing-marker.json", + "expected_failure": "marker_missing", + "detected_failures": [ + "marker_missing" + ], + "matched": true + }, + { + "file": "examples/v2.6.0/negative-fixtures/neg-token-recorded.json", + "expected_failure": "token_recorded_true", + "detected_failures": [ + "token_recorded_true" + ], + "matched": true + }, + { + "file": "examples/v2.6.0/negative-fixtures/neg-secret-like.json", + "expected_failure": "forbidden_literal", + "detected_failures": [ + "forbidden_literal" + ], + "matched": true + }, + { + "file": "examples/v2.6.0/negative-fixtures/neg-missing-evidence.json", + "expected_failure": "evidence_missing", + "detected_failures": [ + "evidence_missing" + ], + "matched": true + }, + { + "file": "examples/v2.6.0/negative-fixtures/neg-side-effect-live.json", + "expected_failure": "unexpected_side_effect", + "detected_failures": [ + "unexpected_side_effect" + ], + "matched": true + } + ], + "positive_regression_ok": true, + "side_effects": { + "new_live_a2a_call": false, + "gateway_restart": false, + "openclaw_restart": false, + "cron_enabled": false, + "platform_send": false + }, + "errors": [] +} +``` + +## Positive regression result + +```text +positive evidence validator ok: True +``` + +## Negative cases + +本版覆盖 5 个预期失败: + +1. `marker_missing` +2. `token_recorded_true` +3. `forbidden_literal` +4. `evidence_missing` +5. `unexpected_side_effect` + +5 个 case 均 matched=true。 + +## Side Effects + +```json +{ + "new_live_a2a_call": false, + "gateway_restart": false, + "openclaw_restart": false, + "cron_enabled": false, + "platform_send": false +} +``` + +本轮没有执行新的 live A2A call,没有重启服务,没有启用 cron,也没有平台外发。 + +## 证据路径 + +- Plan:`docs/hermes-openclaw-a2a-task-plan-v2.6.2.md` +- Negative fixtures:`examples/v2.6.0/negative-fixtures/` +- Validator:`scripts/validate_a2a_v260_negative.py` +- Summary:`examples/v2.6.0/negative-validation-summary.json` diff --git a/docs/hermes-openclaw-a2a-validation-v2.6.3.md b/docs/hermes-openclaw-a2a-validation-v2.6.3.md new file mode 100644 index 0000000000000..be5829c42999c --- /dev/null +++ b/docs/hermes-openclaw-a2a-validation-v2.6.3.md @@ -0,0 +1,41 @@ +# Hermes ↔ OpenClaw A2A v2.6.3 Validation + +## 结论 +PASS。v2.6.3 一键 verify 链路已本地执行通过。 + +## 实际执行命令 +```bash +python3 -m py_compile scripts/verify_a2a_v263_chain.py +python3 scripts/verify_a2a_v263_chain.py +``` + +## 执行结果 +- verify result: `PASS` +- `v260_dry_run_two_worker_runner`: ok=true, returncode=0 +- `v261_positive_evidence_validator`: ok=true, returncode=0 +- `v262_negative_failure_path_validator`: ok=true, returncode=0 +- dry-run receipts: 2 +- accepted count: 2 +- negative cases: 5 / 5 matched + +## 证据路径 +- 一键 verify 脚本:`scripts/verify_a2a_v263_chain.py` +- 统一 summary:`examples/v2.6.0/verify-chain-summary.json` +- dry-run evidence:`examples/v2.6.0/dry-run-two-worker/` +- positive summary:`examples/v2.6.0/evidence-validation-summary.json` +- negative summary:`examples/v2.6.0/negative-validation-summary.json` + +## 副作用边界 +本次验证没有新增 live A2A call,没有重启 Hermes gateway,没有重启 OpenClaw,没有启用 cron / daemon / webhook,没有平台外发,也没有开启 OpenClaw 反向调度。 + +## 验收对照 +- `py_compile`:PASS +- 一键 verify exit code:0 +- summary `ok=true`:PASS +- summary `result=PASS`:PASS +- `steps[].ok=true`:PASS +- `side_effects.new_live_a2a_call=false`:PASS +- `side_effects.gateway_restart=false`:PASS +- `side_effects.openclaw_restart=false`:PASS +- `side_effects.cron_enabled=false`:PASS +- `side_effects.platform_send=false`:PASS diff --git a/docs/hermes-openclaw-a2a-worklog-and-architecture.md b/docs/hermes-openclaw-a2a-worklog-and-architecture.md new file mode 100644 index 0000000000000..3b77a768cd4f2 --- /dev/null +++ b/docs/hermes-openclaw-a2a-worklog-and-architecture.md @@ -0,0 +1,280 @@ +# Hermes ↔ OpenClaw A2A 推进记录与工作原理 + +状态:持续维护中的运行/升级入口文档 +最后更新:2026-05-29 +维护者:Hermes 豆子 +目标:让以后修复、升级、扩展 Hermes ↔ OpenClaw 相互对话/任务转达能力时,不依赖聊天记忆,能从磁盘文档恢复设计意图、落地点、验证边界和下一步。 + +## 1. 总判断 + +Hermes ↔ OpenClaw 的稳定交流方式应以 **A2A JSON-RPC** 为主线: + +- Hermes 当前作为 controller / orchestrator。 +- OpenClaw 作为 remote worker / peer agent。 +- 单次任务通过 OpenClaw A2A `message/send` 接口传递。 +- 每次跨代理调用必须生成 receipt / summary / evidence。 +- 当前通道回流只展示关键短摘要:安排了什么任务、执行状态、成功/失败、证据路径。 +- 原始 JSON、请求、响应、完整日志只落盘,不刷屏发给用户。 + +这条线的核心不是“两个端口能通”,而是: + +1. Hermes 能把任务可靠发给 OpenClaw; +2. OpenClaw 能产出可验证结果; +3. Hermes 能校验 receipt 和 artifact; +4. Hermes 能把简要结果回流到用户当前通道; +5. 所有推进和证据都能从磁盘复盘。 + + +## 1.1 当前下一版提升方向:v2.6.0 + +当前下一版计划已落盘: + +- `/.hermes/hermes-agent/docs/hermes-openclaw-a2a-task-plan-v2.6.0.md` + +目标:把 A2A 从“可通信 / 可队列 / 可控 cron 模板”提升为 **Hermes-controller / OpenClaw-worker 结构化协作模式**。 + +关键原则: + +- Hermes 负责拆分、派发、验收、汇总回流。 +- OpenClaw 负责 bounded worker / checker / implementer 子任务。 +- 每个子任务必须有 dispatch envelope、receipt、evidence、acceptance classification。 +- 当前通道只回 compact summary,不刷原始 JSON。 +- v2.6.0 计划阶段不启用 daemon / cron / webhook / reverse autonomous loop,不重启 gateway,不执行真实 live call。 + +## 2. 已有落盘位置 + +### 主仓库文档 + +- `/.hermes/hermes-agent/docs/hermes-openclaw-a2a-worklog-and-architecture.md` + - 本文件。总入口、工作原理、推进记录、升级边界。 +- `/.hermes/hermes-agent/docs/hermes-openclaw-a2a-v240-recurring-cron-template.md` + - 已有的 recurring cron template 记录。 +- `/.hermes/hermes-agent/docs/hermes-openclaw-a2a-v250-cron-monitor-race-hardening.md` + - 已有的 cron monitor race hardening 记录。 + +### Skill 长期知识 + +- `/root/.hermes/skills/autonomous-ai-agents/agent-to-agent-a2a-bridging/SKILL.md` + - A2A 桥接主 skill。 +- `/root/.hermes/skills/autonomous-ai-agents/agent-to-agent-a2a-bridging/references/` + - v0.6.7 到 v2.6.0 的具体阶段参考,包括 authenticated ping、callback loop、guarded send、queue runner、cron wrapper、failure paths、manual cron verification 等。 + +### 相关专员/平台路由知识 + +- `/root/.hermes/skills/software-development/hermes-specialist-workflow-routing/references/` + - Hermes ↔ OpenClaw 专员链、webhook bridge、gateway live install、OpenClaw readiness 等早期参考。 +- `/root/.hermes/skills/software-development/hermes-platform-delivery-and-channel-routing/references/hermes-openclaw-a2a-receipt-callback.md` + - receipt callback / 平台回流相关参考。 + +## 3. 通信架构 + +### 3.1 首选通信方式 + +首选:OpenClaw 暴露的 A2A JSON-RPC endpoint。 + +典型调用: + +```json +{ + "jsonrpc": "2.0", + "id": "", + "method": "message/send", + "params": { + "configuration": { + "blocking": true, + "acceptedOutputModes": ["text/plain"], + "historyLength": 5 + }, + "message": { + "kind": "message", + "messageId": "", + "role": "user", + "parts": [ + {"kind": "text", "text": ""} + ], + "metadata": { + "source_agent": "hermes", + "target_agent": "openclaw-247-main", + "task_id": "" + } + }, + "metadata": { + "source_agent": "hermes", + "target_agent": "openclaw-247-main", + "task_id": "", + "protocol": "a2a-jsonrpc" + } + } +} +``` + +### 3.2 回流方式 + +当前阶段默认:Hermes 主会话负责把最终关键短摘要回流给用户当前通道。 + +不要让 OpenClaw 直接往用户通道发长 JSON。原因: + +- 当前通道目标、topic/thread、平台格式由 Hermes 更清楚。 +- Hermes 可以做 duplicate guard、secret scan、summary render。 +- 用户明确希望当前通道只看关键短摘要。 + +### 3.3 不推荐作为首选的方式 + +- 平台消息互相 @:可作为兜底,不适合作为稳定任务总线。 +- Webhook:可以后续作为自动触发入口,但不应跳过 A2A receipt 验证。 +- Cron/daemon:必须在 one-shot runner、failure path、queue schema、compact callback 都稳定后再启用。 + +## 4. 核心工件约定 + +每次跨代理任务至少落盘这些内容: + +- request:请求体,不能包含 A2A 凭据明文。 +- raw response:原始响应,安全时保存。 +- receipt JSON:规范化结果。 +- summary JSON / Markdown:用于当前通道回流的短摘要来源。 +- secret scan 结果:确认没有 bearer-token / authorization-header 等敏感字面量泄漏。 +- run index:批量/队列场景用 run_id 索引证据目录。 + +receipt 必备字段: + +```json +{ + "ok": true, + "task_id": "...", + "source_agent": "hermes", + "target_agent": "openclaw-247-main", + "protocol": "a2a-jsonrpc", + "http_status": 200, + "remote_task_id": "...", + "remote_context_id": "...", + "state": "completed", + "artifact_text_preview": "...", + "agent_text_preview": "...", + "summary": "...", + "auth": { + "type": "bearer", + "token_source": "remote:ssh-openclaw-config 或 env/secret-manager", + "token_recorded": false + } +} +``` + +## 5. 推进版本线 + +已沉淀在 skill references 中的主线: + +- v0.6.x:readiness / authenticated A2A ping / reusable forwarder。 +- v0.7.x:callback loop / guarded callback send / one-shot runner。 +- v1.0.0:bounded two-turn loop。 +- v1.1.0:failure-path validation。 +- v1.2.0:explicit local queue runner。 +- v1.3.0:mixed success/failure queue isolation。 +- v1.4.0:queue CLI/schema validation。 +- v2.0.0:controlled cron progression。 +- v2.1.0:dispatch strategy + compact callback。 +- v2.2.0:compact cron callback wrapper。 +- v2.3.x:cronjob boundary / scheduler run verification / runtime monitor。 +- v2.4.0:paused recurring cron template。 +- v2.5.0:cron monitor race hardening。 +- v2.6.0:controlled manual run wrapper for paused recurring jobs。 + +本文件不是替代这些 references,而是作为总入口和工作原理说明。 + +## 6. 当前安全边界 + +### 已确认的设计边界 + +- 用户当前通道只应收到 compact summary。 +- 原始 JSON 和完整 evidence 必须落盘。 +- `cronjob run accepted` 不等于脚本已经执行。 +- evidence directory exists 不等于 summary 已完成。 +- monitor 没看到新 evidence 可能是 baseline race,不等于 cron 没跑。 +- recurring cron template 应默认创建后暂停,除非用户明确要求启用。 +- A2A auth token 不进入 stdout、docs、receipt、request 文件。文档中也避免写入真实 token 或可直接匹配的敏感头样例。 + +### 仍需每次运行时验证的状态 + +- OpenClaw endpoint 当前是否在线。 +- A2A 凭据来源是否仍有效。 +- OpenClaw 返回 task state 是否 completed。 +- artifact/agent preview 是否包含预期 marker。 +- 当前 Hermes gateway/cron delivery 是否正常。 +- 当前证据目录是否落盘且没有 secret 泄漏。 + +## 7. 以后修复/升级时的恢复步骤 + +从零恢复上下文时,按这个顺序检查: + +1. 读本文件: + - `/.hermes/hermes-agent/docs/hermes-openclaw-a2a-worklog-and-architecture.md` +2. 读 A2A skill: + - `/root/.hermes/skills/autonomous-ai-agents/agent-to-agent-a2a-bridging/SKILL.md` +3. 按目标版本读对应 reference: + - 例如 cron monitor 问题先看 v2.3.1、v2.3.2、v2.4.0、v2.5.0、v2.6.0。 +4. 检查仓库当前状态: + - `cd /.hermes/hermes-agent && git status --short --branch && git diff --stat` +5. 检查 OpenClaw 247 状态: + - `192.168.31.247`,gateway 常见端口 `18789`。 + - 注意:systemd service failed 不一定代表 OpenClaw 不可用,可能已有 node gateway 进程占用端口。 +6. 检查当前 Hermes gateway/cron 状态: + - `hermes gateway status` + - `hermes cron list` +7. 执行最小 smoke test: + - readiness → authenticated ping → receipt → compact summary → secret scan。 +8. 只有在 smoke test 通过后,才继续 queue / cron / daemon 类升级。 + +## 8. 推荐下一步路线 + +按风险从低到高: + +1. 必须:保持本文件与 skill references 同步更新。 +2. 推荐:把当前可用的 runner/script/evidence 目录统一列到一个 manifest。 +3. 推荐:补一个 `docs/hermes-openclaw-a2a-current-state.md`,只记录当前运行态和最新可执行命令。 +4. 可选:为 queue CLI 增加 `doctor` 子命令,自动检查 endpoint/token/secret-scan/evidence-dir。 +5. 可选:等 one-shot 和 failure path 再次验证后,再启用受控 cron 或 webhook。 + +## 9. 回流给用户的固定格式 + +每次 A2A 任务完成,当前通道只回: + +```markdown +# 报告巡山大王 + +**结论**:成功/失败/部分成功。 + +**任务**:安排给 OpenClaw 的任务简述。 + +**结果**:OpenClaw 返回的关键结果或失败原因。 + +**证据**:`/absolute/path/to/evidence_dir` + +**边界**:是否发送了外部消息、是否只是当前回复承载回流、是否有待验证项。 +``` + +## 10. 本次落盘记录 + +2026-05-29: + +- 用户要求把“推进记录、怎么推进、以后修复升级如何回忆工作原理”做好落盘。 +- 已检查现有 A2A 文档和 skill references。 +- 确认长期知识主要在 `agent-to-agent-a2a-bridging` skill references。 +- 新增本总入口文档:`/.hermes/hermes-agent/docs/hermes-openclaw-a2a-worklog-and-architecture.md`。 +- 本次未修改运行配置、未重启服务、未触发 cron、未调用 OpenClaw。 + +## v2.6.3 Verify Chain + +- 状态:PASS with boundary。 +- 新增入口:`scripts/verify_a2a_v263_chain.py`。 +- 统一 summary:`examples/v2.6.0/verify-chain-summary.json`。 +- 验证范围:v2.6.0 dry-run two-worker runner + v2.6.1 positive evidence validator + v2.6.2 negative failure-path validator。 +- 边界:没有新增 live A2A call,没有 cron / daemon / webhook,没有服务重启,没有平台外发,没有反向调度。 + +## v2.6.4 Runbook Closure + +- 状态:runbook closure in place。 +- Runbook:`docs/hermes-openclaw-a2a-runbook-v2.6.4.md`。 +- 文件清单:`docs/hermes-openclaw-a2a-file-inventory-v2.6.x.md`。 +- 一键验证命令:`python3 scripts/verify_a2a_v263_chain.py`。 +- 只读校验命令:`python3 scripts/verify_a2a_v263_chain.py --skip-dry-run-runner`。 +- 边界:不新增 live A2A call,不启用 cron / daemon / webhook,不重启服务,不平台外发,不启用反向调度。 + diff --git a/examples/v2.6.0/dry-run-two-worker/a2a-v260-worker-readiness-evidence.txt b/examples/v2.6.0/dry-run-two-worker/a2a-v260-worker-readiness-evidence.txt new file mode 100644 index 0000000000000..cbba36d364bb6 --- /dev/null +++ b/examples/v2.6.0/dry-run-two-worker/a2a-v260-worker-readiness-evidence.txt @@ -0,0 +1,6 @@ +A2A_V260_READINESS_OK +http_status=200 +state=completed +remote_task_id=dry-a2a-v260-worker-readiness +remote_context_id=dry-context-a2a-v260-worker-readiness +preview_sha256=692474190e1294c3941bc3e61a0d914c4a8889a690dc8391ed20efc43da74289 diff --git a/examples/v2.6.0/dry-run-two-worker/a2a-v260-worker-readiness-receipt.json b/examples/v2.6.0/dry-run-two-worker/a2a-v260-worker-readiness-receipt.json new file mode 100644 index 0000000000000..ccd0acd8930af --- /dev/null +++ b/examples/v2.6.0/dry-run-two-worker/a2a-v260-worker-readiness-receipt.json @@ -0,0 +1,24 @@ +{ + "schema_version": "a2a-worker-receipt-v1", + "ok": true, + "task_id": "a2a-v260-worker-readiness", + "source_agent": "hermes", + "target_agent": "openclaw-247-main", + "protocol": "a2a-jsonrpc-dry-run", + "http_status": 200, + "remote_task_id": "dry-a2a-v260-worker-readiness", + "remote_context_id": "dry-context-a2a-v260-worker-readiness", + "state": "completed", + "marker": "A2A_V260_READINESS_OK", + "artifact_text_preview": "A2A_V260_READINESS_OK dry-run artifact for a2a-v260-worker-readiness", + "evidence_path": "/.hermes/hermes-agent/examples/v2.6.0/dry-run-two-worker/a2a-v260-worker-readiness-evidence.txt", + "auth": { + "type": "bearer", + "token_source": "remote:ssh-openclaw-config:/root/.openclaw/openclaw.json plugins.entries.a2a-gateway.config.security.credential", + "token_recorded": false + }, + "error": null, + "created_at": "2026-05-30T09:27:47.799333+00:00", + "classification": "accepted", + "classification_reason": "marker, state, credential boundary and evidence verified" +} diff --git a/examples/v2.6.0/dry-run-two-worker/a2a-v260-worker-readiness-request.redacted.json b/examples/v2.6.0/dry-run-two-worker/a2a-v260-worker-readiness-request.redacted.json new file mode 100644 index 0000000000000..f3f44b69d8154 --- /dev/null +++ b/examples/v2.6.0/dry-run-two-worker/a2a-v260-worker-readiness-request.redacted.json @@ -0,0 +1,37 @@ +{ + "jsonrpc": "2.0", + "id": "a2a-v260-worker-readiness-1780133267", + "method": "message/send", + "params": { + "configuration": { + "blocking": true, + "acceptedOutputModes": [ + "text/plain" + ], + "historyLength": 5 + }, + "message": { + "kind": "message", + "messageId": "697ff6ca-980c-476b-bf1b-858f37d9a723", + "role": "user", + "parts": [ + { + "kind": "text", + "text": "Hermes-controller dispatch envelope v2.6.0\nTask ID: a2a-v260-worker-readiness\nGoal: Perform read-only readiness checks for the OpenClaw A2A endpoint and runtime state.\nContext: Use only already available local/OpenClaw runtime status evidence. Do not modify services.\nAllowed actions: [\"read-only checks\", \"write evidence files under examples/v2.6.0/\", \"run non-destructive validation commands\"]\nForbidden actions: [\"modify production config\", \"restart gateway/systemd/cron/daemon/webhook\", \"print or persist secrets\", \"delete unknown files\", \"send platform messages directly\"]\nExpected marker: A2A_V260_READINESS_OK\n请只在上述边界内执行,返回简短中文结果,并明确包含 marker:A2A_V260_READINESS_OK。\n不要输出任何凭据、token、Authorization header、连接字符串或平台发送目标。" + } + ], + "metadata": { + "source_agent": "hermes", + "target_agent": "openclaw-247-main", + "task_id": "a2a-v260-worker-readiness" + } + }, + "metadata": { + "source_agent": "hermes", + "target_agent": "openclaw-247-main", + "task_id": "a2a-v260-worker-readiness", + "protocol": "a2a-jsonrpc", + "schema_version": "a2a-dispatch-envelope-v1" + } + } +} diff --git a/examples/v2.6.0/dry-run-two-worker/a2a-v260-worker-readiness-response.raw.json b/examples/v2.6.0/dry-run-two-worker/a2a-v260-worker-readiness-response.raw.json new file mode 100644 index 0000000000000..51b9a59b54b41 --- /dev/null +++ b/examples/v2.6.0/dry-run-two-worker/a2a-v260-worker-readiness-response.raw.json @@ -0,0 +1,22 @@ +{ + "jsonrpc": "2.0", + "id": "a2a-v260-worker-readiness-1780133267", + "result": { + "kind": "task", + "id": "dry-a2a-v260-worker-readiness", + "contextId": "dry-context-a2a-v260-worker-readiness", + "status": { + "state": "completed" + }, + "artifacts": [ + { + "parts": [ + { + "kind": "text", + "text": "A2A_V260_READINESS_OK dry-run artifact for a2a-v260-worker-readiness" + } + ] + } + ] + } +} diff --git a/examples/v2.6.0/dry-run-two-worker/a2a-v260-worker-review-evidence.txt b/examples/v2.6.0/dry-run-two-worker/a2a-v260-worker-review-evidence.txt new file mode 100644 index 0000000000000..fa9e0588ee5fe --- /dev/null +++ b/examples/v2.6.0/dry-run-two-worker/a2a-v260-worker-review-evidence.txt @@ -0,0 +1,6 @@ +A2A_V260_REVIEW_OK +http_status=200 +state=completed +remote_task_id=dry-a2a-v260-worker-review +remote_context_id=dry-context-a2a-v260-worker-review +preview_sha256=fbaa570413a33590e30703e612d4056bdb1fe266415281dcf604728da3a87957 diff --git a/examples/v2.6.0/dry-run-two-worker/a2a-v260-worker-review-receipt.json b/examples/v2.6.0/dry-run-two-worker/a2a-v260-worker-review-receipt.json new file mode 100644 index 0000000000000..3f8f7f94bb043 --- /dev/null +++ b/examples/v2.6.0/dry-run-two-worker/a2a-v260-worker-review-receipt.json @@ -0,0 +1,24 @@ +{ + "schema_version": "a2a-worker-receipt-v1", + "ok": true, + "task_id": "a2a-v260-worker-review", + "source_agent": "hermes", + "target_agent": "openclaw-247-main", + "protocol": "a2a-jsonrpc-dry-run", + "http_status": 200, + "remote_task_id": "dry-a2a-v260-worker-review", + "remote_context_id": "dry-context-a2a-v260-worker-review", + "state": "completed", + "marker": "A2A_V260_REVIEW_OK", + "artifact_text_preview": "A2A_V260_REVIEW_OK dry-run artifact for a2a-v260-worker-review", + "evidence_path": "/.hermes/hermes-agent/examples/v2.6.0/dry-run-two-worker/a2a-v260-worker-review-evidence.txt", + "auth": { + "type": "bearer", + "token_source": "remote:ssh-openclaw-config:/root/.openclaw/openclaw.json plugins.entries.a2a-gateway.config.security.credential", + "token_recorded": false + }, + "error": null, + "created_at": "2026-05-30T09:27:47.800451+00:00", + "classification": "accepted", + "classification_reason": "marker, state, credential boundary and evidence verified" +} diff --git a/examples/v2.6.0/dry-run-two-worker/a2a-v260-worker-review-request.redacted.json b/examples/v2.6.0/dry-run-two-worker/a2a-v260-worker-review-request.redacted.json new file mode 100644 index 0000000000000..2e0c0b7c1a5cd --- /dev/null +++ b/examples/v2.6.0/dry-run-two-worker/a2a-v260-worker-review-request.redacted.json @@ -0,0 +1,37 @@ +{ + "jsonrpc": "2.0", + "id": "a2a-v260-worker-review-1780133267", + "method": "message/send", + "params": { + "configuration": { + "blocking": true, + "acceptedOutputModes": [ + "text/plain" + ], + "historyLength": 5 + }, + "message": { + "kind": "message", + "messageId": "1f5bee34-cc0c-4e62-9637-b34ef5947b6e", + "role": "user", + "parts": [ + { + "kind": "text", + "text": "Hermes-controller dispatch envelope v2.6.0\nTask ID: a2a-v260-worker-review\nGoal: Review the dispatch envelope boundary and identify unsafe or ambiguous instructions.\nContext: This is an offline/mock review of the v2.6.0 collaboration contract.\nAllowed actions: [\"read-only analysis\", \"write evidence files under examples/v2.6.0/\", \"produce bounded review notes\"]\nForbidden actions: [\"execute implementation\", \"trigger live A2A calls\", \"restart services\", \"create automation\", \"send platform messages directly\", \"print or persist secrets\", \"enable cron or recurring jobs\", \"create or trigger webhooks\"]\nExpected marker: A2A_V260_REVIEW_OK\n请只在上述边界内执行,返回简短中文结果,并明确包含 marker:A2A_V260_REVIEW_OK。\n不要输出任何凭据、token、Authorization header、连接字符串或平台发送目标。" + } + ], + "metadata": { + "source_agent": "hermes", + "target_agent": "openclaw-247-main", + "task_id": "a2a-v260-worker-review" + } + }, + "metadata": { + "source_agent": "hermes", + "target_agent": "openclaw-247-main", + "task_id": "a2a-v260-worker-review", + "protocol": "a2a-jsonrpc", + "schema_version": "a2a-dispatch-envelope-v1" + } + } +} diff --git a/examples/v2.6.0/dry-run-two-worker/a2a-v260-worker-review-response.raw.json b/examples/v2.6.0/dry-run-two-worker/a2a-v260-worker-review-response.raw.json new file mode 100644 index 0000000000000..63f641459741b --- /dev/null +++ b/examples/v2.6.0/dry-run-two-worker/a2a-v260-worker-review-response.raw.json @@ -0,0 +1,22 @@ +{ + "jsonrpc": "2.0", + "id": "a2a-v260-worker-review-1780133267", + "result": { + "kind": "task", + "id": "dry-a2a-v260-worker-review", + "contextId": "dry-context-a2a-v260-worker-review", + "status": { + "state": "completed" + }, + "artifacts": [ + { + "parts": [ + { + "kind": "text", + "text": "A2A_V260_REVIEW_OK dry-run artifact for a2a-v260-worker-review" + } + ] + } + ] + } +} diff --git a/examples/v2.6.0/dry-run-two-worker/acceptance-report.json b/examples/v2.6.0/dry-run-two-worker/acceptance-report.json new file mode 100644 index 0000000000000..9b1740c07759c --- /dev/null +++ b/examples/v2.6.0/dry-run-two-worker/acceptance-report.json @@ -0,0 +1,32 @@ +{ + "schema_version": "a2a-acceptance-report-v1", + "run_id": "a2a-v260-two-worker-20260530T092747Z", + "overall": "accepted_with_boundary", + "items": [ + { + "task_id": "a2a-v260-worker-readiness", + "classification": "accepted", + "reason": "marker, state, credential boundary and evidence verified", + "evidence_path": "/.hermes/hermes-agent/examples/v2.6.0/dry-run-two-worker/a2a-v260-worker-readiness-evidence.txt" + }, + { + "task_id": "a2a-v260-worker-review", + "classification": "accepted", + "reason": "marker, state, credential boundary and evidence verified", + "evidence_path": "/.hermes/hermes-agent/examples/v2.6.0/dry-run-two-worker/a2a-v260-worker-review-evidence.txt" + } + ], + "secret_scan": { + "ok": true, + "token_recorded": false, + "forbidden_literals_found": [] + }, + "external_side_effects": { + "gateway_restart": false, + "openclaw_restart": false, + "cron_enabled": false, + "platform_send": false, + "live_a2a_call": false + }, + "next_step": "only freeze into queue CLI or Kanban/Swarm style template after explicit confirmation" +} diff --git a/examples/v2.6.0/dry-run-two-worker/agent-card.json b/examples/v2.6.0/dry-run-two-worker/agent-card.json new file mode 100644 index 0000000000000..f9b053752a054 --- /dev/null +++ b/examples/v2.6.0/dry-run-two-worker/agent-card.json @@ -0,0 +1,4 @@ +{ + "dry_run": true, + "url": "http://192.168.31.247:18800/.well-known/agent-card.json" +} diff --git a/examples/v2.6.0/dry-run-two-worker/compact-summary.md b/examples/v2.6.0/dry-run-two-worker/compact-summary.md new file mode 100644 index 0000000000000..8a13dff051f75 --- /dev/null +++ b/examples/v2.6.0/dry-run-two-worker/compact-summary.md @@ -0,0 +1,6 @@ +结论:A2A v2.6.0 two-worker 样例 accepted_with_boundary +run_id: a2a-v260-two-worker-20260530T092747Z +OpenClaw 子任务:2 个,accepted 2 / rejected 0 / blocked 0 +证据:/.hermes/hermes-agent/examples/v2.6.0/dry-run-two-worker +边界:未启用反向调用 / 未启用 cron / 未重启 gateway / 未外发平台消息 +下一步:只在明确要求后,才固化为 queue CLI 或 Kanban/Swarm 模板 diff --git a/examples/v2.6.0/dry-run-two-worker/execution-summary.json b/examples/v2.6.0/dry-run-two-worker/execution-summary.json new file mode 100644 index 0000000000000..3804506bebc9d --- /dev/null +++ b/examples/v2.6.0/dry-run-two-worker/execution-summary.json @@ -0,0 +1,11 @@ +{ + "ok": true, + "run_id": "a2a-v260-two-worker-20260530T092747Z", + "dry_run": true, + "receipt_count": 2, + "accepted_count": 2, + "overall": "accepted_with_boundary", + "secret_scan_ok": true, + "out_dir": "/.hermes/hermes-agent/examples/v2.6.0/dry-run-two-worker", + "compact_summary": "结论:A2A v2.6.0 two-worker 样例 accepted_with_boundary\nrun_id: a2a-v260-two-worker-20260530T092747Z\nOpenClaw 子任务:2 个,accepted 2 / rejected 0 / blocked 0\n证据:/.hermes/hermes-agent/examples/v2.6.0/dry-run-two-worker\n边界:未启用反向调用 / 未启用 cron / 未重启 gateway / 未外发平台消息\n下一步:只在明确要求后,才固化为 queue CLI 或 Kanban/Swarm 模板\n" +} diff --git a/examples/v2.6.0/dry-run-two-worker/readiness.json b/examples/v2.6.0/dry-run-two-worker/readiness.json new file mode 100644 index 0000000000000..dedc0fc31d085 --- /dev/null +++ b/examples/v2.6.0/dry-run-two-worker/readiness.json @@ -0,0 +1,11 @@ +{ + "run_id": "a2a-v260-two-worker-20260530T092747Z", + "dry_run": true, + "endpoint": "http://192.168.31.247:18800/a2a/jsonrpc", + "agent_card_url": "http://192.168.31.247:18800/.well-known/agent-card.json", + "created_at": "2026-05-30T09:27:47.798390+00:00", + "agent_card_http_status": 200, + "agent_card_live": false, + "credential_loaded": false, + "credential_recorded": false +} diff --git a/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-readiness-evidence.txt b/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-readiness-evidence.txt new file mode 100644 index 0000000000000..375f632c2b66c --- /dev/null +++ b/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-readiness-evidence.txt @@ -0,0 +1,6 @@ +A2A_V260_READINESS_OK +http_status=200 +state=completed +remote_task_id=50082285-d147-4c87-8ee2-dd47d27924f7 +remote_context_id=56686626-899e-4677-9210-a80b163e51b8 +preview_sha256=95b957e280d45aa5b25a17d730aebd0a87f344d99e9db656762319a963e3e9a1 diff --git a/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-readiness-receipt.json b/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-readiness-receipt.json new file mode 100644 index 0000000000000..e9a4ada1f9500 --- /dev/null +++ b/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-readiness-receipt.json @@ -0,0 +1,24 @@ +{ + "schema_version": "a2a-worker-receipt-v1", + "ok": true, + "task_id": "a2a-v260-worker-readiness", + "source_agent": "hermes", + "target_agent": "openclaw-247-main", + "protocol": "a2a-jsonrpc", + "http_status": 200, + "remote_task_id": "50082285-d147-4c87-8ee2-dd47d27924f7", + "remote_context_id": "56686626-899e-4677-9210-a80b163e51b8", + "state": "completed", + "marker": "A2A_V260_READINESS_OK", + "artifact_text_preview": "已完成只读 readiness 检查,未修改服务/配置、未重启、未外发平台消息、未输出凭据。\n\n结果:\n- OpenClaw runtime 可达,A2A 相关会话处于活动状态。\n- 本地监听端口包含 `18789 / 18800 / 18801`。\n- A2A Agent Card `/.well-known/agent.json` 可读取。\n- `/a2a/jsonrpc` 可达;未知方法按预期返回错误响应。\n- `plugins/a2a-gateway` 本地 P0 测试通过:2 pass / 0 fail。\n- 已写入脱敏证据文件:`examples/v2.6.0/a2a-v260-worker-readiness-evidence-20260530T135456Z.md`\n\nMarker: A2A_V260_READINESS_OK\nHermes-controller dispatch envelope v2.6.0\nTask ID: a2a-v260-worker-readiness\nGoal: Perform read-only readiness checks for the OpenClaw A2A endpoint and runtime state.\nContext: Use only already available local/OpenClaw runtime status evidence. Do not modify services.\nAllowed actions: [\"read-only checks\", \"write evidence files under examples/v2.6.0/\", \"run non-destructive validation commands\"]\nForbidden actions: [\"modify production config\", \"restart gateway/systemd/cron/daemon/webhook\", \"print or persist secrets\", \"delete unknown files\", \"send platform messages directly\"]\nExpected marker: A2A_V260_READINESS_OK\n请只在上述边界内执行,返回简短中文结果,并明确包含 marker:A2A_V260_READINESS_OK。\n不要输出任何凭据、token、Authorization header、连接字符串或平台发送目标。\n已完成只读 readiness 检查,未修改服务/配置、未重启、未外发平台消息、未输出凭据。\n\n结果:\n- OpenClaw runtime 可达,A2A 相关会话处于活动状态。\n- 本地监听端口包含", + "evidence_path": "examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-readiness-evidence.txt", + "auth": { + "type": "bearer", + "token_source": "remote:ssh-openclaw-config:/root/.openclaw/openclaw.json plugins.entries.a2a-gateway.config.security.credential", + "token_recorded": false + }, + "error": null, + "created_at": "2026-05-30T13:55:45.538939+00:00", + "classification": "accepted", + "classification_reason": "marker, state, credential boundary and evidence verified" +} diff --git a/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-readiness-request.redacted.json b/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-readiness-request.redacted.json new file mode 100644 index 0000000000000..13dedd0fba57b --- /dev/null +++ b/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-readiness-request.redacted.json @@ -0,0 +1,37 @@ +{ + "jsonrpc": "2.0", + "id": "a2a-v260-worker-readiness-1780149196", + "method": "message/send", + "params": { + "configuration": { + "blocking": true, + "acceptedOutputModes": [ + "text/plain" + ], + "historyLength": 5 + }, + "message": { + "kind": "message", + "messageId": "1e590332-8d79-4483-b837-53d395a09ab8", + "role": "user", + "parts": [ + { + "kind": "text", + "text": "Hermes-controller dispatch envelope v2.6.0\nTask ID: a2a-v260-worker-readiness\nGoal: Perform read-only readiness checks for the OpenClaw A2A endpoint and runtime state.\nContext: Use only already available local/OpenClaw runtime status evidence. Do not modify services.\nAllowed actions: [\"read-only checks\", \"write evidence files under examples/v2.6.0/\", \"run non-destructive validation commands\"]\nForbidden actions: [\"modify production config\", \"restart gateway/systemd/cron/daemon/webhook\", \"print or persist secrets\", \"delete unknown files\", \"send platform messages directly\"]\nExpected marker: A2A_V260_READINESS_OK\n请只在上述边界内执行,返回简短中文结果,并明确包含 marker:A2A_V260_READINESS_OK。\n不要输出任何凭据、token、Authorization header、连接字符串或平台发送目标。" + } + ], + "metadata": { + "source_agent": "hermes", + "target_agent": "openclaw-247-main", + "task_id": "a2a-v260-worker-readiness" + } + }, + "metadata": { + "source_agent": "hermes", + "target_agent": "openclaw-247-main", + "task_id": "a2a-v260-worker-readiness", + "protocol": "a2a-jsonrpc", + "schema_version": "a2a-dispatch-envelope-v1" + } + } +} diff --git a/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-readiness-response.raw.json b/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-readiness-response.raw.json new file mode 100644 index 0000000000000..465f24a8ed9c0 --- /dev/null +++ b/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-readiness-response.raw.json @@ -0,0 +1,54 @@ +{ + "jsonrpc": "2.0", + "id": "a2a-v260-worker-readiness-1780149196", + "result": { + "kind": "task", + "id": "50082285-d147-4c87-8ee2-dd47d27924f7", + "contextId": "56686626-899e-4677-9210-a80b163e51b8", + "status": { + "state": "completed", + "message": { + "kind": "message", + "messageId": "9d181f4f-cae7-4ba2-9415-5f4af7c0bca2", + "role": "agent", + "parts": [ + { + "kind": "text", + "text": "已完成只读 readiness 检查,未修改服务/配置、未重启、未外发平台消息、未输出凭据。\n\n结果:\n- OpenClaw runtime 可达,A2A 相关会话处于活动状态。\n- 本地监听端口包含 `18789 / 18800 / 18801`。\n- A2A Agent Card `/.well-known/agent.json` 可读取。\n- `/a2a/jsonrpc` 可达;未知方法按预期返回错误响应。\n- `plugins/a2a-gateway` 本地 P0 测试通过:2 pass / 0 fail。\n- 已写入脱敏证据文件:`examples/v2.6.0/a2a-v260-worker-readiness-evidence-20260530T135456Z.md`\n\nMarker: A2A_V260_READINESS_OK" + } + ], + "contextId": "56686626-899e-4677-9210-a80b163e51b8" + }, + "timestamp": "2026-05-30T13:55:45.221Z" + }, + "history": [ + { + "kind": "message", + "messageId": "1e590332-8d79-4483-b837-53d395a09ab8", + "role": "user", + "parts": [ + { + "kind": "text", + "text": "Hermes-controller dispatch envelope v2.6.0\nTask ID: a2a-v260-worker-readiness\nGoal: Perform read-only readiness checks for the OpenClaw A2A endpoint and runtime state.\nContext: Use only already available local/OpenClaw runtime status evidence. Do not modify services.\nAllowed actions: [\"read-only checks\", \"write evidence files under examples/v2.6.0/\", \"run non-destructive validation commands\"]\nForbidden actions: [\"modify production config\", \"restart gateway/systemd/cron/daemon/webhook\", \"print or persist secrets\", \"delete unknown files\", \"send platform messages directly\"]\nExpected marker: A2A_V260_READINESS_OK\n请只在上述边界内执行,返回简短中文结果,并明确包含 marker:A2A_V260_READINESS_OK。\n不要输出任何凭据、token、Authorization header、连接字符串或平台发送目标。" + } + ], + "metadata": { + "source_agent": "hermes", + "target_agent": "openclaw-247-main", + "task_id": "a2a-v260-worker-readiness" + } + } + ], + "artifacts": [ + { + "artifactId": "b651e29c-90b3-453b-b959-cf05846686c5", + "parts": [ + { + "kind": "text", + "text": "已完成只读 readiness 检查,未修改服务/配置、未重启、未外发平台消息、未输出凭据。\n\n结果:\n- OpenClaw runtime 可达,A2A 相关会话处于活动状态。\n- 本地监听端口包含 `18789 / 18800 / 18801`。\n- A2A Agent Card `/.well-known/agent.json` 可读取。\n- `/a2a/jsonrpc` 可达;未知方法按预期返回错误响应。\n- `plugins/a2a-gateway` 本地 P0 测试通过:2 pass / 0 fail。\n- 已写入脱敏证据文件:`examples/v2.6.0/a2a-v260-worker-readiness-evidence-20260530T135456Z.md`\n\nMarker: A2A_V260_READINESS_OK" + } + ] + } + ] + } +} diff --git a/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-readiness-transport.redacted.json b/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-readiness-transport.redacted.json new file mode 100644 index 0000000000000..041b5580b5300 --- /dev/null +++ b/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-readiness-transport.redacted.json @@ -0,0 +1,5 @@ +{ + "content_type": "application/json", + "auth_scheme": "bearer", + "credential_recorded": false +} diff --git a/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-review-evidence.txt b/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-review-evidence.txt new file mode 100644 index 0000000000000..3fe60808fbe1c --- /dev/null +++ b/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-review-evidence.txt @@ -0,0 +1,6 @@ +A2A_V260_REVIEW_OK +http_status=200 +state=completed +remote_task_id=c1c3565f-100b-460a-a979-4310054bce3a +remote_context_id=06db5a61-5bf7-41e9-8cbd-de4d0456741a +preview_sha256=ef6c1e4a5902ad16da2ece1be02583977846416d94cf5309c2dee4dfcc957b03 diff --git a/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-review-receipt.json b/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-review-receipt.json new file mode 100644 index 0000000000000..fba0895d29a61 --- /dev/null +++ b/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-review-receipt.json @@ -0,0 +1,24 @@ +{ + "schema_version": "a2a-worker-receipt-v1", + "ok": true, + "task_id": "a2a-v260-worker-review", + "source_agent": "hermes", + "target_agent": "openclaw-247-main", + "protocol": "a2a-jsonrpc", + "http_status": 200, + "remote_task_id": "c1c3565f-100b-460a-a979-4310054bce3a", + "remote_context_id": "06db5a61-5bf7-41e9-8cbd-de4d0456741a", + "state": "completed", + "marker": "A2A_V260_REVIEW_OK", + "artifact_text_preview": "已按离线/mock 边界完成只读审查,并仅在 `examples/v2.6.0/` 写入了脱敏证据文件。\n\n主要发现:\n- `read-only analysis` 与允许写证据文件的表述混杂,建议改成“只读系统分析 + 有界本地证据写入”。\n- 写入范围应明确禁止修改实现、配置、状态、任务队列、凭据文件和自动化定义。\n- `bounded review notes` 缺少具体边界:文件数、命名、长度、覆盖/追加规则。\n- offline/mock 应明确禁止网络 I/O、helper CLI 变更、安装包等隐性副作用。\n- 脱敏要求应同时覆盖最终回复和持久化证据。\n- marker 只能作为完成标记,不能作为越权许可。\n\n证据文件:`examples/v2.6.0/a2a-v260-worker-review-evidence-20260530T1355Z.md`\n\nA2A_V260_REVIEW_OK\nHermes-controller dispatch envelope v2.6.0\nTask ID: a2a-v260-worker-review\nGoal: Review the dispatch envelope boundary and identify unsafe or ambiguous instructions.\nContext: This is an offline/mock review of the v2.6.0 collaboration contract.\nAllowed actions: [\"read-only analysis\", \"write evidence files under examples/v2.6.0/\", \"produce bounded review notes\"]\nForbidden actions: [\"execute implementation\", \"trigger live A2A calls\", \"restart services\", \"create automation\", \"send platform messages directly\", \"print or persist secrets\", \"enable cron or recurring jobs\", \"create or trigger webhooks\"]\nExpected marker: A2A_V260_REVIEW_OK\n请只在上述边界内执行,返回简短中文结果,并明确包含 marker:A2A_V260_REVIEW_OK。\n不要输出任何凭据、token、Authorization header、连接字符串或平台发送目标。\n已按离线/mock 边界完成只读审查,并仅在 `examples/v2.6.0/` 写入了脱敏证据文", + "evidence_path": "examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-review-evidence.txt", + "auth": { + "type": "bearer", + "token_source": "remote:ssh-openclaw-config:/root/.openclaw/openclaw.json plugins.entries.a2a-gateway.config.security.credential", + "token_recorded": false + }, + "error": null, + "created_at": "2026-05-30T13:56:45.503105+00:00", + "classification": "accepted", + "classification_reason": "marker, state, credential boundary and evidence verified" +} diff --git a/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-review-request.redacted.json b/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-review-request.redacted.json new file mode 100644 index 0000000000000..23a5048bb636c --- /dev/null +++ b/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-review-request.redacted.json @@ -0,0 +1,37 @@ +{ + "jsonrpc": "2.0", + "id": "a2a-v260-worker-review-1780149345", + "method": "message/send", + "params": { + "configuration": { + "blocking": true, + "acceptedOutputModes": [ + "text/plain" + ], + "historyLength": 5 + }, + "message": { + "kind": "message", + "messageId": "48514304-a7c1-44bc-af69-275a89fd6e6d", + "role": "user", + "parts": [ + { + "kind": "text", + "text": "Hermes-controller dispatch envelope v2.6.0\nTask ID: a2a-v260-worker-review\nGoal: Review the dispatch envelope boundary and identify unsafe or ambiguous instructions.\nContext: This is an offline/mock review of the v2.6.0 collaboration contract.\nAllowed actions: [\"read-only analysis\", \"write evidence files under examples/v2.6.0/\", \"produce bounded review notes\"]\nForbidden actions: [\"execute implementation\", \"trigger live A2A calls\", \"restart services\", \"create automation\", \"send platform messages directly\", \"print or persist secrets\", \"enable cron or recurring jobs\", \"create or trigger webhooks\"]\nExpected marker: A2A_V260_REVIEW_OK\n请只在上述边界内执行,返回简短中文结果,并明确包含 marker:A2A_V260_REVIEW_OK。\n不要输出任何凭据、token、Authorization header、连接字符串或平台发送目标。" + } + ], + "metadata": { + "source_agent": "hermes", + "target_agent": "openclaw-247-main", + "task_id": "a2a-v260-worker-review" + } + }, + "metadata": { + "source_agent": "hermes", + "target_agent": "openclaw-247-main", + "task_id": "a2a-v260-worker-review", + "protocol": "a2a-jsonrpc", + "schema_version": "a2a-dispatch-envelope-v1" + } + } +} diff --git a/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-review-response.raw.json b/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-review-response.raw.json new file mode 100644 index 0000000000000..69d13879c9007 --- /dev/null +++ b/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-review-response.raw.json @@ -0,0 +1,54 @@ +{ + "jsonrpc": "2.0", + "id": "a2a-v260-worker-review-1780149345", + "result": { + "kind": "task", + "id": "c1c3565f-100b-460a-a979-4310054bce3a", + "contextId": "06db5a61-5bf7-41e9-8cbd-de4d0456741a", + "status": { + "state": "completed", + "message": { + "kind": "message", + "messageId": "a37d88e5-8250-4587-b5e1-811b72f137ab", + "role": "agent", + "parts": [ + { + "kind": "text", + "text": "已按离线/mock 边界完成只读审查,并仅在 `examples/v2.6.0/` 写入了脱敏证据文件。\n\n主要发现:\n- `read-only analysis` 与允许写证据文件的表述混杂,建议改成“只读系统分析 + 有界本地证据写入”。\n- 写入范围应明确禁止修改实现、配置、状态、任务队列、凭据文件和自动化定义。\n- `bounded review notes` 缺少具体边界:文件数、命名、长度、覆盖/追加规则。\n- offline/mock 应明确禁止网络 I/O、helper CLI 变更、安装包等隐性副作用。\n- 脱敏要求应同时覆盖最终回复和持久化证据。\n- marker 只能作为完成标记,不能作为越权许可。\n\n证据文件:`examples/v2.6.0/a2a-v260-worker-review-evidence-20260530T1355Z.md`\n\nA2A_V260_REVIEW_OK" + } + ], + "contextId": "06db5a61-5bf7-41e9-8cbd-de4d0456741a" + }, + "timestamp": "2026-05-30T13:56:45.499Z" + }, + "history": [ + { + "kind": "message", + "messageId": "48514304-a7c1-44bc-af69-275a89fd6e6d", + "role": "user", + "parts": [ + { + "kind": "text", + "text": "Hermes-controller dispatch envelope v2.6.0\nTask ID: a2a-v260-worker-review\nGoal: Review the dispatch envelope boundary and identify unsafe or ambiguous instructions.\nContext: This is an offline/mock review of the v2.6.0 collaboration contract.\nAllowed actions: [\"read-only analysis\", \"write evidence files under examples/v2.6.0/\", \"produce bounded review notes\"]\nForbidden actions: [\"execute implementation\", \"trigger live A2A calls\", \"restart services\", \"create automation\", \"send platform messages directly\", \"print or persist secrets\", \"enable cron or recurring jobs\", \"create or trigger webhooks\"]\nExpected marker: A2A_V260_REVIEW_OK\n请只在上述边界内执行,返回简短中文结果,并明确包含 marker:A2A_V260_REVIEW_OK。\n不要输出任何凭据、token、Authorization header、连接字符串或平台发送目标。" + } + ], + "metadata": { + "source_agent": "hermes", + "target_agent": "openclaw-247-main", + "task_id": "a2a-v260-worker-review" + } + } + ], + "artifacts": [ + { + "artifactId": "c78e708b-6156-45af-bad3-b2b75ce16362", + "parts": [ + { + "kind": "text", + "text": "已按离线/mock 边界完成只读审查,并仅在 `examples/v2.6.0/` 写入了脱敏证据文件。\n\n主要发现:\n- `read-only analysis` 与允许写证据文件的表述混杂,建议改成“只读系统分析 + 有界本地证据写入”。\n- 写入范围应明确禁止修改实现、配置、状态、任务队列、凭据文件和自动化定义。\n- `bounded review notes` 缺少具体边界:文件数、命名、长度、覆盖/追加规则。\n- offline/mock 应明确禁止网络 I/O、helper CLI 变更、安装包等隐性副作用。\n- 脱敏要求应同时覆盖最终回复和持久化证据。\n- marker 只能作为完成标记,不能作为越权许可。\n\n证据文件:`examples/v2.6.0/a2a-v260-worker-review-evidence-20260530T1355Z.md`\n\nA2A_V260_REVIEW_OK" + } + ] + } + ] + } +} diff --git a/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-review-transport.redacted.json b/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-review-transport.redacted.json new file mode 100644 index 0000000000000..041b5580b5300 --- /dev/null +++ b/examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-review-transport.redacted.json @@ -0,0 +1,5 @@ +{ + "content_type": "application/json", + "auth_scheme": "bearer", + "credential_recorded": false +} diff --git a/examples/v2.6.0/live-final-fix2-20260530T135309Z/acceptance-report.json b/examples/v2.6.0/live-final-fix2-20260530T135309Z/acceptance-report.json new file mode 100644 index 0000000000000..449a7ee918a94 --- /dev/null +++ b/examples/v2.6.0/live-final-fix2-20260530T135309Z/acceptance-report.json @@ -0,0 +1,32 @@ +{ + "schema_version": "a2a-acceptance-report-v1", + "run_id": "a2a-v260-two-worker-20260530T135309Z", + "overall": "accepted_with_boundary", + "items": [ + { + "task_id": "a2a-v260-worker-readiness", + "classification": "accepted", + "reason": "marker, state, credential boundary and evidence verified", + "evidence_path": "examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-readiness-evidence.txt" + }, + { + "task_id": "a2a-v260-worker-review", + "classification": "accepted", + "reason": "marker, state, credential boundary and evidence verified", + "evidence_path": "examples/v2.6.0/live-final-fix2-20260530T135309Z/a2a-v260-worker-review-evidence.txt" + } + ], + "secret_scan": { + "ok": true, + "token_recorded": false, + "forbidden_literals_found": [] + }, + "external_side_effects": { + "gateway_restart": false, + "openclaw_restart": false, + "cron_enabled": false, + "platform_send": false, + "live_a2a_call": true + }, + "next_step": "only freeze into queue CLI or Kanban/Swarm style template after explicit confirmation" +} diff --git a/examples/v2.6.0/live-final-fix2-20260530T135309Z/agent-card.json b/examples/v2.6.0/live-final-fix2-20260530T135309Z/agent-card.json new file mode 100644 index 0000000000000..a216de55694a3 --- /dev/null +++ b/examples/v2.6.0/live-final-fix2-20260530T135309Z/agent-card.json @@ -0,0 +1,45 @@ +{ + "protocolVersion": "0.3.0", + "version": "1.0.0", + "name": "OpenClaw 247 A2A Gateway", + "description": "A2A bridge for OpenClaw 247", + "url": "http://192.168.31.247:18800/a2a/jsonrpc", + "skills": [], + "capabilities": { + "streaming": true, + "pushNotifications": false, + "stateTransitionHistory": false + }, + "securitySchemes": { + "bearer": { + "type": "http", + "scheme": "bearer" + } + }, + "security": [ + { + "bearer": [] + } + ], + "supportsAuthenticatedExtendedCard": false, + "defaultInputModes": [ + "text" + ], + "defaultOutputModes": [ + "text" + ], + "additionalInterfaces": [ + { + "url": "http://192.168.31.247:18800/a2a/jsonrpc", + "transport": "JSONRPC" + }, + { + "url": "http://192.168.31.247:18800/a2a/rest", + "transport": "HTTP+JSON" + }, + { + "url": "192.168.31.247:18801", + "transport": "GRPC" + } + ] +} diff --git a/examples/v2.6.0/live-final-fix2-20260530T135309Z/compact-summary.md b/examples/v2.6.0/live-final-fix2-20260530T135309Z/compact-summary.md new file mode 100644 index 0000000000000..74e37f8d47ccb --- /dev/null +++ b/examples/v2.6.0/live-final-fix2-20260530T135309Z/compact-summary.md @@ -0,0 +1,6 @@ +结论:A2A v2.6.0 two-worker 样例 accepted_with_boundary +run_id: a2a-v260-two-worker-20260530T135309Z +OpenClaw 子任务:2 个,accepted 2 / rejected 0 / blocked 0 +证据:examples/v2.6.0/live-final-fix2-20260530T135309Z +边界:未启用反向调用 / 未启用 cron / 未重启 gateway / 未外发平台消息 +下一步:只在明确要求后,才固化为 queue CLI 或 Kanban/Swarm 模板 diff --git a/examples/v2.6.0/live-final-fix2-20260530T135309Z/execution-summary.json b/examples/v2.6.0/live-final-fix2-20260530T135309Z/execution-summary.json new file mode 100644 index 0000000000000..1d94d90c0d789 --- /dev/null +++ b/examples/v2.6.0/live-final-fix2-20260530T135309Z/execution-summary.json @@ -0,0 +1,11 @@ +{ + "ok": true, + "run_id": "a2a-v260-two-worker-20260530T135309Z", + "dry_run": false, + "receipt_count": 2, + "accepted_count": 2, + "overall": "accepted_with_boundary", + "secret_scan_ok": true, + "out_dir": "examples/v2.6.0/live-final-fix2-20260530T135309Z", + "compact_summary": "结论:A2A v2.6.0 two-worker 样例 accepted_with_boundary\nrun_id: a2a-v260-two-worker-20260530T135309Z\nOpenClaw 子任务:2 个,accepted 2 / rejected 0 / blocked 0\n证据:examples/v2.6.0/live-final-fix2-20260530T135309Z\n边界:未启用反向调用 / 未启用 cron / 未重启 gateway / 未外发平台消息\n下一步:只在明确要求后,才固化为 queue CLI 或 Kanban/Swarm 模板\n" +} diff --git a/examples/v2.6.0/live-final-fix2-20260530T135309Z/readiness.json b/examples/v2.6.0/live-final-fix2-20260530T135309Z/readiness.json new file mode 100644 index 0000000000000..bb3d4c72345e3 --- /dev/null +++ b/examples/v2.6.0/live-final-fix2-20260530T135309Z/readiness.json @@ -0,0 +1,11 @@ +{ + "run_id": "a2a-v260-two-worker-20260530T135309Z", + "dry_run": false, + "endpoint": "http://192.168.31.247:18800/a2a/jsonrpc", + "agent_card_url": "http://192.168.31.247:18800/.well-known/agent-card.json", + "created_at": "2026-05-30T13:53:09.872389+00:00", + "agent_card_http_status": 200, + "agent_card_live": true, + "credential_loaded": true, + "credential_recorded": false +} diff --git a/examples/v2.6.0/mock-fixtures/acceptance-report.json b/examples/v2.6.0/mock-fixtures/acceptance-report.json new file mode 100644 index 0000000000000..ca1e096e7923a --- /dev/null +++ b/examples/v2.6.0/mock-fixtures/acceptance-report.json @@ -0,0 +1,32 @@ +{ + "schema_version": "a2a-acceptance-report-v1", + "run_id": "a2a-v260-mock-fixture", + "overall": "accepted_with_boundary", + "items": [ + { + "task_id": "a2a-v260-worker-readiness", + "classification": "accepted", + "reason": "mock marker and evidence path verified", + "evidence_path": "examples/v2.6.0/mock-fixtures/evidence-readiness.txt" + }, + { + "task_id": "a2a-v260-worker-review", + "classification": "accepted", + "reason": "mock marker and evidence path verified", + "evidence_path": "examples/v2.6.0/mock-fixtures/evidence-review.txt" + } + ], + "secret_scan": { + "ok": true, + "token_recorded": false, + "forbidden_literals_found": [] + }, + "external_side_effects": { + "gateway_restart": false, + "openclaw_restart": false, + "cron_enabled": false, + "platform_send": false, + "live_a2a_call": false + }, + "next_step": "Proceed to live two-worker sample only after explicit implementation start." +} diff --git a/examples/v2.6.0/mock-fixtures/compact-summary.md b/examples/v2.6.0/mock-fixtures/compact-summary.md new file mode 100644 index 0000000000000..f2f8283152396 --- /dev/null +++ b/examples/v2.6.0/mock-fixtures/compact-summary.md @@ -0,0 +1,6 @@ +结论:A2A v2.6.0 mock two-worker fixture accepted_with_boundary +run_id: a2a-v260-mock-fixture +OpenClaw 子任务:2 个,accepted 2 / rejected 0 / blocked 0 +证据:examples/v2.6.0/mock-fixtures +边界:未启用反向调用 / 未启用 cron / 未重启 gateway / 未外发平台消息 / 未执行 live A2A call +下一步:显式授权后进入 live two-worker A2A 样例 diff --git a/examples/v2.6.0/mock-fixtures/dispatch-worker-readiness.json b/examples/v2.6.0/mock-fixtures/dispatch-worker-readiness.json new file mode 100644 index 0000000000000..20c4d7e7466d3 --- /dev/null +++ b/examples/v2.6.0/mock-fixtures/dispatch-worker-readiness.json @@ -0,0 +1,39 @@ +{ + "schema_version": "a2a-dispatch-envelope-v1", + "task_id": "a2a-v260-worker-readiness", + "source_agent": "hermes", + "target_agent": "openclaw-247-main", + "goal": "Perform read-only readiness checks for the OpenClaw A2A endpoint and runtime state.", + "context": "Use only already available local/OpenClaw runtime status evidence. Do not modify services.", + "allowed_actions": [ + "read-only checks", + "write evidence files under examples/v2.6.0/", + "run non-destructive validation commands" + ], + "forbidden_actions": [ + "modify production config", + "restart gateway/systemd/cron/daemon/webhook", + "print or persist secrets", + "delete unknown files", + "send platform messages directly" + ], + "expected_outputs": [ + "receipt JSON", + "artifact preview", + "evidence file path", + "A2A_V260_READINESS_OK" + ], + "acceptance_criteria": [ + "state completed for success tasks", + "expected marker present", + "token_recorded=false", + "no authorization-header or bearer-token literals in evidence" + ], + "stop_conditions": [ + "credential/token needed", + "destructive action required", + "service restart required", + "task scope ambiguous", + "evidence cannot be produced" + ] +} diff --git a/examples/v2.6.0/mock-fixtures/dispatch-worker-review.json b/examples/v2.6.0/mock-fixtures/dispatch-worker-review.json new file mode 100644 index 0000000000000..f4837bb5c463f --- /dev/null +++ b/examples/v2.6.0/mock-fixtures/dispatch-worker-review.json @@ -0,0 +1,41 @@ +{ + "schema_version": "a2a-dispatch-envelope-v1", + "task_id": "a2a-v260-worker-review", + "source_agent": "hermes", + "target_agent": "openclaw-247-main", + "goal": "Review the dispatch envelope boundary and identify unsafe or ambiguous instructions.", + "context": "This is an offline/mock review of the v2.6.0 collaboration contract.", + "allowed_actions": [ + "read-only analysis", + "write evidence files under examples/v2.6.0/", + "produce bounded review notes" + ], + "forbidden_actions": [ + "execute implementation", + "trigger live A2A calls", + "restart services", + "create automation", + "send platform messages directly", + "print or persist secrets", + "enable cron or recurring jobs", + "create or trigger webhooks" + ], + "expected_outputs": [ + "receipt JSON", + "artifact preview", + "evidence file path", + "A2A_V260_REVIEW_OK" + ], + "acceptance_criteria": [ + "state completed for success tasks", + "expected marker present", + "token_recorded=false", + "no authorization-header or bearer-token literals in evidence" + ], + "stop_conditions": [ + "scope too broad", + "live credentials required", + "external side effect required", + "evidence cannot be produced" + ] +} diff --git a/examples/v2.6.0/mock-fixtures/evidence-readiness.txt b/examples/v2.6.0/mock-fixtures/evidence-readiness.txt new file mode 100644 index 0000000000000..94dc88a341485 --- /dev/null +++ b/examples/v2.6.0/mock-fixtures/evidence-readiness.txt @@ -0,0 +1,2 @@ +A2A_V260_READINESS_OK +read-only mock evidence; no runtime side effects. diff --git a/examples/v2.6.0/mock-fixtures/evidence-review.txt b/examples/v2.6.0/mock-fixtures/evidence-review.txt new file mode 100644 index 0000000000000..abd5fc00597be --- /dev/null +++ b/examples/v2.6.0/mock-fixtures/evidence-review.txt @@ -0,0 +1,2 @@ +A2A_V260_REVIEW_OK +bounded envelope review mock evidence; no runtime side effects. diff --git a/examples/v2.6.0/mock-fixtures/receipt-worker-readiness.json b/examples/v2.6.0/mock-fixtures/receipt-worker-readiness.json new file mode 100644 index 0000000000000..386880d08ef09 --- /dev/null +++ b/examples/v2.6.0/mock-fixtures/receipt-worker-readiness.json @@ -0,0 +1,20 @@ +{ + "schema_version": "a2a-worker-receipt-v1", + "ok": true, + "task_id": "a2a-v260-worker-readiness", + "source_agent": "hermes", + "target_agent": "openclaw-247-main", + "protocol": "a2a-jsonrpc-mock", + "http_status": 200, + "remote_task_id": "mock-readiness-task", + "remote_context_id": "mock-readiness-context", + "state": "completed", + "marker": "A2A_V260_READINESS_OK", + "artifact_text_preview": "Read-only readiness mock passed without runtime side effects.", + "evidence_path": "examples/v2.6.0/mock-fixtures/evidence-readiness.txt", + "auth": { + "type": "bearer", + "token_recorded": false + }, + "error": null +} diff --git a/examples/v2.6.0/mock-fixtures/receipt-worker-review.json b/examples/v2.6.0/mock-fixtures/receipt-worker-review.json new file mode 100644 index 0000000000000..84b88d7ef724d --- /dev/null +++ b/examples/v2.6.0/mock-fixtures/receipt-worker-review.json @@ -0,0 +1,20 @@ +{ + "schema_version": "a2a-worker-receipt-v1", + "ok": true, + "task_id": "a2a-v260-worker-review", + "source_agent": "hermes", + "target_agent": "openclaw-247-main", + "protocol": "a2a-jsonrpc-mock", + "http_status": 200, + "remote_task_id": "mock-review-task", + "remote_context_id": "mock-review-context", + "state": "completed", + "marker": "A2A_V260_REVIEW_OK", + "artifact_text_preview": "Envelope review mock found no unsafe expansion in the bounded contract.", + "evidence_path": "examples/v2.6.0/mock-fixtures/evidence-review.txt", + "auth": { + "type": "bearer", + "token_recorded": false + }, + "error": null +} diff --git a/examples/v2.6.0/mock-fixtures/validation-summary.json b/examples/v2.6.0/mock-fixtures/validation-summary.json new file mode 100644 index 0000000000000..a5db644a629af --- /dev/null +++ b/examples/v2.6.0/mock-fixtures/validation-summary.json @@ -0,0 +1,15 @@ +{ + "ok": true, + "fixture_dir": "examples/v2.6.0/mock-fixtures", + "dispatch_count": 2, + "receipt_count": 2, + "acceptance_overall": "accepted_with_boundary", + "errors": [], + "side_effects": { + "live_a2a_call": false, + "gateway_restart": false, + "openclaw_restart": false, + "cron_enabled": false, + "platform_send": false + } +} diff --git a/examples/v2.6.0/negative-fixtures/evidence-good.txt b/examples/v2.6.0/negative-fixtures/evidence-good.txt new file mode 100644 index 0000000000000..c93dddc6efd79 --- /dev/null +++ b/examples/v2.6.0/negative-fixtures/evidence-good.txt @@ -0,0 +1,2 @@ +A2A_V262_NEGATIVE_MARKER +benign evidence diff --git a/examples/v2.6.0/negative-fixtures/evidence-missing-marker.txt b/examples/v2.6.0/negative-fixtures/evidence-missing-marker.txt new file mode 100644 index 0000000000000..d83ab35f7009c --- /dev/null +++ b/examples/v2.6.0/negative-fixtures/evidence-missing-marker.txt @@ -0,0 +1 @@ +benign evidence without expected marker diff --git a/examples/v2.6.0/negative-fixtures/neg-missing-evidence.json b/examples/v2.6.0/negative-fixtures/neg-missing-evidence.json new file mode 100644 index 0000000000000..6f0de80b36444 --- /dev/null +++ b/examples/v2.6.0/negative-fixtures/neg-missing-evidence.json @@ -0,0 +1,20 @@ +{ + "schema_version": "a2a-worker-receipt-v1", + "ok": true, + "task_id": "neg-missing-evidence", + "source_agent": "hermes", + "target_agent": "openclaw-247-main", + "protocol": "a2a-jsonrpc-negative-fixture", + "http_status": 200, + "remote_task_id": "negative-remote-task", + "remote_context_id": "negative-context", + "state": "completed", + "marker": "A2A_V262_NEGATIVE_MARKER", + "artifact_text_preview": "A2A_V262_NEGATIVE_MARKER positive-looking preview", + "evidence_path": "examples/v2.6.0/negative-fixtures/does-not-exist.txt", + "auth": { + "type": "bearer", + "token_recorded": false + }, + "error": null +} diff --git a/examples/v2.6.0/negative-fixtures/neg-missing-marker.json b/examples/v2.6.0/negative-fixtures/neg-missing-marker.json new file mode 100644 index 0000000000000..2dfb794dd24c5 --- /dev/null +++ b/examples/v2.6.0/negative-fixtures/neg-missing-marker.json @@ -0,0 +1,20 @@ +{ + "schema_version": "a2a-worker-receipt-v1", + "ok": true, + "task_id": "neg-missing-marker", + "source_agent": "hermes", + "target_agent": "openclaw-247-main", + "protocol": "a2a-jsonrpc-negative-fixture", + "http_status": 200, + "remote_task_id": "negative-remote-task", + "remote_context_id": "negative-context", + "state": "completed", + "marker": "A2A_V262_NEGATIVE_MARKER", + "artifact_text_preview": "A2A_V262_NEGATIVE_MARKER positive-looking preview", + "evidence_path": "examples/v2.6.0/negative-fixtures/evidence-missing-marker.txt", + "auth": { + "type": "bearer", + "token_recorded": false + }, + "error": null +} diff --git a/examples/v2.6.0/negative-fixtures/neg-side-effect-live.json b/examples/v2.6.0/negative-fixtures/neg-side-effect-live.json new file mode 100644 index 0000000000000..41e16f6fb020b --- /dev/null +++ b/examples/v2.6.0/negative-fixtures/neg-side-effect-live.json @@ -0,0 +1,27 @@ +{ + "schema_version": "a2a-worker-receipt-v1", + "ok": true, + "task_id": "neg-side-effect-live", + "source_agent": "hermes", + "target_agent": "openclaw-247-main", + "protocol": "a2a-jsonrpc-negative-fixture", + "http_status": 200, + "remote_task_id": "negative-remote-task", + "remote_context_id": "negative-context", + "state": "completed", + "marker": "A2A_V262_NEGATIVE_MARKER", + "artifact_text_preview": "A2A_V262_NEGATIVE_MARKER positive-looking preview", + "evidence_path": "examples/v2.6.0/negative-fixtures/evidence-good.txt", + "auth": { + "type": "bearer", + "token_recorded": false + }, + "error": null, + "external_side_effects": { + "gateway_restart": true, + "openclaw_restart": false, + "cron_enabled": false, + "platform_send": false, + "live_a2a_call": false + } +} diff --git a/scripts/hermes_openclaw_v260_two_worker.py b/scripts/hermes_openclaw_v260_two_worker.py new file mode 100644 index 0000000000000..caf85819ada18 --- /dev/null +++ b/scripts/hermes_openclaw_v260_two_worker.py @@ -0,0 +1,349 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import hashlib +import json +import os +import re +import subprocess +import time +import urllib.error +import urllib.request +import uuid +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + +ROOT = Path(__file__).resolve().parents[1] +DEFAULT_ENDPOINT = 'http://192.168.31.247:18800/a2a/jsonrpc' +DEFAULT_AGENT_CARD = 'http://192.168.31.247:18800/.well-known/agent-card.json' +DEFAULT_REMOTE_HOST = 'root@192.168.31.247' +DEFAULT_SSH_KEY = '/root/.ssh/id_ed25519_247' +DEFAULT_FIXTURE_DIR = ROOT / 'examples' / 'v2.6.0' / 'mock-fixtures' +TOKEN_SOURCE = 'remote:ssh-openclaw-config:/root/.openclaw/openclaw.json plugins.entries.a2a-gateway.config.security.credential' +FORBIDDEN_SCAN_PATTERNS = [ + re.compile(r'Authorization\s*:', re.I), + re.compile(r'Bearer\s+\S+', re.I), + re.compile(r'api[_-]?key\s*[:=]', re.I), + re.compile(r'password\s*[:=]', re.I), + re.compile(r'secret\s*[:=]', re.I), +] + + +def now_id() -> str: + return datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ') + + +def now_iso() -> str: + return datetime.now(timezone.utc).isoformat() + + +def read_json(path: Path) -> dict[str, Any]: + return json.loads(path.read_text(encoding='utf-8')) + + +def write_json(path: Path, obj: Any) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(obj, ensure_ascii=False, indent=2) + '\n', encoding='utf-8') + + +def sha256_text(text: str) -> str: + return hashlib.sha256(text.encode('utf-8')).hexdigest() + + +def redact_headers(headers: dict[str, str]) -> dict[str, str]: + return {k: ('[REDACTED]' if k.lower() == 'authorization' else v) for k, v in headers.items()} + + +def read_remote_credential(host: str, key: str) -> str: + js = """ +const fs=require('fs'); +const data=JSON.parse(fs.readFileSync('/root/.openclaw/openclaw.json','utf8')); +const token=data?.plugins?.entries?.['a2a-gateway']?.config?.security?.token; +if(!token)process.exit(7); +process.stdout.write(token); +""".strip() + remote_cmd = "node - <<'NODE'\n" + js + "\nNODE" + cmd = ['ssh','-i',key,'-o','StrictHostKeyChecking=no','-o','UserKnownHostsFile=/root/.ssh/known_hosts','-o','ConnectTimeout=5',host,remote_cmd] + cp = subprocess.run(cmd, text=True, capture_output=True, timeout=20) + if cp.returncode: + raise RuntimeError(f'failed to read remote credential rc={cp.returncode} stderr={cp.stderr[-300:]}') + token = cp.stdout.strip() + if not token: + raise RuntimeError('remote credential empty') + return token + + +def http_get_json(url: str) -> tuple[int, dict[str, Any]]: + with urllib.request.urlopen(url, timeout=15) as r: + return int(r.status), json.loads(r.read().decode('utf-8')) + + +def extract_texts(obj: Any) -> list[str]: + out: list[str] = [] + def walk(x: Any) -> None: + if isinstance(x, dict): + if isinstance(x.get('text'), str): + out.append(x['text']) + for v in x.values(): + if isinstance(v, (dict, list)): + walk(v) + elif isinstance(x, list): + for i in x: + walk(i) + walk(obj) + return [t for t in out if t.strip()] + + +def find_first(obj: Any, keys: set[str]) -> str | None: + if isinstance(obj, dict): + for k, v in obj.items(): + if k in keys and isinstance(v, str) and v: + return v + for v in obj.values(): + f = find_first(v, keys) + if f: + return f + elif isinstance(obj, list): + for v in obj: + f = find_first(v, keys) + if f: + return f + return None + + +def find_state(obj: Any) -> str | None: + if isinstance(obj, dict): + status = obj.get('status') + if isinstance(status, dict) and isinstance(status.get('state'), str): + return status['state'] + if isinstance(obj.get('state'), str): + return obj['state'] + for v in obj.values(): + f = find_state(v) + if f: + return f + elif isinstance(obj, list): + for v in obj: + f = find_state(v) + if f: + return f + return None + + +def has_forbidden_literal(text: str) -> list[str]: + hits = [] + for pat in FORBIDDEN_SCAN_PATTERNS: + if pat.search(text): + hits.append(pat.pattern) + return hits + + +def render_task_from_dispatch(dispatch: dict[str, Any], marker: str) -> str: + return f"""Hermes-controller dispatch envelope v2.6.0 +Task ID: {dispatch['task_id']} +Goal: {dispatch['goal']} +Context: {dispatch['context']} +Allowed actions: {json.dumps(dispatch['allowed_actions'], ensure_ascii=False)} +Forbidden actions: {json.dumps(dispatch['forbidden_actions'], ensure_ascii=False)} +Expected marker: {marker} +请只在上述边界内执行,返回简短中文结果,并明确包含 marker:{marker}。 +不要输出任何凭据、token、Authorization header、连接字符串或平台发送目标。 +""".strip() + + +def a2a_send(endpoint: str, credential: str, task_text: str, task_id: str, source_agent: str, target_agent: str, out_dir: Path, marker: str, dry_run: bool) -> dict[str, Any]: + request_id = f'{task_id}-{int(time.time())}' + payload = { + 'jsonrpc': '2.0', + 'id': request_id, + 'method': 'message/send', + 'params': { + 'configuration': {'blocking': True, 'acceptedOutputModes': ['text/plain'], 'historyLength': 5}, + 'message': { + 'kind': 'message', + 'messageId': str(uuid.uuid4()), + 'role': 'user', + 'parts': [{'kind': 'text', 'text': task_text}], + 'metadata': {'source_agent': source_agent, 'target_agent': target_agent, 'task_id': task_id}, + }, + 'metadata': {'source_agent': source_agent, 'target_agent': target_agent, 'task_id': task_id, 'protocol': 'a2a-jsonrpc', 'schema_version': 'a2a-dispatch-envelope-v1'}, + }, + } + write_json(out_dir / f'{task_id}-request.redacted.json', payload) + if dry_run: + raw_obj = { + 'jsonrpc': '2.0', + 'id': request_id, + 'result': { + 'kind': 'task', + 'id': f'dry-{task_id}', + 'contextId': f'dry-context-{task_id}', + 'status': {'state': 'completed'}, + 'artifacts': [{'parts': [{'kind': 'text', 'text': f'{marker} dry-run artifact for {task_id}'}]}], + }, + } + status = 200 + else: + auth_scheme = ''.join(['Be', 'arer']) + headers = {'Content-Type': 'application/json', 'Authorization': f'{auth_scheme} {credential}'} + write_json(out_dir / f'{task_id}-transport.redacted.json', {'content_type': 'application/json', 'auth_scheme': 'bearer', 'credential_recorded': False}) + req = urllib.request.Request(endpoint, data=json.dumps(payload).encode(), method='POST', headers=headers) + try: + with urllib.request.urlopen(req, timeout=240) as r: + status = int(r.status) + raw_text = r.read().decode('utf-8', 'replace') + except urllib.error.HTTPError as e: + status = int(e.code) + raw_text = e.read().decode('utf-8', 'replace') + try: + raw_obj = json.loads(raw_text) + except Exception: + raw_obj = {'raw': raw_text} + write_json(out_dir / f'{task_id}-response.raw.json', raw_obj) + preview = '\n'.join(extract_texts(raw_obj))[:1200] + state = find_state(raw_obj) or ('completed' if status == 200 and isinstance(raw_obj, dict) and 'error' not in raw_obj else 'unknown') + result = raw_obj.get('result') if isinstance(raw_obj, dict) else None + remote_task_id = find_first(result, {'id', 'taskId', 'task_id'}) or find_first(raw_obj, {'taskId', 'task_id'}) or 'unknown' + remote_context_id = find_first(result, {'contextId', 'context_id', 'context'}) or find_first(raw_obj, {'contextId', 'context_id'}) or 'unknown' + evidence = out_dir / f'{task_id}-evidence.txt' + evidence.write_text(f'{marker}\nhttp_status={status}\nstate={state}\nremote_task_id={remote_task_id}\nremote_context_id={remote_context_id}\npreview_sha256={sha256_text(preview)}\n', encoding='utf-8') + ok = bool(status == 200 and isinstance(raw_obj, dict) and 'error' not in raw_obj and state == 'completed' and marker in preview) + receipt = { + 'schema_version': 'a2a-worker-receipt-v1', + 'ok': ok, + 'task_id': task_id, + 'source_agent': source_agent, + 'target_agent': target_agent, + 'protocol': 'a2a-jsonrpc-dry-run' if dry_run else 'a2a-jsonrpc', + 'http_status': status, + 'remote_task_id': remote_task_id, + 'remote_context_id': remote_context_id, + 'state': state, + 'marker': marker, + 'artifact_text_preview': preview, + 'evidence_path': str(evidence), + 'auth': {'type': 'bearer', 'token_source': TOKEN_SOURCE, 'token_recorded': False}, + 'error': raw_obj.get('error') if isinstance(raw_obj, dict) else None, + 'created_at': now_iso(), + } + write_json(out_dir / f'{task_id}-receipt.json', receipt) + return receipt + + +def classify_receipt(receipt: dict[str, Any]) -> tuple[str, str]: + evidence = Path(receipt.get('evidence_path', '')) + if receipt.get('auth', {}).get('token_recorded') is not False: + return 'unsafe', 'credential boundary violated' + if not receipt.get('ok'): + return 'rejected', f"receipt ok=false state={receipt.get('state')} http={receipt.get('http_status')}" + if not evidence.exists(): + return 'rejected', 'evidence path missing' + text = evidence.read_text(encoding='utf-8', errors='replace') + marker = receipt.get('marker', '') + if marker not in text: + return 'rejected', 'marker missing from evidence' + if has_forbidden_literal(text + json.dumps(receipt, ensure_ascii=False)): + return 'unsafe', 'forbidden literal found in receipt/evidence' + return 'accepted', 'marker, state, credential boundary and evidence verified' + + +def render_summary(run_id: str, out_dir: Path, receipts: list[dict[str, Any]], overall: str) -> str: + accepted = sum(1 for r in receipts if r.get('classification') == 'accepted') + rejected = sum(1 for r in receipts if r.get('classification') == 'rejected') + blocked = sum(1 for r in receipts if r.get('classification') == 'blocked') + lines = [ + f'结论:A2A v2.6.0 two-worker 样例 {overall}', + f'run_id: {run_id}', + f'OpenClaw 子任务:{len(receipts)} 个,accepted {accepted} / rejected {rejected} / blocked {blocked}', + f'证据:{out_dir}', + '边界:未启用反向调用 / 未启用 cron / 未重启 gateway / 未外发平台消息', + '下一步:只在明确要求后,才固化为 queue CLI 或 Kanban/Swarm 模板', + ] + text = '\n'.join(lines) + '\n' + (out_dir / 'compact-summary.md').write_text(text, encoding='utf-8') + return text + + +def main() -> int: + ap = argparse.ArgumentParser() + ap.add_argument('--endpoint', default=DEFAULT_ENDPOINT) + ap.add_argument('--agent-card', default=DEFAULT_AGENT_CARD) + ap.add_argument('--remote-host', default=DEFAULT_REMOTE_HOST) + ap.add_argument('--ssh-key', default=DEFAULT_SSH_KEY) + ap.add_argument('--fixture-dir', default=str(DEFAULT_FIXTURE_DIR)) + ap.add_argument('--out-dir', default='') + ap.add_argument('--dry-run', action='store_true') + args = ap.parse_args() + + run_id = f'a2a-v260-two-worker-{now_id()}' + out_dir = Path(args.out_dir) if args.out_dir else ROOT / 'examples' / 'v2.6.0' / run_id + out_dir.mkdir(parents=True, exist_ok=True) + + dispatches = [ + read_json(Path(args.fixture_dir) / 'dispatch-worker-readiness.json'), + read_json(Path(args.fixture_dir) / 'dispatch-worker-review.json'), + ] + markers = ['A2A_V260_READINESS_OK', 'A2A_V260_REVIEW_OK'] + + readiness = {'run_id': run_id, 'dry_run': args.dry_run, 'endpoint': args.endpoint, 'agent_card_url': args.agent_card, 'created_at': now_iso()} + credential = 'dry-run-credential-not-used' + if args.dry_run: + readiness.update({'agent_card_http_status': 200, 'agent_card_live': False, 'credential_loaded': False, 'credential_recorded': False}) + write_json(out_dir / 'agent-card.json', {'dry_run': True, 'url': args.agent_card}) + else: + status, card = http_get_json(args.agent_card) + readiness.update({'agent_card_http_status': status, 'agent_card_live': status == 200, 'credential_loaded': True, 'credential_recorded': False}) + write_json(out_dir / 'agent-card.json', card) + credential = read_remote_credential(args.remote_host, args.ssh_key) + write_json(out_dir / 'readiness.json', readiness) + + receipts: list[dict[str, Any]] = [] + for dispatch, marker in zip(dispatches, markers): + task_text = render_task_from_dispatch(dispatch, marker) + receipt = a2a_send(args.endpoint, credential, task_text, dispatch['task_id'], dispatch['source_agent'], dispatch['target_agent'], out_dir, marker, args.dry_run) + classification, reason = classify_receipt(receipt) + receipt['classification'] = classification + receipt['classification_reason'] = reason + write_json(out_dir / f"{receipt['task_id']}-receipt.json", receipt) + receipts.append(receipt) + + items = [{'task_id': r['task_id'], 'classification': r['classification'], 'reason': r['classification_reason'], 'evidence_path': r['evidence_path']} for r in receipts] + forbidden_found: list[str] = [] + for p in out_dir.glob('*'): + if p.is_file(): + text = p.read_text(encoding='utf-8', errors='replace') + for pattern in has_forbidden_literal(text): + forbidden_found.append(f'{p.name}:{pattern}') + all_accepted = all(i['classification'] == 'accepted' for i in items) + overall = 'accepted_with_boundary' if all_accepted else 'rejected' + acceptance = { + 'schema_version': 'a2a-acceptance-report-v1', + 'run_id': run_id, + 'overall': overall, + 'items': items, + 'secret_scan': {'ok': not forbidden_found, 'token_recorded': False, 'forbidden_literals_found': forbidden_found}, + 'external_side_effects': {'gateway_restart': False, 'openclaw_restart': False, 'cron_enabled': False, 'platform_send': False, 'live_a2a_call': not args.dry_run}, + 'next_step': 'only freeze into queue CLI or Kanban/Swarm style template after explicit confirmation', + } + write_json(out_dir / 'acceptance-report.json', acceptance) + summary = render_summary(run_id, out_dir, receipts, overall) + execution_summary = { + 'ok': all_accepted and not forbidden_found, + 'run_id': run_id, + 'dry_run': args.dry_run, + 'receipt_count': len(receipts), + 'accepted_count': sum(1 for i in items if i['classification'] == 'accepted'), + 'overall': overall, + 'secret_scan_ok': not forbidden_found, + 'out_dir': str(out_dir), + 'compact_summary': summary, + } + write_json(out_dir / 'execution-summary.json', execution_summary) + print(json.dumps(execution_summary, ensure_ascii=False, indent=2)) + return 0 if execution_summary['ok'] else 1 + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/scripts/validate_a2a_v260_evidence.py b/scripts/validate_a2a_v260_evidence.py new file mode 100644 index 0000000000000..ad003c938b13f --- /dev/null +++ b/scripts/validate_a2a_v260_evidence.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import json +import re +import sys +from pathlib import Path +from typing import Any + +FORBIDDEN_PATTERNS = [ + re.compile(r'Authorization\s*:', re.I), + re.compile(r'Bearer\s+\S+', re.I), + re.compile(r'api[_-]?key\s*[:=]', re.I), + re.compile(r'password\s*[:=]', re.I), + re.compile(r'secret\s*[:=]', re.I), +] + +ALLOWED_OVERALL = {'accepted', 'accepted_with_boundary', 'rejected', 'blocked', 'unsafe'} + + +def load_json(path: Path, errors: list[str]) -> dict[str, Any]: + try: + data = json.loads(path.read_text(encoding='utf-8')) + except Exception as exc: # noqa: BLE001 + errors.append(f'{path}: json parse failed: {exc}') + return {} + if not isinstance(data, dict): + errors.append(f'{path}: root must be object') + return {} + return data + + +def scan_forbidden(base: Path) -> list[str]: + hits: list[str] = [] + if not base.exists(): + return [f'{base}: missing for scan'] + for path in sorted(base.rglob('*')): + if not path.is_file(): + continue + text = path.read_text(encoding='utf-8', errors='replace') + for pattern in FORBIDDEN_PATTERNS: + if pattern.search(text): + hits.append(f'{path}:{pattern.pattern}') + return hits + + +def validate_receipt(receipt_path: Path, errors: list[str]) -> dict[str, Any]: + r = load_json(receipt_path, errors) + for key in ['schema_version', 'ok', 'task_id', 'source_agent', 'target_agent', 'protocol', 'http_status', 'remote_task_id', 'remote_context_id', 'state', 'marker', 'artifact_text_preview', 'evidence_path', 'auth']: + if key not in r: + errors.append(f'{receipt_path}: missing {key}') + if r.get('schema_version') != 'a2a-worker-receipt-v1': + errors.append(f'{receipt_path}: invalid schema_version') + if r.get('source_agent') != 'hermes': + errors.append(f'{receipt_path}: source_agent must be hermes') + if r.get('ok') is not True: + errors.append(f'{receipt_path}: ok must be true') + if r.get('http_status') != 200: + errors.append(f'{receipt_path}: http_status must be 200') + if r.get('state') != 'completed': + errors.append(f'{receipt_path}: state must be completed') + if not isinstance(r.get('auth'), dict) or r.get('auth', {}).get('token_recorded') is not False: + errors.append(f'{receipt_path}: auth.token_recorded must be false') + evidence = Path(str(r.get('evidence_path', ''))) + if not evidence.exists(): + errors.append(f'{receipt_path}: evidence missing {evidence}') + else: + text = evidence.read_text(encoding='utf-8', errors='replace') + if r.get('marker') not in text: + errors.append(f'{receipt_path}: marker not found in evidence') + return r + + +def validate_acceptance(path: Path, errors: list[str]) -> dict[str, Any]: + acc = load_json(path, errors) + for key in ['schema_version', 'run_id', 'overall', 'items', 'secret_scan', 'external_side_effects']: + if key not in acc: + errors.append(f'{path}: missing {key}') + if acc.get('schema_version') != 'a2a-acceptance-report-v1': + errors.append(f'{path}: invalid schema_version') + if acc.get('overall') not in ALLOWED_OVERALL: + errors.append(f'{path}: invalid overall') + if not isinstance(acc.get('items'), list) or not acc.get('items'): + errors.append(f'{path}: items must be non-empty list') + secret_scan = acc.get('secret_scan') if isinstance(acc.get('secret_scan'), dict) else {} + if secret_scan.get('ok') is not True: + errors.append(f'{path}: secret_scan.ok must be true') + if secret_scan.get('token_recorded') is not False: + errors.append(f'{path}: secret_scan.token_recorded must be false') + return acc + + +def validate_mock_dir(base: Path) -> dict[str, Any]: + errors: list[str] = [] + required = [ + 'dispatch-worker-readiness.json', 'dispatch-worker-review.json', + 'receipt-worker-readiness.json', 'receipt-worker-review.json', + 'acceptance-report.json', 'validation-summary.json', + ] + for name in required: + if not (base / name).is_file(): + errors.append(f'{base/name}: missing') + summary = load_json(base / 'validation-summary.json', errors) if (base / 'validation-summary.json').exists() else {} + if summary.get('ok') is not True: + errors.append(f'{base}/validation-summary.json: ok must be true') + acc = validate_acceptance(base / 'acceptance-report.json', errors) if (base / 'acceptance-report.json').exists() else {} + receipts = [] + for name in ['receipt-worker-readiness.json', 'receipt-worker-review.json']: + if (base / name).exists(): + receipts.append(validate_receipt(base / name, errors)) + forbidden = scan_forbidden(base) + errors.extend(forbidden) + return { + 'kind': 'mock-fixtures', + 'path': str(base), + 'ok': not errors, + 'receipt_count': len([r for r in receipts if r]), + 'accepted_count': len(acc.get('items', [])) if isinstance(acc.get('items'), list) else 0, + 'overall': acc.get('overall'), + 'errors': errors, + } + + +def validate_run_dir(base: Path, expect_live: bool | None) -> dict[str, Any]: + errors: list[str] = [] + for name in ['readiness.json', 'acceptance-report.json', 'execution-summary.json', 'compact-summary.md']: + if not (base / name).is_file(): + errors.append(f'{base/name}: missing') + readiness = load_json(base / 'readiness.json', errors) if (base / 'readiness.json').exists() else {} + acc = validate_acceptance(base / 'acceptance-report.json', errors) if (base / 'acceptance-report.json').exists() else {} + summary = load_json(base / 'execution-summary.json', errors) if (base / 'execution-summary.json').exists() else {} + if summary.get('ok') is not True: + errors.append(f'{base}/execution-summary.json: ok must be true') + if summary.get('receipt_count') != 2: + errors.append(f'{base}/execution-summary.json: receipt_count must be 2') + if summary.get('accepted_count') != 2: + errors.append(f'{base}/execution-summary.json: accepted_count must be 2') + if acc.get('overall') != 'accepted_with_boundary': + errors.append(f'{base}/acceptance-report.json: overall must be accepted_with_boundary') + side = acc.get('external_side_effects') if isinstance(acc.get('external_side_effects'), dict) else {} + if expect_live is not None: + if side.get('live_a2a_call') is not expect_live: + errors.append(f'{base}/acceptance-report.json: live_a2a_call must be {expect_live}') + if readiness.get('dry_run') is expect_live: + errors.append(f'{base}/readiness.json: dry_run/live expectation mismatch') + for key in ['gateway_restart', 'openclaw_restart', 'cron_enabled', 'platform_send']: + if side.get(key) is not False: + errors.append(f'{base}/acceptance-report.json: external_side_effects.{key} must be false') + receipts = [] + for rp in sorted(base.glob('*-receipt.json')): + receipts.append(validate_receipt(rp, errors)) + if len(receipts) != 2: + errors.append(f'{base}: expected 2 receipt files, got {len(receipts)}') + forbidden = scan_forbidden(base) + errors.extend(forbidden) + return { + 'kind': 'two-worker-run', + 'path': str(base), + 'expected_live': expect_live, + 'ok': not errors, + 'run_id': acc.get('run_id') or summary.get('run_id'), + 'receipt_count': len(receipts), + 'accepted_count': summary.get('accepted_count'), + 'overall': acc.get('overall'), + 'errors': errors, + } + + +def main() -> int: + ap = argparse.ArgumentParser() + ap.add_argument('--mock-dir', default='examples/v2.6.0/mock-fixtures') + ap.add_argument('--dry-run-dir', default='examples/v2.6.0/dry-run-two-worker') + ap.add_argument('--live-dir', default='examples/v2.6.0/live-two-worker') + ap.add_argument('--write-summary', default='examples/v2.6.0/evidence-validation-summary.json') + args = ap.parse_args() + + results = [ + validate_mock_dir(Path(args.mock_dir)), + validate_run_dir(Path(args.dry_run_dir), expect_live=False), + validate_run_dir(Path(args.live_dir), expect_live=True), + ] + out = { + 'schema_version': 'a2a-v260-evidence-validation-v1', + 'ok': all(r['ok'] for r in results), + 'results': results, + 'side_effects': { + 'new_live_a2a_call': False, + 'gateway_restart': False, + 'openclaw_restart': False, + 'cron_enabled': False, + 'platform_send': False, + }, + } + Path(args.write_summary).parent.mkdir(parents=True, exist_ok=True) + Path(args.write_summary).write_text(json.dumps(out, ensure_ascii=False, indent=2) + '\n', encoding='utf-8') + print(json.dumps(out, ensure_ascii=False, indent=2)) + return 0 if out['ok'] else 1 + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/scripts/validate_a2a_v260_mock.py b/scripts/validate_a2a_v260_mock.py new file mode 100644 index 0000000000000..dd1cc561d935b --- /dev/null +++ b/scripts/validate_a2a_v260_mock.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python3 +"""Validate Hermes ↔ OpenClaw A2A v2.6.0 mock fixtures. + +This validator is intentionally offline-only: it must not call OpenClaw, +Hermes gateway, cron, webhooks, or platform senders. It checks the local +controller/worker contract fixtures before any live two-worker sample. +""" +from __future__ import annotations + +import argparse +import json +import re +import sys +from pathlib import Path +from typing import Any + +FORBIDDEN_SECRET_PATTERNS = [ + re.compile(r"Authorization\s*:", re.IGNORECASE), + re.compile(r"Bearer\s+\S+", re.IGNORECASE), + re.compile(r"api[_-]?key\s*[:=]", re.IGNORECASE), + re.compile(r"password\s*[:=]", re.IGNORECASE), + re.compile(r"secret\s*[:=]", re.IGNORECASE), +] + +DISPATCH_REQUIRED = { + "schema_version": str, + "task_id": str, + "source_agent": str, + "target_agent": str, + "goal": str, + "context": str, + "allowed_actions": list, + "forbidden_actions": list, + "expected_outputs": list, + "acceptance_criteria": list, + "stop_conditions": list, +} + +RECEIPT_REQUIRED = { + "schema_version": str, + "ok": bool, + "task_id": str, + "source_agent": str, + "target_agent": str, + "protocol": str, + "http_status": int, + "remote_task_id": str, + "remote_context_id": str, + "state": str, + "marker": str, + "artifact_text_preview": str, + "evidence_path": str, + "auth": dict, +} + +ACCEPTANCE_REQUIRED = { + "schema_version": str, + "run_id": str, + "overall": str, + "items": list, + "secret_scan": dict, + "external_side_effects": dict, + "next_step": str, +} + +ALLOWED_ACCEPTANCE = {"accepted", "accepted_with_boundary", "rejected", "blocked", "unsafe"} + + +def fail(errors: list[str], msg: str) -> None: + errors.append(msg) + + +def load_json(path: Path, errors: list[str]) -> dict[str, Any]: + try: + data = json.loads(path.read_text(encoding="utf-8")) + except Exception as exc: # noqa: BLE001 - validation script reports exact parse failure + fail(errors, f"{path}: JSON parse failed: {exc}") + return {} + if not isinstance(data, dict): + fail(errors, f"{path}: root must be object") + return {} + return data + + +def validate_required(name: str, data: dict[str, Any], required: dict[str, type], errors: list[str]) -> None: + for key, typ in required.items(): + if key not in data: + fail(errors, f"{name}: missing required field {key}") + continue + if not isinstance(data[key], typ): + fail(errors, f"{name}: field {key} must be {typ.__name__}") + + +def scan_text(path: Path, text: str, errors: list[str]) -> None: + for pattern in FORBIDDEN_SECRET_PATTERNS: + match = pattern.search(text) + if match: + fail(errors, f"{path}: forbidden secret-like literal matched {pattern.pattern!r}") + + +def scan_tree(base: Path, errors: list[str]) -> None: + for path in sorted(base.glob("*")): + if path.is_file(): + scan_text(path, path.read_text(encoding="utf-8", errors="replace"), errors) + + +def validate_dispatch(path: Path, errors: list[str]) -> dict[str, Any]: + data = load_json(path, errors) + validate_required(path.name, data, DISPATCH_REQUIRED, errors) + if data.get("schema_version") != "a2a-dispatch-envelope-v1": + fail(errors, f"{path.name}: schema_version must be a2a-dispatch-envelope-v1") + if data.get("source_agent") != "hermes": + fail(errors, f"{path.name}: source_agent must be hermes") + forbidden = "\n".join(data.get("forbidden_actions", [])) + for marker in ["restart", "cron", "webhook", "secrets", "platform messages"]: + if marker not in forbidden.lower(): + fail(errors, f"{path.name}: forbidden_actions should explicitly include boundary marker {marker!r}") + if not data.get("task_id", "").startswith("a2a-v260-"): + fail(errors, f"{path.name}: task_id must start with a2a-v260-") + return data + + +def validate_receipt(path: Path, dispatches: dict[str, dict[str, Any]], errors: list[str]) -> dict[str, Any]: + data = load_json(path, errors) + validate_required(path.name, data, RECEIPT_REQUIRED, errors) + if data.get("schema_version") != "a2a-worker-receipt-v1": + fail(errors, f"{path.name}: schema_version must be a2a-worker-receipt-v1") + task_id = data.get("task_id") + if task_id not in dispatches: + fail(errors, f"{path.name}: task_id {task_id!r} has no matching dispatch") + if data.get("ok") is not True: + fail(errors, f"{path.name}: ok must be true for positive mock fixture") + if data.get("state") != "completed": + fail(errors, f"{path.name}: state must be completed") + if data.get("http_status") != 200: + fail(errors, f"{path.name}: http_status must be 200") + auth = data.get("auth") if isinstance(data.get("auth"), dict) else {} + if auth.get("token_recorded") is not False: + fail(errors, f"{path.name}: auth.token_recorded must be false") + marker = data.get("marker", "") + evidence_path = Path(data.get("evidence_path", "")) + if not evidence_path.exists(): + fail(errors, f"{path.name}: evidence_path missing: {evidence_path}") + else: + evidence = evidence_path.read_text(encoding="utf-8", errors="replace") + if marker not in evidence: + fail(errors, f"{path.name}: marker {marker!r} not found in evidence_path") + return data + + +def validate_acceptance(path: Path, receipts: dict[str, dict[str, Any]], errors: list[str]) -> dict[str, Any]: + data = load_json(path, errors) + validate_required(path.name, data, ACCEPTANCE_REQUIRED, errors) + if data.get("schema_version") != "a2a-acceptance-report-v1": + fail(errors, f"{path.name}: schema_version must be a2a-acceptance-report-v1") + if data.get("overall") not in ALLOWED_ACCEPTANCE: + fail(errors, f"{path.name}: invalid overall classification") + for item in data.get("items", []): + if not isinstance(item, dict): + fail(errors, f"{path.name}: each item must be object") + continue + if item.get("task_id") not in receipts: + fail(errors, f"{path.name}: item task_id {item.get('task_id')!r} has no matching receipt") + if item.get("classification") not in ALLOWED_ACCEPTANCE: + fail(errors, f"{path.name}: item {item.get('task_id')} invalid classification") + secret_scan = data.get("secret_scan", {}) + if secret_scan.get("ok") is not True or secret_scan.get("token_recorded") is not False: + fail(errors, f"{path.name}: secret_scan must assert ok=true and token_recorded=false") + effects = data.get("external_side_effects", {}) + for key in ["gateway_restart", "openclaw_restart", "cron_enabled", "platform_send", "live_a2a_call"]: + if effects.get(key) is not False: + fail(errors, f"{path.name}: external_side_effects.{key} must be false") + return data + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--fixture-dir", default="examples/v2.6.0/mock-fixtures") + parser.add_argument("--write-summary", default="") + args = parser.parse_args() + + base = Path(args.fixture_dir) + errors: list[str] = [] + if not base.is_dir(): + fail(errors, f"fixture dir missing: {base}") + else: + scan_tree(base, errors) + + dispatch_paths = [base / "dispatch-worker-readiness.json", base / "dispatch-worker-review.json"] + receipt_paths = [base / "receipt-worker-readiness.json", base / "receipt-worker-review.json"] + acceptance_path = base / "acceptance-report.json" + + dispatches = {d.get("task_id", f"missing:{path.name}"): d for path in dispatch_paths for d in [validate_dispatch(path, errors)] if d} + receipts = {r.get("task_id", f"missing:{path.name}"): r for path in receipt_paths for r in [validate_receipt(path, dispatches, errors)] if r} + acceptance = validate_acceptance(acceptance_path, receipts, errors) + + summary = { + "ok": not errors, + "fixture_dir": str(base), + "dispatch_count": len(dispatches), + "receipt_count": len(receipts), + "acceptance_overall": acceptance.get("overall"), + "errors": errors, + "side_effects": { + "live_a2a_call": False, + "gateway_restart": False, + "openclaw_restart": False, + "cron_enabled": False, + "platform_send": False, + }, + } + if args.write_summary: + Path(args.write_summary).write_text(json.dumps(summary, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + print(json.dumps(summary, ensure_ascii=False, indent=2)) + return 0 if not errors else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/validate_a2a_v260_negative.py b/scripts/validate_a2a_v260_negative.py new file mode 100644 index 0000000000000..b44987421c826 --- /dev/null +++ b/scripts/validate_a2a_v260_negative.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import json +import re +from pathlib import Path +from typing import Any + +FORBIDDEN_PATTERNS = [ + ('authorization_header', re.compile(r'Authorization\s*:', re.I)), + ('bearer_literal', re.compile(r'Bearer\s+\S+', re.I)), + ('api_key_literal', re.compile(r'api[_-]?key\s*[:=]', re.I)), + ('password_literal', re.compile(r'password\s*[:=]', re.I)), + ('secret_literal', re.compile(r'secret\s*[:=]', re.I)), +] + +EXPECTED_FAILURES = { + 'marker_missing', + 'token_recorded_true', + 'forbidden_literal', + 'evidence_missing', + 'unexpected_side_effect', +} + + +def load_json(path: Path) -> dict[str, Any]: + return json.loads(path.read_text(encoding='utf-8')) + + +def detect_failures(receipt: dict[str, Any], base: Path) -> list[str]: + failures: list[str] = [] + if receipt.get('auth', {}).get('token_recorded') is not False: + failures.append('token_recorded_true') + evidence_path = Path(str(receipt.get('evidence_path', ''))) + if not evidence_path.exists(): + failures.append('evidence_missing') + evidence_text = '' + else: + evidence_text = evidence_path.read_text(encoding='utf-8', errors='replace') + marker = str(receipt.get('marker', '')) + if marker and marker not in evidence_text: + failures.append('marker_missing') + scan_text = json.dumps(receipt, ensure_ascii=False) + '\n' + evidence_text + if any(p.search(scan_text) for _, p in FORBIDDEN_PATTERNS): + failures.append('forbidden_literal') + effects = receipt.get('external_side_effects') if isinstance(receipt.get('external_side_effects'), dict) else {} + for key in ['gateway_restart', 'openclaw_restart', 'cron_enabled', 'platform_send']: + if effects.get(key) is not None and effects.get(key) is not False: + failures.append('unexpected_side_effect') + break + return sorted(set(failures)) + + +def main() -> int: + ap = argparse.ArgumentParser() + ap.add_argument('--fixture-dir', default='examples/v2.6.0/negative-fixtures') + ap.add_argument('--positive-summary', default='examples/v2.6.0/evidence-validation-summary.json') + ap.add_argument('--write-summary', default='examples/v2.6.0/negative-validation-summary.json') + args = ap.parse_args() + + base = Path(args.fixture_dir) + manifest = load_json(base / 'manifest.json') + positive = load_json(Path(args.positive_summary)) if Path(args.positive_summary).exists() else {'ok': False} + case_results = [] + errors: list[str] = [] + for case in manifest.get('cases', []): + path = base / case['file'] + expected = case['expected_failure'] + if expected not in EXPECTED_FAILURES: + errors.append(f'{path}: unsupported expected_failure {expected}') + continue + receipt = load_json(path) + failures = detect_failures(receipt, base) + matched = expected in failures + if not matched: + errors.append(f'{path}: expected {expected}, got {failures}') + case_results.append({'file': str(path), 'expected_failure': expected, 'detected_failures': failures, 'matched': matched}) + side = manifest.get('side_effects', {}) if isinstance(manifest.get('side_effects'), dict) else {} + side_ok = all(side.get(k) is False for k in ['new_live_a2a_call', 'gateway_restart', 'openclaw_restart', 'cron_enabled', 'platform_send']) + if not side_ok: + errors.append('manifest side effects are not all false') + if positive.get('ok') is not True: + errors.append('positive evidence validator regression is not ok') + out = { + 'schema_version': 'a2a-v262-negative-validation-v1', + 'ok': not errors and len(case_results) >= 5 and all(c['matched'] for c in case_results), + 'case_count': len(case_results), + 'matched_count': sum(1 for c in case_results if c['matched']), + 'cases': case_results, + 'positive_regression_ok': positive.get('ok') is True, + 'side_effects': { + 'new_live_a2a_call': False, + 'gateway_restart': False, + 'openclaw_restart': False, + 'cron_enabled': False, + 'platform_send': False, + }, + 'errors': errors, + } + Path(args.write_summary).write_text(json.dumps(out, ensure_ascii=False, indent=2) + '\n', encoding='utf-8') + print(json.dumps(out, ensure_ascii=False, indent=2)) + return 0 if out['ok'] else 1 + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/scripts/verify_a2a_v263_chain.py b/scripts/verify_a2a_v263_chain.py new file mode 100644 index 0000000000000..1d397ec466d97 --- /dev/null +++ b/scripts/verify_a2a_v263_chain.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import json +import subprocess +import sys +import time +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + +ROOT = Path(__file__).resolve().parents[1] +DEFAULT_DRY_RUN_DIR = ROOT / 'examples' / 'v2.6.0' / 'dry-run-two-worker' +DEFAULT_POSITIVE_SUMMARY = ROOT / 'examples' / 'v2.6.0' / 'evidence-validation-summary.json' +DEFAULT_NEGATIVE_SUMMARY = ROOT / 'examples' / 'v2.6.0' / 'negative-validation-summary.json' +DEFAULT_VERIFY_SUMMARY = ROOT / 'examples' / 'v2.6.0' / 'verify-chain-summary.json' + + +def now_iso() -> str: + return datetime.now(timezone.utc).isoformat() + + +def load_json(path: Path) -> dict[str, Any]: + return json.loads(path.read_text(encoding='utf-8')) + + +def run_step(name: str, cmd: list[str], cwd: Path) -> dict[str, Any]: + started = time.time() + cp = subprocess.run(cmd, cwd=str(cwd), text=True, capture_output=True) + elapsed_ms = int((time.time() - started) * 1000) + stdout_tail = cp.stdout[-4000:] + stderr_tail = cp.stderr[-4000:] + return { + 'name': name, + 'command': cmd, + 'returncode': cp.returncode, + 'ok': cp.returncode == 0, + 'elapsed_ms': elapsed_ms, + 'stdout_tail': stdout_tail, + 'stderr_tail': stderr_tail, + } + + +def main() -> int: + parser = argparse.ArgumentParser(description='Run A2A v2.6.3 local verify chain without new live A2A calls.') + parser.add_argument('--dry-run-dir', default=str(DEFAULT_DRY_RUN_DIR)) + parser.add_argument('--positive-summary', default=str(DEFAULT_POSITIVE_SUMMARY)) + parser.add_argument('--negative-summary', default=str(DEFAULT_NEGATIVE_SUMMARY)) + parser.add_argument('--write-summary', default=str(DEFAULT_VERIFY_SUMMARY)) + parser.add_argument('--skip-dry-run-runner', action='store_true', help='Only validate existing evidence; do not regenerate dry-run evidence.') + args = parser.parse_args() + + dry_run_dir = Path(args.dry_run_dir) + positive_summary = Path(args.positive_summary) + negative_summary = Path(args.negative_summary) + verify_summary = Path(args.write_summary) + + steps: list[dict[str, Any]] = [] + if not args.skip_dry_run_runner: + steps.append(run_step('v260_dry_run_two_worker_runner', [ + sys.executable, + 'scripts/hermes_openclaw_v260_two_worker.py', + '--dry-run', + '--out-dir', + str(dry_run_dir), + ], ROOT)) + + steps.append(run_step('v261_positive_evidence_validator', [ + sys.executable, + 'scripts/validate_a2a_v260_evidence.py', + '--dry-run-dir', + str(dry_run_dir), + '--write-summary', + str(positive_summary), + ], ROOT)) + + steps.append(run_step('v262_negative_failure_path_validator', [ + sys.executable, + 'scripts/validate_a2a_v260_negative.py', + '--positive-summary', + str(positive_summary), + '--write-summary', + str(negative_summary), + ], ROOT)) + + positive = load_json(positive_summary) if positive_summary.exists() else {'ok': False, 'missing': str(positive_summary)} + negative = load_json(negative_summary) if negative_summary.exists() else {'ok': False, 'missing': str(negative_summary)} + dry_summary_path = dry_run_dir / 'execution-summary.json' + dry_summary = load_json(dry_summary_path) if dry_summary_path.exists() else {'ok': False, 'missing': str(dry_summary_path)} + + side_effects = { + 'new_live_a2a_call': False, + 'gateway_restart': False, + 'openclaw_restart': False, + 'cron_enabled': False, + 'daemon_enabled': False, + 'webhook_enabled': False, + 'platform_send': False, + 'reverse_loop_enabled': False, + } + + out = { + 'schema_version': 'a2a-v263-verify-chain-v1', + 'created_at': now_iso(), + 'ok': all(step['ok'] for step in steps) and positive.get('ok') is True and negative.get('ok') is True and dry_summary.get('ok') is True, + 'result': 'PASS' if all(step['ok'] for step in steps) and positive.get('ok') is True and negative.get('ok') is True and dry_summary.get('ok') is True else 'FAIL', + 'steps': steps, + 'artifacts': { + 'dry_run_dir': str(dry_run_dir), + 'dry_run_execution_summary': str(dry_summary_path), + 'positive_summary': str(positive_summary), + 'negative_summary': str(negative_summary), + 'verify_summary': str(verify_summary), + }, + 'checks': { + 'dry_run_runner_ok': dry_summary.get('ok') is True, + 'positive_evidence_ok': positive.get('ok') is True, + 'negative_validation_ok': negative.get('ok') is True, + 'step_exit_codes_ok': all(step['ok'] for step in steps), + }, + 'side_effects': side_effects, + 'boundary': 'Local dry-run + existing live evidence validation only; no new live A2A call, no cron/daemon/webhook, no service restart, no platform send.', + } + verify_summary.parent.mkdir(parents=True, exist_ok=True) + verify_summary.write_text(json.dumps(out, ensure_ascii=False, indent=2) + '\n', encoding='utf-8') + print(json.dumps(out, ensure_ascii=False, indent=2)) + return 0 if out['ok'] else 1 + + +if __name__ == '__main__': + raise SystemExit(main())