[Bug]: taskId-only continuation still fails required contextId inference in SDK 1.3.0 (follow-up to #1206)

Ouverte
#1,411 0 commentaires 0 réactions 2 personnes assignées Voir sur GitHub

Les mainteneurs répondent en général sous 2 jours

@rohityan y travaille déjà.

Depuis le 1/11/2026.

Évaluation

Cette issue n'a pas encore été évaluée.

Description

What happened?

With a2a-sdk==1.3.0 (Python 3.13), continuing an existing task with only message.task_id assigns a fresh context_id to the executor and its status events. The canonical stored Task still has the original context, so the execution/events disagree with GetTask.

A2A v1.0.1 §3.4.3 requires: "Agents MUST infer contextId from the task" when only taskId is provided. This affects applications using contextId as a checkpoint/thread key.

Previous report and current evidence

This follows #1206, which was closed by the stale bot as not_planned on 2026-09-24, rather than resolved by a fix. Its closing message explicitly asks for a new issue if assistance is still needed. A maintainer suggested overriding _setup_active_task as a temporary workaround.

The behavior below is newly reproduced against released 1.3.0. It is distinct from #1285 (continuation stream snapshot/history) and #1409 (stale blocking INPUT_REQUIRED response). The reproducer emits Task only for the first execution and uses a streaming continuation to isolate context inference.

Minimal reproduction

Install a2a-sdk==1.3.0 and run:

import asyncio
from uuid import uuid4
from a2a.server.agent_execution import AgentExecutor
from a2a.server.context import ServerCallContext
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.tasks import InMemoryTaskStore, TaskUpdater
from a2a.types import (AgentCard, AgentCapabilities, AgentInterface, AgentSkill,
    Message, Part, SendMessageRequest, Task, TaskState, TaskStatus, GetTaskRequest)

class Agent(AgentExecutor):
    def __init__(self):
        self.contexts = []

    async def execute(self, context, queue):
        self.contexts.append(context.context_id)
        if context.current_task is None:
            await queue.enqueue_event(Task(id=context.task_id, context_id=context.context_id,
                status=TaskStatus(state=TaskState.TASK_STATE_SUBMITTED), history=[context.message]))
        updater = TaskUpdater(queue, context.task_id, context.context_id)
        if context.current_task is None:
            await updater.requires_input()
        else:
            await updater.start_work()
            await updater.complete()

    async def cancel(self, context, queue):
        await TaskUpdater(queue, context.task_id, context.context_id).cancel()

async def main():
    agent = Agent()
    card = AgentCard(name='Repro', description='Task-only context inference', version='1',
        capabilities=AgentCapabilities(streaming=True),
        default_input_modes=['text/plain'], default_output_modes=['text/plain'],
        supported_interfaces=[AgentInterface(url='http://localhost/a2a', protocol_binding='JSONRPC', protocol_version='1.0')],
        skills=[AgentSkill(id='repro', name='Repro', description='Pause and resume', tags=['repro'])])
    handler = DefaultRequestHandler(agent_executor=agent, task_store=InMemoryTaskStore(), agent_card=card)
    caller = ServerCallContext()
    def request(text, task_id=''):
        return SendMessageRequest(message=Message(message_id=str(uuid4()), role=1,
            parts=[Part(text=text)], task_id=task_id))
    try:
        first = await handler.on_message_send(request('Analyze'), caller)
        events = [event async for event in handler.on_message_send_stream(request('EU', first.id), caller)]
        canonical = await handler.on_get_task(GetTaskRequest(id=first.id), caller)
        print('stored task context:', first.context_id)
        print('continuation executor context:', agent.contexts[-1])
        print('continuation event contexts:', [event.context_id for event in events])
        print('canonical task context:', canonical.context_id)
        print('inherited:', agent.contexts[-1] == first.context_id)
        assert agent.contexts[-1] != first.context_id
        assert canonical.context_id == first.context_id
    finally:
        await handler.aclose()

asyncio.run(main())

Observed (UUIDs vary):

stored task context: 357aa773-df63-492d-a210-03f26c9eed1c
continuation executor context: 765135ce-f891-4476-a911-5397216d6a26
continuation event contexts: ['765135ce-f891-4476-a911-5397216d6a26', '765135ce-f891-4476-a911-5397216d6a26']
canonical task context: 357aa773-df63-492d-a210-03f26c9eed1c
inherited: False
Expected behavior / suggested regression

Load the existing task through the caller-scoped store before allocating a context for a task-only request. Use its context for RequestContext and every returned event; do not generate a replacement context. Preserve unknown/inaccessible TaskNotFound semantics and explicit task/context mismatch validation.

A regression should assert the executor context, all streamed event contexts, and the canonical Task context are equal for task-only continuation, in both blocking and streaming modes. Our local workaround infers the context through authorized GetTask before delegating; we keep it until an unpatched upgraded SDK passes those checks.

Code of Conduct
  • I agree to follow this project's Code of Conduct
Langage dominant
Python
Étoiles
2.2k
Forks
528
Merge moyen
3 j 15 h
PR mergées (30 j)
45

Préparer son environnement

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Autres issues de a2aproject/a2a-python

Toutes les issues de a2aproject/a2a-python

Issues similaires

Plus d'issues Python

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.