Data-First Skills¶
The Op/skill-declaration split — executable Ops registered by stable ID, inert skill declarations that reference them, and the loader + load-time validator that resolves declarations against the Op registry.
Details¶
What¶
A skill has two halves that work-buddy keeps separate:
- An Op — the executable callable. Python code, registered under a stable
op.<namespace>.<name>ID in the Op registry (work_buddy/mcp_server/op_registry.py). Built-in ops use theop.wb.*namespace and are organized by category underwork_buddy/mcp_server/ops/— one module per category, registering its ops as an import side effect. - A skill declaration — inert data. A
kind: "skill"knowledge-store unit carrying prose (name, description, aliases), the parameter schema, runtime metadata (mutates_state,retry_policy,requires,consent_operations,invokes,is_action,intrinsic_amplifiers,param_aliases,auto_retry,slash_command), and anopfield naming the Op it wraps.
The Workflow side uses the same broad data/code separation through a different compiler seam. A kind: workflow unit is inert authored data; registry compilation produces a WorkflowDefinition, resolves executable aliases and Workflow references to stable definition IDs, computes a revision, and exposes invocation through WorkflowService. A Skill declaration resolves to an Op-backed callable Skill; the shared registry currently holds these as distinct entry types.
Registration¶
Every skill is a declaration. The skill loader (work_buddy/knowledge/skill_loader.py) reads each kind: "skill" unit, resolves its op against the Op registry, checks that schema_version is recognized (wb-skill/v1), and validates the declared parameter schema against the resolved callable's signature. Resolved skills are merged into the gateway registry by _build_registry() before the tool-requirements filter pass, so declared skills with unmet requires are filtered by the same logic as any other.
Op registry¶
work_buddy/mcp_server/op_registry.py is an in-memory table keyed by op ID:
register_op(op_id, fn, *, replace=False): registers a callable. It validates theop.<namespace>.<name>grammar and rejects a duplicate ID unlessreplace=True. Whileload_builtin_opsreloads a module, registering the same function again replaces its earlier entry, and any other duplicate still raises.get_op(op_id)/list_ops(): lookup.load_builtin_ops(): imports thework_buddy/mcp_server/ops/package, whose modules register their ops as an import side effect. A module that is already imported is reloaded instead, which re-runs its registrations and restores its ops afterclear_ops.register_op_effects(op_id, effects)/get_op_effects(op_id): for skills with multi-effect manifests. AnEffectSpecholds aresolvercallable, so it cannot ride in a data declaration. The ops module registers the manifest, and the loader threads it onto the resolvedSkill.
The Op table (_OPS) survives a data-only reload_skill_data (no purge — declarations just re-resolve against the same callables). The dormant invalidate_registry purges work_buddy.* from sys.modules, which rebuilds _OPS fresh on the next registry build.
Load-time validation¶
Resolution failures (missing op, unknown schema version, signature mismatch) are reported by the skill_op_resolution check in docs_validate as warnings — an unresolved declaration is surfaced but does not block the store. The live-store invariants in tests/unit/test_skill_declarations_invariant.py promote these to hard CI errors: every skill unit must be a declaration, every op id must be registered and well formed, every declaration must resolve with zero issues, and the resolved count must equal the unit count (no silent drops).
Why the split exists¶
Welding prose to Python source blocks three things: editing a skill's description through gateway tools, shipping skills as inert shareable artifacts, and letting an agent synthesize a skill without writing Python. A declaration has none of those limits — its prose is editable via the docs_edit workflow, it is inert data safe to share, and an agent can author one. This is the executable/inert seam recorded as commitment C1 in the ecosystem architecture.