01The shape

The knowledge graph is a Postgres table — knowledge_entries — with one row per durable claim about the engagement. Every row carries the tenant's company_id and a kind that says what type of claim it is.

decision
A choice that was made, with a rationale. Most rows. Comes from approved approvals.
client_rule
A constraint the customer imposed. "We never approve vendors without a tax_id." Reviewed and re-stated by humans.
rationale
Why something is true / chosen — the reasoning behind a decision, captured separately for citation.
customization
A D365 (or other system) configuration choice that's specific to this tenant.
issue
A known problem with a known status. Not yet a decision; not silently ignored.
stakeholder
Who has authority over what. Sponsor, champion, blocker, user — same archetypes Olivier captures pre-engagement.

Append-only by convention; soft-deletable per row. The full knowledge graph for a project is what the operator exports under GDPR Article 15 + 20.

02How decisions land here

Operators don't write knowledge_entries rows by hand. Decisions land here as a side effect of the approval workflow:

1
A consultant produces a structured proposal. It lands as an approvals row, attributed to that consultant by team_member_id.
2
A reviewer approves via decide_approval(...). The approval transitions pending → approved.
3
An AFTER UPDATE trigger — tg_approvals_materialise_action — writes a row to knowledge_entries with kind=decision, the approval body as context, and a back-reference to the approval id.
4
Next time any consultant runs on this project, their grounding read pulls recent knowledge_entries filtered by tenant. The new decision is in the system prompt before the model produces a single token.

This is what lets the team compound. Decisions aren't held in a session memory that resets between calls. They sit in a row that every consultant reads, with provenance back to the approval that produced them.

03How consultants read it

Every persona runner runs a query like this before reasoning:

  • Pull the most recent client_rule, decision, and rationale entries for this company (default: last 30).
  • Format each as a single line: - [decision] Vendor approved: ACME Antwerp / GL 60100 / 2026-04-21.
  • Splice into the system prompt under "Knowledge graph — recent baselines, client rules, and decisions" before the persona's role-specific instructions.

Every persona output has a knowledge_grounding field listing the entries that influenced the call. The reviewer sees this on the approval card; future consultants see it in their own grounding context. Provenance flows forward.

04Tenancy and privacy

Postgres row-level security on the table — every SELECT joins through is_company_member(company_id). A reviewer can only see knowledge for tenants they have a membership in. A consultant runner — running under service-role for the agent path — gets the same scoping, but only against the project's tenant.

Cross-tenant queries are structurally impossible: the helper functions that gate access don't accept a "wildcard tenant" parameter, and the table doesn't have a "global rules" mode. The only path that crosses tenants is the platform-admin GDPR audit (export / delete), which is explicitly logged in gdpr_operations with a tenant snapshot. See the audit trail for that story.

05What this isn't

It is not a vector store. It is not RAG over a document corpus. The knowledge graph is structured, deliberate, audit-trailed — each row arrived because a human approved a decision, or an admin captured a client rule. There's no fuzzy match producing memories that nobody can trace.

This matters for compliance and for product trust: when a consultant cites a knowledge entry in a proposal body, you can click through to the original approval, see who decided it, when, and on what evidence. No "the model says it remembered" — only "this row exists because that decision was made on that day."

Most agent platforms forget the engagement between conversations. This one remembers, and shows you what it's reading.