Skip to content

[BUG] Controller-persisted A2A tasks cannot be retrieved or resumed through the runtime #2464

Description

@erauner12

📋 Prerequisites

  • I have searched the existing issues to avoid creating a duplicate
  • By submitting this issue, you agree to follow our Code of Conduct
  • I am using the latest version of the software
  • I have tried to clear cache/cookies or used incognito mode (if ui-related)
  • I can consistently reproduce this issue

🎯 Affected Service(s)

Controller Service

🚦 Impact/Severity

Blocker

🐛 Bug Description

Tasks created and persisted through the kagent controller A2A endpoint are
visible through store-backed ListTasks and through an authenticated REST
GET /api/tasks/{id}, but they cannot be resolved through exact A2A
GetTask or used to resume a persisted input-required task.

The failure is independent of the A2A wire version. It reproduced across all
four create/resume combinations:

  • A2A 0.3 → A2A 0.3
  • A2A 0.3 → A2A 1.0
  • A2A 1.0 → A2A 0.3
  • A2A 1.0 → A2A 1.0

The apparent cause is an ownership/identity mismatch. The controller persists
the task under the authenticated initiating principal, while the runtime later
performs its REST task lookup using the runtime service account. Because task
access is owner-scoped, that lookup reports that the existing task was not
found.

This prevents A2A clients from safely resuming controller-routed human-in-the-loop
interactions. Re-sending the original request is not a safe workaround because
it creates a new task and could duplicate an already accepted action.

Scope clarification

The defects are now tracked separately:

This issue should remain open while the applicable legacy-path fix and regression
coverage are evaluated.

🔄 Steps To Reproduce

  1. Deploy kagent v0.10.0-rc1 with controller task persistence and authenticated,
    owner-scoped task access enabled.

  2. Through the controller A2A endpoint, send a message to an agent that produces
    an input-required task. Retain the returned taskId and contextId.

  3. From a fresh client process authenticated as the same initiating principal:

    • Call ListTasks/tasks/list with the retained contextId.
    • Confirm that the expected task is returned in input-required.
    • Call GetTask/tasks/get with the exact retained taskId.
  4. Observe that exact GetTask returns JSON-RPC error -32001 task_not_found,
    despite ListTasks returning the task.

  5. Send a resume message containing the same taskId and contextId.

  6. Observe that resume fails. Controller logs show the runtime attempting
    GET /api/tasks/<taskId> and receiving Task not found.

  7. Repeat with the four create/resume wire combinations:
    0.3→0.3, 0.3→1.0, 1.0→0.3, and 1.0→1.0.
    All combinations reproduce the failure.

🤔 Expected Behavior

An exact A2A GetTask request should retrieve a controller-persisted task when
the caller is authorized to access it.

A resume message containing the original taskId and contextId should resolve
that persisted input-required task and continue the same interaction,
regardless of whether the compatible 0.3 or 1.0 wire is used.

📱 Actual Behavior

Store-backed ListTasks returns the expected task, but exact A2A GetTask returns:

-32001 task_not_found

Attempting to resume the same task also fails. The controller logs show the
runtime performing:

GET /api/tasks/<redacted-task-id> → Task not found

Database inspection confirms that the task exists. An authenticated REST
request made as the initiating principal can retrieve it.

The observed ownership differs between controller-created and direct-runtime
tasks:

  • Controller-created task: owned by the initiating controller-route principal
  • Runtime lookup: authenticated as the agent runtime service account

This causes the owner-scoped lookup to return not found.

💻 Environment

  • OS and version: macOS, client workstation
  • Kubernetes version/provider: Internal environment; available privately if needed
  • Browser: Not applicable
  • Application version: kagent v0.10.0-rc1
  • Deployment mode: Helm/Kubernetes
  • Authentication mode: trusted-proxy / service-account authentication
  • Task persistence: PostgreSQL-backed controller task store
  • A2A versions tested: 0.3 and 1.0

🔧 CLI Bug Report

Not attached because the reproduction occurred in an internal Kubernetes
environment and the generated report may contain internal infrastructure or
authentication details.

The issue includes sanitized, deterministic reproduction evidence. I can
provide a reviewed and redacted bug report privately if maintainers need
additional diagnostics.

🔍 Additional Context

The store-backed task-query wrapper currently appears to override only
ListTasks. Exact GetTask, CancelTask, and SubscribeToTask continue
through the passthrough/runtime path.

The observed behavior also appears to contradict these existing source comments:

  • a2av0_tasks_list.go:
    “tasks/get is left to the underlying v0 handler, which already routes it to
    the store-backed GetTask”

  • task_query_store.go:
    “GetTask, which already resolves to the same store via the passthrough”

The legacy package remains in current main source, but the supported
current-main controller-v2 uses a different gateway path. Source presence alone
is therefore not runtime evidence for current main.

The current controller-v2/runtime identity-lifecycle defect is tracked
separately in #2465.

For the legacy controller path tracked here, serve exact GetTask requests from
the controller's authoritative, owner-scoped persistent task store before
falling back to the runtime. Persisted resume additionally depends on the
authenticated request-identity contract tracked in #2465.

Focused regression-test expectations for this issue:

  • Verify owner isolation, persistent hits, absent-identity and not-found fallback,
    non-not-found error propagation, requested history shaping, artifact retention,
    and non-mutation of stored tasks.
  • For any maintained release that ships the legacy path, cover persisted exact
    GetTask and HITL resume on both supported wires, including the four cross-wire
    resume combinations.

Temporary client-side mitigation exists only for reads: a client can perform an
exact-ID match through ListTasks after GetTask returns -32001. There is no safe
equivalent for resume because replaying the message could duplicate an accepted
operation.

I can help validate a proposed fix against the dual-wire GetTask and four-way
HITL resume matrix.

Related current-main source:

Reproduction matrix

Created with Read/resumed with ListTasks GetTask Resume
0.3 0.3 task found -32001 failed
0.3 1.0 task found -32001 failed
1.0 0.3 task found -32001 failed
1.0 1.0 task found -32001 failed

📋 Logs

A2A GetTask:
JSON-RPC error: code=-32001, class=task_not_found

Controller/runtime lookup:
GET /api/tasks/<redacted-task-id> → Task not found

Persistence verification:
ListTasks(contextId=<redacted>) → exact task found, state=input-required
REST GET /api/tasks/<redacted-task-id> as initiating principal → task found

📷 Screenshots

No response

🙋 Are you willing to contribute?

  • I am willing to submit a PR to fix this issue

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    Status
    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions