Skip to content

fix(sdk): guard registered memory providers - #2333

Merged
tamirdresher merged 2 commits into
github:devfrom
joslat:joslat/2332-memory-provider-guards
Oct 28, 2026
Merged

tamirdresher merged 2 commits into
github:devfrom
joslat:joslat/2332-memory-provider-guards

Conversation

@joslat

@joslat joslat commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

What

LocalMemoryStore now guards its registered memory providers:

  • every provider call has a timeout;
  • a failing provider can't break providerStatus();
  • recalled results go through governance on the way back;
  • replication audits say which provider received the copy.

Why

Closes #2332

Governance ran before anything reached a provider, but nothing after that. A hung provider stalled memory.write / memory.search with no limit. One throwing or hung status() broke providerStatus() for everyone. And whatever a provider returned from search() went straight to the agent, including classes it was never sent and secret-looking text that would have been rejected on write.

How

All in packages/squad-sdk/src/memory/index.ts:

  • Timeouts. withProviderTimeout() bounds provider write, search and status calls. The limit is configurable through the new LocalMemoryStoreOptions.providerTimeoutMs, with a default of 5000 ms; non-positive or non-finite values fall back to the default. A timeout throws ProviderTimeoutError, which goes through the existing safe provider-error audit path. The call isn't cancelled, so a slow write may still land in the provider later; the option's doc comment says so.
  • providerStatus(). Each provider's status() is wrapped, so a throw or timeout becomes { available: false, reason: '<name> provider status failed (<ErrorName>); raw provider error text omitted' }, and the other providers are still reported.
  • Recall checks.
    • A result is kept only if its class is one the provider declares and one write() routes to providers (LOCAL, DECISION, POLICY), and its title and snippet don't classify as FORBIDDEN.
    • Dropped results produce one reject audit record per provider with a count, and no content.
    • loadGuidance goes through the same normalizeLoadGuidance helper local entries use, falling back to the class default.
  • Audit. The write record for a replicated copy now carries provider: <id>, the same as the error records.
  • The MemoryProvider doc comment describes the return-path contract.

No interface signatures change, and nothing in the CLI registers a provider today, so shipped Squad behaves the same. The changes take effect for SDK users who pass registeredProviders.

I left the scope, limit, ToolRegistry wiring and delete propagation out on purpose. Those need a design decision first (listed in the issue).

Heads-up: this changes the same line as #2319: the safeProviderErrorReason signature, which there accepts the host adapter and here adds 'status'. Whichever merges second needs a one-line rebase, and I'll do it.

Testing

New tests in test/memory-providers.test.ts:

  • a hung search times out, local results still come back, and the timeout is audited;
  • a hung write times out without failing the local write;
  • the replication audit carries the provider id;
  • throwing and hung status() providers are reported as unavailable next to a healthy one;
  • recalled results with an undeclared class, a never-routed class (COPILOT_MEMORY) or a credential-like snippet are dropped and counted, and the audit doesn't contain the snippet;
  • an invalid recalled loadGuidance falls back to the class default.

Results:

  • With the memory/index.ts change reverted, all 6 new tests fail; the two timeout tests hang until the test timeout.
  • test/memory-providers.test.ts, memory-governance, memory-value-benchmark, tools, cli/state-mcp and cli/state-mcp-lifecycle: 174 passed.
  • npm run lint: clean.

⚠️ Quick Check

  • If SDK/CLI source files changed: changeset added (.changeset/memory-provider-guards.md, sdk patch)

PR Readiness Checklist

Branch & Commit

  • Branch created from dev (not main)
  • Branch is up to date with dev (9d7dff8)
  • Verified diff contains only intended changes (3 files)
  • PR is not in draft mode
  • Commit history is clean

Build & Test

Docs

  • No docs describe registeredProviders; the option and the provider contract are documented in the source

Breaking Changes

None. The interface is unchanged. A provider that returned results outside its declared classes, or FORBIDDEN-looking text, will now have those results dropped, which is the intended behavior.

Waivers

None.

@github-actions

github-actions Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

🛫 PR Readiness Check

ℹ️ This comment updates on each push. Last checked: commit b40430c

PR Scope: 📦🔧 Mixed (product + infrastructure)

⚠️ 3 item(s) to address before review

Status Check Details
❌ Single commit 2 commits — consider squashing before review
✅ Not in draft Ready for review
✅ Branch up to date Up to date with dev
❌ Copilot review No Copilot review yet — it may still be processing
✅ Changeset present Changeset file found
✅ Scope clean No .squad/ or docs/proposals/ files
✅ No merge conflicts No merge conflicts
✅ Copilot threads resolved No Copilot review threads
❌ CI passing 13 check(s) still running

Files Changed (3 files, +208 −7)

File +/−
.changeset/memory-provider-guards.md +5 −0
packages/squad-sdk/src/memory/index.ts +79 −7
test/memory-providers.test.ts +124 −0

Total: +208 −7


This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.

@github-actions

Copy link
Copy Markdown
Contributor

🟡 Impact Analysis — PR #2333

Risk tier: 🟡 MEDIUM

📊 Summary

Metric Count
Files changed 3
Files added 1
Files modified 2
Files deleted 0
Modules touched 3
Critical files 1

🎯 Risk Factors

  • 3 files changed (≤5 → LOW)
  • 3 modules touched (2-4 → MEDIUM)
  • Critical files touched: packages/squad-sdk/src/memory/index.ts

📦 Modules Affected

root (1 file)
  • .changeset/memory-provider-guards.md
squad-sdk (1 file)
  • packages/squad-sdk/src/memory/index.ts
tests (1 file)
  • test/memory-providers.test.ts

⚠️ Critical Files

  • packages/squad-sdk/src/memory/index.ts

This report is generated automatically for every PR. See #733 for details.

@tamirdresher

Copy link
Copy Markdown
Collaborator

Thanks Jose, another sharp catch on the memory governance boundary. I verified on dev that packages/squad-sdk/src/memory/index.ts had no timeout on provider.write (:838) or provider.search (:936), ran an unguarded Promise.all in providerStatus() (:1112), and trusted recalled provider.search() hits without re-checking supportedClasses, FORBIDDEN patterns, or loadGuidance (:937). Bounding write, search, and status with withProviderTimeout and filtering recalled hits through his.classify and
ormalizeLoadGuidance with a content-free
eject audit count closes that return-path trust gap cleanly.

I verified the merge-tree against dev is clean and approved the CI workflow runs. As you noted, line 411 of packages/squad-sdk/src/memory/index.ts (safeProviderErrorReason) touches the same signature as #2319. We are in a release freeze for 2.0.0-preview.2 right now and will merge your memory series in order (#2315, #2317, #2319, then #2333) right after the release ships, with that one-line combine on safeProviderErrorReason (provider: Pick<MemoryProvider, 'name'> and operation: 'write' | 'search' | 'status').

LocalMemoryStore trusted registered providers completely once governance
had run on the way in:

- every provider write/search/status call now has a timeout
  (providerTimeoutMs, default 5000 ms), audited as a provider error;
- providerStatus() guards each provider, so one throwing or hung status()
  shows up as unavailable instead of failing the whole call;
- recalled results are checked on the way back: classes the provider
  could not have been sent and FORBIDDEN-classified titles/snippets are
  dropped and audited, and an invalid loadGuidance falls back to the class
  default;
- the replication audit records the provider id.

No interface signatures change.

Closes github#2332
@joslat
joslat force-pushed the joslat/2332-memory-provider-guards branch from 80ca2e6 to c8557b1 Compare October 26, 2026 17:35
@joslat

joslat commented Oct 26, 2026

Copy link
Copy Markdown
Contributor Author

Rebased on dev now that #2319 is in, so you don't need to do the combine. safeProviderErrorReason takes provider: Pick<MemoryProvider, 'name'> and operation: 'write' | 'search' | 'status', and everything else applied cleanly. Still one commit. The memory and tools suites (memory-providers, memory-governance, memory-value-benchmark, memory-class-normalization, memory-store-resolution, tools, cli/state-mcp, cli/state-mcp-lifecycle) give 190 passed, and lint is clean.

@joslat joslat mentioned this pull request Oct 26, 2026
12 of 13 tasks
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.

Registered memory providers aren't guarded: no timeout, one bad status() breaks providerStatus(), recalled results skip governance

2 participants