Skip to content

Instantly share code, notes, and snippets.

@devnacho
Last active March 15, 2026 12:32
Show Gist options
  • Select an option

  • Save devnacho/1efa285d9d316cca44f7dd0ae6d49a85 to your computer and use it in GitHub Desktop.

Select an option

Save devnacho/1efa285d9d316cca44f7dd0ae6d49a85 to your computer and use it in GitHub Desktop.

Agent Memory: A Persistent Knowledge Layer for the AI Agent

Context

The AI layer today is two disconnected islands: a reactive chat agent (stateless between conversations) and batch meeting notes extraction (results sit passively in JSONB). The agent can't recall that "in last Tuesday's meeting, the client mentioned wanting to retire at 58" when the adviser later asks about retirement planning in chat. Every conversation starts from zero.

Agent Memory bridges this gap — a persistent, structured knowledge store that auto-captures facts, decisions, and action items from meetings and conversations, then makes them queryable by the chat agent. It turns the AI from a stateless chatbot into a practice partner that accumulates intelligence over time.

Why this is the single highest-leverage addition:

  • Force multiplier: Meeting notes become 10x more valuable when the agent can reference them in future conversations. Every future feature (briefings, reminders, compliance) becomes a simple query against memory.
  • Unstoppable lock-in: After 6 months, the AI knows things the adviser forgot. You can't switch platforms without losing that accumulated intelligence.
  • Architecturally natural: Fits cleanly into existing patterns (Core context, Oban workers, BAML tools, PubSub).
  • No competitor does this: Financial planning platforms have chatbots. None have an AI that genuinely remembers across interactions.

Phase 1 — MVP: Schema + Context + Agent Tool + Meeting Pipeline

1. Migration: agent_knowledge_entries table

public.agent_knowledge_entries
├── id              binary_id (PK)
├── client_id       binary_id (FK → clients.id, nullable)
├── user_id         binary_id (FK → users.id)
├── source_type     enum (:meeting, :conversation, :manual)
├── source_id       binary_id (polymorphic FK, nullable)
├── entry_type      enum (:fact, :action_item, :decision, :preference, :life_goal)
├── category        string ("income", "expense", "asset", "liability", "demographic", etc.)
├── content         string (human-readable text)
├── data            map/JSONB (structured typed data — mirrors FactData from financial_facts.baml)
├── metadata        map/JSONB (sources, priority, deadline, kind)
├── status          enum (:active, :superseded, :dismissed)
├── superseded_by_id binary_id (self-ref FK, nullable)
├── search_text     tsvector (GENERATED from content + category)
├── inserted_at / updated_at

Indexes:

  • GIN on search_text
  • Composite on (client_id, status, entry_type)
  • On (source_type, source_id) for backfill idempotency

2. Schema: Core.Schemas.AgentKnowledgeEntry

File: apps/core/lib/core/schemas/agent_knowledge_entry.ex

Standard use Core.Schema with belongs_to :client and belongs_to :user. Ecto.Enum for source_type, entry_type, status.

3. Spec: Core.Contexts.AgentMemory.KnowledgeEntrySpec

File: apps/core/lib/core/contexts/agent_memory/knowledge_entry_spec.ex

Follow pattern from AIChatConversationSpecoutput_spec/0 with primitive types, export/1.

4. Context: Core.Contexts.AgentMemoryContext

File: apps/core/lib/core/contexts/agent_memory_context.ex

Core functions:

  • create_entry(attrs) / create_entries(list) — single + bulk insert
  • get_entries(opts) — filtered via TokenOperator (by_client, by_user, by_entry_type, by_status)
  • search_entries(query_text, opts) — full-text search: WHERE client_id = $1 AND status = 'active' AND search_text @@ plainto_tsquery('english', $2)
  • get_client_knowledge_summary(client_id, opts) — recent active entries grouped by entry_type, capped count (what the agent calls)
  • supersede_entry(old_id, new_attrs) — mark old as :superseded, create new
  • dismiss_entry(id) — mark :dismissed
  • PubSub: subscribe/1, broadcast_knowledge_updated/1

5. Meeting Notes → Knowledge Pipeline

File: apps/core/lib/core/workers/meetings/extract_knowledge_worker.ex

Hook point: AnalyzeTranscriptWorker.batch_exhausted/1 — after all meeting analysis branches complete, enqueue ExtractKnowledgeWorker.new(%{meeting_id: meeting_id}).

The worker:

  1. Loads meeting with completed notes JSONB
  2. Maps notes["financial_facts"] → entries with entry_type: :fact (direct 1:1 mapping — StructuredFinancialFact fields align with schema columns)
  3. Maps notes["action_items"] → entries with entry_type: :action_item
  4. Calls AgentMemoryContext.create_entries/1
  5. Broadcasts via PubSub

No additional BAML call needed — the extraction is already done by meeting notes analysis.

6. Chat Agent Integration: MemoryTools

BAML definitions — add to chat_agent.baml:

class SearchClientKnowledge {
    tool_name "SearchClientKnowledge" @alias("tool_name")
    client_id string
    query string
    entry_type string?
}

class GetClientKnowledgeSummary {
    tool_name "GetClientKnowledgeSummary" @alias("tool_name")
    client_id string
}

Add both to the ToolRequest union type.

Tool module: apps/core/lib/core/ai/chat_agent/tools/memory_tools.ex

  • Follow exact pattern from FactFindTools
  • defimpl Tool, for: Baml.SearchClientKnowledge → calls AgentMemoryContext.search_entries/2
  • defimpl Tool, for: Baml.GetClientKnowledgeSummary → calls AgentMemoryContext.get_client_knowledge_summary/2

System prompt addition: instruct the agent to always check client knowledge summary after identifying a client, before answering questions.

Design decision: Explicit tool calls (not auto-injection). Rationale: avoids wasting tokens on irrelevant context, follows existing SearchClient → FetchFactFind sequential pattern, simpler to implement and test.

7. Backfill Mix Task

File: apps/core/lib/mix/tasks/agent_memory/backfill_meetings.ex

Queries completed meetings with non-null notes, maps financial_facts + action_items to knowledge entries, bulk inserts. Idempotent via content hash + source_id dedup.

8. Search Strategy

MVP: PostgreSQL tsvector (no new dependencies)

  • Generated column: to_tsvector('english', coalesce(content, '') || ' ' || coalesce(category, ''))
  • GIN index for fast lookup
  • Combined with filter predicates (client_id, status, entry_type) for precise results

Future: pgvector for semantic search if keyword matching proves insufficient for natural language queries.


Phase 2 — Enhanced (after MVP validated)

  • Knowledge Timeline UI: LiveView component in Practice showing entries grouped by type, with dismiss/status controls
  • Chat Knowledge Sidebar: "What I know about [Client]" panel in ChatLive
  • Conversation Extraction: Async worker + BAML prompt to extract knowledge from chat conversations
  • Entry Superseding: Deduplication across meetings when the same fact is mentioned again with updated values

Phase 3 — Proactive Features (built on memory)

  • Pre-Meeting Briefing: Query knowledge for upcoming meeting's client, generate summary
  • Action Item Reminders: Oban cron scanning entries with entry_type: :action_item and metadata.deadline
  • Practice-Level Insights: Aggregations across all clients ("5 clients exposed to same risk factor")

Long-Term Vision: From Agent Memory to Context Graph

Based on Jaya Gupta's "AI's trillion-dollar opportunity: Context graphs" and Animesh Koratana's "How to build a context graph".

The Core Thesis

The next trillion-dollar platforms won't be built by adding AI to existing systems of record — they'll be built by capturing the reasoning that connects data to action. Today, a CRM stores "20% discount." It doesn't store who approved the deviation, why, what precedent was considered, or what policy exception was invoked. The reasoning lived in a Zoom call or a Slack DM and was never treated as data.

A context graph is the accumulated structure formed by decision traces — not the LLM's chain-of-thought, but a living record of how context turned into action, stitched across entities and time so that precedent becomes searchable.

Why This Matters for Timeline

Financial planning is exactly the kind of domain where context graphs are most valuable. Consider:

  • Exception-heavy decisions: Financial advice is never "one size fits all." Every client has unique circumstances, risk tolerances, and life goals. The adviser's judgment — why they recommended a particular pension structure over another — is the most valuable knowledge in the practice, and it's never captured.
  • "Glue function" par excellence: Financial advisers sit at the intersection of compliance, product providers, client relationships, tax planning, and estate planning. They are the human context carriers that Gupta's article identifies as the prime opportunity for AI systems of record.
  • Precedent matters enormously: "How did we handle the last client who wanted to retire early with a DB pension and a buy-to-let portfolio?" This question is asked in every practice, answered from memory, and never recorded.

How Agent Memory Evolves Into a Context Graph

The MVP (Phase 1) stores atomic facts — the "what." The context graph stores decision traces — the "why." Here's the evolution:

Phase 4 — Decision Traces (extending the knowledge entry model)

Add entry_type: :decision_trace to AgentKnowledgeEntry with richer data JSONB:

%{
  "trigger": "Client asked about early retirement at 58",
  "context_gathered": ["current_pension_value", "projected_growth", "state_pension_age", "lifestyle_expectations"],
  "alternatives_considered": ["defer to 60", "partial retirement at 58", "full retirement at 58 with drawdown"],
  "recommendation": "Partial retirement at 58 with phased drawdown from SIPP",
  "reasoning": "Preserves tax-free lump sum optionality while covering living expenses",
  "outcome": "Client agreed, action items created for pension transfer",
  "participants": ["adviser_id", "client_id"],
  "related_entries": ["fact_id_1", "fact_id_2", "action_item_id_1"]
}

Capture points:

  • The chat agent already has the full conversation context when a recommendation is made. A post-conversation BAML extraction (ExtractDecisionTrace) can identify moments where the adviser made a judgment call and extract the structured trace.
  • Meeting notes already extract action items and financial facts. A second pass can identify decision moments — points where alternatives were discussed and a direction was chosen.

Phase 5 — Precedent Search ("Agents as Informed Walkers")

Koratana's key insight: the ontology of a context graph shouldn't be predefined — it should emerge from agent walks. Each time the chat agent searches for client knowledge, follows a chain of related entries, or retrieves a decision trace, that traversal pattern reveals which entities and relationships actually matter.

Implementation:

  • Log every SearchClientKnowledge and GetClientKnowledgeSummary tool call with the query, results returned, and which results the agent actually used in its response
  • Over time, these "walks" reveal: which categories of knowledge are most frequently accessed together, which decision traces are referenced as precedent, which client attributes are most predictive of similar situations
  • Build a FindSimilarPrecedent tool: given a current client situation, search for decision traces from similar clients/situations using the learned traversal patterns

Phase 6 — World Model for Financial Planning

Koratana's deepest insight: a context graph with enough accumulated structure becomes a world model — not just "what happened" but a model that enables simulation and counterfactual reasoning.

For financial planning, this means:

  • Simulation: "If we recommend a drawdown strategy for this client, based on 50 similar decision traces, what concerns typically arise? What's the probability the client asks to revisit within 12 months?"
  • Counterfactual reasoning: "What would have happened if we'd recommended the annuity instead of the drawdown for clients like X? Based on similar profiles, how did those decisions play out?"
  • Practice-level learning: The agent doesn't need to be retrained. As Koratana writes: "Keep the model fixed, improve the world model it reasons over." Each new meeting, each new conversation, each new decision trace expands the evidence base the agent reasons over at inference time.

The Strategic Implication

This is the path from "AI chatbot for advisers" to "AI system of record for financial planning decisions."

Traditional systems of record store what happened (transactions, holdings, client data). A context graph stores why it happened (the reasoning, the precedent, the judgment). In a regulated industry like financial planning, the "why" is arguably more valuable than the "what" — it's what regulators ask for, what compliance needs, and what makes one practice better than another.

The MVP (Phases 1-3) delivers immediate, tangible value. The context graph vision (Phases 4-6) is what makes the platform irreplaceable.


Appendix A: Neo4j Context Graph Architecture & Mapping to Financial Planning

Based on Neo4j's "Hands On With Context Graphs and Neo4j"

The Neo4j Data Model (reference architecture)

Neo4j's demo uses a two-layer model:

Entities (What exists):

  • Person (customers, employees)
  • Account (checking, savings, trading, margin)
  • Transaction (deposits, withdrawals, transfers)
  • Organization (banks, vendors, counterparties)
  • Policy (business rules and thresholds)

Decision Traces (What happened and why):

  • Decision — the core event with full reasoning
  • DecisionContext — state snapshot at decision time
  • Exception — policy overrides with justification
  • Escalation — when decisions need higher authority
  • Community — clusters of related decisions (auto-detected via Louvain)

Relationships (where graphs shine):

// Causal chain
(:Decision)-[:CAUSED]->(:Decision)
(:Decision)-[:INFLUENCED]->(:Decision)
(:Decision)-[:PRECEDENT_FOR]->(:Decision)
// Context
(:Decision)-[:ABOUT]->(:Person|:Account|:Transaction)
(:Decision)-[:APPLIED_POLICY]->(:Policy)
(:Decision)-[:GRANTED_EXCEPTION]->(:Exception)
(:Decision)-[:TRIGGERED]->(:Escalation)

Key capability: hybrid search — semantic embeddings (text similarity of reasoning) + structural embeddings (FastRP graph topology similarity) for finding precedents that are both about similar things and in similar contexts.

Agent tools in the Neo4j demo:

Tool Purpose
search_customer Find customers and related entities
get_customer_decisions All decisions about a specific customer
find_precedents Hybrid semantic + structural precedent search
find_similar_decisions FastRP-based structural similarity
get_causal_chain Trace what caused a decision and what it led to
record_decision Create new decisions with full context
find_decision_community Louvain clustering of related decisions
get_policy Retrieve applicable business rules

Mapping to Timeline's Financial Planning Domain

Timeline Entities (already exist in the system):

Entity Schema Purpose
Client Core.Schemas.Client The person being advised
Adviser (User) Core.Schemas.User The financial adviser
Firm Core.Schemas.Firm The advisory practice
FactFind Core.Schemas.FactFind 17-section structured client profile
Meeting Core.Schemas.Meeting Recorded client interactions
PlatformPortfolio Core.Schemas.PlatformPortfolio Investment portfolio with drift/rebalance rules
PlatformAccount Core.Schemas.PlatformAccount Client investment account
RiskQuestionnaire Core.Schemas.RiskProfiler.QuestionnaireResponse Risk tolerance assessment
Conversation Core.Schemas.Conversation Threaded async communication
ApplicationEvent Core.Schemas.ApplicationEvent Audit log (28 event kinds)

Timeline Decision Traces (to be captured): → See Appendix B below

Timeline Policies (implicit, should be made explicit):

  • Risk tolerance bands per client profile
  • Rebalance rules (tolerance_limit, annual, drift percentages)
  • KYC/AML compliance requirements
  • Suitability assessment criteria (FCA regulations)
  • Capacity for loss thresholds
  • Fee structures and discount policies

Appendix B: Decision Catalog — What Decisions to Capture and Where They Come From

Category 1: Advice & Recommendation Decisions

These are the highest-value decisions — the "why did the adviser recommend X?" that regulators ask for and competitors can't replicate.

Decision Type Description Capture Point Currently Captured?
Product Recommendation Why a specific pension/ISA/GIA was recommended over alternatives Meeting transcript (AI extraction) + Chat agent conversation Partially — financial facts extracted from meetings, but not the reasoning behind recommendations
Risk Profile Override Adviser overrides the questionnaire score with justification QuestionnaireResponse.score_override field + override_reason + overridden_at Yes — score, reason, and timestamp are captured
Asset Allocation Decision Why this specific allocation split was chosen for this client PlatformAccountAssetAllocationIntent.allocations Only the "what" — the allocation percentages. Not why this split, what alternatives were considered
Drawdown vs Annuity The critical retirement income decision Meeting transcript + suitability report No — lives in PDF reports and meeting recordings, not as structured data
Fund/Provider Selection Why provider X was chosen over provider Y Meeting/conversation No — not captured at all
Contribution Level Why the adviser recommended contributing X per month Chat agent calculation + meeting discussion No — only the final figure in the fact-find
Protection Recommendation Life insurance, income protection, critical illness decisions Meeting transcript Partially — facts extracted, but not the needs analysis reasoning

Category 2: Portfolio & Investment Decisions

Decision Type Description Capture Point Currently Captured?
Rebalance Decision Why a rebalance was triggered and executed PlatformPortfolio drift status + PlatformDriftNotification Only the trigger — drift exceeded threshold. Not: was the adviser consulted? Did they agree? Any exceptions?
Rebalance Rule Change Switching from tolerance-based to annual rebalancing RebalanceRule update Only the new state — not why the rule was changed
Drift Tolerance Override Accepting drift above threshold temporarily Not captured No — adviser may decide to leave drift in place (e.g., market timing view) but this isn't recorded
Model Portfolio Assignment Why this model was chosen for this client Platform setup No — the assignment exists, the reasoning doesn't
Switch/Transfer Moving assets between providers or funds ApplicationEvent (create_platform_transaction) Only the action — not the catalyst (meeting discussion, market event, life change)

Category 3: Client Lifecycle Decisions

Decision Type Description Capture Point Currently Captured?
Onboarding Acceptance Taking on a new client — why, suitability assessment Initial meeting + KYC Only KYC status — not the adviser's judgment on fit
Annual Review Outcome What changed, what was recommended, follow-ups Meeting transcript + action items Partially — facts and actions extracted, but no structured "review outcome"
Client Offboarding Why a client relationship was ended Conversation/meeting No — not captured as a decision
Fee Negotiation Why a specific fee was agreed Meeting/conversation No — only the final fee is stored
Referral Decision Referring to a specialist (tax, legal, mortgage) Meeting action items Partially — may appear as an action item, but not linked to the reasoning

Category 4: Compliance & Regulatory Decisions

Decision Type Description Capture Point Currently Captured?
KYC Determination Approve/decline/needs-review with reasoning KycTransaction status change + ApplicationEvent Yes — status, timestamp, and encrypted payload captured
Suitability Assessment Is this product suitable for this client? Modular report sections Partially — report fields exist (capacity_for_loss, risk_profile_appropriate_investment_strategy) but not as queryable decision records
Capacity for Loss Can the client afford to lose this money? FactFind + modular report Flag only — boolean in report, reasoning in meeting
Vulnerable Client Flag Identifying and documenting vulnerability FactFind health/needs fields Data exists — but no decision record of when/why vulnerability was identified
Complaint Resolution How a complaint was handled and resolved Conversation threads Thread exists — but no structured decision trace

Category 5: AI Agent Decisions (meta-decisions)

Decision Type Description Capture Point Currently Captured?
Tool Call Chain What tools the agent used and in what order to answer a question Chat agent execution trace No — conversation messages saved, but not the tool call sequence
Fact-Find Interpretation How the agent interpreted ambiguous client data Chat agent response No — the response is saved, not the reasoning path
Meeting Note Extraction Confidence Which facts the AI was confident vs uncertain about BAML extraction pipeline No — the 3-phase validation exists but confidence scores aren't persisted

Where Decisions Come From: Capture Points

┌─────────────────────────────────────────────────────────┐
│                    DECISION SOURCES                       │
├─────────────────────────────────────────────────────────┤
│                                                           │
│  ┌─────────────┐    ┌──────────────┐    ┌─────────────┐ │
│  │  MEETINGS    │    │  CHAT AGENT  │    │  PLATFORM   │ │
│  │             │    │              │    │  ACTIONS    │ │
│  │ Transcript  │    │ Conversation │    │             │ │
│  │ → AI Extract│    │ → Tool Calls │    │ Rebalance   │ │
│  │ → Facts     │    │ → Responses  │    │ KYC Status  │ │
│  │ → Actions   │    │ → Choices    │    │ Allocation  │ │
│  │ → Decisions │    │              │    │ Transfers   │ │
│  │   (NEW)     │    │              │    │             │ │
│  └──────┬──────┘    └──────┬───────┘    └──────┬──────┘ │
│         │                  │                    │         │
│         ▼                  ▼                    ▼         │
│  ┌──────────────────────────────────────────────────┐   │
│  │           KNOWLEDGE EXTRACTION LAYER              │   │
│  │                                                    │   │
│  │  ExtractKnowledgeWorker (meetings)                │   │
│  │  ExtractChatKnowledgeWorker (conversations)       │   │
│  │  ExtractDecisionTraceWorker (NEW - Phase 4)       │   │
│  │  ApplicationEvent listener (platform actions)     │   │
│  └──────────────────────┬───────────────────────────┘   │
│                          │                               │
│                          ▼                               │
│  ┌──────────────────────────────────────────────────┐   │
│  │           AGENT KNOWLEDGE ENTRIES                 │   │
│  │                                                    │   │
│  │  Facts | Action Items | Decisions | Preferences   │   │
│  │  ↓ (Phase 4+)                                     │   │
│  │  Decision Traces with full context                │   │
│  └──────────────────────────────────────────────────┘   │
│                                                           │
└─────────────────────────────────────────────────────────┘

Richest Untapped Sources

1. Meeting Transcripts (highest value, requires new BAML extraction)

Meetings are where the real decisions happen. The existing extraction pipeline captures facts and action items but misses decision moments. A new BAML function ExtractDecisionTraces would identify:

  • Moments where alternatives were discussed ("we could do A or B...")
  • The adviser's recommendation and reasoning ("I'd suggest A because...")
  • The client's response and concerns ("but what about...")
  • The agreed outcome ("let's go with A and review in 6 months")

2. ApplicationEvent audit log (structured, already captured, just needs enrichment)

The system already logs 28 event kinds with encrypted payloads. These are pure "state clock" events — what changed. The gap is the "event clock" — why it changed. For each ApplicationEvent, the decision trace would capture:

  • What triggered this action (meeting ID, conversation ID, market event)
  • Who approved it and through what process
  • What the state was before the change (DecisionContext snapshot)

3. Risk Profile Overrides (already partially captured)

QuestionnaireResponse already has score_override, override_reason, overridden_at. This is the closest thing to a decision trace that exists today. The pattern could be generalized.

4. Chat Agent Tool Chains (meta-decisions, zero-cost capture)

Every chat conversation already has the full message history. Adding structured logging of tool calls (SearchClient → FetchFactFind → CalculatorMultiply → response) creates a decision trace for free. This shows how the agent reached its answer.

PostgreSQL-First Implementation vs. Graph DB Aspiration

The Neo4j demo is compelling, but introducing a graph database is a significant infrastructure change. The pragmatic path:

Phase 1-3 (PostgreSQL, current plan): Flat knowledge entries with related_entries in JSONB metadata. Sufficient for basic recall and search. Decision traces stored as rich JSONB with trigger, alternatives, reasoning, outcome fields.

Phase 4-5 (PostgreSQL with recursive queries): Add an agent_knowledge_relations join table:

agent_knowledge_relations
├── source_entry_id    FK → agent_knowledge_entries
├── target_entry_id    FK → agent_knowledge_entries
├── relation_type      enum (:caused, :influenced, :precedent_for, :superseded, :about)
├── metadata           JSONB

This enables causal chain traversal with WITH RECURSIVE CTEs. Not as elegant as Cypher, but functional for chains of depth 3-5 (typical in financial planning).

Phase 6+ (evaluate graph DB): If the relation table grows large and query patterns become deeply recursive (chains > 5 hops, community detection, structural similarity), then evaluate Neo4j or Apache AGE (PostgreSQL graph extension) as a secondary store. The knowledge entries remain in PostgreSQL as the source of truth; the graph DB becomes a projection optimized for traversal queries.

Key Neo4j capabilities to replicate pragmatically in PostgreSQL:

Neo4j Feature PostgreSQL Equivalent Sufficient for Phase 1-5?
Causal chain traversal WITH RECURSIVE CTE on relations table Yes (depth ≤ 5)
Precedent search (semantic) pgvector on content embeddings Yes
Precedent search (structural) Not feasible without graph DB Skip until Phase 6
Community detection (Louvain) Not feasible without graph DB Skip until Phase 6
Node embeddings (FastRP) Not feasible without graph DB Skip until Phase 6
Policy application tracking APPLIED_POLICY relation + Policy table Yes

Appendix C: How Each Context Graph Capability Would Be Used in Financial Planning

1. Causal Chain Traversal — "What led to this, and what did it cause?"

What it does: Given any decision or event, trace backwards to find what caused it and forwards to find what it triggered. In Neo4j this is a simple path query; in PostgreSQL it's a WITH RECURSIVE CTE over the relations table.

Concrete scenarios in financial planning:

Scenario A — Regulatory audit trail: An FCA auditor asks: "Why was this client moved from a cautious to a balanced portfolio?"

The causal chain surfaces:

Annual Review Meeting (2025-09-15)
  → Client disclosed inheritance of £200k (financial fact)
    → Adviser reassessed capacity for loss (decision: increased)
      → Risk questionnaire re-taken (score: 6 → 7)
        → Portfolio reallocated from Cautious to Balanced (action)
          → Rebalance executed (platform transaction)

Without causal chains, the auditor sees: "portfolio changed on Oct 3rd." With them, they see the full justified reasoning path from life event → assessment → action. This is the difference between a compliance checkbox and a defensible audit trail.

Scenario B — Understanding client outcomes: An adviser reviews a client whose portfolio underperformed. The causal chain reveals:

Market volatility event (2025-03)
  → Drift notification: equity allocation dropped 12% below target
    → Adviser decision: delay rebalance (reasoning: "client nervous, prefer to wait")
      → Second drift notification (2025-06)
        → Adviser decision: rebalance now
          → But missed the recovery window → underperformance

This turns hindsight into learnable insight. The adviser can see: "when I delayed rebalancing due to client anxiety, it cost X% over 3 months." This is how practices get systematically better.

Scenario C — Complaint resolution: Client complains: "You put me in the wrong fund." The causal chain instantly surfaces:

Initial meeting → risk assessment → suitability determination → fund selection → client agreement

Every link is timestamped with reasoning. The complaint is resolved in minutes instead of days of file review.

Why it's powerful for us: Financial planning is a regulated industry where "why" matters as much as "what." Every decision must be justifiable. Causal chains transform our platform from a record of outcomes into a record of reasoning — which is exactly what regulators, compliance officers, and advisers themselves need.


2. Precedent Search (Semantic) — "How did we handle this before?"

What it does: Given a current situation described in natural language, find past decisions with similar reasoning, context, or outcomes. Uses pgvector embeddings on decision content to find semantically similar entries.

Concrete scenarios:

Scenario A — New adviser onboarding: A junior adviser joins the practice and encounters their first client wanting to consolidate 4 old workplace pensions. They ask the chat agent: "How have we handled pension consolidation before?"

Precedent search returns the 5 most similar past decisions:

  • "Client had 3 DB pensions — we recommended keeping DB, consolidating only DC pensions due to safeguarded benefits"
  • "Client had 4 DC pensions with high charges — consolidated into SIPP, saved 0.8% in annual fees"
  • "Client wanted to consolidate but one pension had a valuable guaranteed annuity rate — advised keeping it separate"

The junior adviser now has the practice's accumulated wisdom at their fingertips, not just their own limited experience. They're making decisions informed by every similar case the practice has ever handled.

Scenario B — Pre-meeting preparation: Before a review meeting, the agent automatically searches for precedents matching this client's current situation:

  • "Client approaching retirement with mixed DB/DC pensions" → surfaces how similar cases were handled
  • "Client recently divorced with pension sharing order" → surfaces relevant past decisions and pitfalls

The adviser walks into the meeting with a briefing based on collective practice experience, not just this client's file.

Scenario C — Quality assurance: Practice manager asks: "Show me all decisions where we recommended drawdown for clients over 70 with capacity for loss rated low." Semantic search finds these even if the exact terminology varied across different advisers' notes. This surfaces potential suitability concerns across the book.

Why it's powerful for us: Financial advice quality is bottlenecked by individual adviser experience. A 30-year veteran has seen everything; a 3-year adviser hasn't. Precedent search democratizes the practice's collective intelligence. It's the difference between "I think we should..." and "Based on 12 similar cases we've handled, the approach that worked best was..."


3. Precedent Search (Structural) — "Find decisions in similar contexts" (Phase 6)

What it does: Unlike semantic search (which matches on text similarity), structural search uses graph embeddings (FastRP) to find decisions that occupy similar positions in the graph — same types of entities, same relationship patterns — even if the text descriptions are completely different.

Concrete scenario: An adviser is handling a complex case: a business owner client with a SSAS pension, cross-border tax implications, and a recent health diagnosis affecting their retirement timeline.

Semantic search might find: "client with health issue" or "business owner pension" separately. Structural search finds: "here's a past decision that involved the same combination — a business owner entity connected to a self-administered pension, connected to a tax complication, connected to a health-related timeline change." The topology matches, even if the specific details are different.

Why it's powerful (future): This is where the context graph goes beyond "smart search" into genuine pattern recognition. It answers: "I've never seen this exact situation, but I've seen situations with the same shape." This is what experienced advisers do intuitively — and what makes their judgment so valuable. Graph embeddings formalize it.

Why Phase 6: Requires a graph database (FastRP is a GDS algorithm). The value is real but the infrastructure cost is high. Semantic search covers 80% of the need; structural search is the remaining 20% for truly complex cases.


4. Community Detection (Louvain) — "What decisions cluster together?" (Phase 6)

What it does: Automatically groups decisions that are densely interconnected through causal and influence relationships. No manual tagging needed — the algorithm discovers the clusters.

Concrete scenarios:

Scenario A — Discovering systemic issues: Louvain detects a community of 23 decisions across 15 clients, all connected by:

  • A rebalance delay decision → linked to the same market event
  • A risk profile reassessment → all during the same quarter
  • A common fund switch → all moving away from the same provider

The practice manager sees: "We have a cluster of decisions all triggered by Provider X's fund underperformance. 15 clients were affected. 8 have been reviewed. 7 still need attention."

Without community detection, these are 23 isolated records. With it, they're a systemic pattern that demands a practice-wide response.

Scenario B — Identifying advice themes: Over a year, the algorithm discovers clusters like:

  • "Pre-retirement planning" cluster: 45 decisions involving drawdown, annuity, tax-free lump sum, state pension timing
  • "Protection gap" cluster: 30 decisions involving life insurance, income protection, and critical illness after fact-find reviews
  • "Inheritance tax planning" cluster: 20 decisions involving trusts, gifts, pension beneficiary nominations

These clusters become the practice's actual service lines, discovered from data rather than assumed from marketing materials. They reveal what the practice actually does vs. what it says it does.

Scenario C — Training & CPD: Clusters of decisions involving FCA regulatory changes (e.g., Consumer Duty implementation) surface: how did the practice adapt? Which advisers changed their approach first? What worked? This becomes structured CPD material derived from real practice decisions.

Why Phase 6: Louvain requires a graph database. The value is at the practice/firm level (strategic, not transactional). It's incredibly powerful for practice management but not needed for individual adviser productivity, which is the Phase 1-3 priority.


5. Node Embeddings (FastRP) — "What is this decision's structural fingerprint?" (Phase 6)

What it does: Computes a vector representation of each decision based on its position in the graph — what entities it touches, what it caused, what caused it, what policies it applied. Two decisions with similar FastRP embeddings have similar structural context, regardless of their text.

Concrete scenario: The system can answer: "This is a 'high-complexity retirement transition' type decision" — not because anyone labelled it, but because its graph fingerprint (connected to: multiple pension entities, a property entity, a tax planning consideration, a spouse's pension, a health factor) matches the structural pattern of past decisions that were labelled that way.

Why it's powerful (future): This enables automatic complexity scoring, automatic routing (complex cases → senior adviser), and anomaly detection (this decision's structure doesn't match any known pattern → flag for review).

Why Phase 6: Same infrastructure requirements as structural precedent search. The immediate value can be approximated with simpler heuristics (count of related entities, number of decision types involved) until the graph infrastructure justifies the investment.


6. Policy Application Tracking — "What rules were applied to this decision?"

What it does: Links each decision to the specific policies/rules that were applied (or overridden). In PostgreSQL, this is a relation between knowledge entries and a new policies table.

Concrete scenarios:

Scenario A — Consumer Duty compliance: FCA's Consumer Duty requires firms to demonstrate they're acting in clients' best interests. Policy tracking creates an automatic evidence trail:

  • Decision: "Recommended fund switch from active to passive"
  • Applied policies: "Consumer Duty: cost-effectiveness requirement", "Client's stated preference for low-cost options"
  • Outcome: "Annual fee reduction of 0.6%, projected 15-year saving of £45,000"

Every decision is linked to the regulatory principle it satisfies. Compliance reporting becomes a query, not a manual exercise.

Scenario B — Detecting policy drift: The system can answer: "In the last quarter, how often was the 'suitability assessment required' policy applied vs. skipped?" If advisers are consistently bypassing a policy (even with justification), that's a signal — either the policy needs updating or the behaviour needs addressing.

Scenario C — Policy change impact analysis: When a regulation changes (e.g., pension lifetime allowance abolished), the system can find all past decisions that referenced the old policy: "Show me every decision where 'lifetime allowance check' was applied." This surfaces exactly which clients need reviewing and what was decided based on the now-obsolete rule.

Why it's powerful for us: Financial planning is one of the most regulated professional services. The gap between "we have policies" and "we can prove every decision followed policies" is enormous. Policy tracking closes that gap automatically. It transforms compliance from retrospective paperwork into real-time assurance.

Why it's feasible now (Phase 4-5): Unlike community detection or structural search, policy tracking is just a relation + a small table. No graph database needed. A policies table with name, description, category, effective_from, effective_to and a APPLIED_POLICY relation type in the relations table covers it.


Summary: Value Stack by Phase

Phase Capability User Value Business Value
1-3 Knowledge recall "The agent remembers what we discussed" Stickiness, time savings
4 Decision traces "The agent knows why we decided that" Audit trail, compliance
4-5 Causal chains "Trace any outcome back to its root cause" Regulatory defence, learning
4-5 Semantic precedent search "How did we handle this before?" Advice quality, adviser onboarding
4-5 Policy tracking "Prove every decision followed policy" Consumer Duty compliance
6+ Structural search "Find decisions with similar shape" Complex case routing
6+ Community detection "Discover systemic patterns across the book" Practice management, risk
6+ Node embeddings "Automatic complexity scoring" Anomaly detection, QA

The first five rows are achievable with PostgreSQL and deliver transformative value. The last three require a graph database and are the "moat" — once you have them, no competitor can replicate your accumulated decision intelligence.


Critical Files to Modify

File Change
apps/core/priv/baml/chat_agent/baml_src/chat_agent.baml Add MemoryTools to ToolRequest union + system prompt
apps/core/lib/core/workers/meetings/analyze_transcript_worker.ex Enqueue ExtractKnowledgeWorker in batch_exhausted
apps/core/lib/core/ai/chat_agent/tools/fact_find_tools.ex Pattern reference for MemoryTools
apps/core/lib/core/contexts/meetings_context.ex Pattern reference for AgentMemoryContext
apps/core/priv/baml/meeting_notes/baml_src/financial_facts.baml Reference for StructuredFinancialFact → KnowledgeEntry mapping

New Files

File Purpose
apps/core/priv/repo/migrations/*_create_agent_knowledge_entries.exs Table migration
apps/core/lib/core/schemas/agent_knowledge_entry.ex Ecto schema
apps/core/lib/core/contexts/agent_memory_context.ex Context module
apps/core/lib/core/contexts/agent_memory/knowledge_entry_spec.ex Spec module
apps/core/lib/core/workers/meetings/extract_knowledge_worker.ex Post-meeting extraction
apps/core/lib/core/ai/chat_agent/tools/memory_tools.ex Agent tool implementations
apps/core/lib/mix/tasks/agent_memory/backfill_meetings.ex Backfill task
apps/core/test/core/contexts/agent_memory_context_test.exs Context tests
apps/core/test/core/ai/chat_agent/tools/memory_tools_test.exs Tool tests
apps/core/test/core/workers/meetings/extract_knowledge_worker_test.exs Worker tests

Verification

  1. Unit tests: Context CRUD, search, spec export, tool dispatch
  2. Integration test: Create meeting → run analysis → verify knowledge entries created → chat agent calls SearchClientKnowledge → returns relevant entries
  3. Manual test: Send a message in Practice chat like "What do you know about [client name]?" → agent should call GetClientKnowledgeSummary and return knowledge from past meetings
  4. Backfill test: Run mix task on dev DB, verify entries created from existing meeting notes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment