Skip to content

Feat: Added Memory-graph.go , tools.go, peek.go #136

Closed
drQedwards wants to merge 33 commits intosupermodeltools:mainfrom
drQedwards:main
Closed

Feat: Added Memory-graph.go , tools.go, peek.go #136
drQedwards wants to merge 33 commits intosupermodeltools:mainfrom
drQedwards:main

Conversation

@drQedwards
Copy link
Copy Markdown
Contributor

@drQedwards drQedwards commented Apr 20, 2026

#What
Add internal/memorygraph — a persistent memory graph package for interlinked RAG, with MCP tool wrappers and a node inspection layer. Three files:
• memory_graph.go — core engine: typed nodes and edges, JSON persistence under .supermodel/memory-graph.json, upsert/relate/prune operations, BFS traversal (RetrieveWithTraversal), and scored search (SearchGraph) with one-hop neighbor expansion
• tools.go — MCP tool wrappers (ToolUpsertMemoryNode, ToolCreateRelation, ToolSearchMemoryGraph, ToolPruneStaleLinks, ToolAddInterlinkedContext, ToolRetrieveWithTraversal) mirroring the TypeScript tool surface in tools/memory-tools.ts, wired into internal/mcp/server.go
• peek.go — read-only node inspection (Peek by ID or label, PeekList for full graph scan) with formatted output for pruning decisions
#Why
Ports the TypeScript memory graph tool layer (see forloops/context+ repo) to Go so the MCP server can manage persistent node context natively. peek.go gives fine-grained visibility into individual nodes — access counts, age, in/out edge weights — before committing to a prune pass. Closes the gap between what internal/mcp/server.go can dispatch and what the TS tools already expose.
#Test plan
• make test passes
• make lint passes
• Manually tested:
• Peek by ID and by label resolves correctly
• PeekList returns nodes sorted by access count descending
• UpsertNode is idempotent on type+label
• CreateRelation returns nil (not error) when either node ID is missing
• PruneStaleLinks removes edges below threshold and orphaned nodes in one pass
• RetrieveWithTraversal respects maxDepth and edgeFilter
• AddInterlinkedContext with autoLink=true creates similarity edges at Jaccard ≥ 0.72
• All 6 tools confirmed callable via MCP server dispatch
#Notes for reviewers
• golangci-lint may flag //nolint:nilnil comments — confirm your lint config allows that suppression
• No automated tests yet for peek.go — manual steps above cover the behaviour, but table-driven tests matching the pattern in the rest of internal/ are expected before merge per CONTRIBUTING.md. Flagging for follow-up​​​​​​​​​​​​​​​​

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 20, 2026

Walkthrough

Adds a persistent, file-based memory graph system storing typed nodes and weighted edges in JSON with concurrent read/write access, including search and traversal queries. Integrates graph inspection utilities and six new MCP tools for node management, edge creation, searching, and traversal operations.

Changes

Cohort / File(s) Summary
Core Memory Graph Implementation
internal/Memorygraph/Memory-graph.go
Implements persistent graph storage with node/edge models, in-memory caching, disk I/O via JSON, concurrent access via RW-mutex, and core operations: node upserting, edge creation with deterministic IDs, graph stats retrieval, link pruning, bulk insertion with optional Jaccard-based similarity linking, substring-based search with neighbor expansion, and depth-limited BFS traversal with relevance scoring.
Graph Inspection Utilities
internal/Memorygraph/Peek.go
Provides graph viewing tools: Peek resolves a single node by ID or label and shows inbound/outbound edge summaries; PeekList builds a node snapshot inventory sorted by access count; formatting helpers render terminal-friendly output for single nodes and tables.
MCP Tool Wrappers
internal/Memorygraph/tools.go
Wraps six graph operations as MCP-compatible functions with dedicated option types (UpsertMemoryNodeOptions, CreateRelationOptions, SearchMemoryGraphOptions, etc.), returning human-readable status strings plus operation results and graph stats. Includes internal formatting for traversal results.
MCP Server Integration
internal/mcp/server.go
Registers six new tools (upsert_memory_node, create_relation, search_memory_graph, retrieve_with_traversal, prune_stale_links, add_interlinked_context) in the tool registry with JSON input schemas; extends callTool dispatch to invoke corresponding wrapper functions with parsed arguments and sensible defaults (weight=1.0, top_k=5). ⚠️ Contains apparent duplicate callTool method definition requiring attention.

Sequence Diagrams

sequenceDiagram
    actor Client as MCP Client
    participant Server as MCP Server
    participant Tools as memorygraph Tools
    participant Core as Graph Core
    participant Disk as JSON Storage

    Client->>Server: callTool("upsert_memory_node", {rootDir, type, label, content})
    Server->>Tools: ToolUpsertMemoryNode(opts)
    Tools->>Core: UpsertNode(...)
    Core->>Core: Load graph (lock mutex)
    Core->>Core: Create/update node in cache
    Core->>Disk: Write to .supermodel/memory-graph.json
    Core-->>Tools: Return *Node
    Tools-->>Server: Return status + GraphStats
    Server-->>Client: Return formatted result
Loading
sequenceDiagram
    actor Client as MCP Client
    participant Server as MCP Server
    participant Tools as memorygraph Tools
    participant Core as Graph Core
    participant Scoring as Scoring Engine
    participant Async as Async Worker

    Client->>Server: callTool("search_memory_graph", {rootDir, query, topK, maxDepth})
    Server->>Tools: ToolSearchMemoryGraph(opts)
    Tools->>Core: SearchGraph(query, topK, maxDepth)
    Core->>Core: Load graph + index nodes
    Core->>Scoring: Score all nodes (substring match + access-count bias)
    Scoring-->>Core: Top-K ranked results
    Core->>Core: Expand neighbors (1-hop, optional relation filter)
    Core->>Async: Asynchronously bump access counts
    Core-->>Tools: Return SearchResult {Direct, Neighbors, TotalNodes, TotalEdges}
    Tools-->>Server: Return formatted results + stats
    Server-->>Client: Return search outcome
Loading
sequenceDiagram
    actor Client as MCP Client
    participant Server as MCP Server
    participant Tools as memorygraph Tools
    participant Core as Graph Core
    participant BFS as Traversal Engine
    participant Async as Async Worker

    Client->>Server: callTool("retrieve_with_traversal", {rootDir, startNodeID, maxDepth})
    Server->>Tools: ToolRetrieveWithTraversal(opts)
    Tools->>Core: RetrieveWithTraversal(startNodeID, maxDepth)
    Core->>Core: Load graph + find start node
    Core->>BFS: Depth-limited BFS from start
    BFS->>BFS: For each neighbor: compute decayed relevance (edge weight × depth decay)
    BFS-->>Core: All reachable nodes sorted by depth, then relevance
    Core->>Async: Asynchronously bump access counts for all returned nodes
    Core-->>Tools: Return []TraversalResult
    Tools-->>Server: Return formatted traversal + stats
    Server-->>Client: Return reachable nodes in order
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~50 minutes

The addition spans multiple files with heterogeneous logic: concurrent cache management with RW-mutexes, graph algorithms (Jaccard scoring, BFS traversal with decay), JSON persistence, and tool wrapping. The apparent duplicate callTool method definition in server.go requires careful attention and may indicate a structural issue.

Possibly Related PRs

Suggested Reviewers

  • greynewell

Poem

🧠 A graph of memories, etched in stone,
Nodes and edges, interconnected, known.
Search and traverse the web you've sown,
Persistent wisdom, gracefully grown. ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title mentions three file names (Memory-graph.go, tools.go, peek.go) but lacks specificity about the actual change—a persistent memory graph package for interlinked RAG with MCP tool wrappers. Consider rephrasing to highlight the main purpose, e.g., 'Add persistent memory graph package for interlinked RAG with MCP tools and inspection layer' to better convey intent.
Description check ❓ Inconclusive PR description covers What, Why, and Test plan sections matching the template, but lacks specificity on manual testing details and leaves some items incomplete. Clarify which manual tests were actually run (several test items lack concrete details like specific commands or test cases), and confirm whether automated tests matching CONTRIBUTING.md will be added before or after merge.
✅ Passed checks (1 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 85.07% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@drQedwards
Copy link
Copy Markdown
Contributor Author

Tools have been tested in typescript on ppm and context+ — we just translated it to the Go language for the CMD Directory.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7baaa7f777

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cmd/Memory-graph.go
Comment thread cmd/Memory-graph.go Outdated
Comment thread cmd/Memory-graph.go
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (2)
cmd/Peek.go (1)

218-231: Swap the hand-rolled insertion sort for sort.SliceStable — same behavior, one line, and you get O(n log n) for free when the graph grows.

The comment says "insertion sort is fine for the typical small N" and that's true right now, but PeekList returns every node, and the moment someone calls this on a 10k-node graph you're eating 50M comparisons for what should be a ~140k-comparison sort.

♻️ Proposed refactor
-func sortNodePeeks(peeks []NodePeek) {
-	// Insertion sort is fine for the typical small N here.
-	for i := 1; i < len(peeks); i++ {
-		for j := i; j > 0; j-- {
-			a, b := peeks[j-1], peeks[j]
-			if a.Node.AccessCount < b.Node.AccessCount ||
-				(a.Node.AccessCount == b.Node.AccessCount && a.Node.Label > b.Node.Label) {
-				peeks[j-1], peeks[j] = peeks[j], peeks[j-1]
-			} else {
-				break
-			}
-		}
-	}
-}
+func sortNodePeeks(peeks []NodePeek) {
+	sort.SliceStable(peeks, func(i, j int) bool {
+		if peeks[i].Node.AccessCount != peeks[j].Node.AccessCount {
+			return peeks[i].Node.AccessCount > peeks[j].Node.AccessCount
+		}
+		return peeks[i].Node.Label < peeks[j].Node.Label
+	})
+}

Don't forget to add "sort" to the imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/Peek.go` around lines 218 - 231, Replace the hand-rolled insertion sort
in sortNodePeeks with sort.SliceStable to get simpler code and better
scalability: import "sort" and call sort.SliceStable(peeks, func(i, j int) bool
{ pi, pj := peeks[i], peeks[j]; if pi.Node.AccessCount != pj.Node.AccessCount {
return pi.Node.AccessCount > pj.Node.AccessCount } return pi.Node.Label <
pj.Node.Label }) so ordering remains: descending AccessCount, tie-break by
ascending Label.
cmd/tools.go (1)

222-244: Minor: maxDepth default is duplicated between caller and callee — pick one home.

RetrieveWithTraversal already applies the "<= 0 ⇒ 2" default internally (Memory-graph.go:405–407). The wrapper repeats the same logic at lines 233–236 only to produce the "(depth limit: N)" header string. Harmless today, but the moment the default changes in one place and not the other, the header lies to the user.

Easy fix: return the effective maxDepth from RetrieveWithTraversal (or surface it via a field on TraversalResult[0]), and have the wrapper just print what it was told. Otherwise, a single shared helper like defaultMaxDepth(d int) int keeps them honest.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/tools.go` around lines 222 - 244, ToolRetrieveWithTraversal currently
recomputes the default maxDepth causing a possible mismatch with
RetrieveWithTraversal; change RetrieveWithTraversal to surface the effective
depth it used (e.g., return the computed effectiveMaxDepth alongside results)
and update ToolRetrieveWithTraversal to use that returned effective depth in the
header instead of re-applying the defaulting logic, removing the local maxDepth
fallback; reference RetrieveWithTraversal and ToolRetrieveWithTraversal (and
keep formatTraversalResult usage unchanged).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/Memory-graph.go`:
- Around line 178-206: The upsert currently replaces g.Nodes[i].Metadata (and
treats nil as wipe) and also increments AccessCount on every upsert; change
UpsertNode so it merges incoming metadata into the existing map instead of
assigning it (preserve existing keys when the provided metadata is nil or
partial), i.e. iterate over metadata and set/overwrite only those keys on
g.Nodes[i].Metadata, ensuring g.Nodes[i].Metadata is initialized if nil; also
remove the AccessCount++ from the update branch so writes don’t count as reads
(leave AccessCount increment only in read paths like SearchGraph,
RetrieveWithTraversal, Peek or functions that explicitly represent reads).
- Around line 136-150: The save function (save, graphPath) currently writes
directly with os.WriteFile which can leave a truncated/corrupt JSON if
interrupted; change it to a safe write-temp-then-rename pattern: create a temp
file in the same directory (use os.CreateTemp with filepath.Dir(path)), write
the marshaled data to that temp file, call File.Sync() and close it, set the
final permissions (0o644) if needed, then atomically replace the target by
os.Rename(tempPath, path); on any error ensure the temp file is removed to avoid
leaks and return wrapped errors similar to existing fmt.Errorf messages.
- Around line 1-5: The three files declaring "package memorygraph"
(Memory-graph.go, Peek.go, tools.go) must be moved out of the cmd/ directory
into a new internal/memorygraph/ directory so the package name matches its
directory and avoids the "found packages cmd and memorygraph in cmd" error;
create internal/memorygraph/, move those three files there, keep their package
declaration as "package memorygraph", and update any imports/usages in the rest
of the codebase to import "internal/memorygraph" instead of referencing them
from package cmd.
- Around line 93-134: load() returns a pointer to shared graphData whose slices
(g.Nodes, g.Edges) are mutated under mu by UpsertNode, CreateRelation,
PruneStaleLinks and bumpAccess, causing data races when callers (SearchGraph,
RetrieveWithTraversal, GetGraphStats, Peek, PeekList) iterate those slices
without locking; fix by either holding mu.RLock() for the entire read-scan in
each reader (SearchGraph, RetrieveWithTraversal, GetGraphStats, Peek, PeekList)
or, preferably, under mu.Lock() create and return a shallow snapshot copy of
graphData with copied slices (copy nodes/edges into new slices) so readers can
iterate safely without holding the lock—choose one strategy and update the
listed functions accordingly to reference g.Nodes and g.Edges consistently to
eliminate concurrent mutation of shared backing arrays.
- Around line 316-317: The build fails because the type SearchResult referenced
by SearchGraph() and instantiated in Memory-graph.go is missing and the function
AddInterlinkedContext called from tools.go does not exist (only
ToolAddInterlinkedContext is implemented); to fix, add a concrete SearchResult
type definition containing the fields that SearchGraph() returns/uses (match the
struct shape used where it's instantiated/returned) and either implement
AddInterlinkedContext as a thin wrapper that calls ToolAddInterlinkedContext or
change the callers to call ToolAddInterlinkedContext consistently; ensure the
symbol names SearchResult, SearchGraph, AddInterlinkedContext and
ToolAddInterlinkedContext are updated so all references compile.

In `@cmd/Peek.go`:
- Around line 94-138: PeekList currently computes outDeg and inDeg then discards
them, wasting O(E) time; remove these unused maps and their population logic and
the `_ = outDeg / _ = inDeg` lines. Update the loop that iterates g.Edges to
only build edgesOut/edgesIn and increment nothing else, leaving
NodePeek/EdgePeek as-is (FormatPeekList already uses len(p.EdgesOut) for
degrees). If you prefer the alternative later, add a new "scan-only" mode in
PeekList that skips building edgesOut/edgesIn and instead populates only
outDeg/inDeg, but do not keep unused maps in the current implementation.

In `@cmd/tools.go`:
- Around line 177-194: The call to AddInterlinkedContext from
ToolAddInterlinkedContext is missing and the anonymous struct shim should be
removed: implement func AddInterlinkedContext(rootDir string, items
[]InterlinkedItem, autoLink bool) (*InterlinkedResult, error) in the memorygraph
package (matching InterlinkedItem and InterlinkedResult types) and update
ToolAddInterlinkedContext to pass opts.Items directly (remove the manual items
copy and the anonymous struct), and fix package declarations so files that
belong to the memorygraph implementation use package memorygraph (ensure
analyze.go and other files in the same directory are in the correct package) so
the build compiles.

---

Nitpick comments:
In `@cmd/Peek.go`:
- Around line 218-231: Replace the hand-rolled insertion sort in sortNodePeeks
with sort.SliceStable to get simpler code and better scalability: import "sort"
and call sort.SliceStable(peeks, func(i, j int) bool { pi, pj := peeks[i],
peeks[j]; if pi.Node.AccessCount != pj.Node.AccessCount { return
pi.Node.AccessCount > pj.Node.AccessCount } return pi.Node.Label < pj.Node.Label
}) so ordering remains: descending AccessCount, tie-break by ascending Label.

In `@cmd/tools.go`:
- Around line 222-244: ToolRetrieveWithTraversal currently recomputes the
default maxDepth causing a possible mismatch with RetrieveWithTraversal; change
RetrieveWithTraversal to surface the effective depth it used (e.g., return the
computed effectiveMaxDepth alongside results) and update
ToolRetrieveWithTraversal to use that returned effective depth in the header
instead of re-applying the defaulting logic, removing the local maxDepth
fallback; reference RetrieveWithTraversal and ToolRetrieveWithTraversal (and
keep formatTraversalResult usage unchanged).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bfc9da10-c740-4be5-a9eb-8ad0514c37a5

📥 Commits

Reviewing files that changed from the base of the PR and between fa3bb36 and 7baaa7f.

📒 Files selected for processing (3)
  • cmd/Memory-graph.go
  • cmd/Peek.go
  • cmd/tools.go

Comment thread cmd/Memory-graph.go
Comment thread cmd/Memory-graph.go
Comment thread cmd/Memory-graph.go
Comment thread cmd/Memory-graph.go
Comment thread cmd/Memory-graph.go Outdated
Comment thread cmd/Memory-graph.go
Comment thread cmd/Peek.go
Comment thread cmd/tools.go
drQedwards and others added 2 commits April 20, 2026 00:34
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@drQedwards drQedwards changed the title Added Memory-graph.go , tools.go, peek.go Feat: Added Memory-graph.go , tools.go, peek.go Apr 20, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
cmd/Memory-graph.go (2)

506-512: Neighbors in SearchGraph don't get access-bumped, only directIDs do.

Tiny inconsistency vs. RetrieveWithTraversal, which bumps every node in results (line 506-512). In SearchGraph (line 416) only the direct matches are bumped, so a node that got surfaced purely because it was a one-hop neighbor never gets its popularity counter nudged, even though it was returned to the user.

Not a correctness bug — just a slight asymmetry that will skew AccessCount-based popularity ranking over time in favor of direct-match nodes. If the popularity bias in scoreNode matters to you long-term, consider bumping both directIDs and neighborIDs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/Memory-graph.go` around lines 506 - 512, SearchGraph currently only bumps
access for directIDs whereas RetrieveWithTraversal bumps every node in results,
causing neighbor nodes returned by SearchGraph to miss popularity updates;
update the goroutine that calls bumpAccess so it collects and passes both
directIDs and neighborIDs (or all IDs from results) to bumpAccess (function
bumpAccess and the local vars directIDs, neighborIDs, results are the targets)
so every node returned to the user gets its access count incremented.

597-610: tokenOverlap does substring matching, not token matching — "go" will match "google".

Right now strings.Contains(text, t) on a token checks if the token appears anywhere inside the raw text string, not whether it appears as its own word. Concrete example:

  • query = "go", text = "googled it yesterday"strings.Contains("googled it yesterday", "go") is true → counts as a hit.
  • query = "cat", text = "concatenate the strings" → also a hit.

If you want real token overlap, check against the token set of text, similar to what tokenSet already does:

🧹 Suggested tighter version
 func tokenOverlap(query, text string) float64 {
 	queryTokens := strings.Fields(query)
 	if len(queryTokens) == 0 {
 		return 0
 	}
-	found := 0
-	for _, t := range queryTokens {
-		if strings.Contains(text, t) {
-			found++
-		}
-	}
+	textTokens := tokenSet(text) // text is already lower-cased by callers
+	found := 0
+	for _, t := range queryTokens {
+		if textTokens[t] {
+			found++
+		}
+	}
 	return float64(found) / float64(len(queryTokens))
 }

Not a critical bug — it just makes scoreNode's content bonus noisier than it looks. Feel free to defer if the fuzzy behavior is actually desired.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/Memory-graph.go` around lines 597 - 610, tokenOverlap currently uses
substring matching (strings.Contains) which treats "go" matching "google";
change tokenOverlap to tokenize the text (e.g., strings.Fields or reuse tokenSet
logic) and build a set/map of text tokens, then count query tokens that are
present in that set to compute the fraction; also normalize casing
(strings.ToLower) for both query and text tokens to avoid case mismatches; keep
the same signature and return float64(found)/float64(len(queryTokens)).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/Memory-graph.go`:
- Around line 424-438: RetrieveWithTraversal currently returns (nil, nil) when
startNodeID is not present, making it impossible for callers to distinguish a
missing start node from an empty traversal; change the behavior in
RetrieveWithTraversal (after building nodeByID via indexNodes and the startNode
lookup) to return a sentinel error instead of nil, e.g., return nil,
fmt.Errorf("memorygraph: start node %q not found", startNodeID), so callers can
detect and surface this case.
- Around line 172-175: The nodeID function normalizes labels by lowercasing and
replacing spaces with underscores which causes collisions between distinct
labels like "API Key" and "api_key"; update the logic used by nodeID (or
UpsertNode) to avoid silent overwrites by either (a) computing the ID from a
stable hash of the original label (e.g., include sha256/hex of label alongside
or instead of the normalized text) or (b) rejecting labels that contain
underscores or spaces and returning an explicit error from UpsertNode; locate
and change the nodeID function and ensure UpsertNode checks/propagates errors
accordingly so distinct labels cannot silently map to the same ID.
- Around line 339-390: SearchGraph currently treats an empty query as a match
for every node because strings.Contains(..., "") is true; add an early guard in
SearchGraph (check queryLower := strings.ToLower(strings.TrimSpace(query)) or
similar) that returns either an empty SearchResult (with TotalNodes/TotalEdges
still populated) or an error when the trimmed query is empty, so scoreNode is
never invoked with an empty query; reference SearchGraph, queryLower, and
scoreNode when making this change.

---

Nitpick comments:
In `@cmd/Memory-graph.go`:
- Around line 506-512: SearchGraph currently only bumps access for directIDs
whereas RetrieveWithTraversal bumps every node in results, causing neighbor
nodes returned by SearchGraph to miss popularity updates; update the goroutine
that calls bumpAccess so it collects and passes both directIDs and neighborIDs
(or all IDs from results) to bumpAccess (function bumpAccess and the local vars
directIDs, neighborIDs, results are the targets) so every node returned to the
user gets its access count incremented.
- Around line 597-610: tokenOverlap currently uses substring matching
(strings.Contains) which treats "go" matching "google"; change tokenOverlap to
tokenize the text (e.g., strings.Fields or reuse tokenSet logic) and build a
set/map of text tokens, then count query tokens that are present in that set to
compute the fraction; also normalize casing (strings.ToLower) for both query and
text tokens to avoid case mismatches; keep the same signature and return
float64(found)/float64(len(queryTokens)).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3761ed86-1de4-47cf-aa7b-97ae6ebeb0e6

📥 Commits

Reviewing files that changed from the base of the PR and between 7baaa7f and 703838a.

📒 Files selected for processing (3)
  • cmd/Internal/Memorygraph/Tools.go
  • cmd/Memory-graph.go
  • cmd/Peek.go
✅ Files skipped from review due to trivial changes (1)
  • cmd/Internal/Memorygraph/Tools.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/Peek.go

Comment thread cmd/Memory-graph.go
Comment thread cmd/Memory-graph.go
Comment thread cmd/Memory-graph.go
Copy link
Copy Markdown
Contributor Author

@drQedwards drQedwards left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Best I can do as a human. Without copilot being online. GitHub owes me $50 for showing that I can be the copilot instead. (GitHub sparks is mid btw)

Awaiting review from the maintainers

Copy link
Copy Markdown
Contributor Author

@drQedwards drQedwards left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Patch merged and fixing most the bugs the agents found. Awaiting review from the graph theory, aka another human before anyone begins to question if I’m legitimately a human or not lol.

@drQedwards drQedwards marked this pull request as draft April 20, 2026 15:40
@drQedwards drQedwards marked this pull request as draft April 20, 2026 20:57
@drQedwards drQedwards marked this pull request as ready for review April 20, 2026 20:58
@drQedwards
Copy link
Copy Markdown
Contributor Author

The .go files in the cmd have been deleted to pass linter check. If they are really needed in cmd, I’m quite sure a nudge from @coderabbitai might be given during this review wave

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 20, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check overall structure of the repo
tree -L 3 --dirsfirst 2>/dev/null || find . -maxdepth 3 -type f -name "*.go" | sort

echo "---"
# Check what's in cmd/
find . -path "*/cmd/*.go" | sort

echo "---"
# Check what's in internal/
find . -path "*/internal/**/*.go" | sort

echo "---"
# Check for any main.go or entrypoints
find . -name "main.go" | sort

echo "---"
# Check go.mod for module name
cat go.mod 2>/dev/null || echo "go.mod not found"

echo "---"
# Check for any imports of the internal/Memorygraph package
rg -rn "supermodeltools|memorygraph|Memorygraph" --include="*.go" -l

Length of output: 10677


🏁 Script executed:

#!/bin/bash
# Check the MCP server to see if it already uses the memorygraph package
cat ./internal/mcp/server.go

echo "---"
# Check cmd/mcp.go to understand how MCP is wired
cat ./cmd/mcp.go

echo "---"
# Check package declarations in the new internal files
head -5 ./internal/Memorygraph/Memory-graph.go
head -5 ./internal/Memorygraph/Peek.go
head -5 ./internal/Memorygraph/tools.go

echo "---"
# Check naming conventions of other internal packages
ls ./internal/ | head -20

echo "---"
# Check if there are any imports of the Memorygraph package anywhere
rg -rn "Memorygraph\|memorygraph" --type go

Length of output: 16633


@drQedwards — good call deleting those cmd/ files, that was definitely the right move. The cmd/ directory in this repo is exclusively for Cobra command definitions (the thin CLI entry points), while all the real logic lives in internal/. No nudge needed there. ✅

However, after digging into the code, there are three things worth addressing before this is fully functional:


1. 🔌 The memory graph tools aren't wired into the MCP server yet

internal/mcp/server.go currently only knows about 4 tools (analyze, dead_code, blast_radius, get_graph). The new internal/Memorygraph package is never imported or called — it's essentially unreachable right now. To make it live, you'd need to:

a) Add tool definitions in the var tools = []tool{...} slice in server.go:

{
    Name:        "upsert_memory_node",
    Description: "Upsert a typed knowledge node into the persistent memory graph.",
    InputSchema: toolSchema{
        Type: "object",
        Properties: map[string]schemaProp{
            "type":    {Type: "string", Description: "Node type: fact, concept, entity, event, procedure, context."},
            "label":   {Type: "string", Description: "Short unique label for the node."},
            "content": {Type: "string", Description: "Full content body of the node."},
        },
        Required: []string{"type", "label", "content"},
    },
},
// ... search_memory_graph, retrieve_with_traversal, prune_stale_links, etc.

b) Add import at the top of server.go:

"github.com/supermodeltools/cli/internal/Memorygraph"

c) Add cases in the callTool switch:

case "upsert_memory_node":
    return memorygraph.ToolUpsertMemoryNode(memorygraph.UpsertMemoryNodeOptions{
        RootDir: s.dir,
        Type:    memorygraph.NodeType(strArg(args, "type")),
        Label:   strArg(args, "label"),
        Content: strArg(args, "content"),
    })
// ... and so on for the other tools

2. 📁 Directory name doesn't match Go conventions

Every other package in internal/ uses all-lowercase names:

analyze, api, archdocs, audit, blastradius, build, cache, ...

Memorygraph (PascalCase) sticks out. Go convention is lowercase directory names. Renaming to internal/memorygraph/ would keep it consistent and avoid any potential import path confusion.


3. 📄 Memory-graph.go filename has a hyphen

Go filenames conventionally use underscores (or no separator), not hyphens. memory_graph.go would be more idiomatic and avoids any edge-case tooling surprises.


TL;DR: cmd/ deletion = ✅ correct. The internal/Memorygraph package just needs to be (a) wired into internal/mcp/server.go, (b) renamed to internal/memorygraph/, and (c) Memory-graph.gomemory_graph.go. Want me to plan those changes?

@drQedwards
Copy link
Copy Markdown
Contributor Author

Tip

For best results, initiate chat on the files or code changes.
🧩 Analysis chain
@drQedwards — good call deleting those cmd/ files, that was definitely the right move. The cmd/ directory in this repo is exclusively for Cobra command definitions (the thin CLI entry points), while all the real logic lives in internal/. No nudge needed there. ✅

However, after digging into the code, there are three things worth addressing before this is fully functional:

1. 🔌 The memory graph tools aren't wired into the MCP server yet

internal/mcp/server.go currently only knows about 4 tools (analyze, dead_code, blast_radius, get_graph). The new internal/Memorygraph package is never imported or called — it's essentially unreachable right now. To make it live, you'd need to:

a) Add tool definitions in the var tools = []tool{...} slice in server.go:

{
    Name:        "upsert_memory_node",
    Description: "Upsert a typed knowledge node into the persistent memory graph.",
    InputSchema: toolSchema{
        Type: "object",
        Properties: map[string]schemaProp{
            "type":    {Type: "string", Description: "Node type: fact, concept, entity, event, procedure, context."},
            "label":   {Type: "string", Description: "Short unique label for the node."},
            "content": {Type: "string", Description: "Full content body of the node."},
        },
        Required: []string{"type", "label", "content"},
    },
},
// ... search_memory_graph, retrieve_with_traversal, prune_stale_links, etc.

b) Add import at the top of server.go:

"github.com/supermodeltools/cli/internal/Memorygraph"

c) Add cases in the callTool switch:

case "upsert_memory_node":
    return memorygraph.ToolUpsertMemoryNode(memorygraph.UpsertMemoryNodeOptions{
        RootDir: s.dir,
        Type:    memorygraph.NodeType(strArg(args, "type")),
        Label:   strArg(args, "label"),
        Content: strArg(args, "content"),
    })
// ... and so on for the other tools

2. 📁 Directory name doesn't match Go conventions

Every other package in internal/ uses all-lowercase names:

analyze, api, archdocs, audit, blastradius, build, cache, ...

Memorygraph (PascalCase) sticks out. Go convention is lowercase directory names. Renaming to internal/memorygraph/ would keep it consistent and avoid any potential import path confusion.

3. 📄 Memory-graph.go filename has a hyphen

Go filenames conventionally use underscores (or no separator), not hyphens. memory_graph.go would be more idiomatic and avoids any edge-case tooling surprises.

TL;DR: cmd/ deletion = ✅ correct. The internal/Memorygraph package just needs to be (a) wired into internal/mcp/server.go, (b) renamed to internal/memorygraph/, and (c) Memory-graph.gomemory_graph.go. Want me to plan those changes?

@coderabbitai yes plan those changes please

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 20, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@drQedwards — on it! Let me read the full server.go so the diffs are precise.

Here's the plan:

  • Req 1 — Rename internal/Memorygraph/internal/memorygraph/ and Memory-graph.gomemory_graph.go
  • Req 2 — Wire all 6 memory-graph tools into internal/mcp/server.go (tool definitions, import, callTool switch cases)

🏁 Script executed:

#!/bin/bash
# Verify current state of internal/Memorygraph directory
ls internal/Memorygraph/

echo "---"
# Check the package declaration line in each file to confirm no changes needed there
head -3 internal/Memorygraph/Memory-graph.go
head -3 internal/Memorygraph/Peek.go
head -3 internal/Memorygraph/tools.go

echo "---"
# Check if any file in the repo currently imports via the PascalCase path
rg -rn "internal/Memorygraph" --type go

Length of output: 515


🏁 Script executed:

#!/bin/bash
# Read the full server.go to understand exact structure
cat internal/mcp/server.go

echo "---"
# Confirm strArg/floatArg/intArg helpers available
rg -n "func strArg\|func floatArg\|func intArg\|func boolArg" internal/mcp/server.go

echo "---"
# Confirm the s.dir field name used for rootDir
rg -n "s\.dir\|s\.rootDir\|\.dir " internal/mcp/server.go | head -20

echo "---"
# Confirm exact module path
head -3 go.mod

Length of output: 14573


@drQedwards — got everything I need. Here's the full plan with exact diffs:


Change 1 — Rename directory & file

Run these two git mv commands in the repo root:

git mv internal/Memorygraph/Memory-graph.go internal/Memorygraph/memory_graph.go
git mv internal/Memorygraph internal/memorygraph

No content changes needed inside any of the three files — they already declare package memorygraph (lowercase).


Change 2 — Wire memory-graph tools into internal/mcp/server.go

2a — Add import

 import (
 	"bufio"
 	"context"
 	"encoding/json"
 	"fmt"
 	"io"
 	"os"
 	"strings"

 	"github.com/supermodeltools/cli/internal/analyze"
 	"github.com/supermodeltools/cli/internal/api"
 	"github.com/supermodeltools/cli/internal/build"
 	"github.com/supermodeltools/cli/internal/cache"
 	"github.com/supermodeltools/cli/internal/config"
+	memorygraph "github.com/supermodeltools/cli/internal/memorygraph"
 )

2b — Append 6 tool definitions to var tools

Add these entries inside the var tools = []tool{...} slice, after the existing get_graph entry:

 	{
 		Name:        "get_graph",
 		// ... existing entry unchanged
 	},
+	{
+		Name:        "upsert_memory_node",
+		Description: "Upsert a typed knowledge node into the persistent memory graph.",
+		InputSchema: toolSchema{
+			Type: "object",
+			Properties: map[string]schemaProp{
+				"type":    {Type: "string", Description: "Node type: fact, concept, entity, event, procedure, context."},
+				"label":   {Type: "string", Description: "Short unique label for the node."},
+				"content": {Type: "string", Description: "Full content body of the node."},
+			},
+			Required: []string{"type", "label", "content"},
+		},
+	},
+	{
+		Name:        "create_relation",
+		Description: "Create a directed weighted edge between two memory graph nodes.",
+		InputSchema: toolSchema{
+			Type: "object",
+			Properties: map[string]schemaProp{
+				"source_id": {Type: "string", Description: "ID of the source node."},
+				"target_id": {Type: "string", Description: "ID of the target node."},
+				"relation":  {Type: "string", Description: "Relation type, e.g. related_to, depends_on, part_of."},
+				"weight":    {Type: "number", Description: "Edge weight between 0 and 1 (default 1.0)."},
+			},
+			Required: []string{"source_id", "target_id", "relation"},
+		},
+	},
+	{
+		Name:        "search_memory_graph",
+		Description: "Score and retrieve nodes from the memory graph matching a query, with optional one-hop neighbor expansion.",
+		InputSchema: toolSchema{
+			Type: "object",
+			Properties: map[string]schemaProp{
+				"query":     {Type: "string", Description: "Search query string."},
+				"max_depth": {Type: "integer", Description: "Max BFS depth for neighbor expansion (default 1)."},
+				"top_k":     {Type: "integer", Description: "Maximum number of direct results to return (default 5)."},
+			},
+			Required: []string{"query"},
+		},
+	},
+	{
+		Name:        "retrieve_with_traversal",
+		Description: "BFS traversal from a start node up to maxDepth, returning visited nodes with decayed relevance scores.",
+		InputSchema: toolSchema{
+			Type: "object",
+			Properties: map[string]schemaProp{
+				"start_node_id": {Type: "string", Description: "ID of the node to start traversal from."},
+				"max_depth":     {Type: "integer", Description: "Maximum BFS depth (default 3)."},
+			},
+			Required: []string{"start_node_id"},
+		},
+	},
+	{
+		Name:        "prune_stale_links",
+		Description: "Remove edges below a weight threshold and orphaned nodes from the memory graph.",
+		InputSchema: toolSchema{
+			Type:       "object",
+			Properties: map[string]schemaProp{
+				"threshold": {Type: "number", Description: "Minimum edge weight to retain (default 0.1)."},
+			},
+		},
+	},
+	{
+		Name:        "add_interlinked_context",
+		Description: "Bulk-insert nodes and optionally auto-create similarity edges (Jaccard ≥ 0.72) between them.",
+		InputSchema: toolSchema{
+			Type: "object",
+			Properties: map[string]schemaProp{
+				"items":     {Type: "array", Description: "Array of {type, label, content, metadata} node objects to insert."},
+				"auto_link": {Type: "boolean", Description: "If true, auto-create similarity edges between inserted nodes."},
+			},
+			Required: []string{"items"},
+		},
+	},
 }

2c — Add 6 cases to callTool

 func (s *server) callTool(ctx context.Context, name string, args map[string]any) (string, error) {
 	switch name {
 	case "analyze":
 		return s.toolAnalyze(ctx, args)
 	case "dead_code":
 		return s.toolDeadCode(ctx, args)
 	case "blast_radius":
 		return s.toolBlastRadius(ctx, args)
 	case "get_graph":
 		return s.toolGetGraph(ctx, args)
+	case "upsert_memory_node":
+		return memorygraph.ToolUpsertMemoryNode(memorygraph.UpsertMemoryNodeOptions{
+			RootDir: s.dir,
+			Type:    memorygraph.NodeType(strArg(args, "type")),
+			Label:   strArg(args, "label"),
+			Content: strArg(args, "content"),
+		})
+	case "create_relation":
+		w := floatArg(args, "weight")
+		if w == 0 {
+			w = 1.0
+		}
+		return memorygraph.ToolCreateRelation(memorygraph.CreateRelationOptions{
+			RootDir:  s.dir,
+			SourceID: strArg(args, "source_id"),
+			TargetID: strArg(args, "target_id"),
+			Relation: memorygraph.RelationType(strArg(args, "relation")),
+			Weight:   w,
+		})
+	case "search_memory_graph":
+		topK := intArg(args, "top_k")
+		if topK == 0 {
+			topK = 5
+		}
+		return memorygraph.ToolSearchMemoryGraph(memorygraph.SearchMemoryGraphOptions{
+			RootDir:  s.dir,
+			Query:    strArg(args, "query"),
+			MaxDepth: intArg(args, "max_depth"),
+			TopK:     topK,
+		})
+	case "retrieve_with_traversal":
+		return memorygraph.ToolRetrieveWithTraversal(memorygraph.RetrieveWithTraversalOptions{
+			RootDir:     s.dir,
+			StartNodeID: strArg(args, "start_node_id"),
+			MaxDepth:    intArg(args, "max_depth"),
+		})
+	case "prune_stale_links":
+		return memorygraph.ToolPruneStaleLinks(memorygraph.PruneStaleLinksOptions{
+			RootDir:   s.dir,
+			Threshold: floatArg(args, "threshold"),
+		})
+	case "add_interlinked_context":
+		items, err := parseInterlinkedItems(args)
+		if err != nil {
+			return "", fmt.Errorf("add_interlinked_context: invalid items: %w", err)
+		}
+		return memorygraph.ToolAddInterlinkedContext(memorygraph.AddInterlinkedContextOptions{
+			RootDir:  s.dir,
+			Items:    items,
+			AutoLink: boolArg(args, "auto_link"),
+		})
 	default:
 		return "", fmt.Errorf("unknown tool: %s", name)
 	}
 }

2d — Add missing helper functions

These go at the bottom of server.go alongside boolArg and intArg:

 func intArg(args map[string]any, key string) int {
 	v, _ := args[key].(float64)
 	return int(v)
 }
+
+func strArg(args map[string]any, key string) string {
+	v, _ := args[key].(string)
+	return v
+}
+
+func floatArg(args map[string]any, key string) float64 {
+	v, _ := args[key].(float64)
+	return v
+}
+
+// parseInterlinkedItems re-encodes the raw args["items"] array and decodes it
+// into the strongly-typed slice expected by ToolAddInterlinkedContext.
+func parseInterlinkedItems(args map[string]any) ([]memorygraph.InterlinkedItem, error) {
+	raw, _ := args["items"]
+	b, err := json.Marshal(raw)
+	if err != nil {
+		return nil, err
+	}
+	var items []memorygraph.InterlinkedItem
+	if err := json.Unmarshal(b, &items); err != nil {
+		return nil, err
+	}
+	return items, nil
+}

That's the full picture. Three things to land:

  1. git mv for the rename (2 commands)
  2. The server.go diff above

Want me to also stub out the table-driven tests for peek.go that are mentioned as missing in the PR notes?


Failed to handle agent chat message. Please try again.

The ritual of Q
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/mcp/server.go (1)

292-369: ⚠️ Potential issue | 🔴 Critical

callTool got declared twice, nested inside itself — this file won't build.

Look carefully at what happened here: the original callTool opens at line 292, runs its switch, hits default: at 302 and return "", fmt.Errorf("unknown tool: %s", name) at 303 — and then inside that same function body, at line 304, there's another func (s *server) callTool(ctx context.Context, name string, args map[string]any) (string, error) { with its own duplicated switch name {, duplicated case "analyze" / case "dead_code" / case "blast_radius" / case "get_graph" arms, your new memorygraph cases, and its own default. Then you get two closing braces for the inner function (367, 368) and a dangling } on 369.

Go doesn't allow nested function declarations (let alone redeclaring a method on the same receiver), and the outer function never actually returned before the inner one starts. It's basically the same thing that happened in the tools slice — a patch was applied on top of the old code instead of replacing it, so both copies live side-by-side.

The fix is to keep one callTool that contains all cases (old + new) in a single switch, with one default, one closing } for the switch, and one closing } for the function.

🛠 Proposed single-function replacement for lines 292–369
 func (s *server) callTool(ctx context.Context, name string, args map[string]any) (string, error) {
 	switch name {
 	case "analyze":
 		return s.toolAnalyze(ctx, args)
 	case "dead_code":
 		return s.toolDeadCode(ctx, args)
 	case "blast_radius":
 		return s.toolBlastRadius(ctx, args)
 	case "get_graph":
 		return s.toolGetGraph(ctx, args)
-	default:
-		return "", fmt.Errorf("unknown tool: %s", name)
-	 func (s *server) callTool(ctx context.Context, name string, args map[string]any) (string, error) {
-	switch name {
-	case "analyze":
-		return s.toolAnalyze(ctx, args)
-	case "dead_code":
-		return s.toolDeadCode(ctx, args)
-	case "blast_radius":
-		return s.toolBlastRadius(ctx, args)
-	case "get_graph":
-		return s.toolGetGraph(ctx, args)
-	case "upsert_memory_node":
+	case "upsert_memory_node":
 		return memorygraph.ToolUpsertMemoryNode(memorygraph.UpsertMemoryNodeOptions{
 			RootDir: s.dir,
 			Type:    memorygraph.NodeType(strArg(args, "type")),
 			Label:   strArg(args, "label"),
 			Content: strArg(args, "content"),
 		})
 	case "create_relation":
 		w := floatArg(args, "weight")
 		if w == 0 {
 			w = 1.0
 		}
 		return memorygraph.ToolCreateRelation(memorygraph.CreateRelationOptions{
 			RootDir:  s.dir,
 			SourceID: strArg(args, "source_id"),
 			TargetID: strArg(args, "target_id"),
 			Relation: memorygraph.RelationType(strArg(args, "relation")),
 			Weight:   w,
 		})
 	case "search_memory_graph":
 		topK := intArg(args, "top_k")
 		if topK == 0 {
 			topK = 5
 		}
 		return memorygraph.ToolSearchMemoryGraph(memorygraph.SearchMemoryGraphOptions{
 			RootDir:  s.dir,
 			Query:    strArg(args, "query"),
 			MaxDepth: intArg(args, "max_depth"),
 			TopK:     topK,
 		})
 	case "retrieve_with_traversal":
 		return memorygraph.ToolRetrieveWithTraversal(memorygraph.RetrieveWithTraversalOptions{
 			RootDir:     s.dir,
 			StartNodeID: strArg(args, "start_node_id"),
 			MaxDepth:    intArg(args, "max_depth"),
 		})
 	case "prune_stale_links":
 		return memorygraph.ToolPruneStaleLinks(memorygraph.PruneStaleLinksOptions{
 			RootDir:   s.dir,
 			Threshold: floatArg(args, "threshold"),
 		})
 	case "add_interlinked_context":
 		items, err := parseInterlinkedItems(args)
 		if err != nil {
 			return "", fmt.Errorf("add_interlinked_context: invalid items: %w", err)
 		}
 		return memorygraph.ToolAddInterlinkedContext(memorygraph.AddInterlinkedContextOptions{
 			RootDir:  s.dir,
 			Items:    items,
 			AutoLink: boolArg(args, "auto_link"),
 		})
 	default:
 		return "", fmt.Errorf("unknown tool: %s", name)
 	}
 }
-}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/mcp/server.go` around lines 292 - 369, The file contains two nested
declarations of func (s *server) callTool(...) — remove the duplicated inner
function and merge its new memorygraph cases into the original single callTool's
switch so there is only one switch and one default branch; ensure the final
callTool includes the existing arms (toolAnalyze, toolDeadCode, toolBlastRadius,
toolGetGraph) plus the new memorygraph cases (memorygraph.ToolUpsertMemoryNode,
ToolCreateRelation, ToolSearchMemoryGraph, ToolRetrieveWithTraversal,
ToolPruneStaleLinks, ToolAddInterlinkedContext) and that parseInterlinkedItems
error handling and helper callers (strArg, intArg, floatArg, boolArg) remain
intact, then close the switch and function with the correct braces.
🧹 Nitpick comments (1)
internal/mcp/server.go (1)

117-190: Optional: schemas don't expose metadata or edge_filter, which the underlying options support.

Not a bug, just a heads-up for once the compile issues above are sorted. Looking at internal/Memorygraph/tools.go, UpsertMemoryNodeOptions has Metadata map[string]string, SearchMemoryGraphOptions has EdgeFilter []RelationType, CreateRelationOptions has Metadata, etc. — but none of those knobs are advertised in the tool InputSchema here, so MCP clients won't know they exist.

If you want these reachable from MCP, add them to the schemas (type "object" for metadata, "array" for edge_filter) and plumb them through in the callTool cases. If you'd rather keep the surface minimal for v1, totally fine — just mention it in the tool description so it doesn't look like an oversight later.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/mcp/server.go` around lines 117 - 190, The tool schemas
(upsert_memory_node, create_relation, search_memory_graph,
add_interlinked_context, etc.) omit options like Metadata and EdgeFilter present
in the underlying structs (UpsertMemoryNodeOptions.Metadata,
CreateRelationOptions.Metadata, SearchMemoryGraphOptions.EdgeFilter); add
corresponding InputSchema properties (e.g., "metadata" with type "object" for
map[string]string, and "edge_filter" with type "array" for RelationType values)
to the relevant tool entries and then thread those fields through the MCP call
handling (the callTool cases that construct UpsertMemoryNodeOptions,
CreateRelationOptions, SearchMemoryGraphOptions) so the parsed request values
are populated into the underlying option structs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/mcp/server.go`:
- Line 12: Import path uses PascalCase; rename the directory and update the
import to lowercase. Run git mv internal/Memorygraph internal/memorygraph,
update the import line in server.go from
"github.com/supermodeltools/cli/internal/Memorygraph" to
"github.com/supermodeltools/cli/internal/memorygraph", and verify call sites
like memorygraph.ToolUpsertMemoryNode(...) still compile (package name remains
memorygraph).
- Around line 117-190: The file contains literal '+' characters from a pasted
diff in the tools slice and in the callTool switch arms which break Go parsing;
remove the leading '+' from every added line in the tools definitions (the slice
entries with Name: "upsert_memory_node", "create_relation",
"search_memory_graph", "retrieve_with_traversal", "prune_stale_links",
"add_interlinked_context") and from the corresponding new case arms in the
callTool function so each entry starts with '{' or 'case' as normal; after
stripping those '+' chars the file will compile.
- Around line 321-332: The create_relation branch currently treats
floatArg(args, "weight") == 0 as "missing" which conflates a client-provided
zero with absent value; change the logic to check presence in args first (e.g.,
inspect args["weight"] or use a presence check) and only apply the default
Weight=1.0 when the weight key is absent, otherwise pass the parsed value (even
if 0) into memorygraph.ToolCreateRelation via CreateRelationOptions.Weight; keep
floatArg for parsing but use an explicit presence test on args before
defaulting.
- Around line 649-662: parseInterlinkedItems currently treats a missing
args["items"] as nil (which json.Marshal -> "null" and unmarshal -> nil slice),
so the server silently accepts a missing required field; update
parseInterlinkedItems to explicitly validate args["items"] before marshaling:
check presence (raw, ok := args["items"]; if !ok || raw == nil) and return a
clear error (e.g., "missing required field 'items'") and optionally ensure the
decoded result is a non-nil/non-empty []memorygraph.InterlinkedItem before
returning so ToolAddInterlinkedContext never receives an implicit zero-length
input.

---

Outside diff comments:
In `@internal/mcp/server.go`:
- Around line 292-369: The file contains two nested declarations of func (s
*server) callTool(...) — remove the duplicated inner function and merge its new
memorygraph cases into the original single callTool's switch so there is only
one switch and one default branch; ensure the final callTool includes the
existing arms (toolAnalyze, toolDeadCode, toolBlastRadius, toolGetGraph) plus
the new memorygraph cases (memorygraph.ToolUpsertMemoryNode, ToolCreateRelation,
ToolSearchMemoryGraph, ToolRetrieveWithTraversal, ToolPruneStaleLinks,
ToolAddInterlinkedContext) and that parseInterlinkedItems error handling and
helper callers (strArg, intArg, floatArg, boolArg) remain intact, then close the
switch and function with the correct braces.

---

Nitpick comments:
In `@internal/mcp/server.go`:
- Around line 117-190: The tool schemas (upsert_memory_node, create_relation,
search_memory_graph, add_interlinked_context, etc.) omit options like Metadata
and EdgeFilter present in the underlying structs
(UpsertMemoryNodeOptions.Metadata, CreateRelationOptions.Metadata,
SearchMemoryGraphOptions.EdgeFilter); add corresponding InputSchema properties
(e.g., "metadata" with type "object" for map[string]string, and "edge_filter"
with type "array" for RelationType values) to the relevant tool entries and then
thread those fields through the MCP call handling (the callTool cases that
construct UpsertMemoryNodeOptions, CreateRelationOptions,
SearchMemoryGraphOptions) so the parsed request values are populated into the
underlying option structs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8e97416d-1da8-4e43-98fb-b17fadfe4d2c

📥 Commits

Reviewing files that changed from the base of the PR and between d860941 and 89a0375.

📒 Files selected for processing (1)
  • internal/mcp/server.go

Comment thread internal/mcp/server.go
Comment thread internal/mcp/server.go Outdated
Comment thread internal/mcp/server.go Outdated
Comment on lines +321 to +332
+ case "create_relation":
+ w := floatArg(args, "weight")
+ if w == 0 {
+ w = 1.0
+ }
+ return memorygraph.ToolCreateRelation(memorygraph.CreateRelationOptions{
+ RootDir: s.dir,
+ SourceID: strArg(args, "source_id"),
+ TargetID: strArg(args, "target_id"),
+ Relation: memorygraph.RelationType(strArg(args, "relation")),
+ Weight: w,
+ })
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

weight == 0 is ambiguous — can't tell "unset" from "explicitly zero".

floatArg returns the zero value when the key is missing or when the caller legitimately passed 0. So if a client ever wants a zero-weight edge (e.g. to mark "this link exists but contributes nothing to scoring"), your default silently rewrites it to 1.0.

Concrete example: user sends {"source_id":"a","target_id":"b","relation":"related_to","weight":0} → you store weight 1.0. Surprise.

If you want a real default, distinguish "present" from "zero" by looking in args directly before handing off to floatArg.

🛠 One way to disambiguate
-	case "create_relation":
-		w := floatArg(args, "weight")
-		if w == 0 {
-			w = 1.0
-		}
+	case "create_relation":
+		w := 1.0
+		if raw, ok := args["weight"]; ok {
+			if f, ok := raw.(float64); ok {
+				w = f
+			}
+		}
 		return memorygraph.ToolCreateRelation(memorygraph.CreateRelationOptions{
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
+ case "create_relation":
+ w := floatArg(args, "weight")
+ if w == 0 {
+ w = 1.0
+ }
+ return memorygraph.ToolCreateRelation(memorygraph.CreateRelationOptions{
+ RootDir: s.dir,
+ SourceID: strArg(args, "source_id"),
+ TargetID: strArg(args, "target_id"),
+ Relation: memorygraph.RelationType(strArg(args, "relation")),
+ Weight: w,
+ })
case "create_relation":
w := 1.0
if raw, ok := args["weight"]; ok {
if f, ok := raw.(float64); ok {
w = f
}
}
return memorygraph.ToolCreateRelation(memorygraph.CreateRelationOptions{
RootDir: s.dir,
SourceID: strArg(args, "source_id"),
TargetID: strArg(args, "target_id"),
Relation: memorygraph.RelationType(strArg(args, "relation")),
Weight: w,
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/mcp/server.go` around lines 321 - 332, The create_relation branch
currently treats floatArg(args, "weight") == 0 as "missing" which conflates a
client-provided zero with absent value; change the logic to check presence in
args first (e.g., inspect args["weight"] or use a presence check) and only apply
the default Weight=1.0 when the weight key is absent, otherwise pass the parsed
value (even if 0) into memorygraph.ToolCreateRelation via
CreateRelationOptions.Weight; keep floatArg for parsing but use an explicit
presence test on args before defaulting.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai redo this comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@drQedwards, here's a fresh take on this one:


Bug: weight: 0 gets silently overwritten to 1.0

The check if w == 0 { w = 1.0 } can't distinguish between two very different situations:

  1. The caller didn't send weightfloatArg returns 0.0 (zero value) → default to 1.0
  2. The caller explicitly sent weight: 0floatArg also returns 0.0 → gets bumped to 1.0

Fix: check whether "weight" is actually present in args before applying the default.

case "create_relation":
    w := 1.0
    if raw, ok := args["weight"]; ok {
        if f, ok := raw.(float64); ok {
            w = f
        }
    }
    return memorygraph.ToolCreateRelation(memorygraph.CreateRelationOptions{
        RootDir:  s.dir,
        SourceID: strArg(args, "source_id"),
        TargetID: strArg(args, "target_id"),
        Relation: memorygraph.RelationType(strArg(args, "relation")),
        Weight:   w,
    })

This way weight absent → 1.0, weight: 00.0, weight: 0.50.5. Clean and unambiguous.

Comment thread internal/mcp/server.go
Call her HIV, because there are no more + artifacts in this. I’m gay. I get to do that joke. Don’t worry, there is a reason why we do Go instead of “Go” schizo with blank spaces because we are in C with atomic operations at OpenSSL repo.
Goodbye Pascal. Stupid case Linux CI
@drQedwards drQedwards marked this pull request as draft April 20, 2026 21:39
@drQedwards drQedwards marked this pull request as ready for review April 20, 2026 21:39
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

??? Removed letter b artifact, the actual bug. 

There is no duplicate call tool, @coderabbitai you told me to make the case functions followed by updating tool[] … this is structurally sound.
@drQedwards drQedwards marked this pull request as draft April 20, 2026 21:52
@drQedwards drQedwards marked this pull request as ready for review April 20, 2026 21:52
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
greynewell added a commit that referenced this pull request Apr 21, 2026
…142)

Introduces internal/memorygraph — a typed, weighted knowledge graph for
agent memory, persisted at .supermodel/memory-graph.json. Nodes are
classified as fact/concept/entity/event/procedure/context; edges carry
relation types and weights. Provides BFS traversal, scored full-text
search, similarity-based auto-linking, and stale-link pruning.

Six new MCP tools exposed via server.go:
  upsert_memory_node, create_relation, search_memory_graph,
  retrieve_with_traversal, prune_stale_links, add_interlinked_context

Also adds peek.go (node inspection layer) with full test coverage.

Closes #136

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
greynewell added a commit that referenced this pull request Apr 21, 2026
…142)

Introduces internal/memorygraph — a typed, weighted knowledge graph for
agent memory, persisted at .supermodel/memory-graph.json. Nodes are
classified as fact/concept/entity/event/procedure/context; edges carry
relation types and weights. Provides BFS traversal, scored full-text
search, similarity-based auto-linking, and stale-link pruning.

Six new MCP tools exposed via server.go:
  upsert_memory_node, create_relation, search_memory_graph,
  retrieve_with_traversal, prune_stale_links, add_interlinked_context

Also adds peek.go (node inspection layer) with full test coverage.

Closes #136

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Dr. Q and Company <213266729+drQedwards@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant