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 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
- 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. - Pick capabilities by name — metrics and action types from
get_kn_detail/get_object_types, functions and MCP tools fromsearch_capabilities. Use the registered names verbatim; the agent searches by name. - 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). - 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.
- Mark confirmations — any action with side effects gets
confirm: trueand the sentence "do not execute without an explicit yes". The platform does not gate this; the skill does. - Add
references/report-spec.md— the output shape, including the "依据" line naming the capabilities used so the user can check the trace. - Register, publish, mount — see Delivery. A skill is only visible to an agent inside networks it is mounted on.
- Verify end-to-end — one managed interaction should show
get_skill_content→query_metric→execute_tool→execute_actionunder the sameinteraction_id, andbkn_finish_interactionshould returnevidence_status: complete.
Template: assets/templates/SKILL.md.template · assets/templates/report-spec.md.template. Worked example (verified on a 0.1.5 deploy): 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_operationscontainsmatch) may reject==at the resource layer; say so in the step that locates rows. - Finding skills:
search_capabilitieswith a query ranks functions first; passtypes: ["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:
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:
- The entry point must be
handler(event);eventis the tool's input object. - No
from __future__ import ...— the sandbox prepends a wrapper and the import becomes aSyntaxError. - Tool parameter
typeis one ofstring/number/boolean/array/object;integeris rejected with 400. - Reads made through bkn-osdk inside the function land on the caller's interaction as children
of the
execute_tooloperation — no extra wiring.
Skeleton: assets/templates/function.py.template.
Full example (BOM level-1 kitting check):
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:
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_toolcarries the sandbox's managed turn, so the callee's sandbox gets the same caller credential and the trace shows the callee under this function'sexecute_tooloperation, 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 isbkn_osdk.call("/api/agent-retrieval/v1/kn/execute_tool", ...), and the request body must carry abkn_contextbuilt fromBKN_CONVERSATION_ID,BKN_INTERACTION_IDandBKN_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.
Code → tool (openbkn CLI 0.1.5+)
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):
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
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 withsearch_capabilities/get_kn_detail) - Each action with side effects has
confirm: trueand a "do not execute without an explicit yes" step - Steps map outputs of one call to inputs of the next explicitly
-
references/report-spec.mdends with the capabilities used, for trace cross-checking - Function code:
handler(event), no__future__import, parameter types from the allowed set