> ## Documentation Index
> Fetch the complete documentation index at: https://powercontext.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Experience、Skill 与 Review

> 先审再留。Skill 批准后还要显式导出。

这一页把做完的工作变成 Experience (已批准的判断经验。先作为 Candidate 进入 Review。)，再把已批准 Experience 写成托管 Skill (已批准的可重复做法。获批后还要显式导出，不自动获得执行权限。)。重点不是模型写得好不好，而是 Review (批准才创建 Artifact Revision。拒绝只记录原因，不创建结果 Artifact。) 有没有核对精确证据和 Candidate (尚待 Review 的 Experience 或 Skill 提案。) 版本。

![先审再留：提案进入 Candidate，Review 批准或拒绝，Experience 可召回，Skill 还要导出。](https://mintcdn.com/powercontext/1t_ajifOEOG5YQjP/assets/basic-review-flow-zh.svg?fit=max\&auto=format\&n=1t_ajifOEOG5YQjP\&q=85\&s=e0ec40dd9c40d55564df0db1973164d3)

> [!NOTE]
>
> Review 和导出命令以 [官方文档](https://oceanbase.github.io/powercontext/) 的最新发布版本为准。这页帮你跑通一条能看见失败的审核路径。

## 两条创建路径

按已有证据选择路径。类型化提案不需要模型，结果也更容易复现；只有 Server 已有可用生成模型时，才使用生成路径。

| 路径 | 需要生成模型 | 结果 |
| - | -: | - |
| Python Client `propose_experience` / `propose_skill` | 否 | 待处理 Candidate |
| CLI `experience generate` / `skill generate` | 是 | 待处理 Candidate 或 `no_op` |

CLI 当前没有 `experience propose` 或 `skill propose` 子命令。无模型类型化提案使用 Python Client 或 HTTP API。

本教程先用类型化提案建立确定的 Review 闭环，再列出模型生成命令。

## 1. 准备精确证据

先采集一条任务结果 Source。新建 `propose_experience.py`：

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
import asyncio
import os
from uuid import uuid4

from powercontext.client import PowerContextClient
from powercontext.http import (
    CaptureContentSourceRequest,
    ExperienceProposal,
    ProposeExperienceRequest,
)


SERVER_URL = os.getenv(
    "POWERCONTEXT_CLIENT_SERVER_URL",
    "http://127.0.0.1:8000",
)
API_TOKEN = os.getenv("POWERCONTEXT_CLIENT_API_TOKEN")


async def main() -> None:
    run_id = uuid4().hex
    scope_id = f"project:powercontext-101:{run_id}"

    async with PowerContextClient(
        SERVER_URL,
        token=API_TOKEN,
    ) as client:
        evidence = await client.capture_content_source(
            CaptureContentSourceRequest(
                scope_id=scope_id,
                source_id=f"contract-test-result-{run_id}",
                content=(
                    "The OpenAPI client was regenerated and inspected. "
                    "Contract tests passed."
                ),
            )
        )

        candidate = await client.propose_experience(
            ProposeExperienceRequest(
                scope_id=scope_id,
                proposal=ExperienceProposal(
                    situation="The OpenAPI contract changed.",
                    action=(
                        "Regenerate the client, inspect the diff, and run "
                        "contract tests."
                    ),
                    outcome=(
                        "The generated transport matched the contract and "
                        "the tests passed."
                    ),
                    lesson=(
                        "Regenerate and inspect generated clients before "
                        "contract tests."
                    ),
                ),
                source_refs=[evidence.source],
                artifact_refs=[],
                reason="Submit a complete typed Experience proposal.",
            )
        )

        print("scope:", scope_id)
        print("candidate:", candidate.candidate_id)
        print("version:", candidate.version)
        print("status:", candidate.status)
        print("source:", evidence.source)


if __name__ == "__main__":
    asyncio.run(main())
```

运行并保存输出：

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
uv run python propose_experience.py
```

后面的 CLI 命令把 `SCOPE_ID` 和 `CANDIDATE_ID` 替换成脚本输出。

## 2. 检查 Candidate

默认收件箱只列待处理头版本：

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
powercontext candidate list \
  --scope-id SCOPE_ID \
  --family experience
```

查看当前版本：

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
powercontext candidate show \
  --scope-id SCOPE_ID \
  CANDIDATE_ID
```

决定前检查：

* `proposal` 四个字段；
* 精确 `source_refs` 和 `artifact_refs`；
* 只有替换已有 Artifact 时才有的目标；
* `status`、`family`、`reason`；
* 当前 `version`。

不要只读模型总结。打开证据，确认它真的支持提案里的动作、结果和教训。

## 3. 修订是完整替换

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
powercontext candidate revise experience \
  --scope-id SCOPE_ID \
  --expected-version 1 \
  --situation "The OpenAPI contract changed." \
  --action "Regenerate the client, inspect its diff, then run contract tests." \
  --outcome "The generated transport matched the contract." \
  --lesson "Inspect generated drift before contract tests." \
  --source-ref content/SOURCE_ID \
  CANDIDATE_ID
```

`SOURCE_ID` 使用采集响应中的值。

成功后 Candidate 仍是待处理，版本变成 2。再次显示并检查完整提案。

故意用旧版本批准：

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
powercontext candidate approve \
  --scope-id SCOPE_ID \
  --expected-version 1 \
  CANDIDATE_ID
```

应返回 HTTP 409 `candidate_conflict`。不要把预期版本改成 2 就直接重试；先确认当前 v2 正是你要批准的内容。

## 4. 批准或拒绝

批准刚检查的 v2：

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
powercontext candidate approve \
  --scope-id SCOPE_ID \
  --expected-version 2 \
  CANDIDATE_ID
```

保存精确 `result_artifact`：

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
experience/EXPERIENCE_ID@1
```

拒绝是另一条终局分支，需使用另一个待处理 Candidate：

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
powercontext candidate reject \
  --scope-id SCOPE_ID \
  --expected-version 1 \
  --reason "The evidence does not support the proposed lesson." \
  OTHER_CANDIDATE_ID
```

已批准和已拒绝都是终态，不能再次修订、批准或拒绝。

## 5. 验证 Experience 召回

当前没有 `powercontext context prepare` CLI 命令。用 Python Client：

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
import asyncio
import os

from powercontext.client import PowerContextClient
from powercontext.http import PrepareContextRequest


async def main() -> None:
    async with PowerContextClient(
        os.getenv(
            "POWERCONTEXT_CLIENT_SERVER_URL",
            "http://127.0.0.1:8000",
        ),
        token=os.getenv("POWERCONTEXT_CLIENT_API_TOKEN"),
    ) as client:
        prepared = await client.prepare_context(
            PrepareContextRequest(
                scope_id="SCOPE_ID",
                query="When should generated clients be inspected?",
            )
        )
        print(prepared.status)
        print(prepared.content)


if __name__ == "__main__":
    asyncio.run(main())
```

将脚本里的 `SCOPE_ID` 替换成真实值。

批准让当前 Experience 头版本具备召回资格，但不保证每个查询都选中它。Scope、查询相关性和字节预算仍然生效。待处理、已拒绝和历史 Experience Revision 不进入默认召回。

## 6. 创建 Skill Candidate

最稳妥的新 Skill 证据链是引用刚批准的 Experience。新建 `propose_skill.py`：

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
import asyncio
import os

from powercontext.client import PowerContextClient
from powercontext.http import (
    ArtifactReference,
    ProposeSkillRequest,
    SkillProposal,
    SkillValidationItem,
)


async def main() -> None:
    experience = ArtifactReference(
        family="experience",
        artifact_id="EXPERIENCE_ID",
        revision=1,
    )

    async with PowerContextClient(
        os.getenv(
            "POWERCONTEXT_CLIENT_SERVER_URL",
            "http://127.0.0.1:8000",
        ),
        token=os.getenv("POWERCONTEXT_CLIENT_API_TOKEN"),
    ) as client:
        candidate = await client.propose_skill(
            ProposeSkillRequest(
                scope_id="SCOPE_ID",
                proposal=SkillProposal(
                    name="regenerate-client",
                    description=(
                        "Regenerate and verify a generated API client."
                    ),
                    instructions=(
                        "Regenerate the client from the canonical OpenAPI "
                        "document. Inspect the generated diff, run generation "
                        "checks, and then run contract tests."
                    ),
                    validation=[
                        SkillValidationItem(
                            "The generated diff is inspected."
                        ),
                        SkillValidationItem(
                            "The contract tests pass."
                        ),
                    ],
                ),
                source_refs=[],
                artifact_refs=[experience],
                reason=(
                    "Turn the approved Experience into reusable instructions."
                ),
            )
        )
        print(candidate.candidate_id, candidate.version)


if __name__ == "__main__":
    asyncio.run(main())
```

替换 `SCOPE_ID` 和 `EXPERIENCE_ID` 后运行。然后用同一组 Candidate 命令列出、显示、修订和批准。

较长指令建议放进 UTF-8 文件：

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
powercontext candidate revise skill \
  --scope-id SCOPE_ID \
  --expected-version 1 \
  --name regenerate-client \
  --description "Regenerate and verify a generated API client." \
  --instructions-file instructions.md \
  --validation "The generated diff is inspected." \
  --validation "The contract tests pass." \
  --artifact-ref experience/EXPERIENCE_ID@1 \
  SKILL_CANDIDATE_ID
```

`--instructions` 和 `--instructions-file` 必须二选一。

批准后得到：

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
skill/SKILL_ID@1
```

Skill 不会进入 PreparedContext。这是预期行为。

## 7. 读取并导出精确 Skill Revision

先读取：

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
powercontext skill show \
  --scope-id SCOPE_ID \
  --revision 1 \
  SKILL_ID
```

导出到 Codex：

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
powercontext skill export \
  --target codex \
  --scope-id SCOPE_ID \
  --revision 1 \
  --destination .agents/skills/regenerate-client \
  SKILL_ID
```

导出到 Claude Code：

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
powercontext skill export \
  --target claude_code \
  --scope-id SCOPE_ID \
  --revision 1 \
  --destination .claude/skills/regenerate-client \
  SKILL_ID
```

注意目标是 `claude_code`，不是 `claude-code`。

输出目录包含：

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
SKILL.md
powercontext.json
```

检查 `powercontext.json` 是否引用精确 `skill/SKILL_ID@1`。CLI 导出只创建、不覆盖。目标目录已存在时退出。

当前托管 Skill 导出只支持 Codex 和 Claude Code。插件自带内置 Skill 不等于支持托管导出。

## 模型生成路径

配置生成模型后，也可以从精确证据生成 Candidate：

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
powercontext experience generate \
  --scope-id project:example \
  --source-ref content/SOURCE_ID \
  --reason "Extract a reusable lesson from the completed task."
```

从已批准 Experience 生成 Skill：

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
powercontext skill generate \
  --scope-id project:example \
  --origin experience \
  --artifact-ref experience/EXPERIENCE_ID@1 \
  --reason "Turn the reviewed lesson into reusable instructions."
```

返回结果可能是：

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
status: pending, candidate: {...}
```

也可能是：

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
status: no_op, candidate: null
```

`no_op` 不进入收件箱，也没有 Candidate 可以批准。没配置生成模型时，生成返回 503 `generation_unavailable`。

其他 Skill 来源：

* `source`：只能提供一条或多条精确 Source 引用，不能带 Artifact 引用或目标；
* `usage`：提供精确目标 Skill 和至少一条有界 Source。Server 只校验引用形状，审核者仍要判断 Source 是否真的记录了使用效果。

Review 路径只有在失败可见时才完整。Candidate 版本过期或替换目标发生漂移时，Candidate 必须保持待处理；系统不能留下半个 Artifact，也不能静默批准更新后的内容。

## 冲突处理

| 情况 | 结果 | 处理 |
| - | - | - |
| `expected_version` 过期 | 409 `candidate_conflict` | 重新显示，审阅当前版本 |
| Candidate 已终态 | 409 `candidate_terminal` | 不再修改该 Candidate |
| 替换内容目标头版本已推进 | 409 `artifact_conflict` | Candidate 保持待处理，重新评估目标 |
| 证据/证据链不合法 | 422 `invalid_request` | 修正证据，不是 CAS 重试 |
| 导出目标目录已存在 | CLI exit 2 | 检查外部、当前或已漂移的状态，不覆盖 |

同一个替换内容 Candidate 的目标不能在修订时换成另一个 Revision。

## 怎么知道跑通了

* 不接模型，类型化提案也能创建待处理 Candidate。
* 生成可能待处理，也可能无变更。
* Review 始终先显示，再用你核对过的 `expected_version`。
* 过期版本返回 409。
* 批准返回精确 `result_artifact`。
* 已批准的当前 Experience 可以参与相关召回。
* Skill 批准不等于导出，也不等于执行。
* 导出目录里有 `SKILL.md` 和 `powercontext.json`。
* 目标目录已存在，或本地已经漂移时，不会被静默覆盖。

基础用法到这里结束。按你现在的工作，进入下面其中一条路。

> [!TIP]
>
> 自动帮你找回上下文失败时，当前任务还应继续。你明确要求写入 Memory、提交 Handoff、批准或导出时，失败必须能看见。完整说明见 [fail-open](https://powercontext.mintlify.site/zh/mental-model/fail-open.md)。

## 下一步

> [card] [接入 Python Agent](https://powercontext.mintlify.site/zh/frameworks/overview.md)
>
> 第一次接入选 LangGraph 或 LangChain。

> [card] [配置通用或编程 Agent](https://powercontext.mintlify.site/zh/common-agents/overview.md)
>
> Hermes、OpenClaw 走通用 Agent。Codex、Claude Code 走[编程 Agent](https://powercontext.mintlify.site/zh/coding-agents/overview.md)。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.