From f9c67b358e2d30ee8a5d6d18a70ef8c5f8607480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriele=20Cin=C3=A0?= Date: Tue, 24 Mar 2026 19:31:07 +0000 Subject: [PATCH 1/3] feat: add GET /tasks/{task_id}/query/{query_name} endpoint for Temporal workflow queries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expose Temporal workflow queries via the tasks REST API. The endpoint delegates to the existing TemporalAdapter.query_workflow method and relies on the global exception handler for error mapping: - TemporalWorkflowNotFoundError → 404 - TemporalQueryError → 400 (changed from 500 to reflect client error) - Other TemporalError subclasses → 500 Co-Authored-By: Claude Opus 4.6 (1M context) --- agentex/src/adapters/temporal/exceptions.py | 5 +++-- agentex/src/api/routes/tasks.py | 24 ++++++++++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/agentex/src/adapters/temporal/exceptions.py b/agentex/src/adapters/temporal/exceptions.py index 4618c62e..2a9e89d5 100644 --- a/agentex/src/adapters/temporal/exceptions.py +++ b/agentex/src/adapters/temporal/exceptions.py @@ -49,12 +49,13 @@ class TemporalSignalError(ServiceError): code = 500 -class TemporalQueryError(ServiceError): +class TemporalQueryError(ClientError): """ Exception raised when querying a workflow fails. + This typically means the query doesn't exist on the workflow. """ - code = 500 + code = 400 class TemporalCancelError(ServiceError): diff --git a/agentex/src/api/routes/tasks.py b/agentex/src/api/routes/tasks.py index 0cccdc5e..de036d53 100644 --- a/agentex/src/api/routes/tasks.py +++ b/agentex/src/api/routes/tasks.py @@ -1,8 +1,9 @@ -from typing import Annotated +from typing import Annotated, Any from fastapi import APIRouter, Query from fastapi.responses import StreamingResponse +from src.adapters.temporal.adapter_temporal import DTemporalAdapter from src.api.schemas.authorization_types import ( AgentexResource, AgentexResourceType, @@ -215,3 +216,24 @@ async def stream_task_events_by_name( "X-Accel-Buffering": "no", }, ) + + +@router.get( + "/{task_id}/query/{query_name}", + summary="Query Task Workflow", + description="Query a Temporal workflow associated with a task for its current state.", +) +async def query_task_workflow( + task_id: DAuthorizedId(AgentexResourceType.task, AuthorizedOperationType.read), + query_name: str, + temporal_adapter: DTemporalAdapter, +) -> dict[str, Any]: + """ + Query a Temporal workflow by task ID and query name. + Returns the query result from the workflow. + """ + result = await temporal_adapter.query_workflow( + workflow_id=task_id, + query=query_name, + ) + return {"task_id": task_id, "query": query_name, "result": result} From fc5693a389bd938f9dbbba6f5316d4a2e626f06f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriele=20Cin=C3=A0?= Date: Tue, 24 Mar 2026 19:47:16 +0000 Subject: [PATCH 2/3] fix: revert TemporalQueryError to ServiceError (500) Greptile correctly flagged that TemporalQueryError is used as a catch-all in the adapter, not just for missing query handlers. Keeping it as 500 (ServiceError) is correct since connectivity, serialization, and other server-side failures also raise this. Co-Authored-By: Claude Opus 4.6 (1M context) --- agentex/src/adapters/temporal/exceptions.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/agentex/src/adapters/temporal/exceptions.py b/agentex/src/adapters/temporal/exceptions.py index 2a9e89d5..4618c62e 100644 --- a/agentex/src/adapters/temporal/exceptions.py +++ b/agentex/src/adapters/temporal/exceptions.py @@ -49,13 +49,12 @@ class TemporalSignalError(ServiceError): code = 500 -class TemporalQueryError(ClientError): +class TemporalQueryError(ServiceError): """ Exception raised when querying a workflow fails. - This typically means the query doesn't exist on the workflow. """ - code = 400 + code = 500 class TemporalCancelError(ServiceError): From 37eee6997d6ba4e1642b5c3476c29113b391d7c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriele=20Cin=C3=A0?= Date: Tue, 24 Mar 2026 20:15:24 +0000 Subject: [PATCH 3/3] fix: don't pass None arg to Temporal query handle.query(name, None) passes None as a positional argument to the query handler, causing "takes 1 positional argument but 2 were given" for handlers that take no args. Only pass arg when it's not None. Co-Authored-By: Claude Opus 4.6 (1M context) --- agentex/src/adapters/temporal/adapter_temporal.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/agentex/src/adapters/temporal/adapter_temporal.py b/agentex/src/adapters/temporal/adapter_temporal.py index ad11fbd7..f64d0d29 100644 --- a/agentex/src/adapters/temporal/adapter_temporal.py +++ b/agentex/src/adapters/temporal/adapter_temporal.py @@ -188,7 +188,10 @@ async def query_workflow( try: handle = self.client.get_workflow_handle(workflow_id, run_id=run_id) - result = await handle.query(query, arg) + if arg is not None: + result = await handle.query(query, arg) + else: + result = await handle.query(query) logger.info(f"Queried workflow {workflow_id} with query '{query}'") return result except Exception as e: