Skip to content

Map: Harness-grounded graph model across the Context Graph family #275

Description

@antejavor

Destination

A specced, harness-general graph model covering the whole Context Graph family — agent-context-graph's Event Protocol, actions-graph's collection-tier schema (session → agent nesting → tool-call sequences), skills-graph's Skill/Procedure separation, and sessions-graph's Memory/Episode schema — establishing one coherent model and a clear, verified collection-tier/memory-tier boundary across all four components. Every mechanism justified against what real harnesses (Claude Code, Codex) actually provide, not aspirational fields (the pattern that produced dead PARENT_OF/FORKED_FROM shouldn't repeat elsewhere).

End state: the complete node/relationship model, Event Protocol fields, and population rules per component — ready to implement.

Notes

Domain: context-graph in memgraph/ai-toolkit. See context-graph/CONTEXT-MAP.md and each component's CONTEXT.md (gitignored, local-only — sessions-graph, actions-graph, skills-graph, agent-context-graph all have one; keep them updated as this map resolves things, the last map (#259) initially missed three of the four and had to backfill).

Skills every session should consult: /grilling + /domain-modeling for HITL tickets; a /research subagent for research tickets.

Standing preferences for this effort:

  • Model only what's actually extractable from real harness data (verified against primary docs) or explicitly justified as an inference rule with a stated mechanism — never an aspirational field nobody populates. This is the whole reason this map exists: actions-graph's PARENT_OF (nested actions) and Session.parent_session_id/FORKED_FROM (forked sessions) both exist in-schema today but are never populated by any real adapter — verified by reading claude_code.py's adapter and the Event Protocol directly.
  • "General" means grounded in at least two real harnesses (Claude Code, Codex), not designed against one and hoped to generalize.

Prior art / findings already grounded (from direct doc research, not assumption):

  • Both Claude Code (docs) and Codex (docs) use the identical field name tool_use_id to correlate a tool call's before/after events. This primitive is genuinely general and already correctly modeled (ToolCall/ToolResult.tool_use_id).
  • Both harnesses have the identical gap: subagent events carry agent_id/agent_type, but neither harness gives an explicit field linking that back to the specific parent tool_use_id that spawned the subagent. Any parent-linking must be inferred (e.g. temporally), not read from a field, on either harness.
  • Codex's docs state explicitly that subagent hooks report the parent's session_id, not a distinct one — a working assumption for Claude Code too until checked.
  • Neither harness's session-resume/fork signal (source: "resume"/"fork" on Claude Code; source: "resume"/"clear"/"compact" on Codex) carries the actual previous/parent session's ID in the payload. FORKED_FROM may not be populatable from hook data on any harness — a real scope question, not a modeling detail.
  • Claude Code has a PostToolBatch event ("after a full batch of parallel tool calls resolves") implying tool calls can run in parallel/batched — this breaks a naive "exactly one tool call is ever open at a time" assumption that a temporal subagent-parent-inference rule might otherwise rely on.
  • agent-context-graph's Event Protocol (ToolStartEvent/ToolEndEvent) has no field at all to carry a parent/nesting link, even if one were inferred — any inference rule needs a place to put its answer, which doesn't exist yet.
  • Shared prerequisite, spans multiple tickets (surfaced resolving Grilling: does Skill Usage need to attach to the specific nested Agent, not just the flat Session? #280, applies retroactively to Grilling: does a subagent instance become a first-class Agent node, or stay a flat Action pair with a populated PARENT_OF? #278 too) — correction, 2026-08-14: the original claim here ("ToolStartEvent/ToolEndEvent/MessageEvent have no agent_id field at all") was partially wrong. ToolStartEvent/ToolEndEvent already carry an agent_name: str | None field in events.py — it was simply never populated by claude_code.py's adapter for tool events (only for AgentStartEvent/AgentEndEvent construction from SubagentStart/SubagentStop). MessageEvent genuinely had no such field. Fixed by populating the existing field from payload.get("agent_id") in claude_code.py (PreToolUse/PostToolUse/PostToolUseFailure/UserPromptSubmit/UserPromptExpansion/PermissionRequest), and adding agent_name to MessageEvent. Codex's adapter has no SubagentStart/SubagentStop/agent_id handling at all today (only 6 of Codex's documented hook types are wired up) — left unchanged, a pre-existing gap outside this map's scope. Both actions-graph's (:Agent)-[:HAS_ACTION]->(:Action) (Grilling: does a subagent instance become a first-class Agent node, or stay a flat Action pair with a populated PARENT_OF? #278) and skills-graph's (:Agent)-[:USED_SKILL]->(:Skill) (Grilling: does Skill Usage need to attach to the specific nested Agent, not just the flat Session? #280) now have the field they need.

Decisions so far

  • Research: does the Claude Agent SDK / OpenAI Agents SDK give real parent-tool-call correlation, unlike command-hooks? — Not a clean "command-hooks bad, SDKs good" split. As currently wired, all four adapters (Claude Code, Codex, Claude Agent SDK, OpenAI Agents SDK) need the same general inference rule — none of their actually-used API surfaces carries a real parent-tool-call link. But one real, scoped upgrade path exists: Claude Agent SDK's message-stream API (query()/receive_response(), not the hooks={...} API claude.py currently uses) carries a genuine parent_tool_use_id field, verified empirically via a live probe. OpenAI Agents SDK has no equivalent path anywhere in its public surface. Spun out the "build it or not" decision as its own ticket, Grilling: should the Claude Agent SDK adapter consume the message-stream API for a real parent_tool_use_id? #283, since it's a real asymmetry question separate from designing the general rule.
  • Grilling: formalize the subagent-nesting parent-inference rule — Three-tier rule: (1) filter open tool calls (anywhere in the session, any nesting depth) by agent-spawning tool name; (2) if multiple open, disambiguate by matching SubagentStart.agent_type against each candidate's subagent-type input; (3) genuine ambiguity (same type, parallel) returns an honest null, never a guess. Verified explicitly against the real payload schema that agent_id ("agent_abc123def456", independent of tool_use_id) doesn't rescue tier 3 — it IDs the subagent, not its parent call. Explicit requirement, not optional: this is doc-derived, not observed — must be verified end-to-end against a live dev Memgraph instance with real Claude Code and Codex sessions before implementation is trusted.
  • Grilling: first-class Agent node, or populated PARENT_OF? — First-class (:Agent {agent_id, agent_type, started_at, ended_at, last_assistant_message, ...}) node. SubagentStart/SubagentStop stop being separate Action nodes, folding into one entity's lifecycle properties instead of two loose events in the flat timeline — same precedent as the Episode reversal (Grilling: what does continuous episodic-memory organization mean? #261). (:Session)-[:HAS_AGENT]->(:Agent) unconditionally (keeps a tier-3-ambiguous Agent reachable); (:Action)-[:SPAWNED]->(:Agent) only when Grilling: formalize the subagent-nesting parent-inference rule (including parallel/batch tool calls) #277 actually resolves a parent; (:Agent)-[:HAS_ACTION]->(:Action) for the agent's own tool calls, recursing naturally for further nesting. FOLLOWED_BY scoped per-container (session's own chain, each Agent's own separate chain), not one flat chain. Agent.agent_id is the unique key.
    Correction, 2026-08-14 (surfaced during implementation): PARENT_OF/parent_action_id was NOT dead as originally checked — actions-graph has a second, independent integration surface (actions_graph.hooks.ActionTracker, a direct Claude Agent SDK hooks={...} integration bypassing agent-context-graph) that actively populates it for real subagent nesting, tested in test_hooks.py. The original check only looked at the agent-context-graph/connector.py path. Resolution: migrate hooks.py onto the same Agent node model too, rather than freezing it as legacy or deleting it. The unrelated ToolResultToolCall use of parent_action_id/PARENT_OF (in both connector.py and hooks.py) is a different concept, stays as-is.
  • Grilling: keep, drop, or find another path for FORKED_FROM? — Drop FORKED_FROM/Session.parent_session_id entirely, same treatment as PARENT_OF. Verified against the exact, complete SessionStart field table (raw .md source, not a truncated fetch): no field anywhere carries a parent/previous session id, for any source value including fork. Also clarified resume vs. fork aren't the same kind of event — resume re-fires SessionStart for the same session_id (never a linkage question at all); only fork creates a genuinely new session_id, and that's exactly the case missing a parent-id field.
  • Grilling: does Skill Usage need to attach to the specific nested Agent? — Yes: USED_SKILL attaches to the specific Agent when usage happened inside a subagent, not just the flat Session — same relationship type either way, mirroring HAS_ACTION's existing either-container pattern from Grilling: does a subagent instance become a first-class Agent node, or stay a flat Action pair with a populated PARENT_OF? #278. Skill Surfacing gets the same treatment. Surfaced the shared Event Protocol agent_id gap (now in Notes above) rather than treating it as new fog specific to skills-graph.
  • Grilling: does the Episode/Memory model need any change given the nesting redesign? — The ticket's own "probably no change" hypothesis turned out wrong once traced through the actual code: get_session_actions() (actions-graph/core.py:594) does a direct single-hop HAS_ACTION match, not a recursive traversal. Once Grilling: does a subagent instance become a first-class Agent node, or stay a flat Action pair with a populated PARENT_OF? #278 lands, a subagent's tool calls move under (:Agent)-[:HAS_ACTION]->(:Action) — this query would silently stop seeing them, meaning reconciliation quietly loses all subagent activity (no entities extracted, no mention in the Episode). Real regression risk, not a no-op. Output shape stays the same (one flat Episode narrative, Chunks linking back to whichever Action produced them at any depth) — only the action-gathering step needs to traverse the full tree recursively. Recorded as a concrete implementation requirement for Grilling: does a subagent instance become a first-class Agent node, or stay a flat Action pair with a populated PARENT_OF? #278.
  • Grilling: should collection-tier vs. memory-tier be enforced in code, or stay a documented convention? — The map's synthesis ticket. Don't build a separate enforcement mechanism (no marker base class, no lint rule) — make Grilling: retention/compression policy for raw collection-tier data #267's confirmation-gated deletion mechanism (not yet implemented) the actual enforcement point, once built, via an explicit allowlist of collection-tier types (Action, Agent) rather than a denylist. Everything not on that allowlist is structurally protected by construction, with zero extra machinery. A marker class was rejected as symbolic-only (visible but not protective without an enforcement point attached); a lint rule was rejected as disproportionate to how small and stable this set of node types actually is.

This closes out the map's core destination — every structural ticket (#277 inference rule, #278 Agent node, #279 FORKED_FROM, #280 Skill Usage attachment, #281 Episode/nesting interaction, #282 enforcement) is now resolved. Only #283 (the Claude Agent SDK message-stream upgrade-path question) remains open — a real but genuinely separable decision, not required for the core model to be implementable.

Not yet specified

(empty — every fog item so far has graduated straight into a ticket; see the map's child issues.)

Out of scope

  • unstructured2graph/entity-extraction's own graph shape (Chunk/entity nodes) — unaffected by this map, not revisited.
  • The actual Procedure-mining algorithm mechanics (sequence-mining threshold, content-mining pipeline) — already decided in #262, tracked separately at #272. This map only covers where Procedure sits relative to Skill, not how mining works.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions