playbook-implementation-state.md8.1 KBView on GitHub
# Playbook — Implementation State

Current state of the **Deals Playbook** feature: a stage-organized deal playbook
authored in a new "tool notation" — `#trigger` callouts and `@` reference chips
instead of prose `[on:trigger]` tags. This documents what's built, what's a
demo-only hardcode, and what is **not** wired up yet.

Design spec: [playbook-doc.md](./playbook-doc.md). Shipped in commit
`b9fdbc11` (`feat: add Deals Playbook document type with #trigger and @ references`).

---

## 1. What's built

### Backend (`apps/server`)

| File | Role |
| --- | --- |
| `services/documents/document-types.ts` | Adds `playbook` to the `DOCUMENT_TYPE` behavioural enum. |
| `services/documents/doc-type-registry.ts` | Registers two logical `getDoc` types: `playbook` (the doc itself, at `user/playbooks/{aop}`) and `playbook_resource` (the docs `@` refs open, at `user/playbooks/{resources\|knowledge-base\|subagents\|org}/...`). Each has a `parsePath` + `seedFn`. |
| `services/documents/playbook-seed.ts` | Builds the example **Deals** playbook as ProseMirror JSON (stages, `#trigger` callouts, `@` chips, `#slack` chips, tables, Exit-criteria checkbox callouts). |
| `services/documents/playbook-resource-seeds.ts` | Authored markdown content for every `@` reference target (overall-goal, email-style, templates, coaching-framework, discovery-guide, knowledge-base, company-background, org/deals-playbook, two subagents). Parsed to PM JSON via `parseMarkdown`. |
| `db/documents-schema.ts` | Comment-only: documents the new behavioural type. No migration (type enforcement is TS-only). |

Custom nodes persist **schema-agnostically**: `prosemirrorJsonToYDoc`
(`document-saving/hydrate.ts`) maps any node type → `Y.XmlElement`, so the
client-only `triggerNode` / `referenceNode` / `integrationNode` / `blockCallout`
round-trip into the editor where their React NodeViews render them.

### Frontend (`apps/mail/modules/documents/playbook/`)

| File | Role |
| --- | --- |
| `TriggerNode.tsx` | The `#trigger` **callout**. Content-bearing block; header shows an `on:…` badge derived from the config and toggles an inline editor that reuses `TriggerSection` (select type → details, as in agent config). |
| `IntegrationNode.tsx` | Inline `#slack` / `#imessage` / `#mcp` chips. |
| `ReferenceNode.tsx` | Inline `@` chip. Clickable (except system tokens) — calls `options.onOpen(path)`. |
| `BlockCalloutNode.tsx` | Block callout that can hold block content (used for **Exit criteria** + checkbox `taskList`). |
| `HashMention.ts` / `HashMentionList.tsx` | The `#` suggestion menu (trigger + integrations). |
| `ReferenceMention.ts` / `ReferenceMentionList.tsx` | The `@` suggestion menu (resources, KB, org, subagents, system tokens). |
| `references.ts` | Reference namespace metadata (icon/accent), `isOpenableReference`, `resolveReferencePath`, and the `@`-menu option list. |
| `playbookExtensions.ts` | Assembles all nodes + both mention menus; `createPlaybookExtensions({ onOpenReference })`. |
| `PlaybookDocument.tsx` | The standalone surface — `getDoc` by path → `<Document>` with the extensions. |

### Surfaces / entry points

- **`/agents/playbook`** — dedicated page rendering the real `user/playbooks/deals` doc.
- **`/brain`** — `CompanyExplorer.tsx` detects `documentType === 'playbook'` and appends the playbook extensions to the standard editor (and drops the default conversation `@` mention). Clicking a `@` chip resolves → `getDoc` (`playbook_resource`) → opens the doc in the file viewer.

---

## 2. The tool notation

- **`#` menu** → `#trigger` (block callout), `#slack` / `#imessage` / `#mcp` (inline chips).
- **`@` menu** → `referenceNode` chips. Namespaces: `resources/`, `knowledge-base/`, `org/`, `subagents/`, plus the reserved system tokens `crm-updater` / `next-steps`.
- **Trigger config ↔ badge**: `event_occurred`(eventTypes) → `on: email/meeting/slack/crm-sync/any`; `before_meeting` → `on: before-meeting`; `cron` → `on: cron`; `conversation_change` → `on: field-change`.

---

## 3. What's hardcoded / demo-only

> The committed **code** contains no hardcoded user/org IDs — `seedFn`s scope to
> the authenticated session. The hardcoding is in **content** and **manual DB
> seeding**, listed here.

1. **The Deals playbook content is a fixed example.** `playbook-seed.ts` only
   seeds a non-empty doc for `aop === 'deals'`; every other AOP seeds empty. The
   content (stages, templates, copy) is illustrative Cedar-sales content, not
   derived from real deals.

2. **Resource-doc content is authored, not generated.** `playbook-resource-seeds.ts`
   is brief example copy. Unknown reference paths fall back to a titled stub.

3. **Manually-seeded DB rows for one user.** For the live demo, rows were inserted
   directly into the DB for `<email>` (a `playbooks` folder, the
   `deals` playbook, and all 10 resource docs, nested under the folder). These are
   **demo data**, not produced by any app flow. A fresh user gets content only via
   the on-demand `seedFn` when they open `/agents/playbook` or click a reference.

4. **Reference → path mapping is fixed.** `resolveReferencePath` maps every
   `@ref` to `user/playbooks/<ref>` (anchors stripped). All `@resources/templates#…`
   anchors collapse to one `resources/templates` doc.

5. **`@org/…` is modeled under user scope.** For the demo it resolves to
   `user/playbooks/org/...` (a user doc), not a real org-shared document.

---

## 4. What is NOT wired up (visual/config only)

- **Triggers don't execute.** A `#trigger` stores a `TriggerConfig` but nothing
  reads it — no orchestrator subscribes a stage to events based on these blocks.
  Pure configuration UI.
- **System tokens are inert.** `@crm-updater` / `@next-steps` render as styled,
  non-clickable chips. The design calls for them to surface a **live** field
  schema / task list; that live block is not implemented.
- **Integration chips are inert.** `#slack` / `#imessage` / `#mcp` have no backend
  — no channel linking, no send.
- **Subagents are content only.** `subagents/bant-coach` and
  `notify-solutions-engineer` are docs with YAML frontmatter as **text**; they are
  not registered, parsed, or executable.
- **`TriggerSection` runs with empty field defs.** The `conversation_change` and
  conversation-field-filter panels show their "add fields first" empty states,
  because no AOP field schema is passed in.
- **No markdown round-trip for these types.** Neither `playbook` nor
  `playbook_resource` defines `jsonToMarkdown`, so the `content` (markdown mirror)
  column is best-effort / empty on server-seeded docs; the Y.js blob is the source
  of truth.
- **Resource docs render with the generic editor.** Opening a `@` target in
  `/brain` uses the standard document editor (they're stored as `document`), so any
  `@`/`#` text inside them is plain text, not chips.

---

## 5. How seeding actually works

1. **On-demand (general):** `getDoc({ documentType, path })` find-or-creates by
   `(orgId, userId, path)`. Missing → the registered `seedFn` runs
   (`buildDealsPlaybookJson` or `getPlaybookResourceJson`) and the result is encoded
   to a Y.js blob and persisted. Works for any user; content appears on first open.
2. **Pre-seeded (this demo):** the same JSON was encoded and inserted directly so
   the rows exist immediately for Jesse, nested under a `playbooks` folder.

Replacing seed content for an already-open doc is done by inserting a **fresh row**
(new id) so there's no stale client-side Y.js/IndexedDB state to merge against.

---

## 6. Suggested next steps to productionize

- Drive a real folder/AOP structure (`Org` vs `Personal` playbooks) instead of the
  flat `user/playbooks/...` demo layout.
- Make system tokens live: render `@crm-updater` with the AOP field schema and
  `@next-steps` with the current task list.
- Have the orchestrator read `#trigger` blocks to actually subscribe stages to
  events (the design's "presence of a trigger block determines subscription").
- Register `@subagents/*` from their frontmatter so `when_to_use` / `enabled` mean
  something.
- Pass real conversation/custom field defs into `TriggerSection`.
- Add `jsonToMarkdown` for `playbook` so the markdown mirror + search index are
  populated.