---
id: 10058
title: 'Memory Core Graph: Add PR Nodes and RESOLVED_BY Edges for Ticket→PR Mapping'
state: CLOSED
labels:
  - enhancement
  - ai
  - architecture
  - needs-re-triage
assignees: []
createdAt: '2026-04-17T22:48:41Z'
updatedAt: '2026-06-06T17:44:30Z'
githubUrl: 'https://github.com/neomjs/neo/issues/10058'
author: tobiu
commentsCount: 2
parentIssue: null
subIssues: []
subIssuesCompleted: 0
subIssuesTotal: 0
blockedBy: []
blocking: []
closedAt: '2026-06-06T17:44:30Z'
---
# Memory Core Graph: Add PR Nodes and RESOLVED_BY Edges for Ticket→PR Mapping

## Intent / The Why

The Memory Core's Native Edge Graph (`GraphService.mjs`) maps structural and episodic knowledge: class hierarchies (`IMPLEMENTS`, `EXTENDS`), focus priorities (`FOCUS_ON`), system tenets (`SYSTEM_TENET`), and cross-framework analogies (`ANALOGOUS_TO`). However, it has **no representation of the Problem→Solution arc** between GitHub Issues and their resolving Pull Requests.

This matters because:

1. **Deterministic traversal beats fuzzy search.** An agent asking "what resolved issue #10051?" should get `PR #10053` via a single graph hop (`RESOLVED_BY` edge), not by running a vector similarity search and hoping the PR body mentions the ticket number.
2. **The Q&A reward signal.** The ticket body describes the _problem_ (intent, requirements, constraints). The PR body describes the _solution_ (implementation, trade-offs, review feedback). Linking them structurally creates a traversable reward signal that agents can learn from: "this is how problems like X get solved."
3. **Context Frontier enrichment.** When `get_context_frontier` primes an agent session, PR nodes with `RESOLVED_BY` edges would surface recent solution patterns alongside ongoing work items — giving agents implementation precedent, not just problem descriptions.

### Why the Graph (not the KB)?
The KB (ChromaDB) is for _semantic similarity_ — "find me things that sound like X." The graph is for _structural certainty_ — "this PR resolved this issue, period." The relationship is factual, not probabilistic. It belongs in the graph.

## Scope of Work

### 1. PR Node Type

Add support for `type: 'pull-request'` nodes in `GraphService`:

```js
GraphService.upsertNode({
    id         : 'pr-10053',
    type       : 'pull-request',
    name       : 'chore(ai): ticket-intake acceptance protocol (#10051)',
    description: 'Merged PR resolving issue #10051',
    properties : {
        number  : 10053,
        state   : 'MERGED',
        mergedAt: '2026-04-17T...',
        author  : 'tobiu'
    }
});
```

### 2. RESOLVED_BY Edge Type

Add `RESOLVED_BY` as a recognized relationship:

```js
GraphService.linkNodes('issue-10051', 'pr-10053', 'RESOLVED_BY', 5.0, {
    context_source: 'pr-sync',
    extracted_from: 'Resolves #10051'
});
```

Ensure `RESOLVED_BY` is excluded from decay in `decayGlobalTopology()` (same treatment as `IMPLEMENTS`, `EXTENDS`, `SYSTEM_TENET`) — these are factual relationships that should never decay.

### 3. Ingestion Mechanism

The extraction should parse `Resolves #N` / `Closes #N` patterns from PR body frontmatter/markdown. Two options for ingestion point:

- **Option A: Dream Pipeline stage** — A new stage in the REM cycle that reads `resources/content/pulls/*.md`, extracts ticket references, and upserts nodes + edges. This runs asynchronously and is self-healing.
- **Option B: PullRequestSyncer hook** — Inject graph updates directly into the GitHub Workflow server's `PullRequestSyncer` after it writes markdown files. Tighter coupling but real-time.

**Recommendation:** Option A (Dream Pipeline) aligns better with the existing architecture where the graph is populated by periodic background processes, not by real-time syncer hooks.

## Key Files

| File | Action |
|---|---|
| `ai/mcp/server/memory-core/services/GraphService.mjs` | **[MODIFY]** Add `RESOLVED_BY` to decay exclusion list |
| `ai/mcp/server/memory-core/services/SessionService.mjs` (or new DreamStage) | **[MODIFY/NEW]** PR→Issue extraction logic |
| Graph seed data | **[MODIFY]** Optional: backfill existing merged PRs |

## Reference

- `GraphService.linkNodes()` — the API for creating edges
- `GraphService.decayGlobalTopology()` line 272 — `WHERE type NOT IN ('IMPLEMENTS', 'EXTENDS', 'SYSTEM_TENET')` needs `'RESOLVED_BY'` added
- `resources/content/pulls/pr-10019.md` — example PR markdown showing `Resolves #10021` in body

## Acceptance Criteria

- [ ] PR nodes (`type: 'pull-request'`) can be upserted into the graph
- [ ] `RESOLVED_BY` edges link issue nodes to PR nodes
- [ ] `RESOLVED_BY` edges survive decay cycles
- [ ] `query_hybrid_graph(nodeId='issue-10051')` returns the resolving PR as a neighbor
- [ ] `get_context_frontier` surfaces recent PR resolutions alongside active epics

Origin Session ID: 144326a4-c1e8-4eee-80e4-b984f3c79ddc

## Timeline

- 2026-04-17T22:48:43Z @tobiu added the `enhancement` label
- 2026-04-17T22:48:43Z @tobiu added the `ai` label
- 2026-04-17T22:48:43Z @tobiu added the `architecture` label
### @neo-gpt - 2026-06-06T16:27:28Z

**Input from GPT-5.5 (Codex):**

> ● [ARCH_ALIGNMENT]
> 
> Intake verdict: `needs-narrowing` / `needs-re-triage`, not `valid-as-written`.
> 
> V-B-A evidence against the literal prescription:
> 
> - Live issue is unchanged since 2026-04-17T22:48:41Z and is still pre-stale by the 90d/14d inactive workflow, but bot age is not architecture currency.
> - The body points at the old pre-M6 service path (`ai/mcp/server/memory-core/services/GraphService.mjs`). Current authority is `ai/services/memory-core/GraphService.mjs` and `ai/services/ingestion/IssueIngestor.mjs`.
> - Current `IssueIngestor.ingestPullRequestFeedback()` already upserts PR nodes as `PULL_REQUEST` and links PRs to issues with `RESOLVES` edges from `pr-N -> issue-N` for `Resolves|Closes|Fixes #N` patterns.
> - Current `GraphService.getNeighbors()` / `queryNodeTopology()` traverse both inbound and outbound edges, so the old `issue -> pr RESOLVED_BY` direction is not required for issue-rooted topology traversal.
> - `learn/agentos/DreamPipeline.md` now documents the current contract: `Resolves #N` is canonical shipped-work form; the ingestor remains liberal for `Fixes`/`Closes`; narrowing `Closes` is a separate logic question. This supersedes the literal `RESOLVED_BY` naming in this ticket.
> - Historical successor evidence: #9851 / PR #9861 introduced the PR feedback integration lineage, and #10755 / PR #11621 later codified the current `RESOLVES` convention.
> 
> Residual that still looks valid after intake:
> 
> - `GraphService.decayGlobalTopology()` still protects only `IMPLEMENTS`, `EXTENDS`, and `SYSTEM_TENET`, so factual `RESOLVES` edges can decay/prune.
> - The PR-ingestion path lacks direct unit coverage proving PR node creation plus `RESOLVES` edge creation from synced PR markdown.
> 
> Recommended next shape: do not implement this ticket literally. Create or use a fresh narrow ticket for the current residual: protect `RESOLVES` edges from decay and add focused `IssueIngestor.ingestPullRequestFeedback()` tests for PR-node + resolution-edge creation. That ticket should use the current `RESOLVES` contract, not introduce parallel `RESOLVED_BY` semantics.

- 2026-06-06T16:27:48Z @neo-gpt added the `needs-re-triage` label
- 2026-06-06T16:29:30Z @neo-gpt cross-referenced by #12644
- 2026-06-06T16:51:30Z @neo-claude-opus cross-referenced by PR #12645
### @neo-gpt - 2026-06-06T17:44:23Z

**Input from GPT-5.5 (Codex Desktop):**

> ● Re-triage closeout after current-source V-B-A: #10058 is superseded by the modern `RESOLVES` contract and its residuals have now shipped.
> 
> Evidence checked on current `origin/dev` / merged state:
> 
> - `IssueIngestor.ingestPullRequestFeedback()` upserts `PULL_REQUEST` nodes from PR markdown and links close-keyword matches as `RESOLVES` edges from `pr-N` to `issue-N`.
> - `GraphService.getNeighbors()` traverses both inbound and outbound edges, so consumers can reach the resolving PR from the issue without a parallel `RESOLVED_BY` synonym.
> - #12644 narrowed the real residual: protect factual `RESOLVES` provenance edges from decay/pruning and add ingestion coverage.
> - PR #12645 merged #12644 and closed it as completed.
> - Focused verification on the merged surfaces passed: `npm run test-unit -- test/playwright/unit/ai/services/memory-core/GraphService.spec.mjs test/playwright/unit/ai/services/ingestion/IssueIngestor.spec.mjs --workers=1` -> 38 passed.
> 
> Conclusion: keeping #10058 open would preserve stale vocabulary (`RESOLVED_BY`) and a stale implementation prescription. Closing as superseded/not planned; the current delivered contract is `PULL_REQUEST` nodes plus `RESOLVES` edges.

- 2026-06-06T17:44:30Z @neo-gpt closed this issue

