Skip to content

Forward the selected persona through the chat proxy - #1368

Merged
tomchop merged 2 commits into
mainfrom
feat/proxy-persona
Sep 7, 2026
Merged

Forward the selected persona through the chat proxy#1368
tomchop merged 2 commits into
mainfrom
feat/proxy-persona

Conversation

@tomchop

@tomchop tomchop commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

Step 3 groundwork for agent personas. Found while wiring the UI selector, not by reading the diff.

The problem

/api/v2/agents/stream does not forward the request body. It rebuilds it:

agent_payload = {
    "user_id": username,
    "session_id": message.get("session_id"),
    "text": message.get("text"),
    "model": message.get("model"),
}

A persona sent by the UI stops here. The selector would have rendered, changed the request, and had no effect whatsoever — the failure mode that looks like a model bug rather than a plumbing one.

Why the allowlist stays

The obvious fix is to forward message wholesale. That would be a security regression: user_id is taken from the authenticated request, and a caller who could set it in the body would read another user's sessions. The shape is right; it was just missing a field. Added a comment so the next person does not "simplify" it.

Tests

Two, and both properties were previously unguarded:

  • test_stream_forwards_the_selected_personaverified it fails without the one-line change (1 failed, 3 passed), so it is not vacuous.
  • test_stream_cannot_be_told_which_user_it_is — posts user_id: "someone-else" and asserts the authenticated username is what goes out.

The mock scaffolding is the fiddly part and is documented in _mock_streaming_client: httpx.AsyncClient(...) and .stream(...) are both context managers but only the client's is async. stream() is called synchronously and returns the async one, so an AsyncMock in that position produces an un-awaited coroutine instead of a response — which is exactly how the first attempt failed.

4 passed, 2 warnings in 6.01s
ruff check .  -> All checks passed!
ruff format . -> 285 files already formatted

Note

Committed with --no-verify. The pre-commit hook shells out to a host-side uv run ruff, which cannot read this repo's container-created .venv (Permission denied on .venv/bin/python3). Ran the hook's exact commands inside dev-api-1 instead — output above.

Needed by yeti-platform/yeti-agents#12, which reads persona off the request.

The proxy rebuilds the request field by field rather than forwarding the body,
so a persona named by the UI never reached the agent service: the selector
would have appeared to work and changed nothing.

Naming the fields is deliberate and worth keeping -- user_id is taken from the
authenticated request, and forwarding the body wholesale would let a caller set
it and read another user's sessions. Both properties now have a test; the
persona one fails without the added line.
ADKSession declares the fields the UI receives, and anything the agent service
sends that is not declared is discarded on the way out. It reports model and
persona per session; neither was declared, so both arrived as undefined.

That silently broke the per-session model restore added with the model picker:
switching sessions always fell back to the deployment default rather than
showing what the conversation had actually been answered with. Persona would
have landed the same way.

This is the third field to go this way after createTime and title, so the
model now says so.
@tomchop
tomchop merged commit 34c1f68 into main Sep 7, 2026
5 checks passed
@tomchop
tomchop deleted the feat/proxy-persona branch September 7, 2026 07:54
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.

1 participant