Skip to content

Commit abeb7e3

Browse files
committed
Rename memory mirror and add benchmark gates
1 parent 4833f52 commit abeb7e3

9 files changed

Lines changed: 206 additions & 32 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ Goncho can orient the agent by storing evidence, ranking scoped memory, assembli
7777
| `github.com/TrebuchetDynamics/goncho` | Root library package | `RunMigrations`, `NewService`, service calls, context/search/conclude params, and public tool constructors. |
7878
| `github.com/TrebuchetDynamics/goncho/memory` | SQLite opener | `memory.OpenSqlite` when you want a local file-backed store for an embedded host. |
7979
| `github.com/TrebuchetDynamics/goncho/cmd/goncho-bench` | Command only | `go install .../cmd/goncho-bench@latest` for reproducible retrieval reports; do not import it into an agent host. |
80-
| `github.com/TrebuchetDynamics/goncho/agentmemory` | Architecture mirror/port matrix | Inspect the Go-native mirror of upstream agentmemory `355124141625ccc0d740ae08ddaaf77fe2c165ae`: pipeline stages, memory tiers, retrieval streams, hooks, MCP tools, Goncho seams, and explicit residual gaps. |
80+
| `github.com/TrebuchetDynamics/goncho/memorymirror` | Architecture mirror/port matrix | Inspect the Go-native mirror of the upstream broad-memory reference at `355124141625ccc0d740ae08ddaaf77fe2c165ae`: pipeline stages, memory tiers, retrieval streams, hooks, MCP tools, Goncho seams, and explicit residual gaps. |
8181

8282
Stay on public service and tool APIs first. If pkg.go.dev shows a lower-level type before the service examples, treat it as implementation detail until `NewService`, `svc.Context`, `svc.Search`, `svc.Recall`, or a public tool constructor cannot express the host need.
8383

@@ -192,7 +192,7 @@ Most agent memory systems optimize for breadth: more connectors, more tools, mor
192192

193193
Goncho exists because Gormes and Navivox need memory that can run anywhere the agent runs: a workstation, small server, Windows laptop, WSL2 shell, macOS terminal, or Android phone through Termux. The memory layer cannot assume Python packaging, Docker, Redis, hosted vector infrastructure, or always-online cloud services.
194194

195-
Goncho is inspired by broad integration systems like [`agentmemory`](docs/opensource-memory-systems/agentmemory/README.md), and the public `agentmemory` package now carries a source-pinned architecture mirror/port matrix for upstream `https://github.com/rohitg00/agentmemory` commit `355124141625ccc0d740ae08ddaaf77fe2c165ae`. Use `agentmemory.ArchitectureManifest()` to inspect which upstream pipeline stages, four memory tiers, retrieval streams, hooks, and 53 MCP tools are delivered through Goncho seams versus partial, adapter-owned, deferred, or explicitly excluded. Use `agentmemory.NewToolRegistry` when a host wants agentmemory-compatible executable aliases (`memory_save`, `memory_smart_search`, `memory_recall`, `memory_profile`) backed by Goncho's local service APIs.
195+
Goncho is inspired by broad integration systems like [`agentmemory`](docs/opensource-memory-systems/agentmemory/README.md), and the public `memorymirror` package now carries a source-pinned architecture mirror/port matrix for upstream `https://github.com/rohitg00/agentmemory` commit `355124141625ccc0d740ae08ddaaf77fe2c165ae` without adopting the upstream project name as Goncho API surface. Use `memorymirror.ArchitectureManifest()` to inspect which upstream pipeline stages, four memory tiers, retrieval streams, hooks, and 53 MCP tools are delivered through Goncho seams versus partial, adapter-owned, deferred, or explicitly excluded. Use `memorymirror.NewToolRegistry` when a host wants compatible executable aliases (`memory_save`, `memory_smart_search`, `memory_recall`, `memory_profile`) backed by Goncho's local service APIs.
196196

197197
Goncho still makes a different product bet:
198198

codemap.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ Host or service code writes scoped events through `Observe`; Goncho redacts, tru
1616

1717
## Integration
1818

19-
Gormes and other hosts should call the Go API or service wrappers after running `RunMigrations`. The local `memory` and `session` packages provide extraction-safe compatibility for SQLite setup, memory V1 fixtures, FTS-backed turn search, and in-memory session metadata tests. The public `agentmemory` package is a source-pinned architecture mirror/port matrix for rohitg00/agentmemory commit `355124141625ccc0d740ae08ddaaf77fe2c165ae`, mapping its pipeline, tiers, retrieval streams, hooks, and 53 MCP tools onto delivered, partial, adapter-owned, deferred, or excluded Goncho seams; it also exposes agentmemory-compatible executable aliases for save, smart search, recall, and profile backed by the local Goncho service.
19+
Gormes and other hosts should call the Go API or service wrappers after running `RunMigrations`. The local `memory` and `session` packages provide extraction-safe compatibility for SQLite setup, memory V1 fixtures, FTS-backed turn search, and in-memory session metadata tests. The public `memorymirror` package is a source-pinned architecture mirror/port matrix for rohitg00/agentmemory commit `355124141625ccc0d740ae08ddaaf77fe2c165ae`, mapping its pipeline, tiers, retrieval streams, hooks, and 53 MCP tools onto delivered, partial, adapter-owned, deferred, or excluded Goncho seams without adopting the upstream project name as Goncho API surface; it also exposes compatible executable aliases for save, smart search, recall, and profile backed by the local Goncho service.
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
// Package agentmemory records Goncho's Go-native architecture mirror of the
2-
// upstream agentmemory system without importing its TypeScript runtime.
3-
package agentmemory
1+
// Package memorymirror records Goncho's Go-native architecture mirror of the
2+
// upstream broad-memory reference system without importing its TypeScript runtime.
3+
package memorymirror
44

55
import "strings"
66

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package agentmemory
1+
package memorymirror
22

33
import "testing"
44

memorymirror/benchmarks.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package memorymirror
2+
3+
type BenchmarkTargetSet struct {
4+
LongMemEval LongMemEvalTarget `json:"longmemeval_s"`
5+
TokenSavings TokenSavingsTarget `json:"token_savings"`
6+
}
7+
8+
type LongMemEvalTarget struct {
9+
Dataset string `json:"dataset"`
10+
QuestionCount int `json:"question_count"`
11+
EmbeddingModel string `json:"embedding_model"`
12+
ReferenceRecallAnyAt5 float64 `json:"reference_recall_any_at_5"`
13+
ReferenceRecallAnyAt10 float64 `json:"reference_recall_any_at_10"`
14+
ReferenceMRR float64 `json:"reference_mrr"`
15+
SimilarRecallAnyAt10Gap float64 `json:"similar_recall_any_at_10_gap"`
16+
}
17+
18+
type TokenSavingsTarget struct {
19+
PasteFullContextTokensPerYear int `json:"paste_full_context_tokens_per_year"`
20+
SummarizedTokensPerYear int `json:"summarized_tokens_per_year"`
21+
TargetTokensPerYear int `json:"target_tokens_per_year"`
22+
TargetCostUSDPerYear float64 `json:"target_cost_usd_per_year"`
23+
LocalEmbeddingCostUSDPerYear float64 `json:"local_embedding_cost_usd_per_year"`
24+
EmbeddingModel string `json:"embedding_model"`
25+
}
26+
27+
type LongMemEvalEvidence struct {
28+
System string `json:"system"`
29+
Dataset string `json:"dataset"`
30+
QuestionCount int `json:"question_count"`
31+
RecallAnyAt5 float64 `json:"recall_any_at_5"`
32+
RecallAnyAt10 float64 `json:"recall_any_at_10"`
33+
MRR float64 `json:"mrr"`
34+
}
35+
36+
type LongMemEvalAssessment struct {
37+
MeetsSimilarGate bool `json:"meets_similar_gate"`
38+
RecallAnyAt5Delta float64 `json:"recall_any_at_5_delta"`
39+
RecallAnyAt10Delta float64 `json:"recall_any_at_10_delta"`
40+
MRRDelta float64 `json:"mrr_delta"`
41+
Reason string `json:"reason,omitempty"`
42+
}
43+
44+
func BenchmarkTargets() BenchmarkTargetSet {
45+
return BenchmarkTargetSet{
46+
LongMemEval: LongMemEvalTarget{
47+
Dataset: "LongMemEval-S",
48+
QuestionCount: 500,
49+
EmbeddingModel: "all-MiniLM-L6-v2",
50+
ReferenceRecallAnyAt5: 0.952,
51+
ReferenceRecallAnyAt10: 0.986,
52+
ReferenceMRR: 0.882,
53+
SimilarRecallAnyAt10Gap: 0.01,
54+
},
55+
TokenSavings: TokenSavingsTarget{
56+
PasteFullContextTokensPerYear: 19_500_000,
57+
SummarizedTokensPerYear: 650_000,
58+
TargetTokensPerYear: 170_000,
59+
TargetCostUSDPerYear: 10,
60+
LocalEmbeddingCostUSDPerYear: 0,
61+
EmbeddingModel: "all-MiniLM-L6-v2",
62+
},
63+
}
64+
}
65+
66+
func AssessLongMemEval(evidence LongMemEvalEvidence, target LongMemEvalTarget) LongMemEvalAssessment {
67+
assessment := LongMemEvalAssessment{
68+
RecallAnyAt5Delta: evidence.RecallAnyAt5 - target.ReferenceRecallAnyAt5,
69+
RecallAnyAt10Delta: evidence.RecallAnyAt10 - target.ReferenceRecallAnyAt10,
70+
MRRDelta: evidence.MRR - target.ReferenceMRR,
71+
}
72+
if target.QuestionCount > 0 && evidence.QuestionCount != target.QuestionCount {
73+
assessment.Reason = "question count mismatch"
74+
return assessment
75+
}
76+
if assessment.RecallAnyAt5Delta < 0 {
77+
assessment.Reason = "recall_any@5 below reference"
78+
return assessment
79+
}
80+
if assessment.MRRDelta < 0 {
81+
assessment.Reason = "MRR below reference"
82+
return assessment
83+
}
84+
if assessment.RecallAnyAt10Delta < -target.SimilarRecallAnyAt10Gap {
85+
assessment.Reason = "recall_any@10 outside similarity gap"
86+
return assessment
87+
}
88+
assessment.MeetsSimilarGate = true
89+
return assessment
90+
}

memorymirror/benchmarks_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package memorymirror
2+
3+
import (
4+
"encoding/json"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
)
9+
10+
func TestBenchmarkTargetsMatchLongMemEvalAndTokenSavingsReferences(t *testing.T) {
11+
targets := BenchmarkTargets()
12+
if targets.LongMemEval.QuestionCount != 500 || targets.LongMemEval.EmbeddingModel != "all-MiniLM-L6-v2" {
13+
t.Fatalf("LongMemEval target = %+v", targets.LongMemEval)
14+
}
15+
if targets.LongMemEval.ReferenceRecallAnyAt5 != 0.952 || targets.LongMemEval.ReferenceRecallAnyAt10 != 0.986 || targets.LongMemEval.ReferenceMRR != 0.882 {
16+
t.Fatalf("reference metrics = %+v", targets.LongMemEval)
17+
}
18+
if targets.TokenSavings.PasteFullContextTokensPerYear != 19_500_000 || targets.TokenSavings.TargetTokensPerYear != 170_000 || targets.TokenSavings.LocalEmbeddingCostUSDPerYear != 0 {
19+
t.Fatalf("token-savings target = %+v", targets.TokenSavings)
20+
}
21+
}
22+
23+
func TestFrozenLongMemEvalReportMeetsSimilarBenchmarkGate(t *testing.T) {
24+
raw, err := os.ReadFile(filepath.Join(repoRoot(t), "docs", "benchmarks", "results", "longmemeval-s-2026-05-20-goncho.json"))
25+
if err != nil {
26+
t.Fatalf("read frozen LongMemEval report: %v", err)
27+
}
28+
var report LongMemEvalEvidence
29+
if err := json.Unmarshal(raw, &report); err != nil {
30+
t.Fatalf("decode frozen LongMemEval report: %v", err)
31+
}
32+
assessment := AssessLongMemEval(report, BenchmarkTargets().LongMemEval)
33+
if !assessment.MeetsSimilarGate {
34+
t.Fatalf("LongMemEval gate failed: %+v", assessment)
35+
}
36+
if assessment.RecallAnyAt5Delta <= 0 || assessment.MRRDelta <= 0 {
37+
t.Fatalf("Goncho should beat reference R@5/MRR, got %+v", assessment)
38+
}
39+
if assessment.RecallAnyAt10Delta < -0.01 {
40+
t.Fatalf("Goncho R@10 must stay within 1pp of reference, got %+v", assessment)
41+
}
42+
}

memorymirror/naming_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package memorymirror
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"strings"
7+
"testing"
8+
)
9+
10+
func TestPublicMirrorDoesNotUseUpstreamProjectNameAsGonchoPackageName(t *testing.T) {
11+
root := repoRoot(t)
12+
for _, path := range []string{"README.md", "codemap.md"} {
13+
raw, err := os.ReadFile(filepath.Join(root, path))
14+
if err != nil {
15+
t.Fatalf("ReadFile(%s): %v", path, err)
16+
}
17+
text := string(raw)
18+
for _, forbidden := range []string{"goncho/agentmemory", "public `agentmemory` package", "agentmemory.NewToolRegistry", "agentmemory.ArchitectureManifest()"} {
19+
if strings.Contains(text, forbidden) {
20+
t.Fatalf("%s still exposes upstream project name as Goncho package API via %q", path, forbidden)
21+
}
22+
}
23+
}
24+
}
25+
26+
func repoRoot(t *testing.T) string {
27+
t.Helper()
28+
dir, err := os.Getwd()
29+
if err != nil {
30+
t.Fatalf("Getwd: %v", err)
31+
}
32+
for {
33+
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
34+
return dir
35+
}
36+
parent := filepath.Dir(dir)
37+
if parent == dir {
38+
t.Fatalf("could not find go.mod above %s", dir)
39+
}
40+
dir = parent
41+
}
42+
}
Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package agentmemory
1+
package memorymirror
22

33
import (
44
"context"
@@ -21,16 +21,16 @@ type ToolRegistryOptions struct {
2121

2222
func NewToolRegistry(svc *goncho.Service, opts ToolRegistryOptions) []toolmeta.Tool {
2323
return []toolmeta.Tool{
24-
newServiceTool("memory_save", "Explicitly save an important insight, decision, or pattern to Goncho long-term memory using agentmemory-compatible arguments.", json.RawMessage(`{"type":"object","properties":{"content":{"type":"string","description":"The insight or decision to remember"},"type":{"type":"string","description":"Memory type: pattern, preference, architecture, bug, workflow, or fact"},"concepts":{"type":"string","description":"Comma-separated key concepts"},"files":{"type":"string","description":"Comma-separated relevant file paths"},"peer_id":{"type":"string","description":"Optional Goncho peer id"},"session_id":{"type":"string","description":"Optional session id"}},"required":["content"]}`), true, false, func(ctx context.Context, args json.RawMessage) (json.RawMessage, error) {
24+
newServiceTool("memory_save", "Explicitly save an important insight, decision, or pattern to Goncho long-term memory using broad-memory-compatible arguments.", json.RawMessage(`{"type":"object","properties":{"content":{"type":"string","description":"The insight or decision to remember"},"type":{"type":"string","description":"Memory type: pattern, preference, architecture, bug, workflow, or fact"},"concepts":{"type":"string","description":"Comma-separated key concepts"},"files":{"type":"string","description":"Comma-separated relevant file paths"},"peer_id":{"type":"string","description":"Optional Goncho peer id"},"session_id":{"type":"string","description":"Optional session id"}},"required":["content"]}`), true, false, func(ctx context.Context, args json.RawMessage) (json.RawMessage, error) {
2525
return executeMemorySave(ctx, svc, opts, args)
2626
}),
27-
newServiceTool("memory_smart_search", "Hybrid Goncho search exposed through agentmemory's memory_smart_search name.", json.RawMessage(`{"type":"object","properties":{"query":{"type":"string","description":"Search query"},"expandIds":{"type":"string","description":"Comma-separated observation IDs to expand"},"limit":{"type":"number","description":"Max results (default 10)"},"peer_id":{"type":"string","description":"Optional Goncho peer id"},"session_id":{"type":"string","description":"Optional session id"}},"required":["query"]}`), false, true, func(ctx context.Context, args json.RawMessage) (json.RawMessage, error) {
27+
newServiceTool("memory_smart_search", "Hybrid Goncho search exposed through the compatible memory_smart_search name.", json.RawMessage(`{"type":"object","properties":{"query":{"type":"string","description":"Search query"},"expandIds":{"type":"string","description":"Comma-separated observation IDs to expand"},"limit":{"type":"number","description":"Max results (default 10)"},"peer_id":{"type":"string","description":"Optional Goncho peer id"},"session_id":{"type":"string","description":"Optional session id"}},"required":["query"]}`), false, true, func(ctx context.Context, args json.RawMessage) (json.RawMessage, error) {
2828
return executeMemorySmartSearch(ctx, svc, opts, args)
2929
}),
30-
newServiceTool("memory_recall", "Search past Goncho memory for relevant context using agentmemory's memory_recall name.", json.RawMessage(`{"type":"object","properties":{"query":{"type":"string","description":"Search query"},"limit":{"type":"number","description":"Max results to return (default 10)"},"format":{"type":"string","description":"Result format: full, compact, or narrative (default full)"},"token_budget":{"type":"number","description":"Optional token budget to trim returned results"},"peer_id":{"type":"string","description":"Optional Goncho peer id"},"session_id":{"type":"string","description":"Optional session id"}},"required":["query"]}`), false, true, func(ctx context.Context, args json.RawMessage) (json.RawMessage, error) {
30+
newServiceTool("memory_recall", "Search past Goncho memory for relevant context using the compatible memory_recall name.", json.RawMessage(`{"type":"object","properties":{"query":{"type":"string","description":"Search query"},"limit":{"type":"number","description":"Max results to return (default 10)"},"format":{"type":"string","description":"Result format: full, compact, or narrative (default full)"},"token_budget":{"type":"number","description":"Optional token budget to trim returned results"},"peer_id":{"type":"string","description":"Optional Goncho peer id"},"session_id":{"type":"string","description":"Optional session id"}},"required":["query"]}`), false, true, func(ctx context.Context, args json.RawMessage) (json.RawMessage, error) {
3131
return executeMemoryRecall(ctx, svc, opts, args)
3232
}),
33-
newServiceTool("memory_profile", "Return the Goncho peer profile through agentmemory's memory_profile name.", json.RawMessage(`{"type":"object","properties":{"peer_id":{"type":"string","description":"Optional Goncho peer id"}}}`), false, true, func(ctx context.Context, args json.RawMessage) (json.RawMessage, error) {
33+
newServiceTool("memory_profile", "Return the Goncho peer profile through the compatible memory_profile name.", json.RawMessage(`{"type":"object","properties":{"peer_id":{"type":"string","description":"Optional Goncho peer id"}}}`), false, true, func(ctx context.Context, args json.RawMessage) (json.RawMessage, error) {
3434
return executeMemoryProfile(ctx, svc, opts, args)
3535
}),
3636
}
@@ -63,7 +63,7 @@ func (t *serviceTool) Spec() toolmeta.OperationSpec {
6363

6464
func (t *serviceTool) Execute(ctx context.Context, args json.RawMessage) (json.RawMessage, error) {
6565
if t == nil || t.execute == nil {
66-
return nil, errors.New("agentmemory: tool executor is required")
66+
return nil, errors.New("memorymirror: tool executor is required")
6767
}
6868
return t.execute(ctx, args)
6969
}
@@ -88,7 +88,7 @@ func executeMemorySave(ctx context.Context, svc *goncho.Service, opts ToolRegist
8888
if content == "" {
8989
return nil, errors.New("memory_save: content is required")
9090
}
91-
if extra := agentMemoryMetadataSuffix(in.Type, in.Concepts, in.Files); extra != "" {
91+
if extra := upstreamMetadataSuffix(in.Type, in.Concepts, in.Files); extra != "" {
9292
content += "\n" + extra
9393
}
9494
out, err := svc.Conclude(ctx, goncho.ConcludeParams{ProfileID: opts.DefaultProfileID, Peer: peerFromInputs(opts, in.PeerID, in.Peer), Conclusion: content, SessionKey: firstNonEmpty(in.SessionID, opts.DefaultSessionKey), Scope: strings.TrimSpace(in.Type)})
@@ -171,16 +171,16 @@ func executeMemoryProfile(ctx context.Context, svc *goncho.Service, opts ToolReg
171171
return json.Marshal(map[string]any{"success": true, "tool": "memory_profile", "backend": "goncho", "profile": out, "local_first": true})
172172
}
173173

174-
func agentMemoryMetadataSuffix(memoryType, concepts, files string) string {
174+
func upstreamMetadataSuffix(memoryType, concepts, files string) string {
175175
var parts []string
176176
if strings.TrimSpace(memoryType) != "" {
177-
parts = append(parts, "agentmemory.type="+strings.TrimSpace(memoryType))
177+
parts = append(parts, "upstream_memory.type="+strings.TrimSpace(memoryType))
178178
}
179179
if strings.TrimSpace(concepts) != "" {
180-
parts = append(parts, "agentmemory.concepts="+strings.TrimSpace(concepts))
180+
parts = append(parts, "upstream_memory.concepts="+strings.TrimSpace(concepts))
181181
}
182182
if strings.TrimSpace(files) != "" {
183-
parts = append(parts, "agentmemory.files="+strings.TrimSpace(files))
183+
parts = append(parts, "upstream_memory.files="+strings.TrimSpace(files))
184184
}
185185
if len(parts) == 0 {
186186
return ""
@@ -189,7 +189,7 @@ func agentMemoryMetadataSuffix(memoryType, concepts, files string) string {
189189
}
190190

191191
func peerFromInputs(opts ToolRegistryOptions, values ...string) string {
192-
return firstNonEmpty(append(values, opts.DefaultPeerID, "agentmemory")...)
192+
return firstNonEmpty(append(values, opts.DefaultPeerID, "memorymirror")...)
193193
}
194194

195195
func firstNonEmpty(values ...string) string {

0 commit comments

Comments
 (0)