Skip to content

Commit d2054ed

Browse files
author
SqlRush
committed
Accept memory output text selections
1 parent 737bab5 commit d2054ed

3 files changed

Lines changed: 40 additions & 3 deletions

File tree

docs/first-second-parity-audit.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ M6 progress now includes:
144144
- `internal/conversation`: optional auto-compact can now run before the main request, fail open with a consecutive-failure circuit breaker, emit runtime `token_warning` events from compact warning state, persist compact boundary metadata to transcript, write a session-memory summary, inject recalled session-memory snippets into API request context when enabled, and optionally extract turn-end memory facts into session memory.
145145
- `internal/memory`: session-memory summaries can now be loaded with frontmatter field aliases, rolled up/pruned into an archive summary with prior rollup archives excluded from candidate compaction and merged across archive IDs, rune-safe rollup truncation, recalled by query with deterministic scoring and recency ordering, ranked through a model-assisted candidate/session-id selection path with fallback when selected IDs are invalid or excluded, alternate/camel response-key parsing, fenced/prose JSON extraction, scalar session-id parsing, nested/wrapped/collection-alias selection parsing including JSON:API `included` and `collection`/`list` wrappers, non-memory/session typed resource `id` filtering, and nested selected-memory item parsing, and injected into resume context through the same optional recall agent path; deterministic and model-backed memory fact extraction can summarize user preferences, requests, decisions, and tool-use facts, including fenced/prose JSON, wrapped facts responses, provider-style response wrappers, alternate and structured fact field names, nested source objects, nested fact response shapes, and fact kind aliases.
146146
- `internal/memory`: session-memory recall and relevant-memory model selectors now recurse through GraphQL/JSON:API selection wrappers such as `data`, `payload`, `body`, `resource`, `attributes`, `properties`, `attrs`, `viewer`, `edge`, `node`, `nodes`, and `edges`, preserving model-ranked session IDs and memory paths from API-shaped responses.
147-
- `internal/memory`: session-memory recall and relevant-memory model selectors now also unwrap provider-style `choices`, `outputs`, `candidates`, and `generations` responses plus top-level `message`/`content`/`text` envelopes, recovering JSON selection payloads from nested `message.content`, content-block arrays, `content.parts[].text`, and fenced `json` code blocks.
148-
- `internal/memory`: model-backed memory fact extraction now also unwraps provider-style `choices`, `outputs`, `candidates`, and `generations` responses plus top-level `message`/`content`/`text` envelopes, recovering JSON facts payloads from nested `message.content`, content-block arrays, `content.parts[].text`, and fenced `json` code blocks.
147+
- `internal/memory`: session-memory recall and relevant-memory model selectors now also unwrap provider-style `choices`, `outputs`, `candidates`, and `generations` responses plus top-level `message`/`content`/`text`/`output_text` envelopes, recovering JSON selection payloads from nested `message.content`, content-block arrays, `content.parts[].text`, and fenced `json` code blocks.
148+
- `internal/memory`: model-backed memory fact extraction now also unwraps provider-style `choices`, `outputs`, `candidates`, and `generations` responses plus top-level `message`/`content`/`text`/`output_text` envelopes, recovering JSON facts payloads from nested `message.content`, content-block arrays, `content.parts[].text`, and fenced `json` code blocks.
149149
- `internal/session`: remote-history response parsing now also unwraps provider-style `choices`, `outputs`, `candidates`, and `generations` responses plus top-level `message`/`content`/`text` envelopes, recovering event page JSON plus pagination from nested `message.content`, content-block arrays, `content.parts[].text`, and fenced `json` code blocks.
150150
- `internal/session`: remote-history pagination now accepts `starting_after`/`startingAfter`/`after*` cursor aliases from page fields and link URL query parameters when deriving the next `before_id`.
151151
- `internal/session`: project session listing and pagination, prompt-history lock/buffered flush/field aliases, lightweight transcript index/title/text-preview inference, line-offset transcript indexing/window/byte-budget-window/parent-chain/resume/tail/byte-budget-tail loading, AI-title/last-prompt/task/agent/PR/worktree transcript metadata loading and type/field aliases, transcript message/session UUID field aliases, tombstone metadata loading with target/session/reason aliases and delete/relink replay, agent-scoped content replacement metadata/record field-alias loading, session-scoped metadata reappend including AI-title/last-prompt/task-summary, streaming transcript search snippets for resume/search UI, official `subagents/agent-*.jsonl` transcript layout with legacy sidechain listing, agent metadata sidecar read/write/field aliases, sidechain runtime start/append/finish/cancel/fail summary bridging plus parent-chain append/finish, sidechain manager orchestration for spawn/append/finish/cancel/fail/list/resume, sidechain state/list/resume support with content-field aliases, sidechain resume context construction, sidechain conversation and agent-scoped content-replacement reconstruction, transcript tail/window loading and byte-budget tail loading for bounded-memory resume/UI paths, lightweight transcript metadata loading, remote-history token refresh retry, page-field/event-list/records/entries/last-id/cursor/event-id/has-next aliases plus wrapped-data/links/paging/bare-array/keyed-event-map responses, before_id resume, fallback field fill, and duplicate-aware parent linking during transcript materialization.

internal/memory/agent.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,8 @@ func selectionProviderResponseText(raw string) (string, bool) {
10161016
"message",
10171017
"content",
10181018
"text",
1019+
"outputText",
1020+
"output_text",
10191021
} {
10201022
value, ok := object[key]
10211023
if !ok {
@@ -1081,7 +1083,7 @@ func selectionProviderTextFromRaw(raw json.RawMessage, depth int) (string, bool)
10811083
if err := json.Unmarshal(raw, &fields); err != nil {
10821084
return "", false
10831085
}
1084-
for _, key := range []string{"text", "content", "value", "output"} {
1086+
for _, key := range []string{"text", "content", "value", "output", "outputText", "output_text"} {
10851087
if value, ok := fields[key]; ok {
10861088
if text, ok := selectionProviderTextFromRaw(value, depth+1); ok {
10871089
return text, true

internal/memory/memory_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,20 @@ func TestMemoryAgentSelectRelevantMemoriesParsesProviderResponseWrappers(t *test
582582
if len(result.Selected) != 1 || result.Selected[0].Path != opsPath {
583583
t.Fatalf("content selected = %#v", result.Selected)
584584
}
585+
586+
client.response.Content = []contracts.ContentBlock{contracts.NewTextBlock(`{
587+
"output_text": "{\"memoryPaths\":[\"db.md\"],\"query\":\"database output\"}"
588+
}`)}
589+
result, err = (Agent{Client: client}).SelectRelevantMemories(context.Background(), dir, "database", RelevantMemorySelectorOptions{Limit: 1})
590+
if err != nil {
591+
t.Fatal(err)
592+
}
593+
if result.Fallback || result.Query != "database output" || strings.Join(result.SelectedIDs, ",") != "db.md" {
594+
t.Fatalf("output_text result = %#v", result)
595+
}
596+
if len(result.Selected) != 1 || result.Selected[0].Path != dbPath {
597+
t.Fatalf("output_text selected = %#v", result.Selected)
598+
}
585599
}
586600

587601
func TestPrefetchRelevantMemoriesCanUseMemoryAgentSelector(t *testing.T) {
@@ -1215,6 +1229,13 @@ func TestMemoryAgentExtractsProviderResponseWrappedFacts(t *testing.T) {
12151229
text: "accept top-level content facts",
12161230
source: "user_1",
12171231
},
1232+
{
1233+
name: "top-level output text",
1234+
response: `{"output_text":"{\"facts\":[{\"kind\":\"tool_use\",\"content\":\"accept output text facts\",\"sourceId\":\"assistant_1\"}]}"}`,
1235+
kind: FactTool,
1236+
text: "accept output text facts",
1237+
source: "assistant_1",
1238+
},
12181239
}
12191240
for _, tt := range tests {
12201241
t.Run(tt.name, func(t *testing.T) {
@@ -1665,6 +1686,20 @@ func TestMemoryAgentRecallParsesProviderResponseWrappers(t *testing.T) {
16651686
if len(result.Matches) != 1 || result.Matches[0].Summary.SessionID != "prior" {
16661687
t.Fatalf("content matches = %#v", result.Matches)
16671688
}
1689+
1690+
client.response.Content = []contracts.ContentBlock{contracts.NewTextBlock(`{
1691+
"output_text":"{\"query\":\"credential output\",\"selected_session_ids\":[\"other\"]}"
1692+
}`)}
1693+
result, err = (Agent{Client: client}).Recall(context.Background(), root, "credential output", RecallOptions{Limit: 1})
1694+
if err != nil {
1695+
t.Fatal(err)
1696+
}
1697+
if result.Fallback || result.Query != "credential output" || strings.Join(contractIDStrings(result.SelectedIDs), ",") != "other" {
1698+
t.Fatalf("output_text result = %#v", result)
1699+
}
1700+
if len(result.Matches) != 1 || result.Matches[0].Summary.SessionID != "other" {
1701+
t.Fatalf("output_text matches = %#v", result.Matches)
1702+
}
16681703
}
16691704

16701705
func TestMemoryAgentRecallExtractsFencedJSONFromModelProse(t *testing.T) {

0 commit comments

Comments
 (0)