Skip to content

Add portable {skill:} and {subagent:} invocation tokens #898

Description

@williamthorsen

Problem

Skills invoke other skills and subagents in prose ("invoke the capture-event skill"). Nothing marks a genuine runtime invocation as distinct from a passing mention, and the invocation's rendered form is not adapted per harness. A skill's runtime dependency on another artifact can only be captured by hand-declaring it in dependencies: frontmatter, separately from the prose that performs the invocation, where the two can drift apart.

Context

{tool:NAME} tokens already solve the analogous problem for tools: they are rewritten at deploy time in the render chain (tool-name-rewriter.ts), failing the run on an unmapped name. The harness-static tokens {harness_id} and {harness_home_dir} are the closer precedent: they render from per-harness values held in the typed HarnessConfig table (harness.ts), not from the overlay's per-name map. resolveClosure (dependency-resolver.ts) computes the deploy closure from frontmatter edges and fails the run on an edge pointing at a missing artifact. This ticket is a prerequisite for the cross-artifact dependency audit (#879), whose audit emits these tokens.

Proposed solution

Introduce {skill:<slug>} and {subagent:<slug>} tokens (kebab-case slugs), rendered in the deploy render chain alongside {tool:NAME}, in both the skill and subagent transforms.

Render via a per-harness sigil held in HarnessConfig (the home of the comparable {harness_id} / {harness_home_dir} values), not the overlay's per-name map, since the slug is identical across harnesses:

  • {skill:<slug>} renders to the harness skill sigil plus the slug (Claude /, Rovo !).
  • {subagent:<slug>} renders to the harness subagent sigil plus the slug. That sigil is empty on both current harnesses, so it renders to the bare slug, which is how a subagent is dispatched on each; the field exists for uniformity and future harnesses.

Because the sigils live in the typed HarnessConfig record rather than the overlay, a harness with no configured rendering is a compile error, stronger than a deploy-time check.

Make resolveClosure union body tokens with dependencies: frontmatter, so a tokenized invocation is itself a dependency edge. Extract tokens from the include-expanded body, the same surface the render pass emits, so an invocation inside a shared _partials file becomes an edge for every skill that includes it. The resolver's existing missing-artifact check validates token targets, so a token naming a non-existent slug fails the run. An inline invocation is then expressed once, as the token; dependencies: remains for non-inline edges.

Scope the tokens to skills and subagents, the types that pass through the render pass. Rulebooks (whose bodies are embedded without that pass) and collections keep dependencies: / members:. A token in a standalone skill sub-document that no skill includes still renders but is not edge-validated; the convention is to place cross-artifact invocations in the main body or a partial. (#902 adds a lint to catch untokenized invocations.)

Acceptance criteria

Must have

  • {skill:<slug>} and {subagent:<slug>} render to their harness-appropriate form in the deploy render chain, for both skills and subagents.
  • The per-harness sigils live in the typed HarnessConfig table, so a harness missing a sigil fails to compile.
  • resolveClosure treats a body token as a dependency edge, unioned with dependencies: frontmatter, extracted from the include-expanded body.
  • A token naming a non-existent artifact fails the run.
  • The token grammar, per-harness render forms, the sigil's HarnessConfig home, and the auto-edge behavior are documented in authoring guidance.
  • New and modified behavior is covered by tests.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions