← Vault Index
Source: frameworks/plugin-engagement-manager/01-engagement-manager-build-spec.md

Engagement Manager — Build Spec

Revises: 00-engagement-manager-brief.md (2026-08-13). The brief stands as the engagement portrait and the Route/Filter/Hold thinking. This file replaces Parts 3–4 — the skills list and the single-agent design — with a two-axis, two-agent architecture that covers every kind of call Kathryn runs, not just paid 1:1s.

Date: 2026-08-15 Plugin: advisory-sessions (exists, v1.3.0). Widened, not replaced. Status: spec. Nothing built against it yet.


Why this revision

advisory-sessions v1.3.0 was built against one engagement (Saunders) and one shape (a paid 1:1). Kathryn runs at least four kinds of post-session work — AOS client engagements, paid 1:1s, TPC cohort sessions, and prospect calls — and they share a job while producing different artifacts.

Provenance — read before extending this. The routing pattern below is adapted from a third-party reference implementation (Build Lab, 2026-08-12), licensed to Kathryn for use inside her own business. See business-aos/_references/third-party-ip.md for the boundary. The four ideas are restated here in our own terms; nothing verbatim from that source belongs in this repo, because this repo feeds material we sell.

Four things it gets right, and we take:

What does not transfer: that implementation derives everything from the transcript and carries one type axis. Neither works here — see Changes 1 and 2.


Change 1 — two type axes, not one

The reference implementation carries a single call-type axis, and its values describe the relationship — an existing client, a prospect, a partner, internal. That under-determines our work: a Crulliance session and a TPC Momentum Monday sit at the same point on that axis and produce nothing alike.

AxisAnswersSourced fromDetermines
engagement_typewhich body of work this belongs tothe engagement record (lookup)which artifacts exist, where they land, who receives them
session_purposewhat this session was forthe transcript (derived)the shape of the recap and which sections exist

Why they must be separate. Kevin 8/13 and Kevin 8/7 are the same engagement, same participants, same relationship — and produced a seven-decision spec and a nothing-decided check-in respectively. One axis cannot tell them apart, and the 8/13 run only got the right shape because it reasoned its way there by hand and recorded doing so.

engagement_type — values

ValueArtifactsNotes
aos-clientdigest · recap email · session agenda · ledger merge · Drive publish · board cardRC, JB, NA
paid-1on1digest · recap email · personal notes · prep email · calendarSaunders; /recap-1on1 today
tpc-cohortsession JSON · recap · preview · session notes .docx · member files/recap-tpc today
prospectcall notes · scope/SOW draftscope-to-sow today
internalnotes only. No external note.team sessions, build sessions

session_purpose — values

ValueRecap shape leads withObserved in
specthe decisions, written as rulesKevin 8/13
working-sessionthe ground coveredJenn 8/14
decisionthe choice ratified and what it displaces
check-inwhat moved; short note, no decisions sectionKevin 8/7
kickoffthe engagement frame
unclearnothing. Produce the digest, write nothing, say what is needed to tell

spec, working-session and check-in are evidenced in sent mail. decision and kickoff are Kathryn's own vocabulary from the archived advisory-engagement plugin, which asked the human "What type of meeting — kickoff, seasonal, working session, other?" and used the answer for the subject line only.

Multi-principal is not a purpose. Who receives what is a recipients fact, derived from the engagement record's principals plus who was actually present. Do not add it as a type value.


Change 2 — engagement type is looked up, never inferred

The reference implementation reads the call and works out what it was. That holds when a person appears in one context. Ours do not.

Today it is inferred from where the person's folder lives, which is why an 8/12 succession session ran as a TPC cohort call and three outputs had to be suppressed by hand. — 00-engagement-manager-brief.md, Part 2

The engagement record is the precondition. engagements//engagement.json, specced in advisory-sessions/reference/engagement-record.md. A person may appear in more than one engagement; their engagements do not merge. Bev is a TPC member and a separate paid succession engagement.

Nothing in this plugin runs without a record. The failed 8/14 run failed for exactly this reason — a record in another workspace is invisible, and the stop now says so.

The split, stated once: durable facts come from the record; per-session facts come from the transcript. engagementtype is durable. sessionpurpose is per-session.


Change 3 — two agents, split by job

The test: if the job description needs an and also, it is two jobs.

Owns what happens after a call, and also merges the constraint ledger, and also renders five living documents, and also publishes to the client Drive, and also writes the board card.

Two jobs. Split them:

session-manager — processes the session

Capture, classify, produce what the session owes. Everything it makes is reversible or a draft.

Owns: session-ledger · session-digest · internal-record · session-recap · session-prep

record-keeper — maintains the durable record

Ledgers, living documents, publishing. Runs on demand, not on a schedule — the /render separation already established for NA.

Owns: ledger-merge · render · publish · board-write

Not one agent per engagement type. AOS, 1:1 and TPC are the same job with different artifacts — that is what engagement_type is for. Four agents would be four copies of one routing rule drifting apart, which is precisely what the three per-repo cascades already did.


Routing

Written by session-ledger, verified by session-manager, read by everything downstream.

session-ledger        → writes engagement_type (from record) + session_purpose (from transcript)
session-manager       → sanity-checks both, decides which skills run
  engagement_type     → which artifacts, which recipients, where they land
  session_purpose     → the recap's shape and lead
  purpose = unclear   → digest only. Write nothing. Say what is needed to tell.
  type = internal     → no external note, ever
record-keeper         → runs after the gate, on the approved digest, never before

One gate, and it sits between them. Kathryn approves the digest. session-manager stops there; record-keeper never runs on an unapproved source. That is the v2 cascade's phase 3, kept.


Change 4 — where the recap's shape comes from

This is the fourth attempt at this rule. The first three each failed in a way nobody predicted, so this section states what killed them and what would falsify this one.

VersionRuleHow it failed
v1Match a frozen goldenThe golden lost its Action Items section in June; every recap after faithfully reproduced the absence for two months. verify_structure.py still fails recaps Kathryn wrote and sent herself
v2.1.0Copy the last approved recap of the same type (owner/team)"Type" is the audience axis. A narrative working-session recap and a spec recap are both "owner", so the wrong exemplar passes the filter. And the first recap of any purpose has no source at all
v3 (rejected)Every content category gets a sectionOver-generates. Kevin 8/13 had 8 content categories present and 4 sections sent

The rule: four questions, four different sources. None of them is "copy the last email."

Source
Which categories are client-facingfixed — decisions/outcomes · surfaced actions split by owner · next session · forward agenda. Wins, open questions, advisor notes, deliverable inputs: never
Whether a section appearscontent — has content, appears; empty, omitted
Whether a fact may be stated to this recipient yetassertable — see below
How outcomes rendersession_purpose — spec → decisions as a rules list; working session → named topic sections
What sections are calledher sent mail — the only thing that layer is asked for. "Your action items", "On my list"

assertable — the field that does not exist yet

A fact can be decided in the room and still not be safe to state to the recipient. Kevin's 8/7 session decided four things; the recap went out on 8/12, five days late, with no decisions section, because the decisions were pending Ruben. Omitting them was correct — Kevin should not read as settled something his CEO had not weighed in on.

The ledger cannot express this. RC's actions carry recapSurface; its outcomes do not. So the only states are present and absent, and the reason has to live in prose — where it was written down wrong: "the 8/7 session decided nothing", which is false, and which then became the stated precedent for a shape decision on 8/13.

outcome.assertable         : true | false
outcome.assertable_reason  : "pending Ruben — cross-lane decision"

JB and NA already have the concept as client_safe"the renderer refuses to put it in a client view." RC does not. Unify them.

Tested against three approved sends

CaseResult
Kevin 8/13 — spec, 7 decisionsreproduces the sent shape
Jenn 8/14 — working session, 8 outcomes as named topicsreproduces the sent shape
Kevin 8/7, sent 8/12 — decisions held pending Rubenfails without assertable (adds a decisions section that should not be there); correct with it

What would falsify it

What it does not decide: what leads. Kathryn, 2026-08-12: "I like the bottom line up front when there are things that I need their attention on. Sometimes I've done the done things because we actually need to show a win." That is a relational call. Purpose sets a default order; the lead stays her judgment and gets surfaced in the handoff, not decided by a rule.

Why this failure mode is at least visible. v1 and v2.1.0 inherited structure from a source, and a wrong source fails silently — nothing in the draft tells you the exemplar was wrong. A derivation fails loudly: it produces a section that should not be there, or omits one that should, and it is visible in the draft at the gate. That is not an argument that this rule is right. It is an argument that the next time it is wrong, someone will see it.


What already exists

PieceState
advisory-sessions plugin, 7 skills, 1 agentbuilt, v1.3.0, committed 2026-08-15
Engagement record specwritten, reference/engagement-record.md
gateclaims.py, verifyrun.py, render_digest.pybuilt
v2 cascade (six phases, one gate)built, NA + JB, unproven live
session-intake writes a typebuilt — wrong axis (OWNER/TEAM = audience)
Ledger JSON schemadivergent. RC outcomes are objects with status; JB outcomes are bare strings. No shared derivation rule runs across both until this is unified
assertable / client_safe on outcomesJB + NA have the concept; RC does not
Per-client QC verifiers~20 per repo, two registries, documented in cascade.config.json

Build order

  1. Unify the ledger schema, and add assertable + assertable_reason to outcomes. RC and JB disagree today (objects with status vs bare strings), so nothing derived can run across both. This is ahead of everything else because both the routing and the shape rule read from it.
  2. Engagement records. One per live engagement. Nothing runs without them. Start with the ones that have live sessions: Crulliance, Jenn, Elite, Saunders.
  3. Add both fields to session-ledger. engagementtype looked up, sessionpurpose derived. Both written to the ledger; both surfaced in the digest so the gate covers them.
  4. Derive the recap's shape per Change 4 — categories fixed, appearance from content, gating from assertable, rendering from purpose, names from her sent mail. This replaces v2.1.0's shape-from-last-approved-recap. Recipients keep their existing lookup, which is correct and unchanged.
  5. Split the agent in two. session-manager and record-keeper, with the gate between.
  6. Retire the per-repo cascades once one live cycle has run clean end to end.

Steps 0–3 are additive and can ship without touching anything currently running. Step 3 is the one with a track record of failing; run it in shadow against the next two live cycles — produce the derived shape, diff it against what Kathryn actually sends, and change nothing until the diff is boring.


Open


Step 0 — measured against the data, 2026-08-17

Two claims above were checked against the session JSONs and are wrong. The build order stands; what step 0 is changes.

The divergence is per-run, not per-repo. conversation.outcomes[], last two runs each:

RunnItem typeKeys
RC 8/139dicttext status audience evidence
RC 8/076dicttext status audience
JB 8/148str
JB 8/077dicttext status audience
NA 8/118dictoutcome status audience evidence text
NA 8/045dictoutcome audienceno status

JB wrote objects on 8/07 and bare strings on 8/14. NA dropped status between 8/04 and 8/11. NA also keys the body outcome, not text. So this is drift week to week inside one repo, not a per-repo convention — and the recorded safe order (migrate one repo, verify, then the second) does not hold: a migrated repo re-drifts on its next run. Step 0 is a normalizer plus a write-time validator, not a one-time migration. Nothing derived can run across these until the writer is constrained.

clientsafe is prose, not data. No JSON in RC, JB or NA contains clientsafe, clientSafe or assertable. NA's CLAUDE.md describes the concept as live; no run has written it. assertable is therefore new in all three repos — there is no "unify RC up to JB and NA."

assertable is a third axis, and RC 8/07 proves it. All six outcomes carry audience: client:kevin; four carry status: decided; the sent recap had no decisions section.

FieldAnswersRC 8/07
audiencewho may see itall six — Kevin
statushow settled in the roomfour — decided
assertablemay it be stated to them yetfour — no, pending Ruben

Neither existing field can carry it. The derivation rule without assertable generates a four-item decisions section that Kathryn correctly did not send — the failure predicted in Change 4, confirmed in the data.