diff --git a/apps/x/packages/core/src/knowledge/note_creation.ts b/apps/x/packages/core/src/knowledge/note_creation.ts index 26901b9e..5426d9a5 100644 --- a/apps/x/packages/core/src/knowledge/note_creation.ts +++ b/apps/x/packages/core/src/knowledge/note_creation.ts @@ -48,6 +48,7 @@ You are a memory agent. You are given one or more source files (emails, meeting 7. **Detect state changes (status updates, resolved items, role changes)** 8. Create new notes or update existing notes 9. **Apply state changes to existing notes** +10. **Maintain assistant-facing notes for every canonical note you create or update** The core rule: **Meetings and voice memos can create notes freely. Emails require personalized content — and a new People/Organization note from an email also requires the user to have replied at least once in the thread (the Email Reply Gate). Slack and connected-tool artifacts can update existing notes when they carry clear state changes, decisions, commitments, or project facts; they should create new notes only when the artifact clearly identifies a durable person, organization, project, or topic worth tracking.** @@ -977,6 +978,38 @@ One line summarizing this source's relevance to the entity: **2025-01-15** (email): Sarah shared the contract draft. \`\`\` +## Assistant Notes + +Every canonical People, Organizations, Projects, or Topics note you create or update must include a bottom section: + +\`\`\`markdown +## Assistant notes +- [2026-02-03T14:25:00.000Z] Prefers concise technical detail before pricing discussion. +\`\`\` + +These notes are for future assistant context, not for user-facing summaries. + +**Rules:** +- Add assistant note lines only when the source contains durable, entity-specific context worth preserving for future assistant use. +- Add one line for a single clear observation; add more only when there are multiple distinct durable observations. +- Do not add filler. If the source has no useful entity-specific observation beyond what the activity entry already captures, ensure the section exists but leave it without a new bullet. +- Use the current ISO timestamp from the Context section, not just the source date. +- Keep each line concise and specific: one durable observation about who or what the note is about. +- For people, capture subtle useful context when evidenced: working style, preferences, role changes, current company, interests, constraints, or relationship context. +- For organizations, capture useful context about relationship status, decision process, interests, constraints, or how they prefer to work. +- For projects and topics, capture current state, constraints, recurring patterns, or what the assistant should remember when helping with that project/topic. +- Prefer useful but non-obvious observations over restating the activity entry. +- Do not add guesses. +- If the note already has \`## Assistant notes\`, append new lines at the top of that section so it is reverse chronological. +- If the note lacks \`## Assistant notes\`, add the section at the very bottom. +- Deduplicate within the section: do not add the same observation again if an equivalent line already exists; refresh or update the timestamp only when the source reconfirms the same durable observation. +- Do not put user-wide preferences here; those belong in \`knowledge/Agent Notes/\`. This section is scoped to the entity note itself. + +**Examples:** +- \`- [2026-02-03T14:25:00.000Z] Sarah prefers pricing options framed with implementation risk called out explicitly.\` +- \`- [2026-02-03T14:25:00.000Z] Rahul just joined Acme as VP Engineering and is still learning their vendor review process.\` +- \`- [2026-02-03T14:25:00.000Z] Acme's team tends to route security questions through procurement before engineering review.\` + --- # Step 7: Detect State Changes @@ -1131,11 +1164,13 @@ If you discovered new name variants during resolution, add them to Aliases field - **Always use absolute paths** with format \`[[Folder/Name]]\` for all links - Use YYYY-MM-DD format for dates +- Use ISO timestamp format for assistant notes - Be concise: one line per activity entry - Note state changes with \`[Field → value]\` in activity - Escape quotes properly in shell commands - Write only one file per response (notes and \`suggested-topics.md\` follow the same rule) - **Always set \`Last update\`** in the Info section to the YYYY-MM-DD date of the source email or meeting. When updating an existing note, update this field to the new source event's date. +- **Keep \`## Assistant notes\` at the very bottom** for canonical People, Organizations, Projects, or Topics notes, and update it only when there is durable entity-specific context worth preserving. - Keep \`suggested-topics.md\` curated, deduped, and capped to the strongest 8-12 cards --- @@ -1248,6 +1283,7 @@ Before completing, verify: - [ ] Key facts are substantive (no filler) - [ ] Open items are commitments/next steps only - [ ] Empty sections left empty rather than filled with placeholders +- [ ] Canonical entity notes keep \`## Assistant notes\` at the bottom, with new timestamped lines only for durable entity-specific context **State Changes:** - [ ] Detected project status changes diff --git a/apps/x/packages/core/src/knowledge/note_system.ts b/apps/x/packages/core/src/knowledge/note_system.ts index b7becb3f..5be31161 100644 --- a/apps/x/packages/core/src/knowledge/note_system.ts +++ b/apps/x/packages/core/src/knowledge/note_system.ts @@ -40,7 +40,10 @@ const DEFAULT_NOTE_TYPE_DEFINITIONS: NoteTypeDefinition[] = [ {Substantive facts only. Leave empty if none.} ## Open items -{Commitments and next steps only. Leave empty if none.}`, +{Commitments and next steps only. Leave empty if none.} + +## Assistant notes +- [{ISO_TIMESTAMP}] {One concise assistant-facing observation about this person.}`, extractionGuide: "Look for: name, role, organization, email, aliases, relationship context", }, @@ -77,7 +80,10 @@ const DEFAULT_NOTE_TYPE_DEFINITIONS: NoteTypeDefinition[] = [ {Substantive facts only. Leave empty if none.} ## Open items -{Commitments and next steps only. Leave empty if none.}`, +{Commitments and next steps only. Leave empty if none.} + +## Assistant notes +- [{ISO_TIMESTAMP}] {One concise assistant-facing observation about this organization.}`, extractionGuide: "Look for: organization name, type, industry, relationship, domain, key people, projects", }, @@ -116,7 +122,10 @@ const DEFAULT_NOTE_TYPE_DEFINITIONS: NoteTypeDefinition[] = [ {Commitments and next steps only. Leave empty if none.} ## Key facts -{Substantive facts only. Leave empty if none.}`, +{Substantive facts only. Leave empty if none.} + +## Assistant notes +- [{ISO_TIMESTAMP}] {One concise assistant-facing observation about this project.}`, extractionGuide: "Look for: project name, type, status, people involved, organizations, timeline, decisions", }, @@ -149,7 +158,10 @@ const DEFAULT_NOTE_TYPE_DEFINITIONS: NoteTypeDefinition[] = [ {Commitments and next steps only. Leave empty if none.} ## Key facts -{Substantive facts only. Leave empty if none.}`, +{Substantive facts only. Leave empty if none.} + +## Assistant notes +- [{ISO_TIMESTAMP}] {One concise assistant-facing observation about this topic.}`, extractionGuide: "Look for: topic name, keywords, related people/orgs/projects, decisions, key facts", }, @@ -199,11 +211,39 @@ export function getNoteTypeDefinitions(): NoteTypeDefinition[] { // ── Render helper ──────────────────────────────────────────────────────── +function assistantNotesPlaceholder(type: string): string { + const normalized = type.toLowerCase(); + if (normalized === "people") { + return "person"; + } + if (normalized === "organizations") { + return "organization"; + } + if (normalized === "projects") { + return "project"; + } + if (normalized === "topics") { + return "topic"; + } + return "entity"; +} + +function renderTemplateWithAssistantNotes(def: NoteTypeDefinition): string { + if (!["People", "Organizations", "Projects", "Topics"].includes(def.type)) { + return def.template; + } + if (/^## Assistant notes\b/im.test(def.template)) { + return def.template; + } + const target = assistantNotesPlaceholder(def.type); + return `${def.template.trimEnd()}\n\n## Assistant notes\n- [{ISO_TIMESTAMP}] {One concise assistant-facing observation about this ${target}.}`; +} + export function renderNoteTypesBlock(): string { const defs = getNoteTypeDefinitions(); const sections = defs.map( (d) => - `## ${d.type}\n\`\`\`markdown\n${d.template}\n\`\`\``, + `## ${d.type}\n\`\`\`markdown\n${renderTemplateWithAssistantNotes(d)}\n\`\`\``, ); return `# Note Templates\n\n${sections.join("\n\n")}`; }