Commit Graph

3 Commits

Author SHA1 Message Date
Hongming Wang
01b653d6b0 Memory v2 PR-4: namespace resolver + tests
Stacked on PR-1 (#2729). Computes the readable/writable namespace lists
for a workspace from the live workspaces tree at request time. No
precomputed columns, no migrations — re-parenting on canvas takes
effect immediately on the next memory call.

What ships:
  - workspace-server/internal/memory/namespace/resolver.go
    - walkChain: recursive CTE, walks parent_id chain to root, capped
      at depth 50 to defend against malformed/cyclic data
    - derive: maps a chain to (workspace, team, org) namespace strings
    - ReadableNamespaces / WritableNamespaces: the public API
    - CanWrite + IntersectReadable: server-side ACL helpers MCP
      handlers (PR-5) will call before talking to the plugin
  - resolver_test.go: 100% statement coverage

Design choices worth flagging:
  - Today's tree is depth-1 (root + children). The recursive CTE
    handles arbitrary depth so we don't have to revisit the resolver
    when the tree deepens.
  - GLOBAL→org write restriction (memories.go:167-174) is preserved
    by gating the org namespace's Writable flag on parent_id IS NULL.
  - Removed-status workspaces are NOT filtered from the chain walk —
    matches today's TEAM behavior (memories.go:367-372 filters on
    read, not on tree walk).
  - IntersectReadable with empty `requested` returns ALL readable
    namespaces (default-search-everything semantic from the discovery
    tools spec).

This package has zero callers in this PR; integration starts in PR-5.
2026-05-04 07:25:33 -07:00
Hongming Wang
c1cff3169f Memory v2 PR-2: HTTP plugin client + breaker + capability negotiation
Builds on PR-1 (#2729). Implements every endpoint in the OpenAPI spec
plus two operational concerns the agent never sees:

  1. Capability negotiation. Boot/Refresh probes /v1/health and
     captures the plugin's capability list. MCP handlers (PR-5) ask
     SupportsCapability before exposing capability-gated features —
     e.g., agents can only request semantic search when "embedding"
     is reported.

  2. Circuit breaker. Three consecutive failures open the breaker for
     60 seconds; while open, calls fail fast with ErrBreakerOpen.
     Picked these constants because:
       - 3 failures: long enough to skip transient blips, short enough
         to react before all in-flight handlers stack on the timeout
       - 60s cooldown: long enough to back off a flapping plugin,
         short enough that recovery is felt within a single session
     4xx responses do NOT count toward the breaker (those are client
     bugs, not plugin health issues); 5xx + transport errors do.

What ships:
  - workspace-server/internal/memory/client/client.go
  - client_test.go: 100% statement coverage

Coverage corner cases pinned:
  - env-var success branches in New (parseDurationEnv applied)
  - json.Marshal error (via channel in Propagation)
  - http.NewRequestWithContext error (via unbalanced bracket in BaseURL)
  - 204 NoContent on endpoint that normally has a body
  - 4xx vs 5xx breaker behavior (4xx must NOT trip)
  - breaker cooldown elapsed → reset on next success
  - all 6 public endpoints fail-fast when breaker is open

This package has no callers in this PR; integration starts in PR-5.
2026-05-04 06:57:24 -07:00
Hongming Wang
53d823e719 Memory v2 PR-1: OpenAPI plugin contract + Go bindings
First of 11 PRs implementing the memory-system plugin refactor (RFC #2728).
This PR is pure additive scaffolding — no behavior change, no integration
yet. It defines the wire shape between workspace-server and a memory
plugin so PR-2 (HTTP client) and PR-3 (built-in postgres plugin) can be
built against a single source of truth.

What ships:
  - docs/api-protocol/memory-plugin-v1.yaml: OpenAPI 3.0.3 spec covering
    /v1/health, namespace upsert/patch/delete, memory commit, search,
    forget. Auth-free (private network only); workspace-server is the
    only sanctioned client and the security perimeter.
  - workspace-server/internal/memory/contract: typed Go bindings with
    Validate() methods on every wire object so both client (PR-2) and
    server (PR-3) self-check at the boundary.
  - Round-trip JSON tests for every type (catch asymmetric tag bugs).
  - 5 golden vector files under testdata/ pinning the exact wire shape;
    update via UPDATE_GOLDENS=1.

Coverage: 100% of statements in contract.go.

The validation rules encode design decisions worth flagging in review:
  - SearchRequest with empty Namespaces is REJECTED at plugin level —
    workspace-server is required to intersect the readable set
    server-side; an empty list reaching the plugin is a bug.
  - NamespacePatch with no fields is REJECTED — empty patches are
    pointless round-trips.
  - MemoryWrite with whitespace-only Content is REJECTED — zero-info
    memories pollute search results.

No code yet calls into this package; integration starts in PR-2.
2026-05-04 06:45:52 -07:00