Skip to content

feat(instance): add sandbox snapshots and source cloning - #148

Merged
KayIter merged 6 commits into
TencentCloudAgentRuntime:mainfrom
jo-hnny:codex/issue-144-sandbox-snapshot
Oct 15, 2026
Merged

KayIter merged 6 commits into
TencentCloudAgentRuntime:mainfrom
jo-hnny:codex/issue-144-sandbox-snapshot

Conversation

@jo-hnny

@jo-hnny jo-hnny commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

The canonical CLI contract was missing the published SandboxSnapshot API family and instance cloning/restoration inputs. Sync the public contract and expose snapshot creation, inspection, deletion, and restoration in both stable and preview. agr instance create --source-instance-id clones an existing instance; agr instance create --snapshot-id restores an ACTIVE saved disk-only snapshot into a new instance. The source modes are mutually exclusive. Snapshot restoration also rejects ToolId and ToolName; cloning permits an optional selector matching the source tool. These rules apply to flags and --request, including stdin.

  • Sync api.json from tccli commit 58f4f795e322eafedc319dbd2cb798060296f481 and use AGS/common Go SDK v1.3.193. The published StartSandboxInstance.SnapshotId field is ahead of that SDK, so the existing contract-aware signed JSON transport preserves it without typed-SDK field loss.
  • Map the three Snapshot Actions into stable and preview, retaining the Beta warning. Newly published image-cache listing/deletion Actions remain raw-only. No API patch changes are introduced.
  • Preserve omitted versus explicit Memory=false, validate conflicting pagination modes, and return snapshot IDs and continuation tokens. Snapshot deletion follows the existing TTY confirmation behavior with --yes; successful deletion output acknowledges request acceptance.
  • Correct instance delete help to describe its existing TTY confirmation and --yes behavior.
  • Regenerate metadata, registries and descriptors; align help/schema and bilingual examples, including the snapshot restore workflow and source-selection rules.

Closes #144.

Verification

Local validation passed against the final source tree:

  • go test -race ./... -count=1
  • go test -race -tags=preview ./... -count=1
  • golangci-lint run --timeout=5m in stable and preview
  • go run ./cmd/internal/cobragen check
  • go run ./cmd/internal/apipatch check-all
  • go run ./cmd/internal/apipatch coverage
  • Both binaries' help, schema, request skeleton, and Markdown/man generation
  • git diff --check

Local HTTPS integration tests verify signed payloads for flags and JSON/stdin requests, response output, usage errors, source conflicts, and schema contracts. The restore-selector matrix covers SourceInstanceId, ToolId, and ToolName through flags, JSON, and stdin, requiring exit 2 and zero API requests. Full local test runs used an isolated home without cloud credentials.

Real cloud E2E passed on commit 1950d24dae429471dc99e00a32cf497329703dc7 in ap-chongqing, using stable and preview binaries built from the clean committed tree against the public Cloud API and instance data plane: 107/107 assertions passed, 0 failed, 0 skipped.

  • Created a source instance, wrote a marker on its root filesystem, and saved an ACTIVE disk-only snapshot. Both channels restored distinct instances without a tool selector, inherited the saved tool, and recovered snapshot-time contents after the source was changed.
  • Retried each restore through --request with identical parameters and ClientToken; both returned their original instance IDs. Verified independent writes between source and restored instances.
  • Both channels cloned with only --source-instance-id, inherited the source tool, copied current disk contents, and kept later writes independent. Matching tool IDs succeeded; mismatching tool IDs were rejected by the service.
  • Both channels verified the default page size of 20 against 21 visible snapshots, status and snapshot-name filters, changing MaxResults between cursor pages, unchanged Limit with NextToken, and token rejection after changing Limit. Raw API requests confirmed the service rejects positive MaxResults combined with positive Offset or Limit.
  • Verified that omitted Memory follows source policy: it produced a disk-only snapshot on the disk-only source; without that override, the service rejected the default memory snapshot as unsupported.
  • Stopped all 8 test instances and confirmed STOPPED; deleted all 16 test snapshots and confirmed absence. A final list preserved all 5 pre-existing snapshots and contained none of the test snapshots.

The first run stopped because the test supplied 16 values to one Filter, exceeding the service maximum of 10. Its 7 instances and 16 snapshots were also confirmed cleaned up. The successful rerun corrected only that test input. These results establish behavior on the tested route; the Snapshot API remains Beta.

Live E2E candidate identity
  • Head: 1950d24dae429471dc99e00a32cf497329703dc7; base: 5bd4a49f83afc7ab51e916218f67f1039a290958.
  • Source tree: 8446a4092334e1f7c713c6a1e792fc2140de5911; both binaries report the head revision and vcs.modified=false.
  • Stable SHA-256: cb5ba104c355f662d679c168e7be429d692c1bd571d74a35afe8afc91f084614.
  • Preview SHA-256: 2483476bfc3a2350f7c49ab9fd2226424a1c0ffb41979b9939b8fe7852cf5e7d.
  • Run: 2026-10-14T11:35:56.540226+00:00 to 2026-10-14T11:37:10.476047+00:00 (UTC).
  • Assertions: 107 passed / 0 failed / 0 skipped. Cleanup: 8 STOPPED instances / 16 absent snapshots; existing snapshot baseline preserved.

API patch verification is not applicable to this change: the API overlay patches and coverage plan are unchanged, and their operations do not affect the instance cloning or snapshot Actions changed here. Under Patch verification, the retained non-empty overlay alone does not require a new full patch E2E run or independent reproduction. Normal CI and maintainer review still apply.

Release impact

  • Stable and preview
  • Preview only
  • Stable only
  • None

Reason: Both builds inherit the updated canonical contract and expose snapshot lifecycle commands, source-instance cloning, and saved-snapshot restoration, with matching help/schema and examples. The Snapshot API remains Beta in both channels.

@jo-hnny
jo-hnny requested a review from KayIter October 13, 2026 04:54

@jo-hnny jo-hnny 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.

Review 基于当前 head 21bac7062b00bdf11e20d0259ca9d04be56cb4e7(base e517de7)。

契约同步本身没有问题:api/ags/v20250920/api.json 与 tccli 902c4bef 结构化比对,三个 Snapshot Action、相关 Object、StartSandboxInstanceRequest 和 metadata 均无差异。但以下问题需要处理后再合入:1 个阻断项、1 个应修复项、1 个小问题。

Findings

1. 阻断:允许只传 --source-instance-id、不传 tool,与公开契约冲突,且没有实测证据

公开契约中,StartSandboxInstanceRequest.ToolId 和 ToolName 的说明都是"与 ToolName/ToolId 至少有一个要填";SourceInstanceId 只写了"待克隆的源沙箱 ID",没有说明它可以替代 tool 选择。PR 在以下位置放宽了这一要求:

  • internal/commands/instance/create/command.go 的 validateToolSelection:只有 --source-instance-id 时也会通过
  • internal/cli/schema.go:instance.create 的 AnyOf 新增了 {Required: [SourceInstanceId]}
  • instance create 的 help、示例,以及中英文 README:写明克隆时可以省略 tool

此外,"若提供 tool,则必须与源实例一致"和"从源实例磁盘克隆"这两点,公开契约里也没有依据。PR 正文已说明没有运行真实 E2E,所以这些行为目前都未经验证。如果服务端仍要求 tool,用户按 README 中的 agr instance create --source-instance-id ins-source ... 执行会直接失败。

建议二选一:

  • 保留原有的"tool 必填"校验和 schema,只把 --source-instance-id 作为可选 flag 加入;
  • 或者补充真实 E2E 或接口负责人确认,作为可以公开的依据,并推动上游修正契约文档。

2. 应修复:help 和本地校验写入了公开契约中没有的约束

  • 本地拒绝了契约允许的请求。 internal/commands/instance/snapshot/module.go 的 validatePagination 会拒绝"正数 MaxResults 同时带正数 Offset 或 Limit",返回 INVALID_PAGINATION。公开契约只规定了 Offset 与 NextToken 互斥。实测 agr instance snapshot list --max-results 20 --limit 10 -o json 以退出码 2 失败,请求不会发出。
  • help 和 README 中有契约里找不到依据的说法:
    • "memory snapshots are currently unsupported"(当前不支持内存快照)
    • "Omitting Memory follows service policy"(省略 Memory 时遵循服务端策略)
    • "The source instance must be running and support snapshots"(源实例必须运行中且支持快照)
    • "Limit with NextToken is supported"(Limit 可以与 NextToken 同时使用)
    • "keep SnapshotIds, Filters, and Limit unchanged"(翻页时保持这三项不变)
    • Filter 名称示例 status、snapshot-name(契约中 Filter.Name 没有列出可用值)
  • 前后矛盾。 --instance-id 的 usage 写着 "Source instance ID required by the service",但契约中 InstanceId 是可选字段,CLI 也没有强制校验,不传时会直接发出不含 InstanceId 的请求。

建议本地只对契约明确写出的互斥规则做拒绝校验;其余说法要么删除,要么附上可以公开的依据后再保留。

3. 小问题:snapshot list 的页大小没有本地校验

help 写了 "Page sizes are at most 100",但实测 --limit -5 和 --max-results 500 都没有被本地校验拦下,一直走到凭据检查才失败。instance list 会校验 MaxResults 在 0–100 之间,并拒绝负数 Limit/Offset,建议保持一致。另外,positiveInteger 与 instance list 中的 integerRequestValue 逻辑重复,可以考虑复用。

非阻断,供参考

  • instance snapshot delete 不提供交互确认,help 中已写明;instance delete 在 TTY 下会提示确认,并提供 --yes。快照是持久化数据,可以考虑与 instance delete 保持一致。
  • 目前用户可以创建、查询、删除快照,但还没有使用快照恢复实例的入口。这受限于当前公开契约,PR 正文已如实说明。
  • Release impact 选择 "Stable and preview",与 #143 将正式契约放入双通道的做法一致,Beta 警告也已保留,分类合理。

已验证

  • go run ./cmd/internal/cobragen check、go run ./cmd/internal/apipatch check-all:通过
  • go test ./internal/commands/instance/... ./internal/controlplane/... ./internal/cli/... ./cmd/agr/... -count=1:通过;PR 的 CI 全绿
  • 编译 stable 和 preview 二进制:两者 agr schema 中 snapshot 相关内容一致;无 TTY、stdin 关闭时,instance snapshot delete 缺少 ID 会返回结构化 JSON 错误,退出码 2
  • 未运行真实云端 E2E,因此第 1、2 条中服务端的实际行为未能证明或证伪

@jo-hnny jo-hnny 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.

Review 基于当前 head 212cbedf6a368e92269084f7f9bec20490c48655(base 5bd4a49)。

上一轮 review 的问题大多已处理:页大小本地校验、snapshot delete 的 TTY 确认、--snapshot-id 恢复入口均已补齐。api/ags/v20250920/api.json 与 tccli 58f4f795 结构化比对完全一致;stable 与 preview 中 instance.create 和三个 snapshot 命令的 agr schema 输出一致。

剩余 2 个应修复项、3 个小问题。

Findings

1. 应修复:--snapshot-id 与 tool 选择器的组合规则不一致

  • agr instance create --snapshot-id s -t x 能通过本地校验并发出请求(实测走到凭据检查才失败)。
  • 但 PR 对 SourceInstanceId + SnapshotId 做了本地互斥(CONFLICTING_FLAGS,含 --request),这条规则公开契约同样没有写;SnapshotId + ToolId/ToolName 却没有任何处理。
  • schema 中 snapshot-id 的 IncompatibleWith 只有 source-instance-id;help 只说恢复"不需要" tool 选择器、使用快照保存的工具配置,没有说明传了 tool 会怎样。用户或 agent 按习惯多传 -t 时,CLI 没有给出明确语义。

建议二选一,并请接口负责人确认服务端对该组合的行为:

  • 如果服务端不接受该组合:本地拒绝 --snapshot-id 与 --tool-id/--tool-name(含 --request),同步 IncompatibleWith、help 和中英文 README;
  • 如果希望组合规则统一交给服务端:去掉本地的 SourceInstanceId/SnapshotId 互斥,避免只对其中一部分组合做本地校验。

2. 应修复:PR 正文把不适用的 API patch 验证列为合并前提

正文的 "API patch verification — required before merging" 仍有 7 项未勾选,并写明合并前必须完成。但本 PR:

  • 没有修改 api.patch.json、help.patch.json、mapping.patch.json、e2e-coverage.yaml;
  • 现有 patch 的 36 个 op 均不涉及 StartSandboxInstance 或 Snapshot 相关 Action/Object。

PATCH-VERIFICATION.md 明确写了 "an unchanged, non-empty patch does not by itself require a new API review or live E2E run",PR 模板也说明该小节只在 patch 行为受影响时使用。按正文现在的写法,这个 PR 无法满足自己声明的合并条件,也会让 reviewer 等待一份不需要的报告。建议删除该小节,或改为"不适用:patch 未变化,且不涉及本次 Action"。

3. 小问题:克隆路径没有当前 head 的真实验证

正文的 25/25 断言覆盖了 snapshot 创建、恢复、ClientToken 重试和清理,但没有 instance create --source-instance-id。help 和 README 对克隆做了行为承诺:可以不传 tool、传了必须与源实例一致、系统盘复制后写入相互独立。建议在 E2E 中补一个克隆场景,或在 Verification 中明确说明克隆路径未实测。

4. 小问题:help/README 中仍有公开契约没有依据的服务端细节

CLI 本地已按这些规则校验,行为上自洽,但公开契约只写了 Offset 与 NextToken 互斥。以下内容仍缺少可公开的依据:

  • 两种页大小都省略时服务端默认 20;
  • 正数 MaxResults 不能与正数 Offset 或 Limit 组合(本地以 INVALID_PAGINATION 拒绝);
  • 翻页时需保持 Limit 不变、MaxResults 可以变化;
  • 省略 Memory 时服务端默认包含内存;
  • Filter 名称示例 status、snapshot-name(契约中 Filter.Name 未列出可用值)。

其中"Limit 不变、MaxResults 可变"属于翻页 token 的实现细节,后续容易漂移。建议保留的条目附上接口负责人确认或推动上游文档补充;否则只保留契约写明的部分。

5. 小问题

  • PR 顺带修正了 instance delete 的 help(原文写"不提示确认",代码实际会在 TTY 下提示)。修正正确,建议在 Summary 中注明这项范围外改动。
  • internal/commands/instance/snapshot/module.go 的 paginationInteger 与 instance list 的 integerRequestValue 逻辑重复,可以考虑复用。

已验证

  • go test ./internal/commands/instance/... ./internal/cli/... ./internal/controlplane/... ./cmd/agr/... -count=1,以及 -tags=preview 的对应包:通过
  • go run ./cmd/internal/cobragen check、go run ./cmd/internal/apipatch check-all:通过;PR CI 全绿
  • 编译 stable/preview 二进制,在无凭据的隔离 HOME 下实测:
    • --source-instance-id + --snapshot-id(flag 和 --request)→ CONFLICTING_FLAGS,退出码 2
    • --request 与 --snapshot-id 混用 → REQUEST_FLAG_CONFLICT
    • snapshot delete 位置参数与 --request 中 SnapshotId 不一致 → REQUEST_ARG_CONFLICT
    • snapshot list --limit -5 / --max-results 500 / --offset 5 --next-token abc → INVALID_PAGINATION,退出码 2
    • pty 模拟 TTY 执行 snapshot delete,回答 n → 返回 Cancelled,未发出请求
  • 未运行真实云端 E2E,未复核正文中 25/25 报告。

@KayIter KayIter 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.

复审当前 head 1950d24:未发现新的阻断项,同意合入。前两轮提出的恢复选择器冲突校验、help/schema 一致性与克隆实测缺口已处理。stable/preview 定向测试、cobragen check、apipatch check-all 均通过,当前 CI 全绿;作者提供了绑定同一 head 的 107 项云端 E2E 报告。云端运行结果不是我独立复现的结果;Snapshot API 仍为 Beta,保留其兼容性风险提示。

@KayIter
KayIter merged commit b5003c9 into TencentCloudAgentRuntime:main Oct 15, 2026
35 checks passed
@jo-hnny jo-hnny mentioned this pull request Oct 15, 2026
1 of 4 tasks
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.

[P2] Sync upstream SandboxSnapshot contract changes (2026-10-11 tccli 3.1.181.1)

2 participants