Personas ground on teammates via injected cross-surface resolution — never by minting team atoms into knowledge/

0066-persona-grounding-cross-surface-team

decision read as Explain confidence asserted status active 2026-06-20 owner knowledge-architect
Reversibility
two-way door

DEC-0066 — Personas ground on teammates via cross-surface resolution, not by minting team atoms

Reversibility: two-way door. The change is an additive, optional injected resolver plus an additive surface field on PersonaNeighbor (defaulting to 'atom', so no existing reader breaks). No atom ids are minted; nothing in knowledge/ changes; no schema migration. Remove the resolver and neighbours revert to unresolved — exactly the pre-decision behaviour. (Contrast Option 1, which would mint immutable role/agent atom ids — permanent addresses you can only deprecate, never cleanly remove.)

Context — the verified gap

A persona is compiled (compilePersona, packages/runtime/src/persona.ts) by traversing a role's OKF-graph neighbourhood (DEC-0053 Invariant 7, real-edge traversal). But a role's edges also reach its teammates (the expert subagents) and the capabilities it wields (skills) — and those live as build-side operational files (.claude/agents/<id>.md, .claude/skills/<id>/SKILL.md), not as knowledge/ atoms. So an ADR's relates_to: [..., log-auditor] reaches log-auditor, but the persona compiler — which loads only knowledge/ atoms — can't resolve it.

Verified this session against the real, full KB: the forward-deployed-engineer persona grounds on 88/89 reachable targets; the one unresolved target was log-auditor (an agent). Structurally, a persona could never ground on any teammate. This is not link rot — pnpm kb:check is clean because scripts/kb-check.mjs already registers agent/skill stems as known nodes (collectStemIds, ~L162–179) so edges to them aren't flagged. It is a persona-grounding-fidelity gap, and it bites worst exactly where it matters: the fde role's whole job is routing to teammates.

(Distinct from DEC-0053's 41-load-error caveat, which was a reversibility-enum schema issue resolved by DEC-0017 — that class is 0 now, verified. This decision is only the structurally-ungroundable team class.)

The client-vs-Dossier distinction (the constraint that decides it)

This looks like a knowledge-model problem but is really a sovereignty boundary problem. knowledge/ is the client's owned, portable institutional memory (DEC-0001). Dossier's log-auditor/principal-architect/forward-deployed-engineer are Dossier's build-side fleet (DEC-0005). Modelling them as atoms in knowledge/ would make them indistinguishable from real org roles on this dogfood repo, and would invite a client tenant's knowledge/ to carry Dossier's subagents — backwards. A client's personas ground on the client's roles (their own org), never on Dossier's fleet. Whatever we build must be correct for an arbitrary client tenant.

Options considered

  1. Mint the team as role/agent atoms in knowledge/. Rejected. Duplicates the description the .claude file already owns (drift; violates single-source-of-truth — knowledge/model principle "never copy, reference"); puts Dossier's build-side fleet into the client-memory surface (sovereignty); mis-types build machinery as org concepts; doesn't generalize to client tenants.
  2. Teach compilePersona an injected cross-surface resolver that reads the agent/skill operational files as ephemeral grounding nodes. Chosen.
  3. Leave it. Rejected. Defensible for arbitrary dangling targets, but not when the single unresolved class is "your own team," for a role defined by teamwork.

Decision

compilePersona gains an optional, injected resolveCrossSurface(id): CrossSurfaceNode | undefined. For a reachable neighbour with no loaded atom, the compiler consults the resolver; a returned node becomes a resolved grounding neighbour tagged with its surface provenance (agent/skill). PersonaNeighbor gains a surface field (atom default) so the floor stays auditable — you can always see which grounding is the client's own memory vs. a referenced build-side teammate. Cross-surface grounding counts toward the floor (a teammate is real grounding).

Purity is preserved as a seam. compilePersona stays fs-free (its zero-credit unit tests depend on that); the resolver is injected. The impure implementation, buildCrossSurfaceResolver(claudeDir) (packages/runtime/src/cross-surface.ts), reads .claude/agents/*.md (id = stem) + .claude/skills/*/SKILL.md (id = dir) once into a sync id→node map, pulling each file's own name/description frontmatter — read, never copied. The AgentSdkBoardWorker supplies one by default (built from tenant.tenantRoot/.claude); tests inject a map.

Generalises to clients for free: a client tenant with no .claude/agents resolves nothing — byte-identical to the pre-decision behaviour. Dossier's own repo gets its team grounded.

SSOT / drift

No drift by construction: the team's SSOT remains .claude/agents/<id>.md and .claude/skills/<id>/SKILL.md; the compiler reads it, never mints an atom. This extends the same "referenceable across surfaces" treatment kb:check already took (known-node handling) from "don't flag as rot" to "resolve as grounding," closing the seam consistently across both tools.

Verification (built + proven, no fabricated status)

  • compilePersona + buildCrossSurfaceResolver + the worker wiring implemented; @dossier/runtime typecheck clean; 135 tests pass (1 live-skip), including 3 new cross-surface cases (resolver resolves a teammate, tags surface, counts toward floor; a loaded atom is unaffected and the resolver is never consulted for it).
  • scripts/agency-phase0-dogfood.mjs re-run (exit 0): the forward-deployed-engineer persona now grounds 89/89 — zero dangling targets (was 88/89 with log-auditor unresolved). The dogfood preflight was also decoupled to compile against the full real KB (not the 6-dir drain seed) so the grounding proof reflects ground truth.
  • pnpm kb:check clean.

Consequences

  • Personas can ground on the teammates a role routes to — the "grounded in the OKF graph" claim is now true for the team too, without polluting knowledge/.
  • surface makes every grounding's provenance auditable (client-atom vs. build-side teammate).
  • The knowledge/model convention — agent/skill operational files are cross-surface nodes, referenceable + groundable, never minted into knowledge/ — is owned by the Principal Knowledge-Format Architect to record in knowledge/model/index.md; this ADR is its decision record meanwhile.
  • One small duplication remains acceptable: the stem-collection logic exists in both scripts/kb-check.mjs and cross-surface.ts (a readdir of two dirs); factoring them into one shared helper is an optional follow-up, not a blocker.