> ## 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, and Review

> Review first. An approved Skill still needs an explicit export.

This page turns finished work into Experience (An approved judgment. It enters Review as a Candidate first.), then turns an approved Experience into a managed Skill (Approved reusable practice. After approval you still export it explicitly. Export does not grant execution rights.). The point is not how polished the model draft looks. It is whether Review (Approval creates an Artifact Revision. Rejection records a reason and creates no result Artifact.) checked exact evidence and the Candidate (An Experience or Skill proposal still waiting for Review.) version.

![Review first: a proposal becomes a Candidate, Review approves or rejects, Experience can be recalled, and Skill still needs export.](https://mintcdn.com/powercontext/1t_ajifOEOG5YQjP/assets/basic-review-flow-en.svg?fit=max\&auto=format\&n=1t_ajifOEOG5YQjP\&q=85\&s=6447671f8734b0134371730c71636152)

> [!NOTE]
>
> Review and export commands follow the latest published [official docs](https://oceanbase.github.io/powercontext/). This page helps you run one review path where failure stays visible.

## Two creation paths

Choose the path by the evidence you already have. The typed path is model-free and reproducible; generation is useful only when the Server has a working generation provider.

| Path | Generation model required | Result |
| - | -: | - |
| Python Client `propose_experience` / `propose_skill` | No | Pending Candidate |
| CLI `experience generate` / `skill generate` | Yes | Pending Candidate or `no_op` |

The CLI currently has no `experience propose` or `skill propose` subcommand. Use the Python Client or HTTP API for model-free typed proposals.

This tutorial establishes a deterministic Review loop with typed proposals first, then shows model-generation commands.

## 1. Prepare exact evidence

Capture one task-result Source. Create `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())
```

Run it and keep the output:

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

Replace `SCOPE_ID` and `CANDIDATE_ID` in later CLI commands with the values printed by the script.

## 2. Inspect the Candidate

The default Inbox lists pending heads only:

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

Show the current version:

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

Before deciding, inspect:

* all four proposal fields;
* exact `source_refs` and `artifact_refs`;
* `target`, which appears only for replacement;
* `status`, `family`, and `reason`;
* current `version`.

Do not rely on the model's summary. Open the evidence and verify that it supports the proposed action, outcome, and lesson.

## 3. Revise replaces the whole proposal

```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
```

Use the Source ID returned by capture.

The Candidate remains pending and advances to version 2. Show it again and inspect the complete replacement.

Intentionally approve with the stale version:

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

It should return HTTP 409 `candidate_conflict`. Do not change the value to 2 and retry without confirming that current v2 is the proposal you intend to approve.

## 4. Approve or reject

Approve the inspected v2:

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

Keep the exact `result_artifact`:

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

Reject is an alternative terminal branch and needs another pending 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
```

Approved and rejected Candidates are terminal. Neither may be revised, approved, or rejected again.

## 5. Verify Experience recall

There is no `powercontext context prepare` CLI command. Use the 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())
```

Replace `SCOPE_ID` with the actual value.

Approval makes the current Experience head eligible for recall, but it does not guarantee selection for every query. Scope, query relevance, and byte budget still apply. Pending, rejected, and historical Experience Revisions stay outside default recall.

## 6. Create a Skill Candidate

The clearest lineage for a new Skill is the approved Experience. Create `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())
```

Replace `SCOPE_ID` and `EXPERIENCE_ID`, then run the script. Use the same Candidate list, show, revise, and approve commands.

For longer instructions, use a UTF-8 file:

```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
```

Choose exactly one of `--instructions` and `--instructions-file`.

Approval produces:

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

The Skill still does not enter PreparedContext. That is expected.

## 7. Read and export an exact Skill Revision

Read it first:

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

Export to 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
```

Export to 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
```

The target is `claude_code`, not `claude-code`.

The output directory contains:

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

Confirm that `powercontext.json` cites exact `skill/SKILL_ID@1`. CLI export is create-only and refuses an existing destination.

Managed Skill export currently supports Codex and Claude Code only. A bundled Skill shipped with a plugin does not imply managed export support.

## Model-generation path

With a generation model configured, a Candidate may also be generated from exact evidence:

```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."
```

Generate Skill from approved Experience:

```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."
```

The response may be:

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

or:

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

A no-op does not enter the Inbox and has no Candidate to approve. Without a generation model, generate returns 503 `generation_unavailable`.

Other Skill origins:

* `source`: one or more exact Source refs only, with no Artifact refs or target;
* `usage`: an exact target Skill and at least one bounded Source. The Server validates reference shape; the reviewer decides whether the Source truly records useful observed behavior.

A Review path is incomplete until its failure is visible. A stale Candidate version or a drifted replacement target must leave the Candidate pending; it must not create a partial Artifact or silently approve newer content.

## Conflict handling

| Situation | Result | Action |
| - | - | - |
| stale `expected_version` | 409 `candidate_conflict` | Show and review the current version |
| Candidate is terminal | 409 `candidate_terminal` | Do not modify it again |
| replacement target head advanced | 409 `artifact_conflict` | Candidate remains pending; reassess the target |
| invalid evidence or lineage | 422 `invalid_request` | Correct evidence rather than retrying CAS |
| export destination exists | CLI exit 2 | Inspect foreign/current/drifted state; do not overwrite |

A replacement Candidate cannot switch its target to another Revision during revise.

## How you know it worked

* A typed proposal can create a pending Candidate without a model.
* Generate may return pending, or no change.
* Review always shows the Candidate first, then uses the `expected_version` you inspected.
* A stale version returns 409.
* Approval returns an exact `result_artifact`.
* The approved current Experience may participate in relevant recall.
* Approving a Skill does not export it, and does not execute it.
* The export directory contains `SKILL.md` and `powercontext.json`.
* An existing destination, or local drift, is never overwritten silently.

Basic usage ends here. Pick the path that matches the work you are doing now.

> [!TIP]
>
> If PowerContext fails while automatically fetching context, keep working. If you explicitly write a Memory, commit a Handoff, approve a proposal, or export a Skill, you must see the failure. Read [fail-open](https://powercontext.mintlify.site/en/mental-model/fail-open.md) for the full rule.

## Next step

> [card] [Connect a Python Agent](https://powercontext.mintlify.site/en/frameworks/overview.md)
>
> First integration is LangGraph or LangChain.

> [card] [Configure a common or coding Agent](https://powercontext.mintlify.site/en/common-agents/overview.md)
>
> Hermes and OpenClaw are common Agents. Codex and Claude Code are on [coding agents](https://powercontext.mintlify.site/en/coding-agents/overview.md).


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