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

# Ship a self-authored skill

> Turn a session where the agent solved something the hard way into a reusable, verified, human-merged skill.

When an agent works out a hard task through trial and error, that trajectory is a skill worth keeping. This guide turns one session into a reusable [Skill](/concepts/capabilities#skills), authored by the platform, verified by replay, and merged by a human. Nothing reaches a Run unreviewed; the merge is the gate.

Before you start, set the shared variables. `CHECKFU_API_KEY` is bound to the Workspace. `CHECKFU_PRINCIPAL_ID` comes from [Get access](/reference/access#the-three-variables-ready); `SESSION` is the `sess_…` you want to capture, `SKILL_SOURCE` your bound `sksrc_…` repository, and `SERVICE_PRINCIPAL` the `prin_…` the submit acts as.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
export CHECKFU_API_KEY="your_api_key"
export CHECKFU_WORKSPACE_ID="wrkspc_0123456789abcdef0123456789abcdef"
export V="Checkfu-Version: 2026-08-27"
export AUTH="Authorization: Bearer $CHECKFU_API_KEY"
```

The TypeScript tabs below use the [TypeScript SDK](/reference/typescript-sdk) with this client:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { Checkfu } from "@checkfu/sdk"

const checkfu = new Checkfu({
  apiKey: process.env.CHECKFU_API_KEY,
})
```

<Steps>
  <Step title="Find a skill-worthy session (optional)" titleSize="h2">
    Ask whether a Session's trajectory looks worth capturing. This is a read-only structural signal, safe under zero-data-retention because it reads only counts:

    <CodeGroup>
      ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
      curl -s "https://api.checkfu.com/v1/skill-proposals/suggestions/$SESSION" \
        --header "$AUTH" --header "$V"
      ```

      ```ts TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
      const suggestion = await checkfu.skillProposals.suggest(sessionId)
      ```
    </CodeGroup>

    It reports `skill_worthy` with reasons (`many_tool_calls`, `failure_then_success`). It never authors anything; it tells your UI when to offer the button.
  </Step>

  <Step title="Author the proposal" titleSize="h2">
    Name 1–8 source Sessions (the first fixes the task class and its Agent) and a `model_routing_profile_key`. A platform job reads the trajectories and writes one immutable single-file skill.

    <CodeGroup>
      ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
      PROPOSAL_JSON=$(curl -s --request POST "https://api.checkfu.com/v1/skill-proposals" \
        --header "$AUTH" --header "$V" \
        --header "Content-Type: application/json" \
        --data '{
          "source_session_ids": ["'"$SESSION"'"],
          "model_routing_profile_key": "primary",
          "instructions": "Capture the working approach as a reusable skill."
        }')
      PROP=$(jq -r .id <<<"$PROPOSAL_JSON")
      PROPOSAL_VERSION=$(jq -r .version <<<"$PROPOSAL_JSON")
      ```

      ```ts TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
      const proposal = await checkfu.skillProposals.create({
        source_session_ids: [sessionId],
        model_routing_profile_key: "primary",
        instructions: "Capture the working approach as a reusable skill.",
      })
      ```
    </CodeGroup>

    The proposal starts `authoring`, then reaches `drafted` (or `authoring_failed`). Its skill entry is capped at 2560 characters, a verifiability bound explained next.
  </Step>

  <Step title="Verify it by replay" titleSize="h2">
    Verification replays the task in one isolated child Session that carries the whole proposed skill in its context, graded by an [Outcome](/concepts/outcomes) rubric derived from the original failure:

    <CodeGroup>
      ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
      curl -s --request POST "https://api.checkfu.com/v1/skill-proposals/$PROP/verify" \
        --header "$AUTH" --header "$V" \
        --header "Content-Type: application/json" \
        --data '{
          "expected_version": '"$PROPOSAL_VERSION"',
          "principal": "'"$CHECKFU_PRINCIPAL_ID"'"
        }'
      ```

      ```ts TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
      await checkfu.skillProposals.verify(proposal.id, {
        expected_version: proposal.version,
        principal: principalId,
      })
      ```
    </CodeGroup>

    A pass moves the proposal to `verified`; a fail to `verification_failed` (kept, auditable, revisable). Only a `verified` proposal may submit. The skill rides the graded context rather than a staged mount, which is why its entry is bounded.
  </Step>

  <Step title="Submit to the skill repository" titleSize="h2">
    Submit writes the skill to your bound [SkillSource](/concepts/capabilities#skills) repo as a `checkfu/<id>` branch, through the governed Connection. The branch *is* the change proposal. `expected_version` is the verified proposal version you actually reviewed; an exact retry keeps that version, while a stale or changed request conflicts.

    <CodeGroup>
      ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
      # After polling until status is verified, submit the version you reviewed.
      PROPOSAL_VERSION=$(curl -s "https://api.checkfu.com/v1/skill-proposals/$PROP" \
        --header "$AUTH" --header "$V" | jq -r .version)
      curl -s --request POST "https://api.checkfu.com/v1/skill-proposals/$PROP/submit" \
        --header "$AUTH" --header "$V" \
        --header "Content-Type: application/json" \
        --data '{
          "expected_version": '"$PROPOSAL_VERSION"',
          "skill_source": "'"$SKILL_SOURCE"'",
          "acted_as": "'"$SERVICE_PRINCIPAL"'"
        }'
      ```

      ```ts TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
      // Retrieve after polling until status is "verified".
      const verified = await checkfu.skillProposals.retrieve(proposal.id)
      await checkfu.skillProposals.submit(proposal.id, {
        expected_version: verified.version,
        skill_source: skillSourceId,
        acted_as: servicePrincipalId,
      })
      ```
    </CodeGroup>
  </Step>

  <Step title="A human merges; sync delivers" titleSize="h2">
    From here nothing is automatic. A person reviews and merges the branch; you [sync the SkillSource](/concepts/capabilities#skills); the ordinary path versions the skill and future releases carry it. The proposal records the resulting skill version, closing the chain from session to shipped capability.
  </Step>
</Steps>

## Why the gate

The prior art points one way. Hermes stages every self-authored skill behind approval. Voyager admits one only after replay verification, which is where this guide's verify step comes from. ClawHub left its marketplace ungated and grew a measurable malicious-skill supply chain.

So a `verified` proposal is a strong, evidence-backed suggestion, and nothing more. A merged skill is a decision a person made. That difference is the product, not a limitation we intend to remove.

## Next steps

<CardGroup cols={2}>
  <Card title="Capabilities" icon="plug" href="/concepts/capabilities#skills-that-improve-themselves">
    The SkillProposal lifecycle and the SkillSource loop it adopts through.
  </Card>

  <Card title="Improving instructions from Outcomes" icon="wand-magic-sparkles" href="/concepts/agents">
    The sibling loop that improves an agent's instructions instead of its skills.
  </Card>
</CardGroup>
