Skip to content

fix(a2a): serve exact task reads from the persistent store - #2484

Merged
EItanya merged 6 commits into
kagent-dev:release/v0.10.xfrom
erauner12:fix/v0.10-a2a-persistent-get-task
Aug 22, 2026
Merged

EItanya merged 6 commits into
kagent-dev:release/v0.10.xfrom
erauner12:fix/v0.10-a2a-persistent-get-task

Conversation

@erauner12

@erauner12 erauner12 commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Serve exact A2A GetTask requests from the controller's authoritative, owner-scoped persistent task store on release/v0.10.x without delegating to the managed runtime.

Fixes #2464.

Problem

The controller already uses persistent storage for ListTasks, but exact GetTask continued through the passthrough runtime. A persisted task could therefore appear in ListTasks while exact retrieval returned task_not_found.

Change

storeTaskQueryHandler.GetTask now:

  • queries the persistent store with the authenticated caller identity, or an empty identity when none is available;
  • returns a shaped copy with the requested recent-history bound and retained artifacts;
  • maps caller-scoped database.ErrNotFound to A2A ErrTaskNotFound without delegation; and
  • propagates other storage failures for the A2A handler to return with internal-error semantics.

Authorization boundary

Exact GetTask remains scoped to the authenticated caller. It does not substitute ShareContext.UserID: a task-ID-only request cannot prove that the requested task belongs to the shared session. Missing identity and caller-scoped misses return task_not_found without invoking the runtime, preserving the store's authorization boundary.

Why the controller store

The same persistent store already serves task listing and receives runtime persistence callbacks. Making it fully authoritative for exact reads keeps persisted task existence consistent across the A2A query surface and avoids a redundant runtime lookup against the same database.

Tests

  • cd go && go test ./core/internal/a2a
  • cd go && go test -race ./core/internal/a2a -run '^(TestGetTask_|TestWire_GetTaskStoreErrorSemantics)'

Coverage includes persistent hits, absent identity, other-owner and not-found results, backend failures, dual-wire A2A error mapping, requested history shaping, artifact retention, non-mutation, and the share-context boundary above. Every exact-read path asserts that the delegate is not invoked.

Validation

An independent A2A client against a compatible v0.10 deployment retrieved the same persisted task through exact GetTask on both supported 0.3 and 1.0 wires without a ListTasks fallback.

This PR fixes exact reads only. Runtime identity propagation is tracked separately in #2465.

Signed-off-by: Evan Rauner <[email protected]>
(cherry picked from commit fb5da78)
(cherry picked from commit a41e24bd6f7a1d38489c9cad50a49fc2251ae069)
@github-actions github-actions Bot added the bug Something isn't working label Aug 18, 2026
@erauner12
erauner12 marked this pull request as ready for review August 18, 2026 22:03
@erauner12
erauner12 requested a review from a team as a code owner August 18, 2026 22:03
Copilot AI lite review requested due to automatic review settings August 18, 2026 22:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This pull request fixes an inconsistency in the legacy controller A2A query path by serving exact GetTask reads from the controller’s owner-scoped persistent task store (when an authenticated session principal is present) before falling back to the managed runtime, aligning GetTask behavior with the already store-backed ListTasks.

Changes:

  • Added a store-first GetTask implementation in storeTaskQueryHandler, with delegation on absent identity or database.ErrNotFound, and propagation of other store errors.
  • Extended the task-store interface and updated the Postgres client to support owner-scoped GetTask reads.
  • Added regression tests covering persistent hits (history shaping + artifact retention), owner isolation, share-context boundary behavior, fallback paths, and error propagation.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
go/core/internal/a2a/task_query_store.go Implements store-first GetTask and updates task-store interface to support exact reads from persistence.
go/core/internal/a2a/task_query_store_test.go Adds focused unit tests locking in authorization boundary and fallback/error behaviors for GetTask.
go/core/internal/a2a/a2a_handler_mux.go Updates handler-construction docs to reflect that task queries are store-backed when a task store is configured.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 60 to +64
// handler for one agent. kagent persists tasks and is their source of truth,
// so with a store ListTasks is served from it instead of proxying to the agent
// runtime (whose legacy 0.3 transport returns ErrUnsupportedOperation for it);
// every other method, GetTask included, still delegates to the passthrough
// proxy. v0 has no native tasks/list, so the legacy handler is wrapped to
// serve that method from the store too (lowercase TaskState). Without a store
// both wires keep their native behavior, including v0's method-not-found for
// tasks/list.
// so with a store GetTask and ListTasks are served from it instead of proxying
// to the agent runtime. v0 has no native tasks/list, so the legacy handler is
// wrapped to serve that method from the store too (lowercase TaskState).
// Without a store both wires keep their native behavior, including v0's
Comment on lines +33 to +35
// storeTaskQueryHandler answers GetTask and ListTasks from kagent's task
// store, which is the source of truth for persisted tasks. Every other method
// is delegated to the embedded handler unchanged.
@erauner12

Copy link
Copy Markdown
Contributor Author

Related work and forward-port note

#2483 and #2485 repair identity propagation across the managed-runtime path. This PR is independent: authoritative store-backed exact reads remain useful even when runtime forwarding is functioning.

A main-oriented prototype is available in erauner12#12. The store-first/delegate-on-miss behavior transfers directly, but main's handler-mux structure differs from the v0.10 dual native/legacy JSON-RPC wiring. A forward-port should also retain this PR's authenticated-caller and share-context regression coverage rather than deriving read authority from ShareContext.UserID.

@supreme-gg-gg supreme-gg-gg left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Adding the new GetTask method makes sense to me, but I left a question about fallback behaviour


func (h *storeTaskQueryHandler) GetTask(ctx context.Context, req *a2atype.GetTaskRequest) (*a2atype.Task, error) {
userID := callerUserID(ctx)
if userID == "" {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't understand the point of falling back to delegating to the runtime here and below in ErrNotFound. Why not treat the DB as authoritative as ListTask method below? The runtime will eventually read from the DB again which gives you the same result, this also weakens authorization boundary

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point. The delegate-on-miss behavior was meant as a compatibility path for tasks not found in the controller store, but in this setup the persistent store is already the source of truth, and the runtime resolves through that same store.

If identity is missing, or if the caller-scoped lookup returns ErrNotFound, delegating doesn’t help and can only weaken the authorization boundary.

I’ll make GetTask match ListTasks: query the persistent store with the authenticated caller and return the store result or error directly, with no delegation. I’ll update the tests, comments, and PR rationale to match.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated in 85333a83.

GetTask now treats the persistent store as authoritative: absent identity and caller-scoped database.ErrNotFound no longer delegate to the runtime, and not-found is mapped to A2A ErrTaskNotFound. The focused tests assert zero delegate calls for hits, misses, other-owner/share-context reads, absent identity, and backend failures, including 0.3 and 1.0 wire error semantics.

Validation passed:

  • go test ./core/internal/a2a
  • go test -race ./core/internal/a2a -run '^(TestGetTask_|TestWire_GetTaskStoreErrorSemantics)'

The PR description, source comments, and #2464 expectations were updated to match the authoritative-store behavior.

@erauner12 erauner12 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

left response


func (h *storeTaskQueryHandler) GetTask(ctx context.Context, req *a2atype.GetTaskRequest) (*a2atype.Task, error) {
userID := callerUserID(ctx)
if userID == "" {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point. The delegate-on-miss behavior was meant as a compatibility path for tasks not found in the controller store, but in this setup the persistent store is already the source of truth, and the runtime resolves through that same store.

If identity is missing, or if the caller-scoped lookup returns ErrNotFound, delegating doesn’t help and can only weaken the authorization boundary.

I’ll make GetTask match ListTasks: query the persistent store with the authenticated caller and return the store result or error directly, with no delegation. I’ll update the tests, comments, and PR rationale to match.

@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Aug 20, 2026
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Aug 20, 2026
}

func (h *storeTaskQueryHandler) GetTask(ctx context.Context, req *a2atype.GetTaskRequest) (*a2atype.Task, error) {
task, err := h.store.GetTask(ctx, string(req.ID), callerUserID(ctx))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can we do the userid check here like in the last version before this commit, so check the userID == "" first and return an error immediately instead of calling the DB

@erauner12 erauner12 Aug 20, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated in e8b4b1ce. GetTask now checks callerUserID(ctx) first and returns ErrTaskNotFound immediately when it is empty, before calling the persistent store. The absent-identity test now asserts that neither the store nor delegate is invoked.

Validation passed:

  • go test ./core/internal/a2a
  • go test -race ./core/internal/a2a -run '^(TestGetTask_|TestWire_GetTaskStoreErrorSemantics)'

@erauner12
erauner12 force-pushed the fix/v0.10-a2a-persistent-get-task branch from 7ebc3a5 to e8b4b1c Compare August 20, 2026 23:41
@EItanya
EItanya merged commit 03e950f into kagent-dev:release/v0.10.x Aug 22, 2026
31 checks passed
social4hyq pushed a commit to social4hyq/homebrew-core that referenced this pull request Sep 20, 2026
kagent 0.10.0

Created-by: HarmonybrewBot
Commit-by: HarmonybrewBot
Merged-by: HarmonybrewBot
Description: Created by `brew bump`

---

Created with `brew bump-formula-pr`.<details>
  <summary>release notes</summary>
  <pre><!-- Release notes generated using configuration in .github/release.yml at v0.10.0 -->

## What's Changed
### Features
* feat(streaming): configurable nginx, controller, and UI inactivity ti… by @yanivmn in kagent-dev/kagent#1973
* feat(helm): add controller service annotations by @anthonyhaussman in kagent-dev/kagent#2088
* feat: enable sharing of chat sessions by @onematchfox in kagent-dev/kagent#1935
* feat: substrate support for BYO and python runtimes for SandboxAgent CR by @jmhbh in kagent-dev/kagent#2043
* feat(substrate): bump agent substrate to 0.0.7 by @supreme-gg-gg in kagent-dev/kagent#2109
* feat(substrate): bump agent substrate to 0.0.8 by @jjamroga in kagent-dev/kagent#2140
* feat(helm/kagent): support deployment annotations by @anthonyhaussman in kagent-dev/kagent#2143
* feat: add nodeSelector support to agent helm charts by @the-mentor in kagent-dev/kagent#2145
* feat: expose icon, provider, documentation URL and version on agent A2A card by @QuentinBisson in kagent-dev/kagent#2188
* feat: add claude ACP sandbox target by @the-mentor in kagent-dev/kagent#2189
* feat(sandbox-agents): Store session state in `durableDir` volume for declarative sandbox agents by @jmhbh in kagent-dev/kagent#2171
* feat(helm): add UI HTTPRoute by @younsl in kagent-dev/kagent#2165
* feat(helm): bump bundled oauth2-proxy subchart to ~10.7.0 by @elihai-risk in kagent-dev/kagent#2254
* feat: default nodeSelector for agent deployments (--default-agent-node-selector) by @elihai-risk in kagent-dev/kagent#2259
* feat(helm): add podLabels for controller and UI pod templates by @elihai-risk in kagent-dev/kagent#2237
* feat(helm): add extraObjects for arbitrary manifests by @younsl in kagent-dev/kagent#2174
* feat(ui-test): Playwright ui tests foundation stage 0 by @cjlg-soloio in kagent-dev/kagent#2262
* feat: allow read-only shares to read A2A tasks by @QuentinBisson in kagent-dev/kagent#2191
* feat(controller): make Go (ADK) runtime agent image independently configurable by @QuentinBisson in kagent-dev/kagent#2021
* feat(helm): allow custom annotations on the default ModelConfig by @marians in kagent-dev/kagent#2201
* feat(ui-test): Playwright UI E2E suite — helpers + feature flows (stage 2) by @cjlg-soloio in kagent-dev/kagent#2264
* feat: serve A2A ListTasks from the task store by @QuentinBisson in kagent-dev/kagent#2187
* feat(models): add maxCompletionTokens for OpenAI by @anthonytwh in kagent-dev/kagent#2175
* feat(models): wire maxOutputTokens for native Gemini and Vertex by @anthonytwh in kagent-dev/kagent#2147
* feat: add envFrom support to agent deployment spec by @uncycler in kagent-dev/kagent#2321
* feat: add native AWS Bedrock Guardrails support to ModelConfig by @sanskar-singh-2403 in kagent-dev/kagent#2323
* feat(bedrock): configurable Bedrock read/connect timeout by @go-faustino in kagent-dev/kagent#2223
* feat: add deploymentAnnotations to agent deployment spec by @anthonyhaussman in kagent-dev/kagent#2359
* feat(helm/kagent): allow disabling default modelconfig by @anthonyhaussman in kagent-dev/kagent#2142
* feat(sts): send RFC 8707 resource and audience on token exchange by @QuentinBisson in kagent-dev/kagent#2106
* feat(sts): send RFC 8707 resource and audience on token exchange (Python) by @QuentinBisson in kagent-dev/kagent#2107
* Backport of 2407 (pg connection) to 0.10.x release by @supreme-gg-gg in kagent-dev/kagent#2399
* Backport 2409 (db session cleanup) to 0.10.x release by @supreme-gg-gg in kagent-dev/kagent#2403
* Backport of 2408 (s3 skills) to 0.10.x release by @supreme-gg-gg in kagent-dev/kagent#2405
* feat: configurable refresh interval for tool server controllers by @supreme-gg-gg in kagent-dev/kagent#2480
* feat: support file upload in agent chat by @supreme-gg-gg in kagent-dev/kagent#2429
### Bug Fixes
* fix(ui): chat interface overflows viewport causing input to be hidden below fold by @onematchfox in kagent-dev/kagent#2081
* fix(ui): avoid false stale chat send guard by @erauner12 in kagent-dev/kagent#2034
* fix(go-adk): extract system prompt from genai.GenerateContentConfig in Ollama and Bedrock model providers by @supreme-gg-gg in kagent-dev/kagent#2118
* fix(helm): support omitting image.registry in grafana-mcp chart by @michaelspinks in kagent-dev/kagent#2125
* fix(helm): support omitting image.registry in querydoc chart by @michaelspinks in kagent-dev/kagent#2124
* fix(security): patch CRITICAL/HIGH CVEs in golang-adk-full and app images by @jsonmp-k8 in kagent-dev/kagent#2133
* fix(go-adk): openai embeddings generation by @supreme-gg-gg in kagent-dev/kagent#2132
* fix(helm): omit empty repository segment in bundled image path by @michaelspinks in kagent-dev/kagent#2120
* fix: intermittent Postgres deadlock for concurrent memory searches by @supreme-gg-gg in kagent-dev/kagent#2146
* fix(deps): bump google-adk to 1.32.0 for Anthropic thinking blocks by @anxkhn in kagent-dev/kagent#2172
* fix: fix typos in part_converter comments by @mesutoezdil in kagent-dev/kagent#2195
* fix(adk): annotate serialize_metadata_value as str | dict by @anxkhn in kagent-dev/kagent#2173
* fix(ui): fix model name editing on the model config form by @Valyrian-Code in kagent-dev/kagent#1979
* fix: correct package comment in v1alpha2 groupversion_info by @mesutoezdil in kagent-dev/kagent#2183
* fix(crewai): unwrap Part before DataPart check so structured inputs are used by @anxkhn in kagent-dev/kagent#2231
* fix(helm): expose nodeSelector and tolerations for kagent-tools subchart by @the-mentor in kagent-dev/kagent#2213
* fix(helm): make the cluster DNS domain configurable by @shekhargit1912 in kagent-dev/kagent#2209
* fix: parse migration filenames the same way golang-migrate does by @mesutoezdil in kagent-dev/kagent#2194
* fix: build sandbox runtime outside QEMU emulation by @peterj in kagent-dev/kagent#2299
* fix: set kgateway.dev/a2a as appProtoco for BYO agents by @supreme-gg-gg in kagent-dev/kagent#2305
* fix(db): normalize agent name in memory vector search by @mesutoezdil in kagent-dev/kagent#2307
* fix(adk/bedrock): coerce nil tool-call args to empty object by @go-faustino in kagent-dev/kagent#2222
* fix: correct webhook typo in CLI help text by @tziyon31 in kagent-dev/kagent#2203
* fix(build): use AZURE_OPENAI_API_KEY env var name consistently by @mesutoezdil in kagent-dev/kagent#2310
* fix(security): scope task get, create, and delete to the owner by @mesutoezdil in kagent-dev/kagent#2265
* fix(adk): keep token refresh loop alive after a failed read by @mesutoezdil in kagent-dev/kagent#2330
* fix(acp-shim): drain child stdout during terminate to avoid teardown deadlock by @tomsen02 in kagent-dev/kagent#2338
* fix(adk): keep full session state when num_recent_events is set by @mesutoezdil in kagent-dev/kagent#2329
* fix(controller): wire up SessionSharesHandler in NewHandlers (#2363) by @onematchfox in kagent-dev/kagent#2420
* fix: ensure API key passthrough works for OpenAI embedding models by @onematchfox in kagent-dev/kagent#2494
* fix(adk): validate ask_user questions by @erauner12 in kagent-dev/kagent#2486
* Backport fix(golang-adk): resolve user identity from A2A call context to 0.10.x by @buravc in kagent-dev/kagent#2507
* fix(a2a): serve exact task reads from the persistent store by @erauner12 in kagent-dev/kagent#2484
* fix(ui): preserve post-login redirect target through `oauth2-proxy` sign-in by @onematchfox in kagent-dev/kagent#2532
### Documentation
* docs: fix duplicate descriptions and broken link in README by @mesutoezdil in kagent-dev/kagent#2068
* docs: fix typos and errors in component READMEs by @mesutoezdil in kagent-dev/kagent#2070
* docs: fix typos and list formatting in contributor docs by @mesutoezdil in kagent-dev/kagent#2069
* docs: replace outdated AutoGen reference in SECURITY.md by @mesutoezdil in kagent-dev/kagent#2110
* docs(python): fix kagent-langgraph README import to kagent.langgraph by @anxkhn in kagent-dev/kagent#2167
* docs(python): fix KAgentSession import path in kagent-openai README by @anxkhn in kagent-dev/kagent#2163
* docs: fix stale references and 

See merge request: Harmonybrew/homebrew-core!18335
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants