Skip to content

feat(agent): route load-session through a no-op session store port#4768

Closed
mmabrouk wants to merge 1 commit into
fix/agent-session-id-tracingfrom
feat/agent-load-session-store-port
Closed

feat(agent): route load-session through a no-op session store port#4768
mmabrouk wants to merge 1 commit into
fix/agent-session-id-tracingfrom
feat/agent-load-session-store-port

Conversation

@mmabrouk

@mmabrouk mmabrouk commented Jun 19, 2026

Copy link
Copy Markdown
Member

This PR is part of a stack. Review bottom-up.

Each PR's diff is only its own delta. Merge from the bottom. This PR's base is #4767 (merge that first).

Context

This PR routes the agent POST /load-session endpoint through a new SessionStore port instead of returning a hardcoded empty stub. It sits on fix/agent-session-id-tracing and is slice #5 (cold session persistence) in docs/design/agent-workflows/pr-stack.md: the port-only seam.

The cold runtime still receives the full message history on every turn. This PR does not change that. It adds the seam where a server-owned history store will later attach, and it locks the load-session response shape before any storage lands.

What this changes

make_load_session_endpoint used to build a fixed response. It now takes an optional session_store and calls store.load(session_id), then converts each neutral Message back into a Vercel UIMessage at the adapter boundary.

The default store is NoopSessionStore. Its load returns () and its save_turn discards. So with no real adapter wired, the response is identical to before:

Before (hardcoded stub):

{"session_id": "sess_abc", "messages": []}

After (NoopSessionStore, same output):

{"session_id": "sess_abc", "messages": []}

With a real store that returns one user Message, the same path now produces:

{"session_id": "sess_abc", "messages": [
  {"id": "msg-1", "role": "user", "parts": [{"type": "text", "text": "hello"}]}
]}

Key architectural decision to review

Land the persistence seam now with no real adapter. SessionStore and NoopSessionStore live in sdks/python/agenta/sdk/agents/interfaces.py. The tradeoff: the API shape of /load-session and the load / save_turn contract get pinned and exported now, while actual storage ships in a later slice. The runtime keeps sending full history every turn, so nothing depends on the store for correctness yet. The risk is over-fitting the port to a store that does not exist. Check that load returning a Sequence[Message] and save_turn taking messages plus an optional AgentResult will fit a real platform-backed or file-backed adapter without a signature break.

The second thing to scrutinize is the conversion direction at the boundary in routing.py. Loaded neutral Message objects flow back out through message_to_vercel_ui_message, which is the inverse of the inbound vercel_ui_messages_to_messages on /messages. The store speaks neutral messages; the wire speaks Vercel UIMessage. Confirm those two directions stay symmetric so a saved turn round-trips.

How to review this PR

  1. Start with sdks/python/agenta/sdk/agents/interfaces.py. Read the SessionStore ABC and NoopSessionStore. Check the method signatures are the contract you want frozen.
  2. Then sdks/python/agenta/sdk/agents/adapters/vercel/routing.py, make_load_session_endpoint. Confirm the session_store or NoopSessionStore() default preserves old behavior, and that register_agent_message_routes threads the optional session_store through.
  3. Then __init__.py exports for SessionStore and NoopSessionStore.
  4. Likely regression: a store that returns messages but the endpoint mislabels indices or roles. The msg-{idx} ids start at 1 per response, so they are not stable across calls. Check that no consumer treats them as durable.

Tests / notes

test_load_session_uses_session_store_port injects a fake store, asserts load is called with the session id, and asserts the response renders one user message as a Vercel UIMessage. The existing test_load_session_returns_stub_history still passes against the default NoopSessionStore, which proves the no-op path is unchanged.

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d9c09b2f-e8db-4d05-bed7-c07b616311df

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/agent-load-session-store-port

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. feature python Pull requests that update Python code labels Jun 19, 2026
@mmabrouk

Copy link
Copy Markdown
Member Author

Reviewer guide: interesting code

  • sdks/python/agenta/sdk/agents/interfaces.py:89 — the SessionStore ABC; this is the contract that gets frozen, so scrutinize load and save_turn signatures here first.
  • sdks/python/agenta/sdk/agents/interfaces.py:111NoopSessionStore returns () and discards writes, which keeps the default /load-session output empty and unchanged.
  • sdks/python/agenta/sdk/agents/adapters/vercel/routing.py:156store = session_store or NoopSessionStore() is the line that preserves old behavior when no adapter is wired.
  • sdks/python/agenta/sdk/agents/adapters/vercel/routing.py:163 — loaded neutral messages convert back to Vercel UIMessage via message_to_vercel_ui_message, the inverse of the inbound /messages conversion.
  • sdks/python/oss/tests/pytest/utils/test_messages_endpoint.py:235 — the injected fake store proves the port is actually called and the response renders one user message.

messages: Sequence[Message],
result: Optional[AgentResult] = None,
) -> None:
"""Persist one completed cold turn."""

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the contract being frozen ahead of any real store. Confirm save_turn taking messages plus an optional AgentResult fits a file-backed or platform-backed adapter without a later signature break.

make_not_acceptable_response: Callable[[str, Any], Response],
make_stream_response: Callable[[WorkflowStreamingResponse, str], Response],
handle_failure: Callable[[Exception], Any],
session_store: Optional[SessionStore] = None,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The msg-{idx} ids restart at 1 per response, so they are not stable across load-session calls. Confirm no client treats these ids as durable.

session_store: Optional[SessionStore] = None,
):
"""Build the v1 ``POST /load-session`` endpoint over the session-store port."""
store = session_store or NoopSessionStore()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The or NoopSessionStore() default is what keeps the response identical to the old hardcoded stub when no adapter is wired. Worth confirming this is the only place the default is chosen.

response = LoadSessionResponse(
session_id=request.session_id,
messages=[
message_to_vercel_ui_message(message, message_id=f"msg-{idx}")

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Loaded neutral messages convert back to Vercel UIMessage here, the inverse of the inbound vercel_ui_messages_to_messages on /messages. Confirm the two directions stay symmetric so a saved turn round-trips.

@mmabrouk

Copy link
Copy Markdown
Member Author

Superseded. Replacing the path-based stack with PRs sliced by functional area showing final code only, so reviewers don't comment on intermediate scaffolding that a later PR rewrites. See the new set.

@mmabrouk mmabrouk closed this Jun 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature python Pull requests that update Python code size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant