The problem: agentic projects forget the why
A multi-agent development setup produces a mountain of artifacts — PRDs, change requests, tasks, architecture docs, changelogs, commits. Most tooling captures what exists. Almost none captures why each step was taken — and that is exactly the memory a team of agents needs to stay coherent.
Three failure modes fall out of that gap, and every one of them is a lost-context problem:
Actor, not index
Context Graph's whole thesis is the fourth element: every edge carries a RelationContext —
who decided, why, via which channel, under which policy, valid until when. So the
thing you called most important — the reason behind each step — isn't a feature to bolt on. It's the native unit of
storage.
That flips the ambition. Not "an index of progress and code" but a decision-and-state substrate your agents read from and write to — one that can also act: define the methodology, hold current state, surface precedent, and refuse illegal moves.
| CG as index (passive) | CG as actor (this proposal) | |
|---|---|---|
| Role | A queryable mirror of files & code | Source of truth for decisions & state |
| Writes | Synced after the fact | Agents change state through CG |
| Methodology | Lives in prompts & humans | Encoded as ontology + rules + actions |
| On a bad move | Records it | Flags or blocks it, with the reason |
What CG is not — and what it sits on
A crowded adjacent category is the structural code graph — tools like CodeGraph that index symbols, calls, and imports with tree-sitter to make an agent's reading of a codebase cheaper (real, measurable token savings). CG is not that, and shouldn't try to be. The distinction is sharp — it's recoverable vs irrecoverable information:
Two more axes: CG is active (it gates, flags, records) where a code graph is descriptive (a map you
read); and CG is generic (sales, compliance, agentic dev — code is one config) where a code graph is
code-only. So the two stack: a structural index is the ideal feeder for CG's API/Module
objects — giving the "don't rebuild that API" check a complete, exact inventory to govern against.
Grounding: an existing AI dev team
This isn't hypothetical. The analysis maps onto a working methodology — dsivov/ai_development_team — so every proposal below has something real to answer to. (The setup evolves; treat it as the reference for principles, not a frozen spec.)
| In the current setup | What it is |
|---|---|
| Six roles | Manager · Architect · Developer · Integrator · DevOp · Production Engineer — each with distinct permissions and roles/<role>/CLAUDE.md instructions. |
| Hand-off | Markdown queue/*.md for inter-agent messaging, plus TASKS.md, CHANGELOG.md, CHANGEREQUESTS.md. |
| Enforcement (already exists) | hooks/role_enforcer.py validates operations via PreToolUse hooks; git branch-level restrictions enforce role boundaries. |
| Lifecycle | init → manager tasks → architect decomposition → developer impl → integrator test/commit → production-engineer release. |
| Multi-project · skills | projects/<name>/ isolation; each role has ~3 skills that auto-activate by context. |
The project ontology
The ontology layer already lets us declare a typed schema per workspace. A project graph is just the right object and link types for software delivery:
| Object types | Link types (each edge → an rc) |
|---|---|
Discussion, Decision, Feature, ChangeRequest,
Task, Module, API, Commit, Skill,
Role |
Discussion —produces→ Decision, Decision —motivates→ Feature/Task,
Feature —requires→ Task, Task —closed_by→ Commit, Module —exposes→ API,
Task —reuses→ API, CR —supersedes→ Decision, Role —owns→ Module,
Skill —applies_to→ Module |
Typed properties matter here too: a Decision has status (enum: proposed ·
accepted · superseded), an API has a signature and stability, a
Task has state. Those typed values are what the rules gate and precedent search reason over.
presets/agentic-dev/ontology.json.
What's worth remembering
You leaned toward "decisions worth remembering," not every keystroke — but weren't sure where the line falls. Here's a proposal for a principled line, and it comes straight out of CG's own design.
A RelationContext has content only if it can name a rationale and (usually) an actor —
CG even has an is_empty() check for exactly this. So use that as the filter:
| Event | Capture? | Why |
|---|---|---|
| Architecture / tech choice, API contract, a rejected option | yes — node+rc | Pure decision, with rationale. The crown jewels. |
| State transition (task → in-progress → done; feature → accepted) | yes — edge+rc | A decision with an actor and a reason ("moved to done: tests pass, integrator signed off"). |
| Artifact created (PRD, architecture doc, discussion) | node, ingested | Node for the artifact; its content is extracted via ingestion, its decisions emitted. |
| Commit | edge, thin rc | Evidence, not rationale — Task —closed_by→ Commit. The why lives on the decision it implements. |
| File save, lint run, every keystroke | no | Telemetry. Empty rc. Belongs in logs, not the graph. |
ainsert) for prose artifacts — point CG at the discussion HTML or the architecture
doc and let extraction pull entities + decisions out. Runtime emission
(emit_decision_trace) for structured moments — an agent finishing a task calls it directly with a clean,
typed RelationContext. The methodology decides which events get the structured path.
Roles as first-class identities
The roles are distinct, each with its own skills and sessions. The exact set is preset data, not core code — the current setup has six (manager, architect, developer, integrator, devop, production-engineer), and it will keep changing. Whatever the list, being distinct has three clean consequences in the graph:
approved_by / approved_via on every
decision records which agent made it. "Why is this API shaped this way? — the architect decided it in
session 42." Debuggable agents.Role —has_skill→ Skill —applies_to→ Module. Ask CG "what does
the developer need to touch the billing module?" and it answers from the graph, not a static prompt.ApproveArchitecture; the developer may not."
The harness already enforces roles for local tool/git use (role_enforcer.py + branch rules); what CG
lacks is the shared, auditable layer for project-state actions — object-level access control, the next
P3 item. The design, and the two-layer split, are worked out in Closing the gaps · Gap 1.
Methodology as data
You chose the actor / enforcement model — CG shapes agent behaviour, not just records it. The way that becomes real: encode the methodology in the three layers we already have, so the process is data, not prose in a system prompt.
| Methodology as… | Layer | Example |
|---|---|---|
| What states & objects are legal | Ontology | A Task may be proposed|active|done; a Decision may be superseded. |
| What transitions & moves are allowed | Rules | "No Task without a linked Feature." "Flag an API that duplicates an existing one." |
| What an agent may actually do | Actions | ProposeAPI, CreateTask, ApproveArchitecture, MergeIntegration. |
Together those three are a state machine over the project graph: agents advance the project only through governed actions, and the pre-emit gate refuses illegal moves — surfacing the reason. That is the "actor" in concrete terms.
# methodology rule: a new API that looks like an existing one gets flagged, not built
rule "reuse before you build" priority 20
when
sim(relation_type, "CREATE_API") > 0.4
and similar(decision_trace, "EXISTING_CAPABILITY", 0.55)
then
flag("A similar capability already exists — cite it or justify the new API")
end
Worked example: "don't rebuild that API"
The single use case that ties every layer together — a developer agent about to build something that already exists:
- Developer agent, before coding, invokes
ProposeAPI(name, description, module). - The gate runs
sim()over the descriptions of existingAPIobjects and does a precedent search over past decisions. - Duplicate? →
FLAGwith the match cited. The agent reads the reason and reuses instead. - Novel? →
PASS; an audit edge records the new API, attributed to the role and session. - Either way the reasoning is now in the graph — the next agent inherits it for free.
This is ontology (API objects) + rules (the duplicate check) + actions (governed ProposeAPI) + precedent search, composed.
The sync bus: CG replaces shared files
Today the four roles coordinate through shared files — changelogs, task lists, architecture docs — which go stale the moment they're written. Replace the file hand-off with a live query against CG over the MCP server: each agent reads current state on demand and writes decisions back.
local mode for a single module's
detail; global/mix for the whole-project map; CGR3 for multi-hop questions ("what
depends on the auth module, and who approved the last change to it?").Onboarding & the playbook
How does an agent know how to use CG — the config, the flow, its own permissions? A setup wizard tailors and initialises the project; each agent then gets an operating manual. But "manual" splits into two things that must be handled differently.
Two kinds of "knowing CG"
playbook.md the agent reads forever is just queue/*.md again: tweak a rule and it's
stale. So the project half is delivered both ways — a bootstrap snapshot to seed the session, plus a
live, role-scoped manifest the agent re-fetches so it never acts on yesterday's config.
The tailored wizard flow
The manifest — an agent's operating context
One generic, role-scoped call returns everything an agent needs to act; it re-fetches it when the config version bumps.
GET /workspace/manifest?role=developer →
{
"workspace": "acme_app", "role": "developer",
"skills": ["clean-code", "debugging", "impl-patterns"],
"object_types": ["Feature", "Task", "Module", "API", "Commit"],
"actions": [ {"name":"ProposeAPI", "params":[…], "effect":"…"}, {"name":"AdvanceTask", …} ],
"guardrails": [ "new API — confirm reuse (FLAG)", "deprecation needs a linked decision" ],
"lifecycle": { "Task": "proposed → active → done | blocked" },
"mcp": { "url": "…", "tools": ["query", "emit", "invoke"] }
}
The endpoints stay generic — POST /presets/{name}/install,
GET /workspace/manifest?role=, and a POST /onboard that runs install and returns
{workspace, per-role manifests, mcp, installed}. The wizard is orchestration on top; core never learns
"manager" or "Task".
roles/<role>/CLAUDE.md stays — but slimmed to generic CG usage + "fetch your manifest at
session start." The project specifics come from the live manifest, not baked into the file;
role_enforcer.py keeps gating local tools; the manifest tells the agent what CG itself will and won't
allow.
CLAUDE.md snapshot and a live role-scoped
manifest the agent re-fetches (the snapshot bootstraps; the manifest is the source of truth).
Ready vs missing
What this use case can stand on today, and what it's waiting on — mapped to the real state of the codebase.
| Capability | Status | Note |
|---|---|---|
| Typed project schema | ready | Ontology layer — declare the object/link types above. |
| Decision + reasoning capture | ready | RelationContext + emit_decision_trace + ingestion. |
| Reuse / precedent search | ready | find_precedents + sim() in a rule. |
| Methodology enforcement | ready | Rules gate (advise/flag/block) + governed actions. |
| Whole-picture & per-module views | ready | Query modes + CGR3; workspace isolation per project. |
| Agent sync channel | ready | MCP server exposes CG to each role. |
| Per-role permissions (who may do what) | shipped ✓ | Object-level RBAC — opt-in, deny-by-default, gates /actions/invoke (403). Gap 1. |
| Task lifecycle as a real workflow | shipped ✓ | Declarative state machines; transition actions checked + applied on the node (illegal → 409). Gap 2. |
| Deterministic inference (entailment) | gap | "A depends on B, B exposes X…" is LLM synthesis today, not derivation. |
| Auto code/API extraction | feed from below | CG won't parse the repo — by design. Feed API/Module objects from a deterministic structural index (tree-sitter / CodeGraph-style), and reserve LLM extraction for the decisions. |
Closing the gaps
A running analysis of the missing pieces this use case needs, settled one at a time. Each is a design sketch and a recommendation, not a committed build.
Gap 1 — Object-level RBAC shipped ✓
context_graph/rbac/ + /rbac, gating /actions/invoke (403).
Opt-in & deny-by-default; wildcard grants; principal from the JWT. The design below is what got built; only
ReBAC (relationship-derived grants) remains deferred — parsed & stored, enforcement is the next increment.
Your two firm choices — distinct roles and enforcement — both point straight at role-gated actions:
"the architect may ApproveArchitecture; the developer may not." That is the sharpest gap, because the
tempting shortcut is actively unsafe.
What's missing today. Access control stops at the workspace boundary (authenticate in
auth.py, then you get the whole tenant). Inside a workspace there is no principal who can do some
things and not others. Three pieces are absent:
- a principal within the workspace — the four roles as distinct, authenticated identities;
- permissions bound to objects/actions — role → which verbs, on which object types or actions;
- an enforcement point that runs before a state change.
Two layers, not one — CG complements the harness
Crucially, the current setup already enforces roles — in the agent harness
(role_enforcer.py PreToolUse hooks + git branch rules). CG must not try to replace that. The clean
division of labour:
| Layer | Governs | Where | Example |
|---|---|---|---|
| Harness hooks (exists today) | local tool use — file edits, git, branches | per-agent, PreToolUse | "developer can't push to main" |
| CG RBAC (proposed) | project-state actions on the shared graph | server-side, /actions/invoke | "only the manager may create a CR" |
So CG doesn't introduce access control from scratch — it moves the state-changing slice of it out of each agent's scattered hook config into one shared, auditable place. The reframing: not "close a security hole" but "centralize and audit what's already enforced per-agent." (The trap below still applies to that CG-side layer.)
approved_by is a security hole. (1) It's spoofable —
approved_by is data the caller supplies; a developer agent can invoke a merge claiming
approved_by: "integrator" and sail through. (2) It conflates two concerns — rules answer
"is this decision allowed by policy?"; access control answers "is this principal allowed to attempt
it at all?" RBAC that trusts a client-supplied field — or a LIGHTRAG-ROLE header — is theater.
The principal must bind to the authenticated identity (a role claim on the JWT / account), never to
something the agent sets in its own request.
The minimal design — a fourth policy layer
The shape is already familiar: a per-workspace policy next to rules / ontology / actions, enforced at the chokepoint the action layer already gives us.
| Piece | Where |
|---|---|
Role policy — role → grants (verb × object_type / action) | context_graph/rbac/ — per-workspace JSON, versioned (same store pattern) |
| Principal resolution — the role claim | auth.py — from the JWT / account, not a header |
| Enforcement — the check | A pre-gate stage in /actions/invoke: resolve → RBAC → rules gate → side effect |
| Default posture | Two-level — opt-in (no policy = permissive), then deny-by-default within a policy; wildcard grants keep it terse |
Opt-in by default — it scales to zero
The requirement that CG stay simple for single-agent projects (including CG's own development) is met by the same pattern every other layer uses: absent means permissive. No rules → no gate; no ontology → no validation; and now no RBAC policy → allow everything. RBAC exists only where a workspace installs a policy.
manager: *; you enumerate only the restricted roles.Deployment stays identical — no external authorization service, no Zanzibar to stand up. RBAC is one more per-workspace JSON policy on the existing store; ReBAC reuses the graph. Single binary, same as today.
The CG-native upgrade — permissions from the graph (ReBAC)
Static role → actions works, but the project graph already encodes ownership:
Role —owns→ Module —exposes→ API. So a grant can be relationship-derived — "a role may modify the
APIs of modules it owns" — resolved by a graph query at check time. That is ReBAC (relationship-based, à la
Google Zanzibar), and CG is unusually well-placed for it because the relationships are already first-class.
Permissions then evolve with the project instead of living in a separate static table.
admin: * policy is
offered for those who want a uniform audit trail. ·
Principal grain (lean) — role = principal (one credential per role); account → role stays an
optional refinement for per-individual audit.
ReBAC appetite — is "permissions derived from ownership edges" the target, or is static role→action enough for your methodology?
Gap 2 — Workflow orchestration shipped ✓
context_graph/lifecycle/ + /lifecycle: declarative state
machines per object type, a transition action checked (legal move? role allowed?) and applied to the object's node,
illegal jumps → 409. The planner was not built, by design. Cross-object requires guards are
parsed & stored, enforcement deferred.
"Workflow orchestration" is really two gaps, and conflating them is how a project builds the wrong, enormous thing:
Task: proposed →
active → done | blocked; the setup's macro pipeline (init → tasks → decomposition
→ impl → test/commit → release). Legal transitions, guards, entry conditions.What the lifecycle layer is
Mostly composable from what exists, plus one new declarative primitive:
| Piece | Layer |
|---|---|
State values (Task.state) | Ontology enum — ready |
Transitions (AdvanceTask(to:"done")) | Actions — ready |
| Guards / entry conditions | Rules — ready, but hand-written & scattered today |
| New: a named state machine — legal transitions auto-derived, current state + history first-class | context_graph/lifecycle/ — to build |
A transition becomes one guarded action: AdvanceTask(to:"done") → RBAC (may this role?) →
lifecycle check (is active→done legal?) → rules gate (policy: passing commit + integrator sign-off?) →
apply + audit edge (who moved it, why, evidence). The richer part is cross-object entry conditions — "a
Feature can't enter in_progress until an accepted Decision motivates it" — a
guard expressed as a graph query. That is what makes CG's version beat a standalone FSM library.
Open questions & decision log
Decided (this session)
| Question | Lean |
|---|---|
| Granularity | tentative "decisions & state transitions," via the can-you-name-the-why filter. Revisit commit richness. |
| Roles | decided distinct identities, each with own skills + sessions. |
| Enforcement | decided actor / enforcement model — CG shapes behaviour (advise + block). |
Still open — for the next round
REJECT vs soft FLAG? Getting this wrong makes CG either a
nag or a roadblock. Draft the first rule set together?