feat(langgraph): multi-agent helpers on MemtomemStore#460
Merged
Conversation
…ssion, include_shared)
Before this PR, the LangGraph adapter only exposed raw `namespace=`
strings — multi-agent users had to hand-build `f"agent-runtime:{id}"`
on every call, and the multi-agent guide's advertised "LangGraph
adapter for multi-agent" was a thin wrapper around `start_session`
that hard-coded `namespace="default"`.
This PR threads a multi-agent semantic through the adapter:
* New `start_agent_session(agent_id, *, namespace=None)` derives the
namespace from `agent-runtime:<agent_id>` (override available),
records the session in storage, and binds `_current_agent_id` so
subsequent search/add calls inherit the agent scope.
* New `search(..., include_shared: bool | None = None)` parameter
encodes a documented 6-case state table:
| include_shared | _current_agent_id | resolved namespace |
|----------------|-------------------|--------------------------------------|
| None (auto) | "planner" | "agent-runtime:planner,shared" |
| None (auto) | unset | caller's namespace= (legacy) |
| True | "planner" | "agent-runtime:planner,shared" |
| True | unset | raises ValueError (no silent leak) |
| False | "planner" | "agent-runtime:planner" (no shared) |
| False | unset | caller's namespace= |
`True` without a bound agent raises explicitly so a missing
start_agent_session surfaces immediately rather than degrading to a
silent un-pinned search.
* `add(..., namespace=None)` defaults to the bound agent's private
bucket. Pass `namespace="shared"` to publish across agents
mid-session — the explicit value always wins, so the caller can
cross the bucket boundary without re-binding the session.
* `end_session` now resets both `_current_session_id` and
`_current_agent_id`, so a stale agent binding can't survive into a
new session. The legacy `start_session(agent_id, namespace)` is
preserved as a low-level escape hatch with a docstring pointer to
`start_agent_session`.
Explicitly out of scope: implementing LangGraph's `BaseStore`
(`aput` / `aget` / `alist_namespaces`) with the same multi-agent
awareness — tracked as a follow-up RFC. The adapter stays a
hand-rolled wrapper; multi-agent helpers live on `start_agent_session`
and `search(include_shared=)` only.
Test surface (test_langgraph.py):
- `TestMemtomemStoreInit.test_agent_id_none_initially` — fresh store
reports no bound agent.
- `TestResolveSearchNamespace` — 6 cases, one per row of the state
table above. The `True + unset` case asserts the explicit
ValueError so silent fallback can never sneak in.
- `TestResolveAddNamespace` — 4 cases for the `add` namespace defaulting:
no agent / agent + None / agent + explicit override.
- `TestStartAgentSession` — agent binding, derived namespace,
explicit-namespace override, empty-agent-id rejection,
end_session resets both fields.
Co-Authored-By: Claude <[email protected]>
c507736 to
857f96e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Before this PR, the LangGraph adapter (
MemtomemStore) only exposedraw
namespace=strings — multi-agent users had to hand-buildf"agent-runtime:{id}"on every call, and the multi-agent guide'sadvertised "LangGraph adapter for multi-agent" was a thin wrapper
around
start_sessionthat hard-codednamespace="default".This PR threads a multi-agent semantic through the adapter:
start_agent_session(agent_id, *, namespace=None)— derives thenamespace from
agent-runtime:<agent_id>(override available),records the session in storage, binds
_current_agent_id.search(..., include_shared: bool | None = None)— 3-state togglewith a documented 6-case state table.
add(..., namespace=None)— defaults to the bound agent's privatebucket; explicit
namespace="shared"wins so the caller can publishcross-agent without re-binding.
end_sessionresets both_current_session_idand_current_agent_id.start_session(agent_id, namespace)preserved as a low-levelescape hatch.
include_sharedsemantic tableinclude_shared_current_agent_idnamespacefilterNone(auto)"planner""agent-runtime:planner,shared"None(auto)namespace=(legacy)True"planner""agent-runtime:planner,shared"TrueValueError(no silent leak)False"planner""agent-runtime:planner"(no shared)Falsenamespace=Truewithout a bound agent raises explicitly so a missingstart_agent_sessionsurfaces immediately rather than degrading to asilent un-pinned search.
Out of scope
Implementing LangGraph's
BaseStore(aput/aget/alist_namespaces) with the same multi-agent awareness — tracked as afollow-up RFC. The adapter stays a hand-rolled wrapper; multi-agent
helpers live on
start_agent_sessionandsearch(include_shared=)only.
Test plan
TestResolveSearchNamespace— 6 cases, one per row of thestate table above. The
True + unsetcase asserts the explicitValueErrorso silent fallback can never sneak in.TestResolveAddNamespace— 4 cases for the add namespacedefaulting: no agent / agent + None / agent + explicit override.
TestStartAgentSession— agent binding, derived namespace,explicit override, empty-id rejection,
end_sessionreset.uv run pytest -m "not ollama"— 2345 passed, 46 deselected.uv run ruff check+ruff format --check— clean.🤖 Generated with Claude Code