diff --git a/src/agentex/lib/cli/templates/default-claude-code/Dockerfile-uv.j2 b/src/agentex/lib/cli/templates/default-claude-code/Dockerfile-uv.j2 index 461e55c33..93d0f82d1 100644 --- a/src/agentex/lib/cli/templates/default-claude-code/Dockerfile-uv.j2 +++ b/src/agentex/lib/cli/templates/default-claude-code/Dockerfile-uv.j2 @@ -31,18 +31,18 @@ ENV UV_HTTP_TIMEOUT=1000 WORKDIR /app/{{ project_path_from_build_root }} # Copy dependency files for layer caching -COPY {{ project_path_from_build_root }}/pyproject.toml {{ project_path_from_build_root }}/uv.lock ./ +COPY {{ project_path_from_build_root }}/pyproject.toml ./ # Install dependencies (without project itself, for layer caching) RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-install-project --no-dev + uv sync --no-install-project --no-dev # Copy the project code COPY {{ project_path_from_build_root }}/project ./project # Install the project RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-dev + uv sync --no-dev ENV PATH="/app/{{ project_path_from_build_root }}/.venv/bin:$PATH" ENV PYTHONPATH=/app diff --git a/src/agentex/lib/cli/templates/default-claude-code/manifest.yaml.j2 b/src/agentex/lib/cli/templates/default-claude-code/manifest.yaml.j2 index f8217edf9..ee08bc91b 100644 --- a/src/agentex/lib/cli/templates/default-claude-code/manifest.yaml.j2 +++ b/src/agentex/lib/cli/templates/default-claude-code/manifest.yaml.j2 @@ -65,7 +65,7 @@ agent: # Description of what your agent does # Helps with documentation and discovery - description: {{ description }} + description: {{ description | tojson }} # Temporal workflow configuration # Set enabled: true to use Temporal workflows for long-running tasks diff --git a/src/agentex/lib/cli/templates/default-claude-code/project/acp.py.j2 b/src/agentex/lib/cli/templates/default-claude-code/project/acp.py.j2 index 85f98322a..42512c601 100644 --- a/src/agentex/lib/cli/templates/default-claude-code/project/acp.py.j2 +++ b/src/agentex/lib/cli/templates/default-claude-code/project/acp.py.j2 @@ -27,6 +27,7 @@ from agentex.lib.core.harness import UnifiedEmitter from agentex.lib.types.fastacp import AsyncACPConfig from agentex.lib.types.tracing import SGPTracingProcessorConfig from agentex.lib.utils.logging import make_logger +from agentex.types.text_content import TextContent from agentex.lib.sdk.fastacp.fastacp import FastACP from agentex.lib.core.tracing.tracing_processor_manager import add_tracing_processor_config @@ -134,7 +135,11 @@ async def handle_task_create(params: CreateTaskParams): async def handle_task_event_send(params: SendEventParams): """Handle a user message: spawn Claude Code locally and push events to the task stream.""" task_id = params.task.id - prompt = params.event.content.content + content = params.event.content + if not isinstance(content, TextContent): + logger.warning("Ignoring non-text event content (type=%s)", getattr(content, "type", "?")) + return + prompt = content.content logger.info("Processing message for task %s", task_id) await adk.messages.create(task_id=task_id, content=params.event.content) diff --git a/src/agentex/lib/cli/templates/default-codex/Dockerfile-uv.j2 b/src/agentex/lib/cli/templates/default-codex/Dockerfile-uv.j2 index dffe96519..02860b9b9 100644 --- a/src/agentex/lib/cli/templates/default-codex/Dockerfile-uv.j2 +++ b/src/agentex/lib/cli/templates/default-codex/Dockerfile-uv.j2 @@ -31,18 +31,18 @@ ENV UV_HTTP_TIMEOUT=1000 WORKDIR /app/{{ project_path_from_build_root }} # Copy dependency files for layer caching -COPY {{ project_path_from_build_root }}/pyproject.toml {{ project_path_from_build_root }}/uv.lock ./ +COPY {{ project_path_from_build_root }}/pyproject.toml ./ # Install dependencies (without project itself, for layer caching) RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-install-project --no-dev + uv sync --no-install-project --no-dev # Copy the project code COPY {{ project_path_from_build_root }}/project ./project # Install the project RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-dev + uv sync --no-dev ENV PATH="/app/{{ project_path_from_build_root }}/.venv/bin:$PATH" ENV PYTHONPATH=/app diff --git a/src/agentex/lib/cli/templates/default-codex/manifest.yaml.j2 b/src/agentex/lib/cli/templates/default-codex/manifest.yaml.j2 index aef2fcb5f..3c894318f 100644 --- a/src/agentex/lib/cli/templates/default-codex/manifest.yaml.j2 +++ b/src/agentex/lib/cli/templates/default-codex/manifest.yaml.j2 @@ -65,7 +65,7 @@ agent: # Description of what your agent does # Helps with documentation and discovery - description: {{ description }} + description: {{ description | tojson }} # Temporal workflow configuration # Set enabled: true to use Temporal workflows for long-running tasks diff --git a/src/agentex/lib/cli/templates/default-codex/project/acp.py.j2 b/src/agentex/lib/cli/templates/default-codex/project/acp.py.j2 index f3fd91104..f676ef137 100644 --- a/src/agentex/lib/cli/templates/default-codex/project/acp.py.j2 +++ b/src/agentex/lib/cli/templates/default-codex/project/acp.py.j2 @@ -32,6 +32,7 @@ load_dotenv() import agentex.lib.adk as adk from agentex.lib.adk import CodexTurn from agentex.lib.types.acp import SendEventParams, CancelTaskParams, CreateTaskParams +from agentex.types.text_content import TextContent from agentex.lib.core.harness import UnifiedEmitter from agentex.lib.types.fastacp import AsyncACPConfig from agentex.lib.types.tracing import SGPTracingProcessorConfig @@ -57,6 +58,20 @@ acp = FastACP.create( MODEL = os.environ.get("CODEX_MODEL", "o4-mini") +# Serialize turns per task. Two ``task/event/send`` calls for the same task can +# otherwise both read the old ``codex_thread_id`` (or ``None``), run independent +# codex turns, and race to overwrite the stored thread id — forking the session. +# A per-task lock keeps turns sequential without blocking other tasks. +_task_locks: dict[str, asyncio.Lock] = {} + + +def _task_lock(task_id: str) -> asyncio.Lock: + lock = _task_locks.get(task_id) + if lock is None: + lock = asyncio.Lock() + _task_locks[task_id] = lock + return lock + class ConversationState(BaseModel): """Per-task conversation state persisted via ``adk.state``. @@ -150,81 +165,105 @@ async def handle_task_event_send(params: SendEventParams): """Handle each user message: spawn codex, stream events, save thread ID.""" task_id = params.task.id agent_id = params.agent.id - user_message = params.event.content.content - - logger.info("Processing message for task %s", task_id) - - await adk.messages.create(task_id=task_id, content=params.event.content) - - task_state = await adk.state.get_by_task_and_agent(task_id=task_id, agent_id=agent_id) - if task_state is None: - state = ConversationState() - task_state = await adk.state.create(task_id=task_id, agent_id=agent_id, state=state) - else: - state = ConversationState.model_validate(task_state.state) - - state.turn_number += 1 - - async with adk.tracing.span( - trace_id=task_id, - task_id=task_id, - name=f"Turn {state.turn_number}", - input={"message": user_message}, - data={"__span_type__": "AGENT_WORKFLOW"}, - ) as turn_span: - start_ms = int(time.monotonic() * 1000) - process = await _spawn_codex(MODEL, thread_id=state.codex_thread_id) - - assert process.stdin is not None - process.stdin.write(user_message.encode("utf-8")) - await process.stdin.drain() - process.stdin.close() - - turn = CodexTurn( - events=_process_stdout(process), - model=MODEL, + content = params.event.content + if not isinstance(content, TextContent): + logger.warning( + "Ignoring non-text event content (type=%s) for task %s", + getattr(content, "type", "?"), + task_id, ) + return + user_message = content.content - emitter = UnifiedEmitter( - task_id=task_id, - trace_id=task_id, - parent_span_id=turn_span.id if turn_span else None, - ) + logger.info("Processing message for task %s", task_id) - # Guarantee the subprocess is reaped even if auto_send_turn raises - # (e.g. a Redis error); otherwise codex stays blocked writing to a full - # stdout pipe buffer and the OS process leaks until the server restarts. - try: - result = await emitter.auto_send_turn(turn) - finally: - if process.returncode is None: - process.kill() - await process.wait() - - # Record the real wall-clock duration AFTER streaming completes; setting - # it before the stream ran would capture only subprocess spawn overhead. - turn.duration_ms = int(time.monotonic() * 1000) - start_ms - - usage = turn.usage() - - # Persist the codex session id (public accessor; valid post-stream) so the - # next turn resumes the same session. - if turn.session_id: - state.codex_thread_id = turn.session_id - - await adk.state.update( - state_id=task_state.id, + # Serialize the whole turn (echo + the read-modify-write of + # ``codex_thread_id``) so two concurrent turns on the same task cannot fork + # the codex session or interleave their echoed messages. + lock = _task_lock(task_id) + await lock.acquire() + try: + # Echo inside the lock so this turn's message stays ordered with it. + await adk.messages.create(task_id=task_id, content=content) + + task_state = await adk.state.get_by_task_and_agent(task_id=task_id, agent_id=agent_id) + if task_state is None: + state = ConversationState() + task_state = await adk.state.create(task_id=task_id, agent_id=agent_id, state=state) + else: + state = ConversationState.model_validate(task_state.state) + + state.turn_number += 1 + + async with adk.tracing.span( + trace_id=task_id, task_id=task_id, - agent_id=agent_id, - state=state, - ) - - if turn_span: - turn_span.output = { - "final_text": result.final_text, - "model": usage.model, - } + name=f"Turn {state.turn_number}", + input={"message": user_message}, + data={"__span_type__": "AGENT_WORKFLOW"}, + ) as turn_span: + start_ms = int(time.monotonic() * 1000) + + process = await _spawn_codex(MODEL, thread_id=state.codex_thread_id) + + assert process.stdin is not None + process.stdin.write(user_message.encode("utf-8")) + await process.stdin.drain() + process.stdin.close() + + turn = CodexTurn( + events=_process_stdout(process), + model=MODEL, + ) + + emitter = UnifiedEmitter( + task_id=task_id, + trace_id=task_id, + parent_span_id=turn_span.id if turn_span else None, + ) + + # Guarantee the subprocess is reaped even if auto_send_turn raises + # (e.g. a Redis error); otherwise codex stays blocked writing to a full + # stdout pipe buffer and the OS process leaks until the server restarts. + try: + result = await emitter.auto_send_turn(turn) + finally: + if process.returncode is None: + process.kill() + await process.wait() + + # Record the real wall-clock duration AFTER streaming completes; setting + # it before the stream ran would capture only subprocess spawn overhead. + turn.duration_ms = int(time.monotonic() * 1000) - start_ms + + usage = turn.usage() + + # Persist the codex session id (public accessor; valid post-stream) so the + # next turn resumes the same session. + if turn.session_id: + state.codex_thread_id = turn.session_id + + await adk.state.update( + state_id=task_state.id, + task_id=task_id, + agent_id=agent_id, + state=state, + ) + + if turn_span: + turn_span.output = { + "final_text": result.final_text, + "model": usage.model, + } + finally: + lock.release() + # Evict the lock once released and idle (unlocked, no waiters) so + # ``_task_locks`` stays bounded even if the turn raised. There is no + # await between ``_task_lock()`` and acquiring it, so an unlocked, + # waiter-free lock has no in-flight user. + if not lock.locked() and not getattr(lock, "_waiters", None): + _task_locks.pop(task_id, None) @acp.on_task_cancel diff --git a/src/agentex/lib/cli/templates/default-langgraph/Dockerfile-uv.j2 b/src/agentex/lib/cli/templates/default-langgraph/Dockerfile-uv.j2 index 582434ac9..dd3035f7b 100644 --- a/src/agentex/lib/cli/templates/default-langgraph/Dockerfile-uv.j2 +++ b/src/agentex/lib/cli/templates/default-langgraph/Dockerfile-uv.j2 @@ -27,18 +27,18 @@ ENV UV_HTTP_TIMEOUT=1000 WORKDIR /app/{{ project_path_from_build_root }} # Copy dependency files for layer caching -COPY {{ project_path_from_build_root }}/pyproject.toml {{ project_path_from_build_root }}/uv.lock ./ +COPY {{ project_path_from_build_root }}/pyproject.toml ./ # Install dependencies (without project itself, for layer caching) RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-install-project --no-dev + uv sync --no-install-project --no-dev # Copy the project code COPY {{ project_path_from_build_root }}/project ./project # Install the project RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-dev + uv sync --no-dev ENV PATH="/app/{{ project_path_from_build_root }}/.venv/bin:$PATH" ENV PYTHONPATH=/app diff --git a/src/agentex/lib/cli/templates/default-langgraph/manifest.yaml.j2 b/src/agentex/lib/cli/templates/default-langgraph/manifest.yaml.j2 index 2d94ba41c..e6c15cf33 100644 --- a/src/agentex/lib/cli/templates/default-langgraph/manifest.yaml.j2 +++ b/src/agentex/lib/cli/templates/default-langgraph/manifest.yaml.j2 @@ -65,7 +65,7 @@ agent: # Description of what your agent does # Helps with documentation and discovery - description: {{ description }} + description: {{ description | tojson }} # Temporal workflow configuration # Set enabled: true to use Temporal workflows for long-running tasks diff --git a/src/agentex/lib/cli/templates/default-langgraph/project/acp.py.j2 b/src/agentex/lib/cli/templates/default-langgraph/project/acp.py.j2 index 750a271ad..da5d37905 100644 --- a/src/agentex/lib/cli/templates/default-langgraph/project/acp.py.j2 +++ b/src/agentex/lib/cli/templates/default-langgraph/project/acp.py.j2 @@ -22,6 +22,7 @@ from agentex.protocol.acp import SendEventParams, CancelTaskParams, CreateTaskPa from agentex.lib.types.fastacp import AsyncACPConfig from agentex.lib.types.tracing import SGPTracingProcessorConfig from agentex.lib.utils.logging import make_logger +from agentex.types.text_content import TextContent from agentex.lib.adk import LangGraphTurn from project.graph import create_graph @@ -55,7 +56,11 @@ async def handle_task_event_send(params: SendEventParams): """Handle incoming events, streaming tokens and tool calls via Redis.""" graph = await get_graph() task_id = params.task.id - user_message = params.event.content.content + content = params.event.content + if not isinstance(content, TextContent): + logger.warning("Ignoring non-text event content (type=%s)", getattr(content, "type", "?")) + return + user_message = content.content logger.info(f"Processing message for thread {task_id}") diff --git a/src/agentex/lib/cli/templates/default-openai-agents/Dockerfile-uv.j2 b/src/agentex/lib/cli/templates/default-openai-agents/Dockerfile-uv.j2 index 582434ac9..dd3035f7b 100644 --- a/src/agentex/lib/cli/templates/default-openai-agents/Dockerfile-uv.j2 +++ b/src/agentex/lib/cli/templates/default-openai-agents/Dockerfile-uv.j2 @@ -27,18 +27,18 @@ ENV UV_HTTP_TIMEOUT=1000 WORKDIR /app/{{ project_path_from_build_root }} # Copy dependency files for layer caching -COPY {{ project_path_from_build_root }}/pyproject.toml {{ project_path_from_build_root }}/uv.lock ./ +COPY {{ project_path_from_build_root }}/pyproject.toml ./ # Install dependencies (without project itself, for layer caching) RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-install-project --no-dev + uv sync --no-install-project --no-dev # Copy the project code COPY {{ project_path_from_build_root }}/project ./project # Install the project RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-dev + uv sync --no-dev ENV PATH="/app/{{ project_path_from_build_root }}/.venv/bin:$PATH" ENV PYTHONPATH=/app diff --git a/src/agentex/lib/cli/templates/default-openai-agents/manifest.yaml.j2 b/src/agentex/lib/cli/templates/default-openai-agents/manifest.yaml.j2 index deae08dee..b633518be 100644 --- a/src/agentex/lib/cli/templates/default-openai-agents/manifest.yaml.j2 +++ b/src/agentex/lib/cli/templates/default-openai-agents/manifest.yaml.j2 @@ -64,7 +64,7 @@ agent: # Description of what your agent does # Helps with documentation and discovery - description: {{ description }} + description: {{ description | tojson }} # Temporal workflow configuration # Set enabled: true to use Temporal workflows for long-running tasks diff --git a/src/agentex/lib/cli/templates/default-openai-agents/project/acp.py.j2 b/src/agentex/lib/cli/templates/default-openai-agents/project/acp.py.j2 index fd7d7c4c6..66ee31243 100644 --- a/src/agentex/lib/cli/templates/default-openai-agents/project/acp.py.j2 +++ b/src/agentex/lib/cli/templates/default-openai-agents/project/acp.py.j2 @@ -27,6 +27,7 @@ from agentex.lib.types.acp import SendEventParams, CancelTaskParams, CreateTaskP from agentex.lib.types.fastacp import AsyncACPConfig from agentex.lib.types.tracing import SGPTracingProcessorConfig from agentex.lib.utils.logging import make_logger +from agentex.types.text_content import TextContent from agentex.lib.utils.model_utils import BaseModel from agentex.lib.sdk.fastacp.fastacp import FastACP from agentex.lib.core.harness.emitter import UnifiedEmitter @@ -112,7 +113,11 @@ async def handle_task_event_send(params: SendEventParams): agent = get_agent() task_id = params.task.id agent_id = params.agent.id - user_message = params.event.content.content + content = params.event.content + if not isinstance(content, TextContent): + logger.warning("Ignoring non-text event content (type=%s)", getattr(content, "type", "?")) + return + user_message = content.content logger.info(f"Processing message for task {task_id}") diff --git a/src/agentex/lib/cli/templates/default-pydantic-ai/Dockerfile-uv.j2 b/src/agentex/lib/cli/templates/default-pydantic-ai/Dockerfile-uv.j2 index 582434ac9..dd3035f7b 100644 --- a/src/agentex/lib/cli/templates/default-pydantic-ai/Dockerfile-uv.j2 +++ b/src/agentex/lib/cli/templates/default-pydantic-ai/Dockerfile-uv.j2 @@ -27,18 +27,18 @@ ENV UV_HTTP_TIMEOUT=1000 WORKDIR /app/{{ project_path_from_build_root }} # Copy dependency files for layer caching -COPY {{ project_path_from_build_root }}/pyproject.toml {{ project_path_from_build_root }}/uv.lock ./ +COPY {{ project_path_from_build_root }}/pyproject.toml ./ # Install dependencies (without project itself, for layer caching) RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-install-project --no-dev + uv sync --no-install-project --no-dev # Copy the project code COPY {{ project_path_from_build_root }}/project ./project # Install the project RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-dev + uv sync --no-dev ENV PATH="/app/{{ project_path_from_build_root }}/.venv/bin:$PATH" ENV PYTHONPATH=/app diff --git a/src/agentex/lib/cli/templates/default-pydantic-ai/manifest.yaml.j2 b/src/agentex/lib/cli/templates/default-pydantic-ai/manifest.yaml.j2 index 2d94ba41c..e6c15cf33 100644 --- a/src/agentex/lib/cli/templates/default-pydantic-ai/manifest.yaml.j2 +++ b/src/agentex/lib/cli/templates/default-pydantic-ai/manifest.yaml.j2 @@ -65,7 +65,7 @@ agent: # Description of what your agent does # Helps with documentation and discovery - description: {{ description }} + description: {{ description | tojson }} # Temporal workflow configuration # Set enabled: true to use Temporal workflows for long-running tasks diff --git a/src/agentex/lib/cli/templates/default-pydantic-ai/project/acp.py.j2 b/src/agentex/lib/cli/templates/default-pydantic-ai/project/acp.py.j2 index 11d3ab476..245f9ec38 100644 --- a/src/agentex/lib/cli/templates/default-pydantic-ai/project/acp.py.j2 +++ b/src/agentex/lib/cli/templates/default-pydantic-ai/project/acp.py.j2 @@ -29,6 +29,7 @@ from agentex.lib.core.harness import UnifiedEmitter from agentex.lib.types.fastacp import AsyncACPConfig from agentex.lib.types.tracing import SGPTracingProcessorConfig from agentex.lib.utils.logging import make_logger +from agentex.types.text_content import TextContent from agentex.lib.utils.model_utils import BaseModel from agentex.lib.sdk.fastacp.fastacp import FastACP from agentex.lib.adk import PydanticAITurn @@ -97,7 +98,11 @@ async def handle_task_event_send(params: SendEventParams): agent = get_agent() task_id = params.task.id agent_id = params.agent.id - user_message = params.event.content.content + content = params.event.content + if not isinstance(content, TextContent): + logger.warning("Ignoring non-text event content (type=%s)", getattr(content, "type", "?")) + return + user_message = content.content logger.info(f"Processing message for task {task_id}") diff --git a/src/agentex/lib/cli/templates/default/Dockerfile-uv.j2 b/src/agentex/lib/cli/templates/default/Dockerfile-uv.j2 index 582434ac9..dd3035f7b 100644 --- a/src/agentex/lib/cli/templates/default/Dockerfile-uv.j2 +++ b/src/agentex/lib/cli/templates/default/Dockerfile-uv.j2 @@ -27,18 +27,18 @@ ENV UV_HTTP_TIMEOUT=1000 WORKDIR /app/{{ project_path_from_build_root }} # Copy dependency files for layer caching -COPY {{ project_path_from_build_root }}/pyproject.toml {{ project_path_from_build_root }}/uv.lock ./ +COPY {{ project_path_from_build_root }}/pyproject.toml ./ # Install dependencies (without project itself, for layer caching) RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-install-project --no-dev + uv sync --no-install-project --no-dev # Copy the project code COPY {{ project_path_from_build_root }}/project ./project # Install the project RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-dev + uv sync --no-dev ENV PATH="/app/{{ project_path_from_build_root }}/.venv/bin:$PATH" ENV PYTHONPATH=/app diff --git a/src/agentex/lib/cli/templates/default/manifest.yaml.j2 b/src/agentex/lib/cli/templates/default/manifest.yaml.j2 index 61c9064ed..c78ce1f44 100644 --- a/src/agentex/lib/cli/templates/default/manifest.yaml.j2 +++ b/src/agentex/lib/cli/templates/default/manifest.yaml.j2 @@ -65,7 +65,7 @@ agent: # Description of what your agent does # Helps with documentation and discovery - description: {{ description }} + description: {{ description | tojson }} # Temporal workflow configuration # Set enabled: true to use Temporal workflows for long-running tasks diff --git a/src/agentex/lib/cli/templates/sync-claude-code/Dockerfile-uv.j2 b/src/agentex/lib/cli/templates/sync-claude-code/Dockerfile-uv.j2 index 461e55c33..93d0f82d1 100644 --- a/src/agentex/lib/cli/templates/sync-claude-code/Dockerfile-uv.j2 +++ b/src/agentex/lib/cli/templates/sync-claude-code/Dockerfile-uv.j2 @@ -31,18 +31,18 @@ ENV UV_HTTP_TIMEOUT=1000 WORKDIR /app/{{ project_path_from_build_root }} # Copy dependency files for layer caching -COPY {{ project_path_from_build_root }}/pyproject.toml {{ project_path_from_build_root }}/uv.lock ./ +COPY {{ project_path_from_build_root }}/pyproject.toml ./ # Install dependencies (without project itself, for layer caching) RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-install-project --no-dev + uv sync --no-install-project --no-dev # Copy the project code COPY {{ project_path_from_build_root }}/project ./project # Install the project RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-dev + uv sync --no-dev ENV PATH="/app/{{ project_path_from_build_root }}/.venv/bin:$PATH" ENV PYTHONPATH=/app diff --git a/src/agentex/lib/cli/templates/sync-claude-code/manifest.yaml.j2 b/src/agentex/lib/cli/templates/sync-claude-code/manifest.yaml.j2 index 429696a14..4432d1a33 100644 --- a/src/agentex/lib/cli/templates/sync-claude-code/manifest.yaml.j2 +++ b/src/agentex/lib/cli/templates/sync-claude-code/manifest.yaml.j2 @@ -64,7 +64,7 @@ agent: # Description of what your agent does # Helps with documentation and discovery - description: {{ description }} + description: {{ description | tojson }} # Temporal workflow configuration # Set enabled: true to use Temporal workflows for long-running tasks diff --git a/src/agentex/lib/cli/templates/sync-claude-code/project/acp.py.j2 b/src/agentex/lib/cli/templates/sync-claude-code/project/acp.py.j2 index c739a188b..33a89a51e 100644 --- a/src/agentex/lib/cli/templates/sync-claude-code/project/acp.py.j2 +++ b/src/agentex/lib/cli/templates/sync-claude-code/project/acp.py.j2 @@ -27,6 +27,7 @@ from agentex.lib.types.acp import SendMessageParams from agentex.lib.core.harness import UnifiedEmitter from agentex.lib.types.tracing import SGPTracingProcessorConfig from agentex.lib.utils.logging import make_logger +from agentex.types.text_content import TextContent from agentex.lib.sdk.fastacp.fastacp import FastACP from agentex.types.task_message_update import TaskMessageUpdate from agentex.types.task_message_content import TaskMessageContent @@ -130,7 +131,11 @@ async def handle_message_send( ) -> TaskMessageContent | list[TaskMessageContent] | AsyncGenerator[TaskMessageUpdate, None]: """Handle an incoming message: run Claude Code locally and stream events.""" task_id = params.task.id - prompt = params.content.content + content = params.content + if not isinstance(content, TextContent): + logger.warning("Ignoring non-text message content (type=%s)", getattr(content, "type", "?")) + return + prompt = content.content logger.info("Processing message for task %s", task_id) async with adk.tracing.span( diff --git a/src/agentex/lib/cli/templates/sync-codex/Dockerfile-uv.j2 b/src/agentex/lib/cli/templates/sync-codex/Dockerfile-uv.j2 index dffe96519..02860b9b9 100644 --- a/src/agentex/lib/cli/templates/sync-codex/Dockerfile-uv.j2 +++ b/src/agentex/lib/cli/templates/sync-codex/Dockerfile-uv.j2 @@ -31,18 +31,18 @@ ENV UV_HTTP_TIMEOUT=1000 WORKDIR /app/{{ project_path_from_build_root }} # Copy dependency files for layer caching -COPY {{ project_path_from_build_root }}/pyproject.toml {{ project_path_from_build_root }}/uv.lock ./ +COPY {{ project_path_from_build_root }}/pyproject.toml ./ # Install dependencies (without project itself, for layer caching) RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-install-project --no-dev + uv sync --no-install-project --no-dev # Copy the project code COPY {{ project_path_from_build_root }}/project ./project # Install the project RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-dev + uv sync --no-dev ENV PATH="/app/{{ project_path_from_build_root }}/.venv/bin:$PATH" ENV PYTHONPATH=/app diff --git a/src/agentex/lib/cli/templates/sync-codex/manifest.yaml.j2 b/src/agentex/lib/cli/templates/sync-codex/manifest.yaml.j2 index 8810f6175..4e3cc0c3a 100644 --- a/src/agentex/lib/cli/templates/sync-codex/manifest.yaml.j2 +++ b/src/agentex/lib/cli/templates/sync-codex/manifest.yaml.j2 @@ -64,7 +64,7 @@ agent: # Description of what your agent does # Helps with documentation and discovery - description: {{ description }} + description: {{ description | tojson }} # Temporal workflow configuration # Set enabled: true to use Temporal workflows for long-running tasks diff --git a/src/agentex/lib/cli/templates/sync-codex/project/acp.py.j2 b/src/agentex/lib/cli/templates/sync-codex/project/acp.py.j2 index 721721d41..0bc5d66a7 100644 --- a/src/agentex/lib/cli/templates/sync-codex/project/acp.py.j2 +++ b/src/agentex/lib/cli/templates/sync-codex/project/acp.py.j2 @@ -36,6 +36,7 @@ from agentex.lib.types.acp import SendMessageParams from agentex.lib.core.harness import UnifiedEmitter from agentex.lib.types.tracing import SGPTracingProcessorConfig from agentex.lib.utils.logging import make_logger +from agentex.types.text_content import TextContent from agentex.lib.sdk.fastacp.fastacp import FastACP from agentex.types.task_message_update import TaskMessageUpdate from agentex.types.task_message_content import TaskMessageContent @@ -125,7 +126,11 @@ async def handle_message_send( ) -> TaskMessageContent | list[TaskMessageContent] | AsyncGenerator[TaskMessageUpdate, None]: """Handle each message by running ``codex exec`` locally and streaming events.""" task_id = params.task.id - user_message = params.content.content + content = params.content + if not isinstance(content, TextContent): + logger.warning("Ignoring non-text message content (type=%s)", getattr(content, "type", "?")) + return + user_message = content.content logger.info("Processing message for task %s", task_id) start_ms = int(time.monotonic() * 1000) diff --git a/src/agentex/lib/cli/templates/sync-langgraph/Dockerfile-uv.j2 b/src/agentex/lib/cli/templates/sync-langgraph/Dockerfile-uv.j2 index 582434ac9..dd3035f7b 100644 --- a/src/agentex/lib/cli/templates/sync-langgraph/Dockerfile-uv.j2 +++ b/src/agentex/lib/cli/templates/sync-langgraph/Dockerfile-uv.j2 @@ -27,18 +27,18 @@ ENV UV_HTTP_TIMEOUT=1000 WORKDIR /app/{{ project_path_from_build_root }} # Copy dependency files for layer caching -COPY {{ project_path_from_build_root }}/pyproject.toml {{ project_path_from_build_root }}/uv.lock ./ +COPY {{ project_path_from_build_root }}/pyproject.toml ./ # Install dependencies (without project itself, for layer caching) RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-install-project --no-dev + uv sync --no-install-project --no-dev # Copy the project code COPY {{ project_path_from_build_root }}/project ./project # Install the project RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-dev + uv sync --no-dev ENV PATH="/app/{{ project_path_from_build_root }}/.venv/bin:$PATH" ENV PYTHONPATH=/app diff --git a/src/agentex/lib/cli/templates/sync-langgraph/manifest.yaml.j2 b/src/agentex/lib/cli/templates/sync-langgraph/manifest.yaml.j2 index 7bf2cb355..33f2d7b67 100644 --- a/src/agentex/lib/cli/templates/sync-langgraph/manifest.yaml.j2 +++ b/src/agentex/lib/cli/templates/sync-langgraph/manifest.yaml.j2 @@ -64,7 +64,7 @@ agent: # Description of what your agent does # Helps with documentation and discovery - description: {{ description }} + description: {{ description | tojson }} # Temporal workflow configuration # Set enabled: true to use Temporal workflows for long-running tasks diff --git a/src/agentex/lib/cli/templates/sync-langgraph/project/acp.py.j2 b/src/agentex/lib/cli/templates/sync-langgraph/project/acp.py.j2 index c6814b9c4..32d261093 100644 --- a/src/agentex/lib/cli/templates/sync-langgraph/project/acp.py.j2 +++ b/src/agentex/lib/cli/templates/sync-langgraph/project/acp.py.j2 @@ -14,6 +14,7 @@ from agentex.lib.sdk.fastacp.fastacp import FastACP from agentex.protocol.acp import SendMessageParams from agentex.lib.types.tracing import SGPTracingProcessorConfig from agentex.lib.utils.logging import make_logger +from agentex.types.text_content import TextContent from agentex.lib.adk import LangGraphTurn from agentex.types.task_message_content import TaskMessageContent from agentex.types.task_message_delta import TextDelta @@ -63,7 +64,11 @@ async def handle_message_send( graph = await get_graph() thread_id = params.task.id - user_message = params.content.content + content = params.content + if not isinstance(content, TextContent): + logger.warning("Ignoring non-text message content (type=%s)", getattr(content, "type", "?")) + return + user_message = content.content logger.info(f"Processing message for thread {thread_id}") diff --git a/src/agentex/lib/cli/templates/sync-openai-agents-local-sandbox/Dockerfile-uv.j2 b/src/agentex/lib/cli/templates/sync-openai-agents-local-sandbox/Dockerfile-uv.j2 index 582434ac9..dd3035f7b 100644 --- a/src/agentex/lib/cli/templates/sync-openai-agents-local-sandbox/Dockerfile-uv.j2 +++ b/src/agentex/lib/cli/templates/sync-openai-agents-local-sandbox/Dockerfile-uv.j2 @@ -27,18 +27,18 @@ ENV UV_HTTP_TIMEOUT=1000 WORKDIR /app/{{ project_path_from_build_root }} # Copy dependency files for layer caching -COPY {{ project_path_from_build_root }}/pyproject.toml {{ project_path_from_build_root }}/uv.lock ./ +COPY {{ project_path_from_build_root }}/pyproject.toml ./ # Install dependencies (without project itself, for layer caching) RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-install-project --no-dev + uv sync --no-install-project --no-dev # Copy the project code COPY {{ project_path_from_build_root }}/project ./project # Install the project RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-dev + uv sync --no-dev ENV PATH="/app/{{ project_path_from_build_root }}/.venv/bin:$PATH" ENV PYTHONPATH=/app diff --git a/src/agentex/lib/cli/templates/sync-openai-agents-local-sandbox/README.md.j2 b/src/agentex/lib/cli/templates/sync-openai-agents-local-sandbox/README.md.j2 index 9416f2477..c49f0f56f 100644 --- a/src/agentex/lib/cli/templates/sync-openai-agents-local-sandbox/README.md.j2 +++ b/src/agentex/lib/cli/templates/sync-openai-agents-local-sandbox/README.md.j2 @@ -262,7 +262,10 @@ Add sophisticated response generation: @acp.on_message_send async def handle_message_send(params: SendMessageParams): # Analyze input - user_message = params.content.content + content = params.content + if not isinstance(content, TextContent): + return TextContent(author="agent", content="Sorry, I can only handle text messages right now.") + user_message = content.content # Generate response response = await generate_intelligent_response(user_message) diff --git a/src/agentex/lib/cli/templates/sync-openai-agents-local-sandbox/manifest.yaml.j2 b/src/agentex/lib/cli/templates/sync-openai-agents-local-sandbox/manifest.yaml.j2 index bc2910f2a..6377d01cd 100644 --- a/src/agentex/lib/cli/templates/sync-openai-agents-local-sandbox/manifest.yaml.j2 +++ b/src/agentex/lib/cli/templates/sync-openai-agents-local-sandbox/manifest.yaml.j2 @@ -64,7 +64,7 @@ agent: # Description of what your agent does # Helps with documentation and discovery - description: {{ description }} + description: {{ description | tojson }} # Temporal workflow configuration # Set enabled: true to use Temporal workflows for long-running tasks diff --git a/src/agentex/lib/cli/templates/sync-openai-agents-local-sandbox/project/acp.py.j2 b/src/agentex/lib/cli/templates/sync-openai-agents-local-sandbox/project/acp.py.j2 index e394e14c2..14af98351 100644 --- a/src/agentex/lib/cli/templates/sync-openai-agents-local-sandbox/project/acp.py.j2 +++ b/src/agentex/lib/cli/templates/sync-openai-agents-local-sandbox/project/acp.py.j2 @@ -63,7 +63,11 @@ async def handle_message_send( ) -> TaskMessageContent: """Handle incoming messages by running the local-sandbox agent.""" task_id = params.task.id - user_message = params.content.content + content = params.content + if not isinstance(content, TextContent): + logger.warning("Ignoring non-text message content (type=%s)", getattr(content, "type", "?")) + return TextContent(author="agent", content="Sorry, I can only handle text messages right now.") + user_message = content.content logger.info(f"Processing message for task {task_id}") async with adk.tracing.span( diff --git a/src/agentex/lib/cli/templates/sync-openai-agents/Dockerfile-uv.j2 b/src/agentex/lib/cli/templates/sync-openai-agents/Dockerfile-uv.j2 index 582434ac9..dd3035f7b 100644 --- a/src/agentex/lib/cli/templates/sync-openai-agents/Dockerfile-uv.j2 +++ b/src/agentex/lib/cli/templates/sync-openai-agents/Dockerfile-uv.j2 @@ -27,18 +27,18 @@ ENV UV_HTTP_TIMEOUT=1000 WORKDIR /app/{{ project_path_from_build_root }} # Copy dependency files for layer caching -COPY {{ project_path_from_build_root }}/pyproject.toml {{ project_path_from_build_root }}/uv.lock ./ +COPY {{ project_path_from_build_root }}/pyproject.toml ./ # Install dependencies (without project itself, for layer caching) RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-install-project --no-dev + uv sync --no-install-project --no-dev # Copy the project code COPY {{ project_path_from_build_root }}/project ./project # Install the project RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-dev + uv sync --no-dev ENV PATH="/app/{{ project_path_from_build_root }}/.venv/bin:$PATH" ENV PYTHONPATH=/app diff --git a/src/agentex/lib/cli/templates/sync-openai-agents/README.md.j2 b/src/agentex/lib/cli/templates/sync-openai-agents/README.md.j2 index a8ad10799..7711969cd 100644 --- a/src/agentex/lib/cli/templates/sync-openai-agents/README.md.j2 +++ b/src/agentex/lib/cli/templates/sync-openai-agents/README.md.j2 @@ -251,7 +251,10 @@ Add sophisticated response generation: @acp.on_message_send async def handle_message_send(params: SendMessageParams): # Analyze input - user_message = params.content.content + content = params.content + if not isinstance(content, TextContent): + return TextContent(author="agent", content="Sorry, I can only handle text messages right now.") + user_message = content.content # Generate response response = await generate_intelligent_response(user_message) diff --git a/src/agentex/lib/cli/templates/sync-openai-agents/manifest.yaml.j2 b/src/agentex/lib/cli/templates/sync-openai-agents/manifest.yaml.j2 index 965769233..875fcc5e0 100644 --- a/src/agentex/lib/cli/templates/sync-openai-agents/manifest.yaml.j2 +++ b/src/agentex/lib/cli/templates/sync-openai-agents/manifest.yaml.j2 @@ -64,7 +64,7 @@ agent: # Description of what your agent does # Helps with documentation and discovery - description: {{ description }} + description: {{ description | tojson }} # Temporal workflow configuration # Set enabled: true to use Temporal workflows for long-running tasks diff --git a/src/agentex/lib/cli/templates/sync-openai-agents/project/acp.py.j2 b/src/agentex/lib/cli/templates/sync-openai-agents/project/acp.py.j2 index 4e2517838..41029f2ce 100644 --- a/src/agentex/lib/cli/templates/sync-openai-agents/project/acp.py.j2 +++ b/src/agentex/lib/cli/templates/sync-openai-agents/project/acp.py.j2 @@ -98,7 +98,12 @@ async def handle_message_send( ) return - user_prompt = params.content.content + content = params.content + if not isinstance(content, TextContent): + logger.warning("Ignoring non-text message content (type=%s)", getattr(content, "type", "?")) + return + + user_prompt = content.content # Retrieve the task state. Each event is handled as a new turn, so we need to get the state for the current turn. task_state = await adk.state.get_by_task_and_agent(task_id=params.task.id, agent_id=params.agent.id) diff --git a/src/agentex/lib/cli/templates/sync-pydantic-ai/Dockerfile-uv.j2 b/src/agentex/lib/cli/templates/sync-pydantic-ai/Dockerfile-uv.j2 index 582434ac9..dd3035f7b 100644 --- a/src/agentex/lib/cli/templates/sync-pydantic-ai/Dockerfile-uv.j2 +++ b/src/agentex/lib/cli/templates/sync-pydantic-ai/Dockerfile-uv.j2 @@ -27,18 +27,18 @@ ENV UV_HTTP_TIMEOUT=1000 WORKDIR /app/{{ project_path_from_build_root }} # Copy dependency files for layer caching -COPY {{ project_path_from_build_root }}/pyproject.toml {{ project_path_from_build_root }}/uv.lock ./ +COPY {{ project_path_from_build_root }}/pyproject.toml ./ # Install dependencies (without project itself, for layer caching) RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-install-project --no-dev + uv sync --no-install-project --no-dev # Copy the project code COPY {{ project_path_from_build_root }}/project ./project # Install the project RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-dev + uv sync --no-dev ENV PATH="/app/{{ project_path_from_build_root }}/.venv/bin:$PATH" ENV PYTHONPATH=/app diff --git a/src/agentex/lib/cli/templates/sync-pydantic-ai/README.md.j2 b/src/agentex/lib/cli/templates/sync-pydantic-ai/README.md.j2 index a8ad10799..7711969cd 100644 --- a/src/agentex/lib/cli/templates/sync-pydantic-ai/README.md.j2 +++ b/src/agentex/lib/cli/templates/sync-pydantic-ai/README.md.j2 @@ -251,7 +251,10 @@ Add sophisticated response generation: @acp.on_message_send async def handle_message_send(params: SendMessageParams): # Analyze input - user_message = params.content.content + content = params.content + if not isinstance(content, TextContent): + return TextContent(author="agent", content="Sorry, I can only handle text messages right now.") + user_message = content.content # Generate response response = await generate_intelligent_response(user_message) diff --git a/src/agentex/lib/cli/templates/sync-pydantic-ai/manifest.yaml.j2 b/src/agentex/lib/cli/templates/sync-pydantic-ai/manifest.yaml.j2 index 965769233..875fcc5e0 100644 --- a/src/agentex/lib/cli/templates/sync-pydantic-ai/manifest.yaml.j2 +++ b/src/agentex/lib/cli/templates/sync-pydantic-ai/manifest.yaml.j2 @@ -64,7 +64,7 @@ agent: # Description of what your agent does # Helps with documentation and discovery - description: {{ description }} + description: {{ description | tojson }} # Temporal workflow configuration # Set enabled: true to use Temporal workflows for long-running tasks diff --git a/src/agentex/lib/cli/templates/sync-pydantic-ai/project/acp.py.j2 b/src/agentex/lib/cli/templates/sync-pydantic-ai/project/acp.py.j2 index 061ae0e08..1a3c6f0a9 100644 --- a/src/agentex/lib/cli/templates/sync-pydantic-ai/project/acp.py.j2 +++ b/src/agentex/lib/cli/templates/sync-pydantic-ai/project/acp.py.j2 @@ -22,6 +22,7 @@ from agentex.protocol.acp import SendMessageParams from agentex.lib.core.harness import UnifiedEmitter from agentex.lib.types.tracing import SGPTracingProcessorConfig from agentex.lib.utils.logging import make_logger +from agentex.types.text_content import TextContent from agentex.lib.sdk.fastacp.fastacp import FastACP from agentex.types.task_message_update import TaskMessageUpdate from agentex.types.task_message_content import TaskMessageContent @@ -67,7 +68,12 @@ async def handle_message_send( agent = get_agent() task_id = params.task.id - user_message = params.content.content + content = params.content + if not isinstance(content, TextContent): + logger.warning("Ignoring non-text message content (type=%s)", getattr(content, "type", "?")) + return + + user_message = content.content logger.info(f"Processing message for task {task_id}") # Open a per-message turn span. Tool calls below nest underneath this diff --git a/src/agentex/lib/cli/templates/sync/Dockerfile-uv.j2 b/src/agentex/lib/cli/templates/sync/Dockerfile-uv.j2 index 582434ac9..dd3035f7b 100644 --- a/src/agentex/lib/cli/templates/sync/Dockerfile-uv.j2 +++ b/src/agentex/lib/cli/templates/sync/Dockerfile-uv.j2 @@ -27,18 +27,18 @@ ENV UV_HTTP_TIMEOUT=1000 WORKDIR /app/{{ project_path_from_build_root }} # Copy dependency files for layer caching -COPY {{ project_path_from_build_root }}/pyproject.toml {{ project_path_from_build_root }}/uv.lock ./ +COPY {{ project_path_from_build_root }}/pyproject.toml ./ # Install dependencies (without project itself, for layer caching) RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-install-project --no-dev + uv sync --no-install-project --no-dev # Copy the project code COPY {{ project_path_from_build_root }}/project ./project # Install the project RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-dev + uv sync --no-dev ENV PATH="/app/{{ project_path_from_build_root }}/.venv/bin:$PATH" ENV PYTHONPATH=/app diff --git a/src/agentex/lib/cli/templates/sync/README.md.j2 b/src/agentex/lib/cli/templates/sync/README.md.j2 index a8ad10799..7711969cd 100644 --- a/src/agentex/lib/cli/templates/sync/README.md.j2 +++ b/src/agentex/lib/cli/templates/sync/README.md.j2 @@ -251,7 +251,10 @@ Add sophisticated response generation: @acp.on_message_send async def handle_message_send(params: SendMessageParams): # Analyze input - user_message = params.content.content + content = params.content + if not isinstance(content, TextContent): + return TextContent(author="agent", content="Sorry, I can only handle text messages right now.") + user_message = content.content # Generate response response = await generate_intelligent_response(user_message) diff --git a/src/agentex/lib/cli/templates/sync/manifest.yaml.j2 b/src/agentex/lib/cli/templates/sync/manifest.yaml.j2 index 965769233..875fcc5e0 100644 --- a/src/agentex/lib/cli/templates/sync/manifest.yaml.j2 +++ b/src/agentex/lib/cli/templates/sync/manifest.yaml.j2 @@ -64,7 +64,7 @@ agent: # Description of what your agent does # Helps with documentation and discovery - description: {{ description }} + description: {{ description | tojson }} # Temporal workflow configuration # Set enabled: true to use Temporal workflows for long-running tasks diff --git a/src/agentex/lib/cli/templates/sync/project/acp.py.j2 b/src/agentex/lib/cli/templates/sync/project/acp.py.j2 index ce5069a4c..d7d6f51d2 100644 --- a/src/agentex/lib/cli/templates/sync/project/acp.py.j2 +++ b/src/agentex/lib/cli/templates/sync/project/acp.py.j2 @@ -20,7 +20,13 @@ async def handle_message_send( params: SendMessageParams ) -> TaskMessageContent | list[TaskMessageContent] | AsyncGenerator[TaskMessageUpdate, None]: """Default message handler with streaming support""" + content = params.content + if not isinstance(content, TextContent): + return TextContent( + author="agent", + content="Sorry, I can only handle text messages right now.", + ) return TextContent( author="agent", - content=f"Hello! I've received your message. Here's a generic response, but in future tutorials we'll see how you can get me to intelligently respond to your message. This is what I heard you say: {params.content.content}", + content=f"Hello! I've received your message. Here's a generic response, but in future tutorials we'll see how you can get me to intelligently respond to your message. This is what I heard you say: {content.content}", ) \ No newline at end of file diff --git a/src/agentex/lib/cli/templates/temporal-claude-code/Dockerfile-uv.j2 b/src/agentex/lib/cli/templates/temporal-claude-code/Dockerfile-uv.j2 index eb93d0aeb..f8746c573 100644 --- a/src/agentex/lib/cli/templates/temporal-claude-code/Dockerfile-uv.j2 +++ b/src/agentex/lib/cli/templates/temporal-claude-code/Dockerfile-uv.j2 @@ -39,18 +39,18 @@ ENV UV_HTTP_TIMEOUT=1000 WORKDIR /app/{{ project_path_from_build_root }} # Copy dependency files for layer caching -COPY {{ project_path_from_build_root }}/pyproject.toml {{ project_path_from_build_root }}/uv.lock ./ +COPY {{ project_path_from_build_root }}/pyproject.toml ./ # Install dependencies (without project itself, for layer caching) RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-install-project --no-dev + uv sync --no-install-project --no-dev # Copy the project code COPY {{ project_path_from_build_root }}/project ./project # Install the project RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-dev + uv sync --no-dev ENV PATH="/app/{{ project_path_from_build_root }}/.venv/bin:$PATH" diff --git a/src/agentex/lib/cli/templates/temporal-claude-code/manifest.yaml.j2 b/src/agentex/lib/cli/templates/temporal-claude-code/manifest.yaml.j2 index 0842d7fa3..9aa2b2b2f 100644 --- a/src/agentex/lib/cli/templates/temporal-claude-code/manifest.yaml.j2 +++ b/src/agentex/lib/cli/templates/temporal-claude-code/manifest.yaml.j2 @@ -73,7 +73,7 @@ agent: # Description of what your agent does # Helps with documentation and discovery - description: "{{ description }}" + description: {{ description | tojson }} # Temporal workflow configuration # This enables your agent to run as a Temporal workflow for long-running tasks diff --git a/src/agentex/lib/cli/templates/temporal-claude-code/project/workflow.py.j2 b/src/agentex/lib/cli/templates/temporal-claude-code/project/workflow.py.j2 index e0c3a46e5..8191ad80f 100644 --- a/src/agentex/lib/cli/templates/temporal-claude-code/project/workflow.py.j2 +++ b/src/agentex/lib/cli/templates/temporal-claude-code/project/workflow.py.j2 @@ -80,9 +80,13 @@ class {{ workflow_class }}(BaseWorkflow): async def on_task_event_send(self, params: SendEventParams) -> None: """Handle a user message: spawn Claude Code and push events to the task stream.""" async with self._turn_lock: - self._turn_number += 1 task_id = params.task.id - prompt = params.event.content.content + content = params.event.content + if not isinstance(content, TextContent): + logger.warning("Ignoring non-text event content (type=%s)", getattr(content, "type", "?")) + return + self._turn_number += 1 + prompt = content.content logger.info("Turn %d for task %s", self._turn_number, task_id) await adk.messages.create(task_id=task_id, content=params.event.content) diff --git a/src/agentex/lib/cli/templates/temporal-codex/Dockerfile-uv.j2 b/src/agentex/lib/cli/templates/temporal-codex/Dockerfile-uv.j2 index c72c7144c..7e31387fa 100644 --- a/src/agentex/lib/cli/templates/temporal-codex/Dockerfile-uv.j2 +++ b/src/agentex/lib/cli/templates/temporal-codex/Dockerfile-uv.j2 @@ -39,18 +39,18 @@ ENV UV_HTTP_TIMEOUT=1000 WORKDIR /app/{{ project_path_from_build_root }} # Copy dependency files for layer caching -COPY {{ project_path_from_build_root }}/pyproject.toml {{ project_path_from_build_root }}/uv.lock ./ +COPY {{ project_path_from_build_root }}/pyproject.toml ./ # Install dependencies (without project itself, for layer caching) RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-install-project --no-dev + uv sync --no-install-project --no-dev # Copy the project code COPY {{ project_path_from_build_root }}/project ./project # Install the project RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-dev + uv sync --no-dev ENV PATH="/app/{{ project_path_from_build_root }}/.venv/bin:$PATH" diff --git a/src/agentex/lib/cli/templates/temporal-codex/manifest.yaml.j2 b/src/agentex/lib/cli/templates/temporal-codex/manifest.yaml.j2 index d2a3df5c1..067567059 100644 --- a/src/agentex/lib/cli/templates/temporal-codex/manifest.yaml.j2 +++ b/src/agentex/lib/cli/templates/temporal-codex/manifest.yaml.j2 @@ -73,7 +73,7 @@ agent: # Description of what your agent does # Helps with documentation and discovery - description: "{{ description }}" + description: {{ description | tojson }} # Temporal workflow configuration # This enables your agent to run as a Temporal workflow for long-running tasks diff --git a/src/agentex/lib/cli/templates/temporal-codex/project/workflow.py.j2 b/src/agentex/lib/cli/templates/temporal-codex/project/workflow.py.j2 index 39325ed60..1004ebfb8 100644 --- a/src/agentex/lib/cli/templates/temporal-codex/project/workflow.py.j2 +++ b/src/agentex/lib/cli/templates/temporal-codex/project/workflow.py.j2 @@ -84,11 +84,16 @@ class {{ workflow_class }}(BaseWorkflow): """Handle a new user message: spawn codex, stream events via UnifiedEmitter.""" logger.info("Received task event: %s", params.task.id) async with self._turn_lock: + content = params.event.content + if not isinstance(content, TextContent): + logger.warning("Ignoring non-text event content (type=%s)", getattr(content, "type", "?")) + return + self._turn_number += 1 await adk.messages.create(task_id=params.task.id, content=params.event.content) - user_message = params.event.content.content + user_message = content.content async with adk.tracing.span( trace_id=params.task.id, diff --git a/src/agentex/lib/cli/templates/temporal-langgraph/Dockerfile-uv.j2 b/src/agentex/lib/cli/templates/temporal-langgraph/Dockerfile-uv.j2 index 2a3f1108b..6746869df 100644 --- a/src/agentex/lib/cli/templates/temporal-langgraph/Dockerfile-uv.j2 +++ b/src/agentex/lib/cli/templates/temporal-langgraph/Dockerfile-uv.j2 @@ -33,18 +33,18 @@ ENV UV_HTTP_TIMEOUT=1000 WORKDIR /app/{{ project_path_from_build_root }} # Copy dependency files for layer caching -COPY {{ project_path_from_build_root }}/pyproject.toml {{ project_path_from_build_root }}/uv.lock ./ +COPY {{ project_path_from_build_root }}/pyproject.toml ./ # Install dependencies (without project itself, for layer caching) RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-install-project --no-dev + uv sync --no-install-project --no-dev # Copy the project code COPY {{ project_path_from_build_root }}/project ./project # Install the project RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-dev + uv sync --no-dev ENV PATH="/app/{{ project_path_from_build_root }}/.venv/bin:$PATH" diff --git a/src/agentex/lib/cli/templates/temporal-langgraph/manifest.yaml.j2 b/src/agentex/lib/cli/templates/temporal-langgraph/manifest.yaml.j2 index 18cffd54a..b9216929f 100644 --- a/src/agentex/lib/cli/templates/temporal-langgraph/manifest.yaml.j2 +++ b/src/agentex/lib/cli/templates/temporal-langgraph/manifest.yaml.j2 @@ -73,7 +73,7 @@ agent: # Description of what your agent does # Helps with documentation and discovery - description: "{{ description }}" + description: {{ description | tojson }} # Temporal workflow configuration # This enables your agent to run as a Temporal workflow for long-running tasks diff --git a/src/agentex/lib/cli/templates/temporal-langgraph/project/workflow.py.j2 b/src/agentex/lib/cli/templates/temporal-langgraph/project/workflow.py.j2 index d1621fb8c..14bafabc1 100644 --- a/src/agentex/lib/cli/templates/temporal-langgraph/project/workflow.py.j2 +++ b/src/agentex/lib/cli/templates/temporal-langgraph/project/workflow.py.j2 @@ -93,8 +93,12 @@ class {{ workflow_class }}(BaseWorkflow): async def on_task_event_send(self, params: SendEventParams) -> None: """Handle a new user message: echo it, then run the agent graph durably.""" logger.info(f"Received task event for task {params.task.id}") + content = params.event.content + if not isinstance(content, TextContent): + logger.warning("Ignoring non-text event content (type=%s)", getattr(content, "type", "?")) + return self._turn_number += 1 - user_text = params.event.content.content + user_text = content.content # Echo the user's message so it shows up as a chat bubble. await adk.messages.create(task_id=params.task.id, content=params.event.content) diff --git a/src/agentex/lib/cli/templates/temporal-openai-agents/Dockerfile-uv.j2 b/src/agentex/lib/cli/templates/temporal-openai-agents/Dockerfile-uv.j2 index 625592d31..0d9801016 100644 --- a/src/agentex/lib/cli/templates/temporal-openai-agents/Dockerfile-uv.j2 +++ b/src/agentex/lib/cli/templates/temporal-openai-agents/Dockerfile-uv.j2 @@ -33,18 +33,18 @@ ENV UV_HTTP_TIMEOUT=1000 WORKDIR /app/{{ project_path_from_build_root }} # Copy dependency files for layer caching -COPY {{ project_path_from_build_root }}/pyproject.toml {{ project_path_from_build_root }}/uv.lock ./ +COPY {{ project_path_from_build_root }}/pyproject.toml ./ # Install dependencies (without project itself, for layer caching) RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-install-project --no-dev + uv sync --no-install-project --no-dev # Copy the project code COPY {{ project_path_from_build_root }}/project ./project # Install the project RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-dev + uv sync --no-dev ENV PATH="/app/{{ project_path_from_build_root }}/.venv/bin:$PATH" diff --git a/src/agentex/lib/cli/templates/temporal-openai-agents/manifest.yaml.j2 b/src/agentex/lib/cli/templates/temporal-openai-agents/manifest.yaml.j2 index ee5e473d2..b9216929f 100644 --- a/src/agentex/lib/cli/templates/temporal-openai-agents/manifest.yaml.j2 +++ b/src/agentex/lib/cli/templates/temporal-openai-agents/manifest.yaml.j2 @@ -73,7 +73,7 @@ agent: # Description of what your agent does # Helps with documentation and discovery - description: {{ description }} + description: {{ description | tojson }} # Temporal workflow configuration # This enables your agent to run as a Temporal workflow for long-running tasks diff --git a/src/agentex/lib/cli/templates/temporal-openai-agents/project/workflow.py.j2 b/src/agentex/lib/cli/templates/temporal-openai-agents/project/workflow.py.j2 index 2b81bb335..af8b7a299 100644 --- a/src/agentex/lib/cli/templates/temporal-openai-agents/project/workflow.py.j2 +++ b/src/agentex/lib/cli/templates/temporal-openai-agents/project/workflow.py.j2 @@ -100,6 +100,11 @@ class {{ workflow_class }}(BaseWorkflow): async def on_task_event_send(self, params: SendEventParams) -> None: logger.info(f"Received task message instruction: {params}") + content = params.event.content + if not isinstance(content, TextContent): + logger.warning("Ignoring non-text event content (type=%s)", getattr(content, "type", "?")) + return + # Increment turn number for tracing self._state.turn_number += 1 @@ -108,7 +113,7 @@ class {{ workflow_class }}(BaseWorkflow): self._parent_span_id = params.task.id # Add the user message to conversation history - self._state.input_list.append({"role": "user", "content": params.event.content.content}) + self._state.input_list.append({"role": "user", "content": content.content}) # Echo back the client's message to show it in the UI await adk.messages.create(task_id=params.task.id, content=params.event.content) diff --git a/src/agentex/lib/cli/templates/temporal-pydantic-ai/Dockerfile-uv.j2 b/src/agentex/lib/cli/templates/temporal-pydantic-ai/Dockerfile-uv.j2 index 625592d31..0d9801016 100644 --- a/src/agentex/lib/cli/templates/temporal-pydantic-ai/Dockerfile-uv.j2 +++ b/src/agentex/lib/cli/templates/temporal-pydantic-ai/Dockerfile-uv.j2 @@ -33,18 +33,18 @@ ENV UV_HTTP_TIMEOUT=1000 WORKDIR /app/{{ project_path_from_build_root }} # Copy dependency files for layer caching -COPY {{ project_path_from_build_root }}/pyproject.toml {{ project_path_from_build_root }}/uv.lock ./ +COPY {{ project_path_from_build_root }}/pyproject.toml ./ # Install dependencies (without project itself, for layer caching) RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-install-project --no-dev + uv sync --no-install-project --no-dev # Copy the project code COPY {{ project_path_from_build_root }}/project ./project # Install the project RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-dev + uv sync --no-dev ENV PATH="/app/{{ project_path_from_build_root }}/.venv/bin:$PATH" diff --git a/src/agentex/lib/cli/templates/temporal-pydantic-ai/manifest.yaml.j2 b/src/agentex/lib/cli/templates/temporal-pydantic-ai/manifest.yaml.j2 index ee5e473d2..b9216929f 100644 --- a/src/agentex/lib/cli/templates/temporal-pydantic-ai/manifest.yaml.j2 +++ b/src/agentex/lib/cli/templates/temporal-pydantic-ai/manifest.yaml.j2 @@ -73,7 +73,7 @@ agent: # Description of what your agent does # Helps with documentation and discovery - description: {{ description }} + description: {{ description | tojson }} # Temporal workflow configuration # This enables your agent to run as a Temporal workflow for long-running tasks diff --git a/src/agentex/lib/cli/templates/temporal-pydantic-ai/project/workflow.py.j2 b/src/agentex/lib/cli/templates/temporal-pydantic-ai/project/workflow.py.j2 index 66a91d7a8..6dcca3002 100644 --- a/src/agentex/lib/cli/templates/temporal-pydantic-ai/project/workflow.py.j2 +++ b/src/agentex/lib/cli/templates/temporal-pydantic-ai/project/workflow.py.j2 @@ -85,6 +85,13 @@ class {{ workflow_class }}(BaseWorkflow): async def on_task_event_send(self, params: SendEventParams) -> None: """Handle a new user message: echo it, then run the agent durably.""" logger.info(f"Received task event: {params.task.id}") + + content = params.event.content + if not isinstance(content, TextContent): + logger.warning("Ignoring non-text event content (type=%s)", getattr(content, "type", "?")) + return + user_message = content.content + self._turn_number += 1 # Echo the user's message so it shows up in the UI as a chat bubble. @@ -94,7 +101,7 @@ class {{ workflow_class }}(BaseWorkflow): trace_id=params.task.id, task_id=params.task.id, name=f"Turn {self._turn_number}", - input={"message": params.event.content.content}, + input={"message": user_message}, ) as span: # temporal_agent.run() is the magic line. Internally it schedules # a model activity (LLM HTTP call) and, for each tool the model @@ -107,7 +114,7 @@ class {{ workflow_class }}(BaseWorkflow): # without it the agent would respond to each user message as if # it had never seen the conversation before. result = await temporal_agent.run( - params.event.content.content, + user_message, message_history=self._message_history, deps=TaskDeps( task_id=params.task.id, diff --git a/src/agentex/lib/cli/templates/temporal/Dockerfile-uv.j2 b/src/agentex/lib/cli/templates/temporal/Dockerfile-uv.j2 index 625592d31..0d9801016 100644 --- a/src/agentex/lib/cli/templates/temporal/Dockerfile-uv.j2 +++ b/src/agentex/lib/cli/templates/temporal/Dockerfile-uv.j2 @@ -33,18 +33,18 @@ ENV UV_HTTP_TIMEOUT=1000 WORKDIR /app/{{ project_path_from_build_root }} # Copy dependency files for layer caching -COPY {{ project_path_from_build_root }}/pyproject.toml {{ project_path_from_build_root }}/uv.lock ./ +COPY {{ project_path_from_build_root }}/pyproject.toml ./ # Install dependencies (without project itself, for layer caching) RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-install-project --no-dev + uv sync --no-install-project --no-dev # Copy the project code COPY {{ project_path_from_build_root }}/project ./project # Install the project RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --no-dev + uv sync --no-dev ENV PATH="/app/{{ project_path_from_build_root }}/.venv/bin:$PATH" diff --git a/src/agentex/lib/cli/templates/temporal/manifest.yaml.j2 b/src/agentex/lib/cli/templates/temporal/manifest.yaml.j2 index ee5e473d2..b9216929f 100644 --- a/src/agentex/lib/cli/templates/temporal/manifest.yaml.j2 +++ b/src/agentex/lib/cli/templates/temporal/manifest.yaml.j2 @@ -73,7 +73,7 @@ agent: # Description of what your agent does # Helps with documentation and discovery - description: {{ description }} + description: {{ description | tojson }} # Temporal workflow configuration # This enables your agent to run as a Temporal workflow for long-running tasks