forked from zstackio/zstack
-
Notifications
You must be signed in to change notification settings - Fork 0
<doc>[docs]: <description #3945
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
zstack-robot-2
wants to merge
5
commits into
zsv_5.0.1
Choose a base branch
from
sync/tao.gan/5.1.0-ZSV-10538@@3
base: zsv_5.0.1
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
fb33aef
<doc>[docs]: snapshot single-delete documentation set (overview, scen…
taogan21 9b05c73
<fix>[storage]: decouple alive-chain membership from vmState in Volum…
taogan21 b93a085
<doc>[docs]: snapshot single-delete scenarios, bugs, and proposals
taogan21 adfbda7
<fix>[storage]: symmetric snapshot group disband on chain delete
taogan21 e02de7a
<fix>[storage]: VM-scoped snapshot group integrity check + cascade
taogan21 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| # 单快照节点删除(scope=single)— 总览 | ||
|
|
||
| > 需求:ZSV-5799 "支持删除快照不删除链" | ||
| > 关联 MR:zstack#7674 / premium#10776 / zstack-utility#5743 | ||
| > 入口 API:`APIDeleteVolumeSnapshotGroupMsg`(含 `direction` + `scope` 字段) | ||
|
|
||
| --- | ||
|
|
||
| ## 文档索引 | ||
|
|
||
| | 文档 | 内容 | | ||
| |---|---| | ||
| | [01-api-and-fields.md](01-api-and-fields.md) | API 入口、字段、枚举定义 | | ||
| | [02-call-chain.md](02-call-chain.md) | 处理链路总览(Group → Tree → Storage) | | ||
| | [03-direction-resolution.md](03-direction-resolution.md) | `resolveDirection()` 决策表与 fromVOs 构建 | | ||
| | [04-scope-and-stepDelete.md](04-scope-and-stepDelete.md) | scope 分支与 stepDelete 递归 | | ||
| | [05-commit-db-swap.md](05-commit-db-swap.md) | Commit 路径 DB 翻转(最关键) | | ||
| | [06-pull-db-rewrite.md](06-pull-db-rewrite.md) | Pull / pullToVolume DB 改写 | | ||
| | [07-group-passthrough.md](07-group-passthrough.md) | Group 透传与并发、失败聚合 | | ||
| | [08-hypervisor-online-commit.md](08-hypervisor-online-commit.md) | 在线 libvirt blockCommit + pivot | | ||
| | [09-agent-qemu-img.md](09-agent-qemu-img.md) | agent 端 qemu-img 三种命令对比 | | ||
| | [10-storage-backend-matrix.md](10-storage-backend-matrix.md) | Local/NFS/SMP/SharedBlock/Ceph 后端差异 | | ||
| | [11-sibling-rebase.md](11-sibling-rebase.md) | 分叉链兄弟节点 rebase | | ||
| | [12-fullrebase-and-cleanup.md](12-fullrebase-and-cleanup.md) | fullRebase 树根删除与残留清理 | | ||
| | [13-premium-and-cdp.md](13-premium-and-cdp.md) | Premium / CDP / 灾备兼容性 | | ||
| | [14-limitations-and-todos.md](14-limitations-and-todos.md) | 已知限制 / TODO / FIXME | | ||
|
|
||
| --- | ||
|
|
||
| ## 一图概览 | ||
|
|
||
| ``` | ||
| [祖父] ── [待删节点 X] ── [子 Y] ── ... | ||
| │ | ||
| ┌──────────┴───────────┐ | ||
| │ scope=single │ | ||
| │ direction=commit │ 在线VM 且 X≠latest | ||
| │ → Y 差量写入 X 文件 │ | ||
| │ → DB: 互换 path, Y.parent=X.parent | ||
| │ | ||
| │ direction=pull │ 离线 或 X=latest | ||
| │ → 祖父+X 合并入 Y(rebase) | ||
| │ → DB: Y.parent = X.parent | ||
| ``` | ||
|
|
||
| ## 仓库根 | ||
|
|
||
| - `/d/0zw/zw/zstack/` —— 开源主库 | ||
| - `/d/0zw/zw/premium/` —— Premium(独立 git) | ||
| - `/d/0zw/zw/zstack-utility/` —— Python agent | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| # 01 — API 入口与字段定义 | ||
|
|
||
| ## 1.1 `APIDeleteVolumeSnapshotGroupMsg`(快照组删除) | ||
|
|
||
| **文件**:`header/src/main/java/org/zstack/header/storage/snapshot/group/APIDeleteVolumeSnapshotGroupMsg.java:24` | ||
|
|
||
| ```java | ||
| @APIParam(required = false, validValues = {"pull", "commit", "auto"}) | ||
| private String direction = "auto"; | ||
|
|
||
| @APIParam(required = false, validValues = {"single", "chain", "auto"}) | ||
| private String scope = "chain"; // 默认保留旧行为 | ||
| ``` | ||
|
|
||
| REST 路径:`DELETE /volume-snapshots/group/{uuid}` | ||
|
|
||
| ## 1.2 `APIDeleteVolumeSnapshotMsg`(单快照删除) | ||
|
|
||
| **文件**:`header/.../APIDeleteVolumeSnapshotMsg.java:49` | ||
|
|
||
| ```java | ||
| @APIParam(required = false, validValues = {"pull", "commit", "auto"}) | ||
| private String direction = "auto"; | ||
|
|
||
| @APIParam(required = false, validValues = {"single", "chain", "auto"}) | ||
| private String scope = "chain"; // 默认 chain,向后兼容 | ||
| ``` | ||
|
|
||
| REST 路径:`DELETE /volume-snapshots/{uuid}` | ||
|
|
||
| ## 1.3 枚举类 | ||
|
|
||
| ### `DeleteVolumeSnapshotDirection` — `header/.../DeleteVolumeSnapshotDirection.java:3` | ||
|
|
||
| | 值 | 语义 | | ||
| |---|---| | ||
| | `Pull("pull")` | 下拉方向:父快照内容合入子快照 | | ||
| | `Commit("commit")` | 上提方向:子快照内容合入父快照 | | ||
| | `Auto("auto")` | 系统自动判断 | | ||
|
|
||
| ### `DeleteVolumeSnapshotScope` — `header/.../DeleteVolumeSnapshotScope.java:3` | ||
|
|
||
| | 值 | 语义 | | ||
| |---|---| | ||
| | `Single("single")` | 只删除当前单节点,保留整条链 | | ||
| | `Chain("chain")` | 删除当前节点及所有后代(旧默认) | | ||
| | `Auto("auto")` | 系统自动判断(实际等同 single) | | ||
|
|
||
| ## 1.4 传递结构体 | ||
|
|
||
| `VolumeSnapshotDeletionStructs` — `header/.../VolumeSnapshotDeletionStructs.java:5` | ||
| 跨层透传 `direction + scope + 快照列表`。 | ||
|
|
||
| ## 1.5 兼容性 | ||
|
|
||
| API 默认值 `scope = "chain"` 保持向后兼容;**必须显式传 `scope=single`** 才会触发新功能。 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| # 02 — 处理链路总览 | ||
|
|
||
| ## 2.1 快照组删除链路 | ||
|
|
||
| ``` | ||
| APIDeleteVolumeSnapshotGroupMsg | ||
| └─ VolumeSnapshotGroupBase.handle() GroupBase.java:163 | ||
| └─ handleDelete() GroupBase.java:187 | ||
| └─ DeleteVolumeSnapshotGroupInnerMsg (携带 scope/direction) | ||
| └─ While 循环每个 VolumeSnapshotVO GroupBase.java:212 | ||
| └─ DeleteVolumeSnapshotMsg(scope,direction) | ||
| └─ VolumeSnapshotTreeBase | ||
| └─ deletion() TreeBase.java:358 | ||
| ├─ scope=chain → deleteChainFlows() :487 | ||
| └─ scope=single → deleteSingleFlows() :828 | ||
| └─ stepDelete() :875 | ||
| ├─ 叶节点 → deleteVolumeSnapshotAndSyncVolumeSize | ||
| ├─ 单子节点 → resolveDirection → commit() / pull() | ||
| └─ 多子节点 → pull() (强制) | ||
| ``` | ||
|
Comment on lines
+5
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 处理链路代码块缺少语言声明(MD040)。 Line 5 的代码围栏建议改成 🧰 Tools🪛 markdownlint-cli2 (0.22.1)[warning] 5-5: Fenced code blocks should have a language specified (MD040, fenced-code-language) 🤖 Prompt for AI Agents |
||
|
|
||
| ## 2.2 关键透传点 | ||
|
|
||
| `VolumeSnapshotGroupBase.java:221-228`: | ||
| ```java | ||
| DeleteVolumeSnapshotMsg rmsg = new DeleteVolumeSnapshotMsg(); | ||
| rmsg.setScope(msg.getScope()); | ||
| rmsg.setDirection(msg.getDirection()); | ||
| bus.makeTargetServiceIdByResourceUuid(rmsg, VolumeSnapshotConstant.SERVICE_ID, ...); | ||
| ``` | ||
|
|
||
| ## 2.3 关键类索引 | ||
|
|
||
| | 文件 | 作用 | | ||
| |---|---| | ||
| | `header/.../APIDeleteVolumeSnapshotMsg.java:49` | 单快照 API 入口 | | ||
| | `header/.../APIDeleteVolumeSnapshotGroupMsg.java:24` | 快照组 API 入口 | | ||
| | `storage/.../group/VolumeSnapshotGroupBase.java:212` | Group → 单快照消息分发 | | ||
| | `storage/.../VolumeSnapshotTreeBase.java:473` | scope 分支点 | | ||
| | `storage/.../VolumeSnapshotTreeBase.java:875` | stepDelete 递归 | | ||
| | `storage/.../VolumeSnapshotTreeBase.java:921` | commit() 流程 | | ||
| | `storage/.../VolumeSnapshotTreeBase.java:1097` | pull() 流程 | | ||
| | `storage/.../VolumeTree.java:364` | resolveDirection 决策 | | ||
| | `storage/.../VolumeTree.java:418/471` | updateDatabaseAfter Pull/Commit | | ||
| | `plugin/kvm/.../KVMHost.java:1043/1159` | 在线 commit/pull | | ||
| | `kvmagent/plugins/vm_plugin.py:3915` | libvirt blockCommit 核心 | | ||
| | `zstacklib/utils/linux.py:1389` | qcow2 工具函数 | | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| # 03 — direction 决策(resolveDirection) | ||
|
|
||
| ## 3.1 核心代码 | ||
|
|
||
| **文件**:`storage/src/main/java/org/zstack/storage/snapshot/VolumeTree.java:364` | ||
|
|
||
| ```java | ||
| public DeleteVolumeSnapshotDirection resolveDirection( | ||
| String targetSnapshotUuid, // 待删节点(dst, 老节点) | ||
| String childSnapshotUuid, // 待删节点的子节点(src, 新节点) | ||
| String initialDirection, // 用户传入的 direction | ||
| boolean targetSnapshotIsLatest, // 待删节点是否 latest | ||
| VmInstanceState vmState) { | ||
|
|
||
| boolean online = | ||
| (vmState == VmInstanceState.Running || vmState == VmInstanceState.Paused) | ||
| && getAliveChainSnapshotUuids().contains(targetSnapshotUuid) | ||
| && getAliveChainSnapshotUuids().contains(childSnapshotUuid); | ||
|
|
||
| boolean shouldUseCommitStrategy = current && !targetSnapshotIsLatest && online; | ||
|
|
||
| if (Objects.equals(initialDirection, DeleteVolumeSnapshotDirection.Pull.toString()) | ||
| && shouldUseCommitStrategy) { | ||
| throw new IllegalArgumentException( | ||
| "the snapshot will be deleted by block 'commit', but the direction is 'pull', " + | ||
| "change the direction to 'commit' or 'auto'."); | ||
| } | ||
|
|
||
| if (initialDirection == null) return DeleteVolumeSnapshotDirection.Commit; | ||
|
|
||
| if (Objects.equals(initialDirection, DeleteVolumeSnapshotDirection.Auto.toString())) { | ||
| return shouldUseCommitStrategy | ||
| ? DeleteVolumeSnapshotDirection.Commit | ||
| : DeleteVolumeSnapshotDirection.Pull; | ||
| } | ||
|
|
||
| return DeleteVolumeSnapshotDirection.fromString(initialDirection); | ||
| } | ||
| ``` | ||
|
|
||
| ## 3.2 决策表 | ||
|
|
||
| | current | targetIsLatest | online | initialDirection | 结果 | | ||
| |---|---|---|---|---| | ||
| | 任意 | 任意 | 任意 | `null` | **Commit**(兜底) | | ||
| | true | false | true | `pull` | **抛 IllegalArgumentException** | | ||
| | true | false | true | `auto` | **Commit** | | ||
| | 其它组合 | — | — | `auto` | **Pull** | | ||
| | 任意 | 任意 | 任意 | `commit` | **Commit** | | ||
| | 任意 | 任意 | 任意 | `pull`(合法) | **Pull** | | ||
|
|
||
| ## 3.3 关键字段含义 | ||
|
|
||
| | 字段 | 含义 | | ||
| |---|---| | ||
| | `current` (`VolumeTree.current`,第38行) | 来自 `VolumeSnapshotTreeVO.current`,true 表示快照链尾连着活跃 volume | | ||
| | `targetSnapshotIsLatest` | 来自 `VolumeSnapshotVO.latest = 1`,调用方传 `currentRoot.isLatest()` | | ||
| | `aliveChain` | volume 沿 backing chain 上溯到根的所有节点,代表"qemu 当前持有的文件链" | | ||
|
|
||
| ## 3.4 调用方 | ||
|
|
||
| `VolumeSnapshotTreeBase.java:904`: | ||
| ```java | ||
| DeleteVolumeSnapshotDirection direction = volumeTree.resolveDirection( | ||
| currentRoot.getUuid(), // 待删节点 | ||
| child.getUuid(), // 子节点 | ||
| msg.getDirection(), // 用户传入 | ||
| currentRoot.isLatest(), // 来自 DB | ||
| vmState); | ||
| ``` | ||
|
|
||
| ## 3.5 `VolumeTree.fromVOs()` 构建过程 | ||
|
|
||
| `VolumeTree.java:260-327`: | ||
|
|
||
| 1. 校验:至多一个根(`parentUuid == null`)、至多一个 latest | ||
| 2. 若 `current && 有 latest`,把 **volume 自身作为虚拟叶节点** 挂到 latest 之后(uuid = volume uuid) | ||
| 3. HashMap 还原 parent/children | ||
| 4. 从 volume 虚拟节点向上收集 `aliveChain` | ||
|
|
||
| ```java | ||
| // 步骤 3:构建树 | ||
| Map<String, VolumeSnapshotLeaf> map = new HashMap<>(); | ||
| for (VolumeSnapshotInventory inv : invs) { | ||
| VolumeSnapshotLeaf leaf = map.computeIfAbsent(inv.getUuid(), k -> new VolumeSnapshotLeaf()); | ||
| leaf.inventory = inv; | ||
| if (inv.getParentUuid() != null) { | ||
| VolumeSnapshotLeaf parent = map.computeIfAbsent(inv.getParentUuid(), k -> new VolumeSnapshotLeaf()); | ||
| parent.children.add(leaf); | ||
| leaf.parent = parent; | ||
| } else { | ||
| tree.root = leaf; | ||
| } | ||
| } | ||
|
|
||
| // 步骤 4:计算 aliveChain | ||
| if (tree.current) { | ||
| VolumeSnapshotLeaf leaf = tree.getSnapshotLeaf(volumeInv.getUuid()); | ||
| tree.aliveChain = leaf != null ? leaf.getAncestors() : new ArrayList<>(); | ||
| } | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| # 04 — scope 分支与 stepDelete 递归 | ||
|
|
||
| ## 4.1 scope 分支点 | ||
|
|
||
| **文件**:`VolumeSnapshotTreeBase.java:473` | ||
|
|
||
| ```java | ||
| if (Objects.equals(msg.getScope(), DeleteVolumeSnapshotScope.Chain.toString())) { | ||
| deleteChainFlows(); // 旧行为:删当前 + 所有后代 | ||
| } else { | ||
| deleteSingleFlows(); // single/auto:只删当前节点 | ||
| } | ||
| ``` | ||
|
|
||
| 注意:`scope=auto` 也走 `deleteSingleFlows()` 分支;只有显式 `chain` 走级联删除。 | ||
|
|
||
| ## 4.2 stepDelete 完整代码 | ||
|
|
||
| **文件**:`VolumeSnapshotTreeBase.java:875-918` | ||
|
|
||
| ```java | ||
| private void stepDelete(Completion completion) { | ||
| // 1) 从 DB 拉取整棵树最新状态 | ||
| List<VolumeSnapshotVO> vos = Q.New(VolumeSnapshotVO.class) | ||
| .eq(VolumeSnapshotVO_.treeUuid, currentRoot.getTreeUuid()).list(); | ||
| boolean current = Q.New(VolumeSnapshotTreeVO.class) | ||
| .eq(VolumeSnapshotTreeVO_.uuid, currentRoot.getTreeUuid()) | ||
| .select(VolumeSnapshotTreeVO_.current).findValue(); | ||
|
|
||
| // 2) 重建内存树 | ||
| VolumeTree volumeTree = VolumeTree.fromVOs(vos, current, VolumeInventory.valueOf(volume)); | ||
| List<VolumeSnapshotLeaf> children = | ||
| volumeTree.getSnapshotLeaf(currentRoot.getUuid()).getChildren(); | ||
|
|
||
| // 3) 终止条件:无子节点 | ||
| if (children.isEmpty()) { | ||
| deleteVolumeSnapshotAndSyncVolumeSize(completion); | ||
| return; | ||
| } | ||
|
|
||
| // 4) 递归 completion | ||
| Completion comp = new Completion(completion) { | ||
| @Override public void success() { stepDelete(completion); } | ||
| @Override public void fail(ErrorCode e) { completion.fail(e); } | ||
| }; | ||
|
|
||
| // 5) 找 online 子节点(vm running/paused 且在 aliveChain) | ||
| VolumeSnapshotLeaf onlineChild = children.stream() | ||
| .filter(c -> volumeTree.isOnline(current, currentRoot.getUuid(), c.getUuid(), vmState)) | ||
| .findFirst().orElse(null); | ||
|
|
||
| VolumeSnapshotLeaf child = children.get(0); | ||
|
|
||
| if (children.size() == 1) { | ||
| DeleteVolumeSnapshotDirection direction = volumeTree.resolveDirection( | ||
| currentRoot.getUuid(), child.getUuid(), | ||
| msg.getDirection(), currentRoot.isLatest(), vmState); | ||
| boolean online = volumeTree.isOnline(current, currentRoot.getUuid(), child.getUuid(), vmState); | ||
| if (direction == Commit) commit(child, volumeTree, online, comp); | ||
| else pull(child, volumeTree, online, comp); | ||
| } else { | ||
| // 多子节点(分叉链) | ||
| if (onlineChild != null && child.getUuid().equals(onlineChild.getUuid())) { | ||
| child = children.get(1); // 优先处理非 online 子节点 | ||
| } | ||
| boolean online = volumeTree.isOnline(current, currentRoot.getUuid(), child.getUuid(), vmState); | ||
| pull(child, volumeTree, online, comp); // 多子节点统一 pull | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## 4.3 递归特性 | ||
|
|
||
| | 维度 | 说明 | | ||
| |---|---| | ||
| | 终止条件 | `children.isEmpty()` | | ||
| | 每次递归 | 处理一个子节点;commit/pull 后子节点数 -1 | | ||
| | 最坏深度 | 子节点总数(**不是链深度**) | | ||
| | 多子节点策略 | 强制 pull;优先非 online 子节点 | | ||
| | 失败处理 | `comp.fail()` 直接上抛,**已完成的中间步骤不回滚**,依赖存储幂等 | | ||
|
|
||
| ## 4.4 多子节点优先非 online 原因 | ||
|
|
||
| online 子节点的 backing file 正在被 qemu 持有写 I/O,修改它有风险; | ||
| 先处理非 online 子节点,把它们逐个 pull 掉;最后 online 子节点剩一个,落入"单子节点"分支正常处理。 | ||
|
|
||
| ## 4.5 特殊短路 | ||
|
|
||
| `VolumeSnapshotTreeBase.java:836`: | ||
| ```java | ||
| if (VolumeSnapshotConstant.STORAGE_SNAPSHOT_TYPE.toString().equals(currentRoot.getType()) | ||
| || Objects.equals(currentRoot.getVolumeType(), VolumeType.Memory.toString())) { | ||
| deleteVolumeSnapshotAndSyncVolumeSize(completion); | ||
| return; | ||
| } | ||
| ``` | ||
|
|
||
| CDP / 存储快照 / 内存快照绕过 commit/pull,直接调用存储删除。 | ||
|
|
||
| ## 4.6 VmState 限制 | ||
|
|
||
| `:854` 仅允许 `Running / Paused / Destroyed / Stopped / Destroying`,其它(如 Migrating / Unknown)直接失败。 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
为代码块补充语言标识以通过 Markdown lint。
Line 32 的 fenced code block 缺少语言类型,当前会触发
MD040。建议改为```text(或mermaid,若后续改成可渲染图)。🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 32-32: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents