# Create Skill

> Guides authoring of an OpenBKN Skill package (SKILL.md + references) that declares which knowledge-network capabilities it uses — metrics, functions, actions, MCP tools — so an agent reading it knows what to call, how, and which steps need user confirmation. Also covers writing a sandbox function with bkn-osdk and registering it as a tool. Use when creating a skill, writing SKILL.md for the execution factory, declaring capability dependencies, or turning Python code into a function tool. When the openbkn skill is loaded, use it for `openbkn skill register` / `tool create` after files exist.

- Skill: `openbkn-ai/create-skill` (Agent Skill, multi-file: 6 files)
- Install (CLI): `npx skillmds@latest add openbkn-ai/create-skill`
- Raw SKILL.md: https://api.skillmd.com/api/skills/openbkn-ai/create-skill/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: openbkn-ai (https://skillmd.com/u/openbkn-ai)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/openbkn-ai/create-skill

---


# Create Skill

Author a Skill package the execution factory can register and an agent can follow. No platform
code is involved: the agent already has `get_skill_content`, `search_capabilities`,
`get_kn_detail`, `query_metric`, `execute_tool` and `execute_action`; the Skill's job is to say
**which** capabilities to use and **how**.

## Works with the openbkn skill

**create-skill** writes the files; the **openbkn** skill runs `openbkn skill register`,
`openbkn skill set-status`, `openbkn function run`, `openbkn tool create` once they exist.
[create-bkn](../create-bkn/SKILL.md) models the network itself — a Skill assumes the network,
its metrics and its actions already exist.

## Package layout

```
{skill_dir}/
├── SKILL.md                 # required; frontmatter name + description (+ metadata.uses)
└── references/              # optional: report format, business rules, IO contract
    └── report-spec.md
```

The execution factory parses only frontmatter `name`, `description` and `metadata` (free-form,
stored as-is). The body is prose the agent reads — the "capability dependencies" table below is
a convention, not a schema.

## Workflow

1. **Fix the scope** — one knowledge network (`bkn_scope`). List the user questions that
   trigger this skill and the parameters the agent must obtain from the user.
2. **Pick capabilities by name** — metrics and action types from `get_kn_detail` /
   `get_object_types`, functions and MCP tools from `search_capabilities`. Use the registered
   names verbatim; the agent searches by name.
3. **Declare them twice** — machine-readable in frontmatter `metadata.uses[]`
   (`type`, `name`, `purpose`, `confirm`), and for the agent in the body table
   (name | type | purpose | how to call | how to read the result | confirmation).
4. **Write the steps** — numbered, one platform call per step, with parameter mapping between
   steps. Say which step ends early on a good result and which step must ask the user first.
5. **Mark confirmations** — any action with side effects gets `confirm: true` and the sentence
   "do not execute without an explicit yes". The platform does not gate this; the skill does.
6. **Add `references/report-spec.md`** — the output shape, including the "依据" line naming the
   capabilities used so the user can check the trace.
7. **Register, publish, mount** — see [Delivery](#delivery). A skill is only visible to an
   agent inside networks it is mounted on.
8. **Verify end-to-end** — one managed interaction should show `get_skill_content` →
   `query_metric` → `execute_tool` → `execute_action` under the same `interaction_id`, and
   `bkn_finish_interaction` should return `evidence_status: complete`.

Template: [assets/templates/SKILL.md.template](assets/templates/SKILL.md.template) ·
[assets/templates/report-spec.md.template](assets/templates/report-spec.md.template).
Worked example (verified on a 0.1.5 deploy):
[references/examples/demand-deliverability-assessment/](references/examples/demand-deliverability-assessment/).

## Writing rules

- **Names are lookup keys.** A misspelled name means the agent cannot find the capability.
- **Only mounted capabilities.** Tell the agent to report "not mounted" rather than fall back to
  a same-named capability in another network or the platform catalog.
- **Text fields locate with `match`.** Fields carrying a full-text index (`condition_operations`
  contains `match`) may reject `==` at the resource layer; say so in the step that locates rows.
- **Finding skills:** `search_capabilities` with a query ranks functions first; pass
  `types: ["skill"]` when the goal is the skill itself.
- **No secrets, no URLs.** A skill names capabilities; the platform carries credentials and the
  managed turn.

## Functions (bkn-osdk)

A function is Python that runs in the platform sandbox and is registered as a tool. The sandbox
ships `bkn_osdk` and injects `BKN_BASE_URL`, `BKN_TOKEN`, `BKN_CONVERSATION_ID`,
`BKN_INTERACTION_ID` and `BKN_PARENT_OPERATION_ID`, so the code configures nothing:

```python
from bkn_osdk import kn

def handler(event):
    rows = kn.query_object_instance(event["kn_id"], "order", limit=500,
                                    filters=[{"field": "status", "op": "==", "value": "paid"}],
                                    properties=["order_no", "amount"], response_format="json")
    return {"count": len(rows.get("datas") or [])}
```

Hard rules, all verified against a live sandbox:

1. The entry point must be `handler(event)`; `event` is the tool's input object.
2. No `from __future__ import ...` — the sandbox prepends a wrapper and the import becomes a
   `SyntaxError`.
3. Tool parameter `type` is one of `string` / `number` / `boolean` / `array` / `object`;
   `integer` is rejected with 400.
4. Reads made through bkn-osdk inside the function land on the caller's interaction as children
   of the `execute_tool` operation — no extra wiring.

Skeleton: [assets/templates/function.py.template](assets/templates/function.py.template).
Full example (BOM level-1 kitting check):
[references/examples/functions/l1_kitting_check.py](references/examples/functions/l1_kitting_check.py).
Details of `kn.query_metric` / `kn.run_sql` and the generated ontology layer: the bkn-osdk
README under `python/`.

### Calling another function

A function may call another function mounted on the same network through the platform:

```python
from bkn_osdk import kn

def handler(event):
    answer = kn.execute_tool(event["kn_id"], event["box_id"], event["tool_id"],
                             {"kn_id": event["kn_id"], "product": event["product"], "qty": 50})
    body = answer.get("body", answer)           # the callee's raw response
    if body.get("exit_code") not in (0, None):  # HTTP 200 does not mean the callee succeeded
        return {"ok": False, "reason": body.get("stderr", "")[-300:]}
    return {"ok": True, **(body.get("result") or {})}
```

- `kn.execute_tool` carries the sandbox's managed turn, so the callee's sandbox gets the same
  caller credential and the trace shows the callee under this function's `execute_tool`
  operation, with the callee's own reads below it.
- Needs a sandbox bkn-osdk that has `kn.execute_tool` (bkn-sdk #100 or later). On an older
  SDK the only route is `bkn_osdk.call("/api/agent-retrieval/v1/kn/execute_tool", ...)`, and
  the request body **must** carry a `bkn_context` built from `BKN_CONVERSATION_ID`,
  `BKN_INTERACTION_ID` and `BKN_PARENT_OPERATION_ID`; without it the callee's sandbox is
  given no credential at all.
- The callee's own mounting and permissions apply. There is no depth or cycle guard — do not
  write functions that call each other.

Example: [references/examples/functions/call_l1_check.py](references/examples/functions/call_l1_check.py).

### Code → tool (openbkn CLI 0.1.5+)

```bash
openbkn function run ./fn.py --event '{"kn_id":"<kn>","product":"P1","qty":50}' --pass-token
openbkn toolbox create --name my_functions --type function     # name: letters, digits, _, CJK
openbkn tool create ./fn.py --toolbox <box-id> --name fn \
    --description "..." \
    --inputs '[{"name":"kn_id","type":"string","required":true}]' \
    --outputs '[{"name":"kitting_ok","type":"boolean"}]'
openbkn tool enable <tool-id> --toolbox <box-id>              # tools start disabled
openbkn toolbox publish <box-id>
```

Mount the tool on the network (CLI subcommand pending bkn-sdk #90; the REST call works today):

```bash
openbkn call -X POST /api/bkn-backend/v1/knowledge-networks/<kn>/capabilities \
  -d '{"capabilities":[{"capability_type":"function","box_id":"<box-id>","capability_id":"<tool-id>"}]}'
```

## Delivery

```bash
openbkn skill register ./{skill_dir}          # -> skill_id, status unpublish
openbkn skill set-status <skill_id> published
openbkn call -X POST /api/bkn-backend/v1/knowledge-networks/<kn>/capabilities \
  -d '{"capabilities":[{"capability_type":"skill","capability_id":"<skill_id>"}]}'
```

## Validation checklist

- [ ] Frontmatter has `name`, `description`, `metadata.bkn_scope`, `metadata.uses[]`
- [ ] Every name in `uses[]` also appears in the body table, spelled identically
- [ ] Every capability is mounted on `bkn_scope` (check with `search_capabilities` /
      `get_kn_detail`)
- [ ] Each action with side effects has `confirm: true` and a "do not execute without an
      explicit yes" step
- [ ] Steps map outputs of one call to inputs of the next explicitly
- [ ] `references/report-spec.md` ends with the capabilities used, for trace cross-checking
- [ ] Function code: `handler(event)`, no `__future__` import, parameter types from the allowed set

