Serving a capability
Start with a small operation that has no external effects. The public Skulk™ extension API lets a provider declare its input and output, join discovery, and handle calls inside a Skulk™ process.
This guide uses skulk.extensions. A separately supervised plugin uses the managed capability SDK, with isolated dependencies and a different lifecycle. SDK 0.4.0 and child protocol 4 support unary and all three streaming modes. See managed streaming for that path and its release requirements.
unaryOne request, one result
server_streamingRequest, progressive output
client_streamingProgressive input, final result
bidirectionalInput and output together
1. Describe the operation
The descriptor is the contract a caller discovers. Use an exact semantic version and JSON Schemas. Inputs are validated before your handler runs, outputs after it returns.
from skulk.extensions import CapabilityDescriptor
ECHO_DESCRIPTOR = CapabilityDescriptor(
id="echo", version="1.0.0", title="Echo",
description="Return the supplied text unchanged.",
io_mode="unary",
input_schema={
"type": "object",
"properties": {"text": {"type": "string"}},
"required": ["text"], "additionalProperties": False,
},
output_schema={
"type": "object",
"properties": {"text": {"type": "string"}},
"required": ["text"],
},
)
2. Implement the provider
The extension has a name, a Skulk™ compatibility constraint, and a list of descriptors. A unary call returns a JSON object.
from skulk.extensions import CapabilityCall, ExtensionContext
class EchoProvider:
name = "echo-provider"
skulk_requires = ">=2.0,<3"
def chat_middleware(self) -> None:
return None
def capabilities(self) -> list[CapabilityDescriptor]:
return [ECHO_DESCRIPTOR]
async def handle_call(
self, context: ExtensionContext, call: CapabilityCall,
) -> dict[str, object]:
return {"text": call.payload["text"]}
3. Register the extension
Add a Python entry point to your package. Install the package into a dedicated Skulk™ development environment and restart that node; entry points are discovered at startup.
[project.entry-points."skulk.extensions"]
echo-provider = "your_package:EchoProvider"
The complete reference echo package is available in the public Skulk™ repository. It includes the package layout and provider implementation. Its recorded example compatibility constraint is broader than the 2.0 constraint used here; test your own declared range.
4. Discover before calling
The tag says where to look. The descriptor says what can be called. Fetch the exact descriptor and revision on each negotiation; never invent a revision or use an old cached descriptor as proof of readiness.
curl http://localhost:52415/v1/capabilities
The result has node_id, capabilities, and revisions. revisions maps an exact id@version to its content digest. A peer can be described with ?node_id=....
Use the descriptor explorer to inspect the schema, then call the pinned contract. Extension callers normally use context.describe_node and context.call_capability, rather than handling transport themselves.
5. Make failure part of the contract
Test invalid input, output validation, deadline expiry, unavailable providers, and restart. A valid unary envelope returns HTTP 200 with ok and a typed result or error. Switch on error.code; malformed envelopes receive HTTP 422.
For all four I/O modes and the complete handler lifecycle, read the normative extension guide.