feat(agent-platform-backend): proxy kagent session detail and tasks - #2002
Merged
Conversation
Add the transport the upcoming session detail page needs: - GET /kagent/sessions/:id — one session plus its stored events - GET /kagent/sessions/:id/tasks — the session's A2A tasks Both require ?installation= and a forwarded user token, and pass kagent's JSON through verbatim like the existing routes. The conversation comes from .../tasks, not from the events on .../sessions/:id — that is what kagent's own UI renders from, and the payloads differ: an event's `data` is a JSON *string* holding an A2A message and carries no state or token usage, while task history is already structured and carries both. Events remain useful for timestamps, which A2A messages lack. No A2A-Version header is sent: kagent treats a missing header as the legacy v0 wire on both v0.9.9 and v0.10, the shape its UI consumes. Session ids stay opaque (real ones mix 64-char hex and UUIDs), so nothing validates a format and ids are URL-encoded before use. A session belonging to another user answers 404, as a deleted one does. Both are expected outcomes for a stale deep link, so neither returns a 5xx, which MiddlewareFactory.error() would forward to Sentry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C2M37V7ubYwimkkS7YZ9iz
marians
commented
Jul 29, 2026
marians
commented
Jul 29, 2026
Three different things arrive as a 404 and all three reported the same message, which was wrong for two of them: - Nothing listening at the host: keeps "kagent is not available here", the fleet-wide wording the frontend's silent classification relies on. - kagent answering "no such resource" (404 with a JSON body, since its error middleware always answers JSON): now "that session does not exist". Previously a bookmarked link to a deleted session reported an outage on a perfectly healthy installation. - The endpoint not existing (404 with a non-JSON body, because kagent registers no custom NotFoundHandler and net/http answers text/plain): now "this kagent predates that endpoint". Without this an installation on an older kagent would report "session not found" for every session on every page load, and with no version probe there is nothing else to go on. kagent's own message is not forwarded: its middleware appends the underlying error, so a session 404 reads "Session not found: no rows in result set". Also drop the session id's trim(). Express hands over the decoded segment, so an id with surrounding whitespace was trimmed here and a different id re-encoded upstream — a 404 indistinguishable from a missing session, on a value the docs declare opaque. The empty-id guard went with it: :sessionId cannot match an empty segment, so /kagent/sessions/ reaches the list route and the guard was unreachable. Both behaviours are now pinned by tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C2M37V7ubYwimkkS7YZ9iz
1 task
…er read Ask kagent for `limit=1` on GET /kagent/sessions/:id. The caller wants the session object and nothing else, but kagent bundles the session's stored events into that response and they dominate it: on a real 4-turn gazelle session, 591 KB of events against 261 bytes of session metadata. The events were originally going to supply per-message timestamps, since A2A messages carry none. They cannot. kagent's Go type calls each event's `data` a "JSON-serialized protocol.Message", but the decoded value is an ADK event — author, content, invocation_id, partial, timestamp, usage_metadata — with no messageId anywhere, so there is nothing to correlate with task history. 36 events yielded zero usable ids. Its invocation_id does correlate, but only per turn, which the task's own timestamp already gives. Both v0.9.9 and v0.10 parse `limit` with the same handler code, and a version that ignored it would return everything — the previous behaviour — so this can only help. Also corrects the README and doc comments, which described events as A2A messages and as the timestamp source. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C2M37V7ubYwimkkS7YZ9iz
marians
commented
Jul 29, 2026
marians
commented
Jul 29, 2026
marians
commented
Jul 29, 2026
Three review findings, all about claims that were imprecise rather than behaviour that was wrong. limit=0 does NOT mean "no events". kagent gates its LIMIT clause on opts.Limit > 0 (client_postgres.go), so zero reads as unlimited and the obvious-looking simplification — "ask for zero, we don't read them" — would silently restore the full 591 KB payload. Now stated at the call site, in the README, and in the test that guards the URL, so a failure there explains itself instead of just reporting a diff. "Both parse limit with the same handler code" was not literally true. Semantics match, but v0.9.9 parses inline in HandleGetSession while v0.10 uses the extracted eventQueryOptionsFromRequest. The claim only needed "both honour the param", so it now says that. The changeset said "Two details worth knowing" followed by three, with the limit paragraph floating between the lead-in and the bullets. Folded in as the first bullet. Also trims the *response*, not the request. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C2M37V7ubYwimkkS7YZ9iz
1 task
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
What does this PR do?
Adds the transport the upcoming Agent Platform session detail page needs. Two new proxy routes, both requiring
?installation=and a forwarded user token, both passing kagent's JSON through verbatim like the existing ones:GET /kagent/sessions/:idGET /kagent/sessions/:id/tasksNothing calls them yet; the frontend lands in follow-up PRs.
What is the effect of this change to users?
None yet — no UI consumes these routes in this PR.
Any background context you can provide?
Three decisions worth reviewing:
1. The conversation comes from
…/tasks, not from…/sessions/:id'sevents. This is what kagent's own UI renders from (ui/src/components/chat/ChatInterface.tsx→extractMessagesFromTasks), and the two payloads carry genuinely different things:events[]Task[]datais a JSON string holding an A2A message (doubly encoded)historyalready structuredstatus.state{adk,kagent}_usage_metadatacreated_atSo the frontend will fetch both: tasks for the timeline and state, events only to recover per-message timestamps — A2A messages carry none of their own. Hence two routes rather than one.
2. No
A2A-Versionheader is sent. kagent'sNegotiateA2AWireVersiontreats a missing header as the legacy v0 wire on both v0.9.9 (our pin) and v0.10, which is the shape kagent's UI consumes and therefore the best-tested one. There's a test asserting the header stays absent so it isn't added casually; opting into the v1 wire should be a deliberate migration.3. Session ids stay opaque. Real responses mix 64-character hex strings and UUIDs, so nothing validates a format — only that a path segment is present. Ids are
encodeURIComponent-ed before interpolation, with a test that a/or?in an id can't retarget the request.One consequence to be aware of: a session belonging to another user answers 404 exactly as a deleted one does, because kagent scopes the lookup by the token's user id. Both are expected outcomes for a stale deep link, so neither returns a 5xx — anything
>= 500is logged aterrorbyMiddlewareFactory.error()and forwarded to Sentry. Both cases are covered by tests asserting< 500.Backend tests: 54 → 72.
Do the docs need to be updated?
Yes, done in this PR — the plugin README gains the two routes, the events-vs-tasks comparison, the wire-version rationale, and the id-opacity note.
Should this change be mentioned in the release notes?