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

# Ship a self-authored Skill

> Turn Session evidence into a verified Skill proposal, require a human merge, and deliver a new immutable SkillVersion to future Sessions.

A difficult Session can suggest a reusable Skill. It cannot install one into itself.

The safe adoption path is:

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
Session evidence
  -> version-fenced SkillProposal
  -> isolated verification Session
  -> governed repository branch
  -> human review and merge
  -> SkillSource sync
  -> immutable SkillVersion
  -> future AgentVersion and Session
```

Nothing in this flow changes the source Session, a live AgentVersion, or an existing SkillVersion.

Before starting, set `CHECKFU_API_KEY`, `CHECKFU_PRINCIPAL_ID`, `CHECKFU_ENVIRONMENT_ID`, `SERVICE_PRINCIPAL`, the source `SESSION`, and the target `SKILL_SOURCE`. The examples use `Checkfu-Version: 2026-08-31`.

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

<Warning>
  Plain HTTP against the Checkfu API needs no private package.
  The Checkfu CLI and TypeScript SDK are optional private-alpha artifacts; the HTTP requests work without them.
  Part of this flow is behind a rollout fence; a deployment that has not enabled it refuses the request rather than running it.
  Confirm [capability status](/getting-started/status) before making availability part of your application's contract.
</Warning>

```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="Check whether the trajectory is worth capturing" titleSize="h2">
    The suggestion endpoint is read-only. It looks at structural signals such as many Tool calls or failure followed by success. It does not author or install anything.

    <CodeGroup>
      ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
      curl "https://api.checkfu.com/v1/skill-proposals/suggestions/$SESSION" \
        --header "Authorization: Bearer $CHECKFU_API_KEY" \
        --header "Checkfu-Version: 2026-08-31"
      ```

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

  <Step title="Create a version-fenced proposal" titleSize="h2">
    Name one to eight unique source Sessions. The current request contract also requires the logical `model_routing_profile_key` used by the authoring job. That transitional field selects authoring execution for this operation; it is not the Agent's public identity or a replacement for Environment admission.

    Retain one idempotency key for the logical create attempt.

    <CodeGroup>
      ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
      curl --request POST "https://api.checkfu.com/v1/skill-proposals" \
        --header "Authorization: Bearer $CHECKFU_API_KEY" \
        --header "Checkfu-Version: 2026-08-31" \
        --header "Idempotency-Key: skill-from-$SESSION" \
        --header "Content-Type: application/json" \
        --data '{
          "source_session_ids": ["'"$SESSION"'"],
          "model_routing_profile_key": "primary",
          "instructions": "Capture the working approach as a reusable Skill."
        }'
      ```

      ```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.",
        },
        { idempotencyKey: `skill-from-${sessionId}` },
      )
      ```
    </CodeGroup>

    The proposal moves from `authoring` to `drafted` or `authoring_failed`. The current artifact is one instructional `SKILL.md`, bounded so the verification Session can carry the complete proposed content. There is no deterministic fallback author when model authoring fails.
  </Step>

  <Step title="Verify in an isolated Session" titleSize="h2">
    Verification reserves an ordinary Session for the exact proposal version, acting Principal, and Environment. It runs the task class with the proposed Skill in graded context and reconciles the verdict from the Session event log.

    <CodeGroup>
      ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
      curl --request POST \
        "https://api.checkfu.com/v1/skill-proposals/$PROPOSAL_ID/verify" \
        --header "Authorization: Bearer $CHECKFU_API_KEY" \
        --header "Checkfu-Version: 2026-08-31" \
        --header "Idempotency-Key: verify-$PROPOSAL_ID-$PROPOSAL_VERSION" \
        --header "Content-Type: application/json" \
        --data '{
          "expected_version": '"$PROPOSAL_VERSION"',
          "principal": "'"$CHECKFU_PRINCIPAL_ID"'",
          "environment_id": "'"$CHECKFU_ENVIRONMENT_ID"'"
        }'
      ```

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

    An exact retry uses the same proposal version, Principal, Environment, request body, and idempotency key. Verify returns the proposal in `verifying` and launches a verification Session. Retrieve until the proposal is `verified` or `verification_failed`; do not treat launch as a pass. Verification is evidence, not adoption.
  </Step>

  <Step title="Submit a governed repository change" titleSize="h2">
    Wait for the verification Session to settle, then submit only the verified proposal version to a SkillSource. Stop on `verification_failed` or timeout — submit rejects any other status. The acting Principal must own and hold the current required Connection authority for the source repository.

    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    deadline=$((SECONDS + 300))
    while true; do
      status="$(curl --silent --show-error --fail \
        "https://api.checkfu.com/v1/skill-proposals/$PROPOSAL_ID" \
        --header "Authorization: Bearer $CHECKFU_API_KEY" \
        --header "Checkfu-Version: 2026-08-31" | jq -r '.status')"
      if [ "$status" = "verified" ]; then
        break
      fi
      if [ "$status" = "verification_failed" ]; then
        echo "verification failed" >&2
        exit 1
      fi
      if [ "$SECONDS" -ge "$deadline" ]; then
        echo "verification timed out while status=$status" >&2
        exit 1
      fi
      sleep 2
    done
    ```

    <CodeGroup>
      ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
      curl --request POST \
        "https://api.checkfu.com/v1/skill-proposals/$PROPOSAL_ID/submit" \
        --header "Authorization: Bearer $CHECKFU_API_KEY" \
        --header "Checkfu-Version: 2026-08-31" \
        --header "Idempotency-Key: submit-$PROPOSAL_ID-$PROPOSAL_VERSION" \
        --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"}}
      const deadline = Date.now() + 5 * 60 * 1000
      let current = await checkfu.skillProposals.retrieve(proposal.id)
      while (current.status === "verifying") {
        if (Date.now() > deadline) {
          throw new Error("Skill proposal verification timed out")
        }
        await new Promise((resolve) => setTimeout(resolve, 2000))
        current = await checkfu.skillProposals.retrieve(proposal.id)
      }
      if (current.status !== "verified") {
        throw new Error(`Skill proposal verification ended in ${current.status}`)
      }

      const submission = await checkfu.skillProposals.submit(
        proposal.id,
        {
          expected_version: current.version,
          skill_source: skillSourceId,
          acted_as: servicePrincipalId,
        },
        { idempotencyKey: `submit-${proposal.id}-${current.version}` },
      )
      ```
    </CodeGroup>

    Checkfu does not inject or return the source Connection's custodied Git Credential value through the Skill, branch content, model context, harness configuration, Session events, sandbox files, commit message, pull-request body, or Tool payloads. The trusted provider path binds credential resolution to the exact Connection, immutable Connection revision, and provider named by the admitted effect.

    Submission creates a repository branch or equivalent change proposal. It does not merge the change, sync the SkillSource, update an Agent, create an AgentVersion, or alter the source Session.

    A lost response after the branch effect is not proof that Git rejected it. Reconcile the retained submission attempt or provider state before retrying; do not create a second branch effect merely because the first response was lost.
  </Step>

  <Step title="Merge, sync, and adopt deliberately" titleSize="h2">
    A person reviews and merges the repository change. Then sync the SkillSource. Sync creates a new immutable SkillVersion for the accepted content.

    Select that SkillVersion in a later Agent update. The configuration-changing update creates the next immutable AgentVersion. Start a new Session from that version; existing Sessions keep their admitted SkillVersion.
  </Step>
</Steps>

## What the gate protects

Session-derived instructions can contain mistakes, prompt injection, private data, customer-supplied secrets, or a procedure that worked once for the wrong reason. Verification tests the artifact. Human review decides whether it belongs in the shared capability set. Immutable versioning preserves exactly what was adopted.

<Warning>
  Do not put provider Credentials, application API keys, private Session payloads, session cookies, passwords, or hidden reasoning into the Skill. Checkfu's structural guarantee covers custodied Credential values that it never injects or returns through the Skill path; it does not sanitize arbitrary text copied from source Sessions or supplied by your application.
</Warning>

A Skill can describe how to call a governed Tool, but the Tool still follows its own authority path. Connected Tools use Connection permission, policy, approval, exact revision/provider binding, revocation, and credential attachment at runtime. Custom tools use ActionPolicy and customer execution instead.

<CardGroup cols={2}>
  <Card title="Capabilities, Tools, and Skills" icon="plug" href="/concepts/capabilities#skills">
    Understand Skill, SkillVersion, and SkillSource ownership.
  </Card>

  <Card title="Connections and credentials" icon="shield" href="/concepts/capabilities">
    See the trust path used by the repository submission effect.
  </Card>
</CardGroup>


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