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

Aberta
#1,411 0 comentários 0 reações 2 responsáveis Ver no GitHub

Mantenedores costumam responder em até 2 dias

@rohityan já está trabalhando nisso.

Desde 1/11/2026.

Avaliação

Esta issue ainda não foi avaliada.

Descrição

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
Linguagem predominante
Python
Estrelas
2.2k
Forks
528
Merge médio
3d 15h
PRs com merge (30d)
45

Preparar o ambiente

Primeiros passos

  1. Leia a issue inteira e depois o guia de contribuição do projeto.
  2. Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
  3. Faça um fork do repositório e trabalhe em uma branch.
  4. Abra um pull request que referencie o número da issue.

Mais de a2aproject/a2a-python

Todas as issues de a2aproject/a2a-python

Issues semelhantes

Mais issues de Python

Receba novas issues na sua caixa de entrada

Um resumo curto de issues do GitHub para quem está começando.