Skip to content

Commit fabdc54

Browse files
authored
sdk: Rework against compacted memory(action=...) tool surface (#210, #208)
* planning: Add design for SDK rework against compacted tool surface The memoryhub SDK still calls per-action MCP tools but the deployed primary server only exposes register_session + memory after #198/#202. Issue #202 called for keeping per-action tools as deprecation aliases; that step was skipped. The kagenti-adk integration in kagenti/adk#231 hit this when run against the live server. Decision recorded: take the SDK forward to the new surface rather than back-porting server-side aliases. Public method signatures stay stable; only the wire format changes. Bump SDK 0.6.0 → 0.7.0. Tracks #210. Assisted-by: Claude Code (Opus 4.7) Signed-off-by: rdwj <wjackson@redhat.com> * sdk: Rework client against compacted memory(action=...) tool surface Every public MemoryHubClient method now dispatches through the unified `memory` tool (#198/#202) instead of the legacy per-action tool names. Public Python API is unchanged — only the wire format changes. This restores end-to-end behavior against the primary memory-hub-mcp deployment, which exposes only register_session + memory after the consolidation. The cutover replaces the deprecation-alias path that was scoped in #202 but never shipped. Signature stability is the load-bearing property here: the kagenti-adk MemoryStore wrapper (kagenti/adk#231) calls search, write, read, update, delete, etc. directly — those signatures are preserved, so consumers only need to bump the dependency pin. Smoke-tested against the live primary server: search, get_session, and list_projects all succeed. Note: server-side max_results behavior on search appears to over-return appendix entries; that's a separate server bug, not SDK. Tracks #210. Assisted-by: Claude Code (Opus 4.7) Signed-off-by: rdwj <wjackson@redhat.com> * sdk: Bump to 0.7.0 with BREAKING wire-format note The 0.6.0 → 0.7.0 cutover swaps every MCP tool call from the legacy per-action names to memory(action=..., options={...}). Public Python API is unchanged but the wire format is incompatible with servers that only expose the per-action surface, and 0.6.0 is incompatible with the primary memory-hub-mcp deployment. Tracks #210. Assisted-by: Claude Code (Opus 4.7) Signed-off-by: rdwj <wjackson@redhat.com> * sdk: Add kagenti-adk contract test (#208) Pins the SDK surface that kagenti-adk's MemoryHubMemoryStoreInstance depends on: constructor (api_key + OAuth modes), search/write/read/ update/delete signatures, the WriteResult.curation.reason path that the wrapper raises MemoryRejectionError on, and NotFoundError on missing-id reads/deletes. Uses the SDK's existing mocked transport — no live server needed. A failure here is the cue to coordinate with kagenti-adk maintainers before shipping the SDK release. Closes #208. Assisted-by: Claude Code (Opus 4.7) Signed-off-by: rdwj <wjackson@redhat.com> --------- Signed-off-by: rdwj <wjackson@redhat.com>
1 parent 9fb9aa8 commit fabdc54

6 files changed

Lines changed: 700 additions & 202 deletions

File tree

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# SDK rework against compacted memory(action=...) tool surface
2+
3+
Status: Proposed — 2026-04-29
4+
Tracks: (issue to be filed)
5+
Author: @rdwj (drafted with Claude Code Opus 4.7)
6+
7+
## Why this exists
8+
9+
Issues #198/#201/#202 reduced the MemoryHub MCP server's tool surface from 10
10+
per-action tools to a single `memory(action=...)` dispatcher (plus
11+
`register_session`). That work shipped on the `memory-hub-mcp` (primary)
12+
deployment, which is the only deployment exposing the full operation surface.
13+
The minimal deployment retains a 4-tool subset for legacy integrations.
14+
15+
Issue #202's scope explicitly called for keeping the per-action tools as
16+
temporary aliases for a deprecation window. That aliasing step was never
17+
shipped, and the `memoryhub` Python SDK (currently v0.6.0) still calls the
18+
old per-action tool names (`search_memory`, `read_memory`, `write_memory`,
19+
etc.). The result: `MemoryHubClient` cannot talk to the primary deployment
20+
at all — `register_session` succeeds but every operational method raises
21+
`ToolError: Unknown tool: '<name>'`.
22+
23+
This was caught when the kagenti-adk integration in
24+
[kagenti/adk PR #231](https://github.com/kagenti/adk/pull/231) was tested
25+
against the live primary server. The PR is paused until this is fixed.
26+
27+
## What this rework is
28+
29+
The cutover. We take the SDK forward to the new surface instead of
30+
back-porting compatibility aliases on the server. Wes confirmed
31+
2026-04-29: "we did flag the SDK as necessary follow-on work and we
32+
should have done that very next thing and did not. This is our chance
33+
to rectify that."
34+
35+
## Scope
36+
37+
1. Rewrite `MemoryHubClient` internals so every method dispatches via
38+
`memory(action=..., memory_id=..., query=..., content=..., scope=...,
39+
project_id=..., options={...})` instead of calling per-action tool
40+
names directly.
41+
2. Keep all public method signatures stable. The kagenti-adk wrapper
42+
already calls `client.search(query, scope=..., project_id=...,
43+
max_results=...)`, etc.; that surface must not change. Only the wire
44+
format the SDK emits to the MCP server changes.
45+
3. `register_session` stays a separate tool call — it is unchanged on
46+
the server.
47+
4. Update `sdk/tests/test_client.py` to assert the new payload shape:
48+
`call_tool("memory", {"action": "search", ...})` instead of
49+
`call_tool("search_memory", ...)`.
50+
5. Bump SDK version `0.6.0 → 0.7.0`. Update `sdk/CHANGELOG.md` with a
51+
"BREAKING (wire format)" entry that links this rework, #198, and
52+
#202.
53+
6. Bump kagenti-adk's `memoryhub>=0.5.0` pin to `memoryhub>=0.7.0` in a
54+
coordinated PR-#231 follow-up commit.
55+
56+
## Action mapping
57+
58+
The `memory` tool's accepted actions are documented in
59+
`memory-hub-mcp/src/tools/memory.py`. The mapping for each public SDK
60+
method:
61+
62+
| SDK method | action | top-level args | options keys |
63+
|---|---|---|---|
64+
| `search` | `search` | `query`, `scope`, `project_id` | `max_results`, `weight_threshold`, `current_only`, `mode`, `max_response_tokens`, `include_branches`, `focus`, `session_focus_weight`, `domains`, `domain_boost_weight`, `raw_results`, `owner_id` |
65+
| `read` | `read` | `memory_id`, `project_id` | `include_versions`, `history_offset`, `history_max_versions`, `hydrate` |
66+
| `write` | `write` | `content`, `scope`, `project_id` | `weight`, `parent_id`, `branch_type`, `metadata`, `domains`, `force`, `owner_id` |
67+
| `update` | `update` | `memory_id`, `content`, `project_id` | `weight`, `metadata`, `domains` |
68+
| `delete` | `delete` | `memory_id`, `project_id` | (none) |
69+
| `report_contradiction` | `report` | `memory_id`, `project_id` | `observed_behavior`, `confidence` |
70+
| `resolve_contradiction` | `resolve` | (none) | `contradiction_id`, `resolution_action`, `resolution_note` |
71+
| `get_similar` | `similar` | `memory_id`, `project_id` | `threshold`, `max_results`, `offset` |
72+
| `get_relationships` | `relationships` | `memory_id`, `project_id` | `relationship_type`, `direction`, `include_provenance` |
73+
| `create_relationship` | `relate` | `project_id` | `source_id`, `target_id`, `relationship_type`, `metadata` |
74+
| `set_curation_rule` | `set_rule` | (none) | `name`, `tier`, `action_type`, `config`, `scope_filter`, `enabled`, `priority` |
75+
| `list_projects` | `list_projects` | (none) | `filter` |
76+
| `create_project` | `create_project` | `project_id` | `project_name`, `description`, `invite_only` |
77+
| `add_project_member` | `add_member` | `project_id` | `user_id`, `role` |
78+
| `remove_project_member` | `remove_member` | `project_id` | `user_id` |
79+
| `get_session` | `status` | (none) | (none) |
80+
| `set_session_focus` | `set_focus` | `project_id` | `focus` |
81+
| `get_focus_history` | `focus_history` | `project_id` | `start_date`, `end_date` |
82+
83+
Two server-side normalizations to remember when wiring the dispatch:
84+
85+
- `relationships` action takes `memory_id` at the top level but the
86+
server's `manage_graph` tool internally renames it to `node_id`. The
87+
SDK should pass `memory_id` per the dispatcher contract; the server
88+
handles the rename.
89+
- `describe_project`, `add_member`, `remove_member`, `create_project`
90+
take `project_id` at the top level, not `project_name`. The
91+
dispatcher normalizes.
92+
93+
## Implementation shape
94+
95+
Add one private helper:
96+
97+
```
98+
async def _call_action(
99+
self,
100+
action: str,
101+
*,
102+
memory_id: str | None = None,
103+
query: str | None = None,
104+
content: str | None = None,
105+
scope: str | None = None,
106+
project_id: str | None = None,
107+
options: dict | None = None,
108+
) -> dict:
109+
payload = {"action": action}
110+
if memory_id is not None: payload["memory_id"] = memory_id
111+
if query is not None: payload["query"] = query
112+
if content is not None: payload["content"] = content
113+
if scope is not None: payload["scope"] = scope
114+
if project_id is not None: payload["project_id"] = project_id
115+
if options: payload["options"] = options
116+
return await self._call("memory", payload)
117+
```
118+
119+
Each public method becomes a thin wrapper that builds its own `options`
120+
dict and calls `_call_action`. The error-classification logic in
121+
`_call` is unchanged — the server returns the same error envelopes for
122+
the dispatched action as it did for the per-action tools.
123+
124+
## Backwards compatibility
125+
126+
- Public Python API: **stable**. No method signature changes, no
127+
imports moved. Existing callers (kagenti-adk wrapper, internal
128+
scripts) continue to compile after a version bump.
129+
- Wire format: **breaking**. SDK 0.6.0 cannot talk to a server with
130+
per-action tools removed. SDK 0.7.0 cannot talk to a server that
131+
only exposes per-action tools. We pin kagenti-adk to 0.7.0+; the
132+
minimal-tool deployment is for legacy connectors and is not in the
133+
SDK's target set.
134+
135+
## Out of scope
136+
137+
- Re-introducing server-side per-action tools as aliases. Decision
138+
recorded above.
139+
- Updating fipsagents/agent-template SDK consumers. Track separately
140+
if any are pinned <0.7.0.
141+
- `manage_curation` / `set_curation_rule` parity with the dispatcher.
142+
The server still exposes `manage_curation` independently for tier-2
143+
callers; the SDK uses the dispatcher path for `set_rule`, `report`,
144+
`resolve`. No behavioral change.
145+
146+
## Open questions
147+
148+
- Do we want a `--use-legacy-tools` flag on the SDK for talking to the
149+
minimal deployment? Recommend **no** — minimal is a 4-tool subset
150+
used by legacy connectors, not a full target. Document in the README
151+
instead.
152+
- Should the rework also update the SDK's docstrings to describe the
153+
dispatcher path? Recommend **yes**, but only on methods where the
154+
new wire format affects observable behavior (e.g., error messages
155+
reference the action name).
156+
157+
## References
158+
159+
- `planning/mcp-single-tool-schema.md` — the original action-dispatch design.
160+
- `memory-hub-mcp/src/tools/memory.py` — the dispatcher implementation.
161+
- `sdk/src/memoryhub/client.py` — the SDK to be reworked.
162+
- `sdk/tests/test_client.py` — the test suite to update.
163+
- `kagenti/adk` PR #231 — the integration that surfaced the gap.

sdk/CHANGELOG.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,21 @@
22

33
All notable changes to the `memoryhub` SDK package.
44

5-
## [0.6.0] — 2026-04-23
5+
## [0.7.0] — 2026-04-29
6+
7+
- **BREAKING (wire format)**: `MemoryHubClient` now dispatches every
8+
operation through the unified `memory(action=..., options={...})` MCP
9+
tool introduced by the server-side consolidation in #198/#202.
10+
Per-action tool names (`search_memory`, `read_memory`, `write_memory`,
11+
`update_memory`, `delete_memory`, `report_contradiction`,
12+
`get_similar_memories`, `get_relationships`, `create_relationship`,
13+
`set_curation_rule`, `manage_session`, `manage_project`,
14+
`set_session_focus`, `get_focus_history`) are no longer called.
15+
This release **cannot** talk to a server that only exposes the legacy
16+
per-action tools; older releases (≤ 0.6.0) **cannot** talk to the
17+
primary `memory-hub-mcp` deployment. The Python API is unchanged —
18+
consumers only need to update the dependency pin. Tracks #210, closes
19+
the alias gap left by #202.
620

721
- **Stub result compatibility**: Default `content` and `owner_id` fields to
822
empty strings in the `Memory` model so cache-optimized search responses

sdk/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "memoryhub"
7-
version = "0.6.0"
7+
version = "0.7.0"
88
description = "Centralized, governed memory for AI agents"
99
readme = "README.md"
1010
license = "Apache-2.0"

0 commit comments

Comments
 (0)