Skip to content

refactor(tutorials): migrate to the unified harness surface + renumber#428

Merged
declan-scale merged 4 commits into
nextfrom
declan-scale/tutorials-unified-surface
Jun 23, 2026
Merged

refactor(tutorials): migrate to the unified harness surface + renumber#428
declan-scale merged 4 commits into
nextfrom
declan-scale/tutorials-unified-surface

Conversation

@declan-scale

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

Copy link
Copy Markdown
Contributor

Summary

Second slice of #425. Migrates every tutorial onto the canonical unified harness surface and renumbers them onto the NNN_<name> paradigm.

  • Retires the duplicate pre-unified harness_* tutorials.
  • Migrates tutorial acp.py (and temporal agent.py/workflow.py) to the unified surface (UnifiedEmitter / Turn / convert_* helpers).
  • Fixes the 060/130/140 numbering collision; codex takes fresh 070/140/150 slots.

Why non-breaking

The unified surface already exists on next, and the deprecated tracing handlers are still present here. Consumers are migrated first; the handler removal lands in the follow-up harness-consolidation PRs.

Notes

Part of a stacked split of #425. Targets next; merge after #427.

🤖 Generated with Claude Code

Greptile Summary

This PR migrates all tutorials onto the canonical UnifiedEmitter / Turn / convert_* harness surface, retires the old harness_* duplicate directories, and renumbers them onto the NNN_<name> paradigm (codex takes the freshly freed 070/140/150 slots).

  • Sync & async base tutorials (030_langgraph, 040_pydantic_ai, 050_openai_agents, 100–120 counterparts): replace bespoke tracing callbacks and per-provider stream helpers with UnifiedEmitter.yield_turn / auto_send_turn wrapping the appropriate Turn object.
  • Temporal tutorials (110_pydantic_ai, 120_openai_agents, 130_langgraph): class renames and docstring updates only; new 150_codex adds a full Temporal-durable codex workflow with a subprocess activity (run_codex_turn) that correctly passes created_at=workflow.now() for deterministic replay and uses incremental UTF-8 decoding for stdout.
  • Dockerfiles for all three Codex tutorials now install nodejs + npm + @openai/codex, placing the codex binary on PATH at runtime.

Confidence Score: 5/5

Safe to merge; the migration is mechanical and all runtime-critical paths (deterministic timestamps, Codex CLI installation, retry policies on the openai-agents activity) look correct.

The changes are a consistent mechanical swap of per-provider streaming helpers for the unified emitter surface across ~200 tutorial files. The new Temporal Codex tutorial correctly handles subprocess I/O in an activity, passes workflow.now() for replay determinism, and the Dockerfiles now install the Codex CLI. No logic paths were altered in the core SDK itself.

examples/tutorials/10_async/10_temporal/120_openai_agents/project/workflow.py carries a stale class name from the deleted 140_harness_openai slot.

Important Files Changed

Filename Overview
examples/tutorials/10_async/10_temporal/120_openai_agents/project/workflow.py New Temporal workflow for 120_openai_agents; correctly passes created_at=workflow.now() and sets RetryPolicy(maximum_attempts=3). Class name At140HarnessOpenaiWorkflow is a stale copy from the deleted 140_harness_openai slot.
examples/tutorials/10_async/10_temporal/120_openai_agents/project/activities.py New HarnessActivities hosting run_openai_agent; correctly threads created_at through to auto_send_turn and forwards input_list for multi-turn memory.
examples/tutorials/10_async/10_temporal/150_codex/project/workflow.py New Temporal Codex workflow; correctly passes created_at=workflow.now() and persists codex thread ID on the workflow instance for multi-turn sessions.
examples/tutorials/10_async/10_temporal/150_codex/project/activities.py New run_codex_turn activity; correct incremental UTF-8 decoder for stdout, DEVNULL for stderr to avoid deadlock, and properly drains stdin before closing.
examples/tutorials/00_sync/030_langgraph/project/acp.py Migrated to UnifiedEmitter.yield_turn(LangGraphTurn); drops bespoke create_langgraph_tracing_handler and convert_langgraph_to_agentex_events in favour of the unified surface.
examples/tutorials/10_async/00_base/100_langgraph/project/acp.py Migrated from stream_langgraph_events to UnifiedEmitter.auto_send_turn(LangGraphTurn); clean swap with no functional regressions.
examples/tutorials/00_sync/070_codex/Dockerfile New Dockerfile; correctly installs nodejs, npm, and @openai/codex so the codex exec binary is on PATH at runtime.
examples/tutorials/10_async/10_temporal/130_langgraph/project/workflow.py Minor change: class renamed from At130LanggraphWorkflow to AtHarnessLanggraphWorkflow; whitespace fix in slice expression. No logic change.
examples/tutorials/10_async/10_temporal/110_pydantic_ai/project/workflow.py Class renamed from At110PydanticAiWorkflow to HarnessPydanticAiWorkflow; docstring condensed; no logic change.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    subgraph Before["Before (per-provider helpers)"]
        A1["create_langgraph_tracing_handler\n+ convert_langgraph_to_agentex_events"] --> S1["stream / Redis / Temporal channel"]
        A2["create_pydantic_ai_tracing_handler\n+ convert_pydantic_ai_to_agentex_events"] --> S1
        A3["OpenAIAgentsPlugin\n(bespoke tracing wiring)"] --> S1
    end

    subgraph After["After (unified harness surface)"]
        B1["LangGraphTurn(stream)"] --> UE["UnifiedEmitter\n.yield_turn() — sync\n.auto_send_turn() — async/Temporal"]
        B2["PydanticAITurn(stream)"] --> UE
        B3["OpenAITurn(result)"] --> UE
        B4["CodexTurn(events)"] --> UE
        UE --> CH["Single canonical\nchannel (HTTP yield / Redis / Temporal activity)"]
    end

    Before -.->|"migration"| After
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"}}}%%
flowchart TD
    subgraph Before["Before (per-provider helpers)"]
        A1["create_langgraph_tracing_handler\n+ convert_langgraph_to_agentex_events"] --> S1["stream / Redis / Temporal channel"]
        A2["create_pydantic_ai_tracing_handler\n+ convert_pydantic_ai_to_agentex_events"] --> S1
        A3["OpenAIAgentsPlugin\n(bespoke tracing wiring)"] --> S1
    end

    subgraph After["After (unified harness surface)"]
        B1["LangGraphTurn(stream)"] --> UE["UnifiedEmitter\n.yield_turn() — sync\n.auto_send_turn() — async/Temporal"]
        B2["PydanticAITurn(stream)"] --> UE
        B3["OpenAITurn(result)"] --> UE
        B4["CodexTurn(events)"] --> UE
        UE --> CH["Single canonical\nchannel (HTTP yield / Redis / Temporal activity)"]
    end

    Before -.->|"migration"| After
Loading

Comments Outside Diff (5)

  1. examples/tutorials/10_async/10_temporal/150_codex/project/workflow.py, line 96-108 (link)

    P2 No explicit retry_policy on the codex activity — default retries could duplicate streamed events

    Without an explicit retry_policy, Temporal's default (up to 10 attempts) applies. The run_codex_turn activity calls UnifiedEmitter.auto_send_turn, which streams events to Redis as they arrive. If the activity fails mid-stream and Temporal retries it, the retry spawns a fresh codex process that pushes its events again — producing duplicate messages in the task stream. Adding RetryPolicy(maximum_attempts=1) prevents this, matching the expectation that each turn produces exactly one stream of output.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: examples/tutorials/10_async/10_temporal/150_codex/project/workflow.py
    Line: 96-108
    
    Comment:
    **No explicit `retry_policy` on the codex activity — default retries could duplicate streamed events**
    
    Without an explicit `retry_policy`, Temporal's default (up to 10 attempts) applies. The `run_codex_turn` activity calls `UnifiedEmitter.auto_send_turn`, which streams events to Redis as they arrive. If the activity fails mid-stream and Temporal retries it, the retry spawns a fresh codex process that pushes its events again — producing duplicate messages in the task stream. Adding `RetryPolicy(maximum_attempts=1)` prevents this, matching the expectation that each turn produces exactly one stream of output.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code

  2. examples/tutorials/00_sync/070_codex/Dockerfile, line 5-18 (link)

    P1 Install Codex CLI

    The sync, async, and Temporal Codex tutorials all shell out to codex exec, but their Dockerfiles only install OS and Python dependencies. In packaged images, requests or activities will fail at subprocess startup because codex is missing from PATH. This affects examples/tutorials/00_sync/070_codex/Dockerfile, examples/tutorials/10_async/00_base/140_codex/Dockerfile, and examples/tutorials/10_async/10_temporal/150_codex/Dockerfile. Please install the Codex CLI and its runtime in each image, or avoid depending on an external executable.

    Artifacts

    Repro: focused subprocess and Dockerfile omission check

    • Contains supporting evidence from the run (text/x-python; charset=utf-8).

    Repro: command output showing codex absent and all three subprocess paths fail

    • Keeps the command output available without making the summary code-heavy.

    View artifacts

    T-Rex Ran code and verified through T-Rex

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: examples/tutorials/00_sync/070_codex/Dockerfile
    Line: 5-18
    
    Comment:
    **Install Codex CLI**
    
    The sync, async, and Temporal Codex tutorials all shell out to `codex exec`, but their Dockerfiles only install OS and Python dependencies. In packaged images, requests or activities will fail at subprocess startup because `codex` is missing from `PATH`. This affects `examples/tutorials/00_sync/070_codex/Dockerfile`, `examples/tutorials/10_async/00_base/140_codex/Dockerfile`, and `examples/tutorials/10_async/10_temporal/150_codex/Dockerfile`. Please install the Codex CLI and its runtime in each image, or avoid depending on an external executable.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code

  3. examples/tutorials/10_async/00_base/140_codex/Dockerfile, line 6-18 (link)

    P1 Install Codex CLI

    The sync, async, and Temporal Codex tutorials all spawn codex exec --json from their agent or worker code, but the migrated Dockerfiles only install OS and Python dependencies. Containers built from examples/tutorials/00_sync/070_codex/Dockerfile, examples/tutorials/10_async/00_base/140_codex/Dockerfile, and examples/tutorials/10_async/10_temporal/150_codex/Dockerfile will fail on the first live request or activity because codex is not on PATH. Please package the Codex CLI in each image, such as by installing Node/npm and @openai/codex, before running the agent or worker.

    Artifacts

    Repro: Docker build attempt showing docker is unavailable in this environment

    • Keeps the command output available without making the summary code-heavy.

    Repro: fallback harness that inspects the anchored Dockerfile and runs Docker build/run if available

    • Contains supporting evidence from the run (text/x-python; charset=utf-8).

    Repro: fallback harness output showing no Codex CLI install step in the anchored Dockerfile

    • Keeps the command output available without making the summary code-heavy.

    View artifacts

    T-Rex Ran code and verified through T-Rex

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: examples/tutorials/10_async/00_base/140_codex/Dockerfile
    Line: 6-18
    
    Comment:
    **Install Codex CLI**
    
    The sync, async, and Temporal Codex tutorials all spawn `codex exec --json` from their agent or worker code, but the migrated Dockerfiles only install OS and Python dependencies. Containers built from `examples/tutorials/00_sync/070_codex/Dockerfile`, `examples/tutorials/10_async/00_base/140_codex/Dockerfile`, and `examples/tutorials/10_async/10_temporal/150_codex/Dockerfile` will fail on the first live request or activity because `codex` is not on `PATH`. Please package the Codex CLI in each image, such as by installing Node/npm and `@openai/codex`, before running the agent or worker.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code

  4. examples/tutorials/10_async/10_temporal/120_openai_agents/project/workflow.py, line 81-87 (link)

    P1 Pass deterministic timestamps

    This Temporal workflow streams AgentEx messages from the activity through UnifiedEmitter.auto_send_turn, but the activity params do not carry created_at=workflow.now(). When the activity is retried after partially streaming a turn, the same logical agent messages can be persisted again with new server-side timestamps, which can reorder or duplicate them relative to the echoed user message and other turns. Please pass a deterministic created_at through RunHarnessAgentParams and forward it to auto_send_turn.

    Artifacts

    Repro: focused runtime harness with mocks for workflow and activity timestamp capture

    • Contains supporting evidence from the run (text/x-python; charset=utf-8).

    Repro: harness output showing missing created_at in workflow params and auto_send_turn call

    • Keeps the command output available without making the summary code-heavy.

    View artifacts

    T-Rex Ran code and verified through T-Rex

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: examples/tutorials/10_async/10_temporal/120_openai_agents/project/workflow.py
    Line: 81-87
    
    Comment:
    **Pass deterministic timestamps**
    
    This Temporal workflow streams AgentEx messages from the activity through `UnifiedEmitter.auto_send_turn`, but the activity params do not carry `created_at=workflow.now()`. When the activity is retried after partially streaming a turn, the same logical agent messages can be persisted again with new server-side timestamps, which can reorder or duplicate them relative to the echoed user message and other turns. Please pass a deterministic `created_at` through `RunHarnessAgentParams` and forward it to `auto_send_turn`.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code

  5. General comment

    P1 Temporal tutorial migration is incomplete across claimed unified harness surfaces

    • Bug
      • Head still lacks the requested unified surface in several changed non-ledger files. The validation output reports that 110_pydantic_ai/project/acp.py lacks UnifiedEmitter, Turn, and convert_; 120_openai_agents/project/acp.py lacks UnifiedEmitter, Turn, and convert_; 120_openai_agents/project/activities.py lacks convert_* usage; 130_langgraph/project/acp.py lacks Turn and convert_; 130_langgraph/project/graph.py lacks UnifiedEmitter, Turn, and convert_; and 130_langgraph/project/workflow.py lacks Turn. These are not base-only gaps: they remain in the after run at head.
    • Cause
      • The migration appears to have introduced UnifiedEmitter/Turn usage only in some implementation/test paths, while leaving several ACP/graph/workflow surfaces thin or on legacy helper paths such as emit_langgraph_messages rather than consistently adopting the claimed UnifiedEmitter/Turn/convert_* contract.
    • Fix
      • Update the affected tutorial files to use the unified harness contract at the intended layer, e.g. wrap provider streams/messages in the appropriate Turn classes, route delivery through UnifiedEmitter.auto_send_turn, and use the matching convert_* helpers where the tutorial contract says conversion should be demonstrated. If ACP files are intentionally thin and should not import these helpers, narrow the migration contract/tests so ACP is not claimed as part of the unified surface.

    T-Rex Ran code and verified through T-Rex

Reviews (8): Last reviewed commit: "fix(tutorials): pass deterministic creat..." | Re-trigger Greptile

@declan-scale declan-scale force-pushed the declan-scale/tutorials-unified-surface branch from f35bef7 to b3efb55 Compare June 23, 2026 13:54
@declan-scale declan-scale changed the base branch from next to declan-scale/harness-test-fakes June 23, 2026 13:54
@declan-scale declan-scale force-pushed the declan-scale/tutorials-unified-surface branch from b3efb55 to 1e6b84c Compare June 23, 2026 15:22
@declan-scale declan-scale changed the title refactor(tutorials)!: migrate to the unified harness surface + renumber refactor(tutorials): migrate to the unified harness surface + renumber Jun 23, 2026
@declan-scale

Copy link
Copy Markdown
Contributor Author

@greptile review

1 similar comment
@declan-scale

Copy link
Copy Markdown
Contributor Author

@greptile review

@declan-scale declan-scale force-pushed the declan-scale/harness-test-fakes branch from 5953df6 to a84f83b Compare June 23, 2026 19:34
@declan-scale declan-scale force-pushed the declan-scale/tutorials-unified-surface branch 2 times, most recently from 5f23b89 to f06e819 Compare June 23, 2026 19:56

@danielmillerp danielmillerp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok yes please

Base automatically changed from declan-scale/harness-test-fakes to next June 23, 2026 20:10
Retire the duplicate pre-unified `harness_*` tutorials and migrate every
tutorial onto the canonical unified harness surface (UnifiedEmitter / Turn /
convert_* helpers). Renumber onto the `NNN_<name>` paradigm, fixing the
060/130/140 collision; codex takes fresh 070/140/150 slots.

Non-breaking: example sources only; no shipped SDK API changes. The unified
surface already exists; the deprecated tracing handlers are still present and
are removed in a follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@declan-scale declan-scale force-pushed the declan-scale/tutorials-unified-surface branch from f06e819 to 9b36537 Compare June 23, 2026 20:13
Comment thread examples/tutorials/10_async/10_temporal/130_langgraph/manifest.yaml
Comment thread examples/tutorials/10_async/10_temporal/130_langgraph/manifest.yaml
declan-scale and others added 3 commits June 23, 2026 16:24
… at130-langgraph

Greptile review on #428: the 130_langgraph temporal tutorial's graph.py builds
ChatOpenAI(model=MODEL_NAME) but the manifest only mapped REDIS_URL, so a
deployed worker would fail on its first model call. Add the OPENAI_API_KEY
credential and the deployment.global.agent name/description block to match the
sibling migrated tutorials (e.g. at110-pydantic-ai).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Greptile review on #428: the sync/async/temporal codex tutorials spawn
`codex exec --json` but their Dockerfiles installed only OS+Python deps, so a
live request/activity would fail with codex not on PATH. Add nodejs/npm and
`npm install -g @openai/codex` to all three images.

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

Greptile review on #428: thread workflow.now() through RunHarnessAgentParams to
auto_send_turn so a retried activity re-emits the turn's messages with stable
timestamps instead of new server-side ones (which could reorder/duplicate them).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@declan-scale declan-scale merged commit ebaf617 into next Jun 23, 2026
96 of 98 checks passed
@declan-scale declan-scale deleted the declan-scale/tutorials-unified-surface branch June 23, 2026 20:52
@stainless-app stainless-app Bot mentioned this pull request Jun 23, 2026
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