The Decision Graph: The Repository as Institutional Memory
Status: Phase 1 (capture) implemented - M14, v0.16: the record format and docs/decisions/
(M14-1), the draft-a-decision step (M14-2), propose + approve + commit at the PRD, design, and
plan-evolution gates (M14-3/4), and the worthiness bar + de-duplication (M14-5); see FR-25 and
docs/architecture.md. Phases 2 (link & query) and 3 (impact) are not
scheduled. This document pins the concept down and records the architectural choices that make
it tractable rather than a swamp.
The problem
Bloom's Guiding Principles already promise that it will "maintain a living understanding of the project throughout its lifecycle." Today it does not. Bloom knows the current state - the PRD's latest revision, open tickets, live GitHub status - but it has no memory of why the project is the way it is. Ask it, a year in:
"Why was Supabase chosen for this project, and what happens if we move to Netlify?"
and it has nothing to answer with. The reasoning existed - in a Telegram conversation, in a PRD revision that has since been overwritten, in a PR review comment - but it was never captured as a first-class thing, and nothing links it to the milestones, issues, code, and infrastructure it determined.
This is the gap between a Product Owner who tracks work and one who genuinely owns the product.
The idea in one line
Make decisions first-class, durable artifacts in the project repository, linked by typed edges to the milestones, issues, PRs, discussions, and code they affect - so the rationale of the product accumulates instead of evaporating, and so "what would change if we revisited X?" becomes a query rather than an archaeology project.
Prior art (do not reinvent this)
Two established practices cover most of this, and we should borrow their formats rather than greenfielding:
- Architecture Decision Records (ADR / MADR / RFCs) - a decision as a short, immutable, numbered markdown document with context, the decision, alternatives considered, and consequences. Decades of practice; well-understood file conventions and lifecycle states.
- Requirements traceability (the traceability matrix in classical systems engineering) - the discipline of maintaining explicit links from a requirement through design, implementation, and test.
The novel part here is not either practice; it is (a) the linked dimension that most ADR practice lacks in tooling, and (b) that an agent already sitting in the conversation can capture the record, which is the reason ADR practice usually dies.
Architectural decision 1: GitHub is the source of truth; Bloom keeps a derived index
This is the most important choice in the document.
Decision records live in the project repository - docs/decisions/NNNN-slug.md, following ADR
convention, optionally mirrored as a decision: labelled GitHub issue for discussion. Bloom
maintains a derived index (Postgres) for fast traversal and query.
Why this way round:
- It mirrors Bloom's existing architecture exactly: GitHub is the source of truth for engineering state (FR-7), and Bloom's run state and dashboard are read models over it. A Bloom-owned graph would be a second source of truth, and it would drift.
- It is the only version that actually delivers the goal. "The repo becomes institutional memory of the product" is only true if the memory survives Bloom. A decision graph that lives solely in Bloom's database dies with the tool, is invisible to anyone reading the repo, and cannot be reviewed in a PR.
- It gets durability, history, diffing, access control, and human review for free.
- The derived index is disposable and rebuildable from the repo, which means it can never be the thing that goes subtly wrong and lies.
The cost is that graph queries traverse an index that can lag the repo. That is the correct trade - the same one Bloom already makes everywhere else.
Architectural decision 2: narrow, typed edges - not a general knowledge graph
An untyped "semantic graph" of everything-related-to-everything becomes an unqueryable swamp within months. The value comes from a small number of edges with precise meanings:
| Edge | From -> To | Meaning |
|---|---|---|
motivates | decision -> milestone / ticket | This work exists because of this decision |
implements | PR -> decision | This change carries out the decision |
affects | decision -> component / service / infra | Blast-radius surface |
supersedes | decision -> decision | Replaces an earlier decision |
derives_from | decision -> PRD revision / conversation | Where the rationale came from |
constrains | decision -> decision | A later decision must respect this one |
Everything else stays out until a concrete query demands it. Adding an edge type is cheap; removing one after code depends on it is not.
Architectural decision 3: structure first, embeddings only as a fallback
It is tempting to reach for embeddings and RAG immediately. Resist for the primary structure: structural edges give exact, explainable answers ("these four tickets are open against files this decision touched"), and vectors give fuzzy recall. Use semantic search only as a ranking and discovery layer - finding candidate related conversations, suggesting a decision might be relevant
- never as the thing that determines impact. An impact analysis that is 80% right is worse than useless, because it is trusted.
The record format
ADR-shaped, with front-matter carrying the machine-readable edges:
---
id: 18
title: Use Supabase for persistence
status: accepted # proposed | accepted | superseded | deprecated
date: 2026-03-04
deciders: [client, bloom-po]
affects: [api, persistence, deployment]
supersedes: []
motivates: [milestone/M2, issue/41, issue/47]
derives_from: [prd@rev7, thread/4242#msg-118]
---
## Context
...why this came up, what constraints applied...
## Decision
Use Supabase (hosted Postgres) as the durable state store.
## Alternatives considered
- Plain Postgres on the VM - rejected: ops burden
- Firebase - rejected: relational model needed
## Consequences
- Session pooler DSN required for the deployment topology
- Ties the job queue's atomic-claim semantics to Postgres
Rendered back to the client, that becomes the summary shape sketched in the original idea:
Decision #18 - Use Supabase for persistence
Chosen because: relational model + managed ops
Alternatives: self-hosted Postgres, Firebase
Affects: api, persistence, deployment
Motivated: M2, #41, #47
Related discussion: thread 4242 (2026-03-04)
Approved by: Client
Status: accepted
Capture is the hard part, not storage
Every ADR practice in history dies the same way: people stop writing them. Storage is trivial; capture discipline is the whole game.
Bloom has a structural advantage no ADR tool has: it is already inside the conversation where the decision is being made, and it already runs approval gates the client is used to answering.
So: auto-propose a decision record at the approval gates that already exist.
| Existing gate | Decision it should propose |
|---|---|
| PRD approval (FR-4) | Scope, target users, success criteria, explicit non-goals |
| Design approval (FR-23) | The chosen design direction, and the options rejected |
| Plan-evolution approval (FR-19) | What changed in scope and why - the request is a decision |
| Architecture specialist findings (FR-21) | Accepted architectural tradeoffs |
| Escalation resolution (FR-12) | How a stuck ticket was unblocked, and on what grounds |
The client's only added burden is approving a pre-drafted record - the same interaction pattern Bloom has already proven three times over. Alternatives considered and rejected are captured at the moment they are rejected, which is the one time that information is cheap and accurate.
This also unifies two things that look separate: a targeted change request (M11-6) and a decision are the same event viewed twice. Every approval gate emitting a decision node is the mechanism behind both.
Impact analysis: you get code-level blast radius almost for free
The instinct is that decision -> code is the hard edge requiring semantic search over the codebase. It mostly is not, because of an edge that already exists in GitHub:
decision --implements--> PR --(its own diff)--> changed files --> tests + open tickets touching them
Every PR already records exactly which files it changed. So if PRs reference the decision they carry out - one convention, enforceable in the PR template and by Bloom's own review - then decision -> affected code is exact and free, with no embeddings at all. That single edge is the highest-value thing to capture early.
Answering the motivating question then decomposes into cheap traversals:
- Find decision #18 (
Use Supabase). implementsedges -> the PRs that carried it out -> their diffs -> the touched files/modules.- Tests covering those files.
- Open tickets referencing those files or the affected components.
motivatesedges -> the milestones and tickets that exist because of it.constrainsedges -> later decisions that assumed it (the job queue's Postgres claim semantics).- Infra implications from
affects.
That is a real, explainable answer with citations - not a plausible-sounding summary.
What this is not
- Not a general knowledge graph of the codebase. Typed edges, bounded set (decision 2).
- Not an autonomous decision-maker. Bloom proposes and records; the client decides. Recording a decision must never make one.
- Not a replacement for the PRD. The PRD is the current desired state; decisions are the immutable log of how it got there. The PRD answers what, the graph answers why.
- Not a second source of truth. The repo holds it; Bloom indexes it (decision 1).
Risks
- Staleness is worse than absence. A graph that confidently reports an obsolete decision is
actively harmful. Mitigated by mandatory
statusandsupersedesedges, and by treating an unresolved contradiction as a surfaced conflict rather than a silent pick. - Capture fatigue. If Bloom proposes a decision record on every trivial turn, the client will start rubber-stamping or disabling it. The classifier for "this is decision-worthy" needs a high bar, and the frequency needs tuning against real usage.
- Index drift. The derived index must be rebuildable from the repo on demand, and treated as a cache - never patched in place as though it were truth.
- Scope. This is realistically two milestones (capture, then query/impact), not one. Trying to land both at once is how it becomes a permanently half-finished feature.
- Retrofit. Existing projects have no decision history. Backfilling from PRD revisions and merged PRs is possible but lossy, and should be clearly marked as inferred rather than approved.
Sketch of a phased delivery
Phase 1 - Capture. The record format, docs/decisions/, auto-proposal at the PRD and
plan-evolution gates, client approval, commit to the repo. No graph queries yet. Delivers value
immediately: the project starts accumulating rationale.
Phase 2 - Link & query. The derived index, the implements PR convention enforced in review,
a /why <topic> conversational query, and a dashboard Decisions view.
Phase 3 - Impact. Blast-radius traversal (the Supabase -> Netlify query), conflict/supersession detection, and decision context injected into planning and review so Bloom stops re-litigating settled questions.
Open questions
- Does a decision record belong to one project/repo only, or should cross-project decisions exist for an owner with several projects?
- How much of a decision should be quoted from the Telegram conversation, given the conversation may contain things the client would not want committed to a repo? (Leaning: link and summarize, never transcribe verbatim without approval.)
- Should specialist reviewers (FR-21) be able to propose decisions, or only the PO?
- Does the client want decision records in the project repo (visible to their engineers) or in a Bloom-side artifact? The default should be the repo, but this is the client's call and may need to be configurable.