Skill plays role relation

Skill roles as plays_role relation

Status: Accepted (2026-05-28)

Context

ax needs to rank skills by usage × role weight to answer "what made X work" - framing skills that open a session matter more than incidental tool-call skills. Friction F5 in the dogfood session 3d6a3531 showed that flat invocation-count ranking inverts importance: dogfood (1 invocation, produced the entire demo) ranked below batch-read-upfront (5 invocations, mechanical reads).

Two design constraints made a flat field on skill inadequate:

  1. Multi-role is natural. A skill like dogfood is both a framing skill and a producer. Encoding a single role: string loses the secondary role; encoding roles: array<string> loses per-role metadata.
  2. Source and confidence matter. A role tagged by the user (source="user") overrides a role inferred from a brief (source="brief"), which in turn outranks one parsed from frontmatter (source="frontmatter"). These priorities cannot be expressed on a flat field without collapsing provenance.

The grill decision Q3 (role: enum field vs RELATE edge) resolved to RELATE edge on both grounds.

Decision

Model skill→role linkage as a plays_role RELATION in SurrealDB:

DEFINE TABLE IF NOT EXISTS role SCHEMAFULL;
DEFINE FIELD name   ON role TYPE string;
DEFINE FIELD weight ON role TYPE float DEFAULT 1.0;
DEFINE INDEX IF NOT EXISTS role_name_uq ON role FIELDS name UNIQUE;
 
DEFINE TABLE IF NOT EXISTS plays_role TYPE RELATION FROM skill TO role;
DEFINE FIELD confidence ON plays_role TYPE float DEFAULT 1.0;
DEFINE FIELD source     ON plays_role TYPE string;        -- "frontmatter" | "brief" | "user"
DEFINE FIELD weight     ON plays_role TYPE option<float>; -- per-edge override of role.weight
DEFINE FIELD rationale  ON plays_role TYPE option<string>;
DEFINE FIELD since      ON plays_role TYPE datetime DEFAULT time::now();
DEFINE INDEX IF NOT EXISTS plays_role_in  ON plays_role FIELDS in;
DEFINE INDEX IF NOT EXISTS plays_role_out ON plays_role FIELDS out;

Role nodes are upserted lazily as briefs and frontmatter pull them in. No static taxonomy seed file is shipped; ax-owned skills declare role: in their frontmatter, third-party skills are classified via agent-filled briefs (P3.3). The weighted query traverses invoked → skill → plays_role → role and multiplies invocation score by coalesce(edge.weight, role.weight).

Consequences

Pros:

Cons: