Anonymous View
Skip to content

feat(cli): add codex init templates (sync / async / temporal)#436

Open
declan-scale wants to merge 18 commits into
declan-scale/init-templates-claude-codefrom
declan-scale/init-templates-codex
Open

feat(cli): add codex init templates (sync / async / temporal)#436
declan-scale wants to merge 18 commits into
declan-scale/init-templates-claude-codefrom
declan-scale/init-templates-codex

Conversation

@declan-scale

@declan-scale declan-scale commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Final slice of #425. Adds the codex agentex init templates across all three tiers.

  • New default-codex, sync-codex, temporal-codex template dirs.
  • Wires DEFAULT_CODEX, SYNC_CODEX, TEMPORAL_CODEX into init.py (enum, project-files map, prompts). TemplateType is now complete at 19.
  • Scaffolded code imports CodexTurn from the agentex.lib.adk facade.

Test plan

Notes

Stacked on #435. Retarget to next after the chain merges.

🤖 Generated with Claude Code

Greptile Summary

This PR completes the Codex CLI harness templates slice by adding default-codex, sync-codex, and temporal-codex scaffolds and wiring them into agentex init. All three tiers share the same CodexTurn + UnifiedEmitter harness pattern used by the sibling claude-code templates.

  • All previously flagged issues from the stacked review have been addressed: @openai/codex is installed in every Dockerfile, OPENAI_API_KEY is mapped in every manifest and .env.example, subprocess cleanup uses try/finally with process.kill() + process.wait() in all three acp.py templates, and the temporal workflow serializes concurrent signal handlers with asyncio.Lock().
  • The temporal Dockerfile-uv variant is missing ENV PYTHONPATH=/app that is present in the non-uv counterpart and in the default-codex uv variant — a minor inconsistency that is unlikely to cause runtime failures given the current import structure but is worth aligning.

Confidence Score: 5/5

Safe to merge; all blocking issues from previous review rounds have been resolved and the new code follows established harness patterns.

All previously raised issues (subprocess leaks, wrong credential in manifests, missing CLI install, concurrent-turn state forking, undefined notebook variable) are demonstrably fixed in the current diff. The remaining note is a minor inconsistency between two Dockerfile variants that does not affect runtime behaviour.

temporal-codex/Dockerfile-uv.j2 — missing PYTHONPATH alignment with its non-uv sibling.

Important Files Changed

Filename Overview
src/agentex/lib/cli/commands/init.py Adds DEFAULT_CODEX, SYNC_CODEX, TEMPORAL_CODEX to TemplateType enum and wires them into the project-files map and interactive prompts — clean, pattern-consistent addition.
src/agentex/lib/cli/templates/default-codex/project/acp.py.j2 Async ACP handler with CodexTurn + UnifiedEmitter; subprocess cleanup correctly guarded by try/finally with process.kill() + process.wait(), multi-turn thread ID persisted via adk.state.
src/agentex/lib/cli/templates/sync-codex/project/acp.py.j2 Sync streaming handler yields events via yield_turn; subprocess cleanup correctly wrapped in try/finally inside the async generator, process killed before wait on any exception or client disconnect.
src/agentex/lib/cli/templates/temporal-codex/project/workflow.py.j2 Temporal workflow with asyncio.Lock() serializing signal handlers to prevent concurrent turns from reading the same stale codex thread ID.
src/agentex/lib/cli/templates/temporal-codex/project/activities.py.j2 Temporal activity running codex subprocess; try/finally guarantees process.kill()+wait() even if auto_send_turn raises.
src/agentex/lib/cli/templates/temporal-codex/Dockerfile-uv.j2 Installs codex CLI via npm and uses dynamic uname -m detection for tctl arch; missing ENV PYTHONPATH=/app present in the non-uv sibling.
src/agentex/lib/cli/templates/temporal-codex/Dockerfile.j2 Installs codex CLI via npm; dynamic tctl architecture detection with uname -m correctly handles amd64/arm64; ENV PYTHONPATH=/app present.
src/agentex/lib/cli/templates/default-codex/manifest.yaml.j2 Credentials correctly map OPENAI_API_KEY matching what codex exec reads.
src/agentex/lib/cli/templates/sync-codex/manifest.yaml.j2 Correctly maps OPENAI_API_KEY; acp_type is sync matching the template purpose.
src/agentex/lib/cli/templates/temporal-codex/manifest.yaml.j2 Temporal workflow config enabled, OPENAI_API_KEY mapped with comment, REDIS_URL included for the async event push path.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant UI
    participant ACP as ACP Server (FastACP)
    participant Handler as on_task_event_send / on_message_send
    participant Codex as codex exec subprocess
    participant Redis
    participant State as adk.state

    UI->>ACP: task/event/send (user message)
    ACP->>Handler: dispatch params
    Handler->>State: get ConversationState (thread_id)
    Handler->>Codex: asyncio.create_subprocess_exec codex exec json
    Handler->>Codex: write prompt to stdin, close stdin
    loop CodexTurn streaming
        Codex-->>Handler: stdout JSON line
        Handler->>Redis: UnifiedEmitter event push
        Redis-->>UI: real-time token stream
    end
    Handler->>Codex: process.kill() + wait() finally
    Handler->>State: update codex_thread_id
    Handler-->>ACP: return / generator exhausted
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant UI
    participant ACP as ACP Server (FastACP)
    participant Handler as on_task_event_send / on_message_send
    participant Codex as codex exec subprocess
    participant Redis
    participant State as adk.state

    UI->>ACP: task/event/send (user message)
    ACP->>Handler: dispatch params
    Handler->>State: get ConversationState (thread_id)
    Handler->>Codex: asyncio.create_subprocess_exec codex exec json
    Handler->>Codex: write prompt to stdin, close stdin
    loop CodexTurn streaming
        Codex-->>Handler: stdout JSON line
        Handler->>Redis: UnifiedEmitter event push
        Redis-->>UI: real-time token stream
    end
    Handler->>Codex: process.kill() + wait() finally
    Handler->>State: update codex_thread_id
    Handler-->>ACP: return / generator exhausted
Loading

Comments Outside Diff (1)

  1. src/agentex/lib/cli/templates/default-codex/manifest.yaml.j2, line 599-613 (link)

    P1 LITELLM_API_KEY credential won't satisfy codex exec

    The active credentials entry maps LITELLM_API_KEY into the container, but codex exec reads OPENAI_API_KEY directly from the environment. When a user scaffolds this template and deploys, the Kubernetes secret for litellm-api-key is mounted but OPENAI_API_KEY is never set, so every codex exec invocation will fail with an auth error. The credential name (and the env: entry below it) should reference OPENAI_API_KEY / openai-api-key to match what the CLI actually consumes. The same issue is present in sync-codex/manifest.yaml.j2.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/agentex/lib/cli/templates/default-codex/manifest.yaml.j2
    Line: 599-613
    
    Comment:
    **`LITELLM_API_KEY` credential won't satisfy `codex exec`**
    
    The active credentials entry maps `LITELLM_API_KEY` into the container, but `codex exec` reads `OPENAI_API_KEY` directly from the environment. When a user scaffolds this template and deploys, the Kubernetes secret for `litellm-api-key` is mounted but `OPENAI_API_KEY` is never set, so every `codex exec` invocation will fail with an auth error. The credential name (and the `env:` entry below it) should reference `OPENAI_API_KEY` / `openai-api-key` to match what the CLI actually consumes. The same issue is present in `sync-codex/manifest.yaml.j2`.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code

Reviews (17): Last reviewed commit: "fix(cli): wire OPENAI_API_KEY and serial..." | Re-trigger Greptile

@declan-scale declan-scale force-pushed the declan-scale/init-templates-claude-code branch from a92d37b to ff359de Compare June 23, 2026 15:28
@declan-scale declan-scale force-pushed the declan-scale/init-templates-codex branch from 9d0ea68 to a891d35 Compare June 23, 2026 15:28
@declan-scale

Copy link
Copy Markdown
Contributor Author

@greptile review

Comment thread src/agentex/lib/cli/templates/temporal-codex/Dockerfile-uv.j2
Comment thread src/agentex/lib/cli/templates/default-codex/.env.example.j2
Comment thread src/agentex/lib/cli/templates/sync-codex/dev.ipynb.j2
@declan-scale declan-scale force-pushed the declan-scale/init-templates-claude-code branch from ff359de to e031d5d Compare June 23, 2026 15:45
@declan-scale declan-scale force-pushed the declan-scale/init-templates-codex branch from a891d35 to be6b158 Compare June 23, 2026 15:47
@declan-scale

Copy link
Copy Markdown
Contributor Author

@greptile review

Comment thread src/agentex/lib/cli/templates/default-codex/project/acp.py.j2 Outdated
Comment thread src/agentex/lib/cli/templates/sync-codex/project/acp.py.j2 Outdated
Comment thread src/agentex/lib/cli/templates/temporal-codex/project/activities.py.j2 Outdated
@declan-scale declan-scale force-pushed the declan-scale/init-templates-claude-code branch from e031d5d to 8f7854a Compare June 23, 2026 16:25
@declan-scale declan-scale force-pushed the declan-scale/init-templates-codex branch from be6b158 to 2eefec2 Compare June 23, 2026 16:25
@declan-scale

Copy link
Copy Markdown
Contributor Author

@greptile review

Comment thread src/agentex/lib/cli/templates/default-codex/Dockerfile.j2
@declan-scale declan-scale force-pushed the declan-scale/init-templates-claude-code branch from 8f7854a to 6e3f0cf Compare June 23, 2026 16:52
@declan-scale declan-scale force-pushed the declan-scale/init-templates-codex branch from 2eefec2 to 42db83c Compare June 23, 2026 16:52
@declan-scale

Copy link
Copy Markdown
Contributor Author

@greptile review

@declan-scale declan-scale force-pushed the declan-scale/init-templates-codex branch from 42db83c to 889f793 Compare June 23, 2026 17:07
@declan-scale

Copy link
Copy Markdown
Contributor Author

@greptile review

@declan-scale declan-scale force-pushed the declan-scale/init-templates-codex branch from 889f793 to ace66e2 Compare June 23, 2026 17:25
@declan-scale

Copy link
Copy Markdown
Contributor Author

@greptile review

Comment thread src/agentex/lib/cli/templates/default-codex/manifest.yaml.j2 Outdated
Comment thread src/agentex/lib/cli/templates/temporal-codex/project/workflow.py.j2 Outdated
@declan-scale

Copy link
Copy Markdown
Contributor Author

@greptile review

@declan-scale declan-scale force-pushed the declan-scale/init-templates-claude-code branch from 47ea82c to 2003757 Compare June 23, 2026 19:53
@declan-scale declan-scale force-pushed the declan-scale/init-templates-codex branch from d489113 to 412b1c6 Compare June 23, 2026 19:53
@declan-scale declan-scale force-pushed the declan-scale/init-templates-claude-code branch from 2003757 to 755d18e Compare June 23, 2026 19:57
@declan-scale declan-scale force-pushed the declan-scale/init-templates-codex branch from 412b1c6 to 3aaa35e Compare June 23, 2026 19:57
… handler

Collapse _langgraph_async / _langgraph_messages / _langgraph_tracing into
_langgraph_sync (emit_langgraph_messages, convert helpers) and _langgraph_turn
(stream_langgraph_events). Span tracing is now derived from the canonical
stream by UnifiedEmitter, so create_langgraph_tracing_handler is removed.

Public facade names are unchanged except the removed handler. All in-repo
consumers were migrated to the unified surface in the preceding PRs.

BREAKING CHANGE: create_langgraph_tracing_handler is removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
declan-scale and others added 17 commits June 23, 2026 18:25
Address Greptile review: the breaking-change entry listed
create_pydantic_ai_tracing_handler as removed, but that handler is still
exported here and is only removed in the following PR. Narrow this entry to
the LangGraph handler actually removed in this change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ng handler

Collapse _pydantic_ai_async / _pydantic_ai_tracing into _pydantic_ai_sync
(convert helper) and _pydantic_ai_turn (stream_pydantic_ai_events). Span
tracing is derived from the canonical stream by UnifiedEmitter, so
create_pydantic_ai_tracing_handler is removed.

BREAKING CHANGE: create_pydantic_ai_tracing_handler is removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This PR removes create_pydantic_ai_tracing_handler from the public adk
surface; document it here (the previous PR's entry was narrowed to the
LangGraph handler it actually removed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… at facade

Promote the OpenAI Turn and sync tap to the canonical adk/_modules layout
(_openai_turn.py, _openai_sync.py), matching every other harness. Leave
back-compat shims at providers/_modules/{openai_turn,sync_provider}.py so
existing import paths keep working; the larger Temporal/MCP provider stays
under adk.providers.

Export OpenAITurn, openai_usage_to_turn_usage and convert_openai_to_agentex_events
from agentex.lib.adk so the OpenAI harness is a first-class facade citizen like
ClaudeCodeTurn / CodexTurn (fixes Greptile follow-up: templates can import from
the package facade instead of a private module path).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add offline sync/async/temporal integration suites for the openai, claude_code
and codex harnesses (+76 tests), mirroring the existing langgraph/pydantic_ai
coverage. Extend the harness-integration live-matrix to all five harnesses and
switch the path trigger to a test_harness_*.py glob so new suites are picked up
automatically.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the missing async-base OpenAI Agents SDK template, wiring
DEFAULT_OPENAI_AGENTS into the init flow (enum, project files, async prompt).
The scaffolded acp.py imports OpenAITurn from the agentex.lib.adk facade,
matching the other Turn-based templates.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address Greptile review: the async base handler ran each turn with only the
current user_message, so multi-turn context was dropped. Persist the OpenAI
input list per task via adk.state and replay it into Runner.run_streamed,
matching the sync and temporal openai-agents templates.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add default-claude-code, sync-claude-code and temporal-claude-code templates
across all three tiers, wiring the new TemplateType entries into the init flow.
Scaffolded code imports ClaudeCodeTurn from the agentex.lib.adk facade.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Install the Claude Code CLI (@anthropic-ai/claude-code) in all three
  Dockerfiles so deployed images can actually run `claude`.
- Wire ANTHROPIC_API_KEY (credential + env) in the default, sync and temporal
  manifests; the `claude` subprocess does not read LITELLM_API_KEY.
- Surface CLI failures: capture a bounded stderr tail and raise on a non-zero
  exit instead of silently completing the turn.
- Serialize temporal turns with an asyncio.Lock so overlapping signals don't
  race on _session_id, and raise the activity timeout to 30m for agentic runs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Match the manifest fix: the claude-code templates spawn the `claude` CLI,
which reads ANTHROPIC_API_KEY rather than LITELLM_API_KEY. Update the
default, sync and temporal .env.example scaffolds accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Round-3 Greptile parity: the uv-path Dockerfile-uv.j2 variants (default, sync,
temporal) installed node/npm but not the `claude` CLI, leaving use_uv=True
containers non-functional. Mirror the npm install -g @anthropic-ai/claude-code
step already added to the pip Dockerfiles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Round-4 Greptile review:
- await proc.stdin.drain() before close() in all three claude-code subprocess
  helpers (matches the codex helpers; flushes large prompts before EOF).
- Stop the manifest env block from setting ANTHROPIC_API_KEY to an empty string,
  which would shadow the credential mapping / .env value at runtime. The key now
  comes solely from the credential mapping (deploy) or .env (local).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add default-codex, sync-codex and temporal-codex templates across all three
tiers, wiring the new TemplateType entries into the init flow. Scaffolded code
imports CodexTurn from the agentex.lib.adk facade.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address Greptile review: wrap the streaming call in try/finally across the
default, sync and temporal codex templates so the codex subprocess is killed
and reaped even when auto_send_turn / yield_turn raises or the async generator
is abandoned. Previously a failed turn left codex blocked on a full stdout pipe
buffer, leaking an OS process per failure until the server/worker restarted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address Greptile review: the default, sync and temporal codex Dockerfiles
installed nodejs/npm but never the codex CLI, so scaffolded containers hit
FileNotFoundError on `codex` for every request. Add
`npm install -g @openai/codex`, matching the claude-code templates.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Round-3 Greptile parity: the uv-path Dockerfile-uv.j2 variants (default, sync,
temporal) installed node/npm but not the `codex` CLI. Mirror the
npm install -g @openai/codex step already added to the pip Dockerfiles so
use_uv=True containers are functional.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Round-5 Greptile review (parity with the claude-code fixes):
- default/sync/temporal codex manifests now map OPENAI_API_KEY (the key the
  codex CLI actually reads) instead of LITELLM_API_KEY, and no longer set an
  empty-string env value that would shadow it at runtime.
- temporal-codex workflow serializes signal turns with an asyncio.Lock so
  overlapping messages don't race on _codex_thread_id and fork the session.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@declan-scale declan-scale force-pushed the declan-scale/init-templates-claude-code branch from 63904c2 to f819115 Compare June 23, 2026 22:29
@declan-scale declan-scale force-pushed the declan-scale/init-templates-codex branch from 5d5a0e8 to b5cbe37 Compare June 23, 2026 22:29
@declan-scale declan-scale force-pushed the declan-scale/init-templates-claude-code branch from f819115 to 4f2002c Compare June 24, 2026 00:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants