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:
- Type is a field, not a judgment made fresh each run. The capture skill writes it down, the agent verifies it, downstream steps read it. The failure it prevents is a client-facing note going out on an internal call.
- Unclear is a first-class value, not a fallback. An unclassifiable session produces the capture artifact, writes nothing outbound, and states what would be needed to classify it.
- Route / Filter / Hold — the three kinds of decision any teammate makes. What does this session need · what actually counts · what needs Kathryn's yes.
- Act on the reversible, hold the irreversible. Already load-bearing in our own IP — it appears in the 8/13 brief and governs
drive_place.py. A principle, not a phrase.
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.
| Axis | Answers | Sourced from | Determines |
|---|---|---|---|
engagement_type | which body of work this belongs to | the engagement record (lookup) | which artifacts exist, where they land, who receives them |
session_purpose | what this session was for | the 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
| Value | Artifacts | Notes |
|---|---|---|
aos-client | digest · recap email · session agenda · ledger merge · Drive publish · board card | RC, JB, NA |
paid-1on1 | digest · recap email · personal notes · prep email · calendar | Saunders; /recap-1on1 today |
tpc-cohort | session JSON · recap · preview · session notes .docx · member files | /recap-tpc today |
prospect | call notes · scope/SOW draft | scope-to-sow today |
internal | notes only. No external note. | team sessions, build sessions |
session_purpose — values
| Value | Recap shape leads with | Observed in |
|---|---|---|
spec | the decisions, written as rules | Kevin 8/13 |
working-session | the ground covered | Jenn 8/14 |
decision | the choice ratified and what it displaces | — |
check-in | what moved; short note, no decisions section | Kevin 8/7 |
kickoff | the engagement frame | — |
unclear | nothing. 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/, 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.
| Version | Rule | How it failed |
|---|---|---|
| v1 | Match a frozen golden | The 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.0 | Copy 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 section | Over-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-facing | fixed — decisions/outcomes · surfaced actions split by owner · next session · forward agenda. Wins, open questions, advisor notes, deliverable inputs: never |
| Whether a section appears | content — has content, appears; empty, omitted |
| Whether a fact may be stated to this recipient yet | assertable — see below |
| How outcomes render | session_purpose — spec → decisions as a rules list; working session → named topic sections |
| What sections are called | her 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
| Case | Result |
|---|---|
| Kevin 8/13 — spec, 7 decisions | reproduces the sent shape |
| Jenn 8/14 — working session, 8 outcomes as named topics | reproduces the sent shape |
| Kevin 8/7, sent 8/12 — decisions held pending Ruben | fails without assertable (adds a decisions section that should not be there); correct with it |
What would falsify it
- A recap where two sessions with identical content, purpose and assertability should still carry different sections. That would mean structure is not derivable and something else is deciding it.
- A section Kathryn wants that maps to no content category — i.e. the fixed client-facing list is wrong, not just incomplete.
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
| Piece | State |
|---|---|
advisory-sessions plugin, 7 skills, 1 agent | built, v1.3.0, committed 2026-08-15 |
| Engagement record spec | written, reference/engagement-record.md |
gateclaims.py, verifyrun.py, render_digest.py | built |
| v2 cascade (six phases, one gate) | built, NA + JB, unproven live |
session-intake writes a type | built — wrong axis (OWNER/TEAM = audience) |
| Ledger JSON schema | divergent. 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 outcomes | JB + NA have the concept; RC does not |
| Per-client QC verifiers | ~20 per repo, two registries, documented in cascade.config.json |
Build order
- Unify the ledger schema, and add
assertable+assertable_reasonto outcomes. RC and JB disagree today (objects withstatusvs 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. - Engagement records. One per live engagement. Nothing runs without them. Start with the ones that have live sessions: Crulliance, Jenn, Elite, Saunders.
- Add both fields to
session-ledger.engagementtypelooked up,sessionpurposederived. Both written to the ledger; both surfaced in the digest so the gate covers them. - 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. - Split the agent in two.
session-managerandrecord-keeper, with the gate between. - 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
- Plugin name.
advisory-sessionsis accurate for AOS, 1:1 and TPC; it stretches forprospect. Renaming costs a reinstall everywhere and gains a word. Recommend keeping it. decisionandkickoffpurposes are carried from Kathryn's vocabulary, not yet evidenced in a sent recap. Confirm they are real shapes before building routing for them.- Where the QC verifiers live once the per-repo cascades retire. Two registries exist today (
cascade.config.jsonfor digest-time, therecap-emailfrontmatter for send-time) andpreflight.pyshells out to neither.
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:
| Run | n | Item type | Keys |
|---|---|---|---|
| RC 8/13 | 9 | dict | text status audience evidence |
| RC 8/07 | 6 | dict | text status audience |
| JB 8/14 | 8 | str | — |
| JB 8/07 | 7 | dict | text status audience |
| NA 8/11 | 8 | dict | outcome status audience evidence text |
| NA 8/04 | 5 | dict | outcome audience — no 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.
| Field | Answers | RC 8/07 |
|---|---|---|
audience | who may see it | all six — Kevin |
status | how settled in the room | four — decided |
assertable | may it be stated to them yet | four — 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.