Skip to content

feat(agent-runtime): dispatch frozen configuration and verify Node adapters - #106

Merged
EricWvi merged 8 commits into
ora-space:mainfrom
wanglongan587:feat/336-agent-model-runtime
Oct 21, 2026
Merged

EricWvi merged 8 commits into
ora-space:mainfrom
wanglongan587:feat/336-agent-model-runtime

Conversation

@wanglongan587

@wanglongan587 wanglongan587 commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

run admission -> immutable private Agent/model snapshot
  Controller contract: package / declaration / revision / model / exact Settings JSON
  current Node handshake: target / adapter version / protocols
  dispatch: exact capability match or bounded refusal before the first prompt

Adds Node capability persistence and an explicit deployment target gate (plugins.personal_agent_target, empty by default). A stale or disconnected Node is not definitive refusal evidence. Runtime tokens and upstream keys never enter this control contract.

Base: #101, including its exact-number replay fix, is merged. Rebased onto main 308057c; the diff contains only this Cloud control/capability delivery. On the rebase, AgentSessionSpec.agent_configuration moved to field 9 because main now uses 8 for workspace_directory, and the migration moved to 0060_node_model_adapter.sql after main's 0058/0059. Controller/Node and plugin adapter changes are separate coordinated deliveries; keep the deployment gate empty until they are deployed.

ADR: plugin-owned-model-adapters and personal-agent-configurations remain approved. This PR implements the Cloud control/capability portion, not the final cross-host rollout or publication acceptance.

Started under the run ora-space/circus#336 and handed over to the run referenced below.

Public failure codes: publicSessionFailureCode now passes the adapter path's bounded codes through instead of collapsing them into agent_failed (ADR D2 keeps an incomplete configuration, an incompatible one, a refused grant and a failed preparation apart): the startup scan's agent_adapter_unavailable and the Node's agent_model_configuration_required, agent_model_configuration_invalid, agent_model_protocol_mismatch, model_grant_mismatch, agent_model_adapter_failed and agent_model_adapter_not_applied. The Thread has a message for each of them; any other text still becomes agent_failed. Today the Node reports agent_model_adapter_failed and agent_model_adapter_not_applied only as internal detail (a configured start that fails ends as agent_start_failed or agent_unavailable); they are allowlisted so a Node that reports them as the session detail is shown precisely, not as agent_failed. docs/agent-runs/startup-settlement lists the agent_adapter_unavailable settlement.

Evidence

  • Before: frozen private configuration stopped at Cloud and only a model-proxy boolean reached dispatch.
    After: generated protobuf roundtrip preserves exact numeric text, legacy inputs stay absent, and current incarnation capabilities fence dispatch.
  • Linux PostgreSQL race tests passed for immutable/private startup snapshots, capability refusal before prompts, existing model-proxy/stale-Node cases, and fresh/upgrade migration (now 0060, TestMigration0060ModelAdapterFreshAndUpgrade).
  • Configuration and control codec tests passed. Local Windows devsetup tests encounter the existing Unix key-mode assertion; Linux/CI verification remains required before readiness.

Merge Danger

Door: one-way

Migration 0060 adds empty capability evidence to existing incarnations. Apply through cloudctl before starting this server. No existing Node is certified automatically.

Blast Radius: agent-admission

Empty target retains disabled admission. Do not enable the gate or publish configuration declarations before compatible hosts and plugins are deployed and verified.

Rebase verification (head 132cc6a, onto main 308057c): the five patches keep their content apart from the field and migration renumbering above, the regenerated internal/controlpb matches buf generate, and the migration READMEs (zh/en) gained the 0060 table row and entry. Locally: checkformat, buf lint, golangci-lint (0 issues), go vet ./integration and the unit tests of the touched packages pass; the PostgreSQL/race/integration gates are left to CI.

Merge order: this PR → ora-space/desktop#708 (re-pins third_party/cloud to the main commit containing it) → ora-space/claude-code-agent#4 and ora-space/opencode-agent#10. Re-check the migration number against main right before merging: the open #111 also adds a 0060_*.sql; whichever merges second moves to 0061.

Refs ora-space/circus#354

@ObsisMc

ObsisMc commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator

Migration number collision with main: this PR (head b5ef31a) adds internal/core/migrations/0051_node_model_adapter.sql, and main (ff72afa) already has 0051_workflow_run_cancel.sql. The runner applies files by name, so a fresh database and an upgraded one would run these two in different orders. Please renumber to 0052_node_model_adapter.sql (and integration/node_model_adapter_migration_test.go, which refers to the file by name) when rebasing.

Found while merging this PR with main and #103 for a pre-merge integration rehearsal; the two merge cleanly otherwise.

@wanglongan587

Copy link
Copy Markdown
Collaborator Author

说明:本 PR 的作者仍是 wanglongan587(分支来自 #336 的交接),GitHub 不允许作者给自己的 PR 提交 Request changes,所以这份审查以普通评论提交;请按 review 意见处理。若需要原生 review 作为合并条件,请 owner 另指派 reviewer 或直接采信本条。

审查版本:1be061007486a412893bf19b6ee7e4530fdf40f5(base 35b536f),对照当前 upstream/main eb5ae5b1d9a7ad97170e7446ead86f9c47855096。结论:Request changes(以评论形式)。

阻塞项是迁移编号、rebase 和 CI 状态;代码本身没有发现阻塞缺陷。

阻塞

  1. 迁移号与 README:main 现在已有 0058_agent_run_controlled_reclamation.sql、0059_agent_restore_stop_evidence.sql,本 PR 的 0058_node_model_adapter.sql 与 main 同号。按迁移规则必须顺延到 0060,同时改 integration/node_model_adapter_migration_test.go 的测试名和 migrationsBefore(...) 参数、中英 internal/core/migrations/README*.md 的条目,以及 README 里 “There are currently ... SQL files” 的计数(main 当前 61,加本迁移应为 62)。迁移内容本身(jsonb 对象、默认 {}、不回填、既有实例按空能力处理)没问题。

  2. 需要 rebase:PR 当前 CONFLICTING/DIRTY(base 35b536f 已落后 main eb5ae5b)。rebase 后请确认 AgentSessionSpec.agent_configuration 仍用字段 9(main 目前只到 8,字段 9 空闲)、internal/controlpb 重新生成且与 buf generate 一致,以及 proto/README 没有别的漂移。

  3. CI:check 失败在 integration 包 panic: test timed out after 10m0s。这是 main 35b536f 上同样存在的问题(circus#414 追踪,修复在 #431/#432),不是本 PR 引入;但合并前需要 CI 绿,或在确认 main 修复后重跑。

非阻塞

  • target 集合与 desktop#708 不一致:Cloud 这里只接受 x86_64-unknown-linux-gnu / aarch64-unknown-linux-gnu,desktop Node 却对任何 *-unknown-linux-gnu 宣告 adapter 能力。其它 GNU target 的 Node 会在登记时整条被 invalid_node_report 拒绝,而不是仅不宣告能力。建议两边取齐。
  • Settings 上限:agentConfigurationObject 接受 256 KiB settings,desktop Node 的 MAX_AGENT_SETTINGS_BYTES 是 64 KiB;请确认声明编译器的上限,避免同一份冻结配置 Cloud 接受、Node 拒绝。
  • 能力重注册比较:registerReportedNode 用 jsonText(modelAdapter) 做相等比较,Controller 重连时若 protocol 顺序变化会得到 409 node_capability_conflict。当前 Node 固定顺序,暂不影响;以后建议按集合比较或规范排序。

已核对

sessionStartSpec 从 run_agent_configurations 读冻结值、不重读个人默认(有测试覆盖冻结后编辑默认值不改变已准入会话);dispatchRunExecution 要求 Controller 回传的 input 与 execution_work.input 逐字相等,Controller 不能伪造配置或 Settings;declaredAgentCapable 在 claim 预选 SQL、启动证据和 dispatch 三处使用同一语义;PersonalAgentRuntime 为空时不接受 declared run;空能力不会被当作已认证;agent_adapter_unavailable 在首条 prompt 前结算;JSON 路由和旧 Node 响应会移除 modelAdapter;proto 是新增字段、不改既有编号;冻结 Settings 用 UseNumber 保留数字原文,回执按原字节比较。

未验证

本地没有跑 PostgreSQL/race(Windows 环境),以 CI 和作者本地结果为准;部署侧仍应保持 personal_agent_target 为空,直到兼容 Node/插件部署并完成真实验收。此结论只针对 1be0610;rebase 后请重新请求 review。

@ObsisMc
ObsisMc force-pushed the feat/336-agent-model-runtime branch from 1be0610 to 132cc6a Compare October 21, 2026 09:19
@wanglongan587

Copy link
Copy Markdown
Collaborator Author

说明:本 PR 的作者仍是 wanglongan587(分支来自 #336 的交接),GitHub 不允许作者给自己的 PR 提交 review,所以这份复审以普通评论提交。若需要原生 review 作为合并条件,请 owner 另指派 reviewer,或明确采信本条评论。

审查版本:132cc6a88c518c267774a37cdb2d4570dfd0968e(merge-base 308057c;对照审核时的 upstream/main 66533b4f592aa24ef654b18fdd2ff68907504302)。结论:代码、迁移与兼容性未发现阻塞,可合并。上一轮的三项阻塞已解决:迁移顺延到 0060 且 README/测试名/migrationsBefore 同步;已 rebase 到 308057c,agent_configuration 在字段 9;CI check 在 132cc6a 上通过(run 39576022259)。

以下非阻塞事项建议本轮顺手处理(都很小):

  1. 迁移号协调:当前 main 最后是 0059(共 61 个 SQL),本 PR 的 0060_node_model_adapter.sql 正确。但开着的 feat(scheduled-task): Cloud 首期 Scheduled Task 存储、调度 worker 与公开 API(第 3–4 步) #111 也新增 internal/core/migrations/0060_scheduled_tasks.sql——谁先合并谁占号。若 feat(scheduled-task): Cloud 首期 Scheduled Task 存储、调度 worker 与公开 API(第 3–4 步) #111 先合,请 rebase 到 0061 并同步测试名、migrationsBefore、中英 README 计数与文件数(按 memory/rules/migration-numbering.md)。合并前请 owner 再核对一次。
  2. 公开错误码 allowlist 缺新码:internal/core/agent_run_failure.go:15-23 的 publicSessionFailureCode 没有 agent_model_configuration_invalid、agent_model_protocol_mismatch、agent_model_configuration_required、model_grant_mismatch、agent_model_adapter_failed,公开 failureCode(thread_read.go:117、agent_run_preparation.go:105)会全部回落 agent_failed。Node wire 侧的有界错误码是对的,但 ADR D2「区分配置不完整/不兼容/授权拒绝/准备失败」在 Cloud 产品面不成立。建议加一行 + 测试;desktop#708 的复审也把这条列为跨 PR 事项。
  3. 结算文档漏新失败原因:docs/agent-runs/startup-settlement.md:36 与 .en.md:48 的「尚未登记派发」结算表只列了 cancelled / model_proxy_unavailable / agent_start_failed,没有本次新增的「fresh Node 明确缺少声明的 adapter 能力 → failed/agent_adapter_unavailable」;而 docs/models/model-access.md:65 正引用该表。中英各补一行即可。
  4. 重复的 Refs:描述里同时有 Refs ora-space/circus#336 与 Refs ora-space/circus#354。按约定产品 PR 只关联运行 issue,建议把 #336 改成 Parent 说明,避免 bot/合并者把 PR 同时挂到开发 issue。
  5. 测试覆盖(非阻塞):没有测试走 report_node_register 的 capability 校验/落库/同 incarnation 冲突/旧空能力兼容,也没有断言 gRPC NodeRecord 投影含 model_adapter;dispatch 层 agent_adapter_required 409、扫描预选 SQL、迁移的既存行/CHECK、declared-run 的 stale/disconnected 反例均无直接断言。CI 的 PostgreSQL + race 覆盖了路径语法与结构,缺的是语义断言。
  6. Settings 256 KiB 两侧口径(低风险,可选):创建侧检查的是解析前调用者 values 的 compact JSON(internal/core/personal_agent_settings.go:14),wire 检查的是解析后 settings::text(含空格与默认值补齐);若声明默认值极大,理论上存在准入后 RecordDispatch 被 invalid_dispatch 拒绝的边界。建议至少在注释里写明口径差异。

已核对

  • 迁移:0060 内容只加 node_instances.model_adapter jsonb NOT NULL DEFAULT '{}' + CHECK;fresh/upgrade/repeat 与 CheckSchema 覆盖。
  • rebase:308057c..66533b4 的 7 个文件与本 PR 33 个文件零重叠,git merge-tree 无冲突;字段 7/8/9 未重编号,新消息与 NodeRecord/RegisterNodeRequest 新增字段均为追加;本地 buf generate 无 drift。
  • 能力门禁:注册校验(空能力=诚实遗留;有则 contractVersion=1、target∈两个 Linux gnu、protocols 非空无重复、必须 modelProxy)、dispatch/启动证据/扫描预选语义一致;stale/disconnected 不作为确定性拒绝证据。
  • 冻结配置:11 个键不含 token/上游 Key/凭据引用;execution_work.input 逐字比较;UseNumber 保留数字原文;legacy 缺字段不解释成新路径。
  • 部署门禁:plugins.personal_agent_target 默认空,配置模板/README/文档一致;空值真正关闭准入。
  • 与 desktop#708 c91935c 的 proto 字段、target 集合、256 KiB 上限一致;旧 Node 缺 capability 时靠 Controller 门禁与 target 保持空来保证安全。
  • 本地:checkformat、buf lint、buf generate 无 drift、go build ./...、触及包 go vet、相关包单元测试通过;本地未跑 PostgreSQL/race(同 head 的 CI check 覆盖)。cmd/devsetup 的 Windows mode 断言在本地 main 上也失败,与本 PR 无关。
  • 交付物:33 个文件均为交付物(代码、迁移、生成代码、正式文档、模板、测试),无过程记录;5 个 commit 各自单一目的。
  • PR 描述的 ADR 状态正确:两份 ADR 仍 approved,本 PR 只实现 Cloud 控制/能力部分;implemented specs PR 由 #354 后续的落地运行开。

最后:本运行其余三个 PR 的复审结论见 ora-space/circus#354。

publicSessionFailureCode collapsed every plugin-owned adapter failure into agent_failed, so a run could not say whether its configuration was missing, inconsistent, refused or not applied (ADR D2). The Node's bounded codes and the startup scan's agent_adapter_unavailable now pass through; anything else still becomes agent_failed.
The new public failure codes get their own message; an unknown code or provider text still shows the generic failure.
The startup scan also settles an undispatched run as failed/agent_adapter_unavailable when a fresh Node definitively lacks the frozen declaration's adapter capability; model-access already points at this table.

@wanglongan587 wanglongan587 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

审查版本:f454afe2170ee1fedac696b44ffe4447980dc9b5(增量 132cc6a..f454afe:3 commits、8 files、+64/−3)。结论:通过(无阻塞)。

说明:本 PR 的作者是 @wanglongan587(本人),GitHub 不允许作者提交原生 review,本轮继续以评论形式给出审查结论。请 owner 另指派 reviewer,或明确采信这条评论(已在运行 issue ora-space/circus#354 里提出)。

本轮增量

  • A. 范围:132cc6a..f454afe 正好 3 个 commit(96a1072 公开错误码、023c35c 前端文案、f454afe 结算文档),无 merge commit;除这三处外没有 proto、controlpb、controlgrpc、能力语义或迁移文件变化。
  • B. publicSessionFailureCode:放行 16 个有界码,新增 7 个(启动扫描的 agent_adapter_unavailable;Node 的 agent_model_configuration_required、agent_model_configuration_invalid、agent_model_protocol_mismatch、model_grant_mismatch、agent_model_adapter_failed、agent_model_adapter_not_applied)。精确 switch,未知码与供应商文本仍回落 agent_failed;Go 表驱动测试覆盖全部新码与拒绝用例(internal/core/agent_run_failure_test.go),未过度放宽。
  • C. 前端:7 个新码各有文案(frontend/src/features/issues/thread/thread-failure.ts),模块 README 中英同步;本 head 的 frontend job 通过。
  • D. 文档:docs/agent-runs/startup-settlement.{md,en.md} 的 agent_adapter_unavailable 行与 internal/core/agent_run_startup.go、execution_startup.go 一致;docs/models/model-access*、docs/operations/configuration* 无过期清单。
  • E. 描述:已改为 Refs ora-space/circus#354,#336 作为来源说明、不再出现 Refs;两份 ADR 仍为 approved(specs 3c41a34)。
  • F. 迁移:main 最新是 0059,本 PR 的 0060_node_model_adapter.sql 顺延正确;internal/core/migrations/README{,.en}.md 计数 61→62 与实际 62 个 SQL 一致;#111 的 0060_scheduled_tasks.sql 冲突已在描述里写明「后合并者顺延 0061」。
  • G. 回归抽查(未被新 commit 改坏):agent_configuration 字段 9、execution_work.input 逐字比较、claim/scan/dispatch 三处能力语义、proto 数值文本保真(codec 测试)、legacy 输入缺省、plugins.personal_agent_target 默认空。

非阻塞

  1. 描述里「The Thread names each of them」对 agent_model_adapter_failed / agent_model_adapter_not_applied 目前不成立:它们是 Node 内部的 PluginAgentError detail,配置会话启动失败实际报 agent_start_failed / agent_unavailable。建议把描述收窄为「预留给 Node 内部 adapter 失败」,或由 desktop 后续真正上报(follow-up,不阻塞)。
  2. agent_plugin_unavailable、prior_revision_*、workspace_unavailable 等其他有界码仍回落 agent_failed;建议记 follow-up,定「哪些码对用户公开」并建唯一清单(现在 Go、proto、前端、文档各自枚举)。
  3. 前端测试只覆盖 7 个新码中的 2 个;建议表驱动覆盖全部。
  4. 合并前按描述再核对迁移号:#111 先合则本 PR 顺延 0061(同步测试名与 README 计数)。

验证与未覆盖

  • 本地:go build ./...、go test ./internal/core/ ./internal/controlgrpc/ ./internal/config/ ./internal/api/router/、go vet ./integration 通过;新单测与 codec 测试通过。本地全量 cmd 测试的失败均为环境/已知问题(devsetup key-mode 已在主 clone 复现,与 PR 无关)。
  • CI:本 head check、frontend 均 success(check 带 PostgreSQL 与 -race ./...)。
  • 本地未跑 PostgreSQL/race,以 CI 为证据;未做真实 gateway/模型调用验收。

@EricWvi EricWvi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

owner 审查 f454afe2170ee1fedac696b44ffe4447980dc9b5:Approve。本 PR 名义作者是本运行的 reviewer,无法提交原生 review,由 owner 直接审查。

核对:

  • 能力证据:RegisterNode 上报 modelAdapter。validAgentAdapterCapability 只接受带 modelProxy 的 v1、两个 Linux target,以及不重复的 openai-completions / anthropic-messages;重复注册时 jsonText 不一致按 node_capability_conflict 拒绝。老 Node 落成 '{}',不会被当作支持(迁移注释和 modelAdapterMessage 都是"缺省即不支持")。
  • 派发与启动结算一致:dispatchRunExecution 用 declaredAgentCapable 门禁;inspectAgentStartup 用同一函数判定 adapterUnavailable,结算为 agent_adapter_unavailable / capability_rejected;scanStartingAgentRuns 的 declaredAdapterMismatchSQL 与 Go 判定一一对应(target、contractVersion 文本比较、protocols ? protocol)。
  • 会话规格:run_agent_configurations + run_model_bindings 冻结后下发;没有 binding 时显式报错。settings_json 以字符串传递,保留数字的原样写法;回读时限制 256 KiB、要求单个 JSON 对象,并对数值做有界校验。
  • 公开错误码:publicSessionFailureCode 放行新增的 7 个有界码,未知码仍回落 agent_failed。
  • proto / 迁移:agent_configuration = 9、model_adapter = 10/8 都是新增字段;0060_node_model_adapter.sql 只加一列带默认值和类型 CHECK 的 jsonb。当前 main 1f5f3d5 的迁移到 0059,0060 空着。
  • 基线漂移:PR 基点 308057c 之后,main 没有新增迁移,也没有和本 PR 重叠的文件(期间改动的是 modelgateway、workflowasset、残留回收查询和测试);git merge-tree 合并无冲突。cloud#130 改成了模板 fixture,但 TestMigration0060ModelAdapterFreshAndUpgrade 走 testSchema 的空 schema,不受影响。
  • 部署开关 plugins.personal_agent_target 默认为空,关闭时不会接纳带声明的 Agent;按 #452 的结论,打开前必须先关闭 #452。

非阻塞:reviewer 记下的其他有界码回落清单,以及前端只覆盖 7 个码中的 2 个,留作 follow-up。

@EricWvi
EricWvi merged commit e2b5a38 into ora-space:main Oct 21, 2026
2 checks passed
ObsisMc added a commit to ObsisMc/desktop that referenced this pull request Oct 21, 2026
…adapter contract

third_party/cloud moves from ora-space/cloud#106's branch head 132cc6a to Cloud main 4d35aad, which merged it. proto/ is identical between the two, and proto:check regenerates the committed client without a diff.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants