David
Blumenau
Menu
Feature atlas
EngineThree engines, one chat contract

Change the runtime. Keep a readable chat.

The problem

Supporting several agent runtimes can fragment the chat UI and force client code to understand each engine's streaming protocol.

Three adapters, inspected

Different messages.
A familiar conversation.

A reply, a tool at work, an answer. Each runtime describes those moments differently. These saved translations show how they become readable chat events. The engines share event names; their fields and capabilities still differ.

Saved offline translation · synthetic input · no model or tool runs here.

Choose a saved translation to inspect

Claude SDK messages

A stream and its final snapshot.

The SDK sends a text delta, then a complete assistant message. The adapter keeps the readable text from appearing twice.

What the adapter receives

  1. 01

    Text delta

  2. 02

    Assistant message + tool block

  3. 03

    User message + result block

  4. 04

    Terminal result message

What the chat can show Saved output

  1. textA readable reply
    Checking the fixture.
  2. tool_useBash starts
    printf 'fixture-ok\n'
  3. tool_resultThe tool returns
    fixture-ok
    
  4. doneThe turn finishes
    Fixture complete.

The sample command is text in the fixture. It is never executed.

The useful difference

One sentence, once.

The saved SDK delta carries “Checking the fixture.” The next assistant message includes that same sentence and a tool call. The adapter emits the tool call without repeating the sentence.

  1. Text delta: Checking the fixture.text · Checking the fixture.
  2. Full message: same text + tool blocktool_use · Bash (no repeated text)

Claude source proof uses real SDK message classes with mocked SDK transport and the source’s unit-harness branch. It checks translation inside invocation; it does not run the lifetime dispatcher or CLI.

Inspect the saved input and output

Selected records from this offline capture. Empty output means no event was emitted. The receipt defines the field selection; retained values are unchanged. Any /tmp/fixture-workspace path is synthetic.

[
  {
    "input_index": 1,
    "input": {
      "sdk_type": "StreamEvent",
      "fields": {
        "uuid": "synthetic-delta-0-21",
        "session_id": "synthetic-claude-session",
        "parent_tool_use_id": null,
        "event": {
          "type": "content_block_delta",
          "index": 0,
          "delta": {
            "type": "text_delta",
            "text": "Checking the fixture."
          }
        }
      }
    },
    "outputs": [
      {
        "type": "text",
        "content": "Checking the fixture.",
        "accumulated": "Checking the fixture.",
        "parent_tool_use_id": null,
        "session_id": "synthetic-claude-session"
      }
    ]
  },
  {
    "input_index": 2,
    "input": {
      "sdk_type": "AssistantMessage",
      "fields": {
        "content": [
          {
            "sdk_type": "TextBlock",
            "fields": {
              "text": "Checking the fixture."
            }
          },
          {
            "sdk_type": "ToolUseBlock",
            "fields": {
              "id": "synthetic-call-1",
              "name": "Bash",
              "input": {
                "command": "printf 'fixture-ok\\n'"
              }
            }
          }
        ],
        "model": "synthetic-model",
        "parent_tool_use_id": null
      }
    },
    "outputs": [
      {
        "type": "tool_use",
        "tool_use_id": "synthetic-call-1",
        "name": "Bash",
        "input": {
          "command": "printf 'fixture-ok\\n'"
        },
        "parent_tool_use_id": null,
        "ui_resource_uri": null
      }
    ]
  },
  {
    "input_index": 3,
    "input": {
      "sdk_type": "UserMessage",
      "fields": {
        "content": [
          {
            "sdk_type": "ToolResultBlock",
            "fields": {
              "tool_use_id": "synthetic-call-1",
              "content": "fixture-ok\n",
              "is_error": false
            }
          }
        ],
        "parent_tool_use_id": null
      }
    },
    "outputs": [
      {
        "type": "tool_result",
        "tool_use_id": "synthetic-call-1",
        "content": "fixture-ok\n",
        "is_error": false,
        "parent_tool_use_id": null,
        "ui_resource_uri": null
      }
    ]
  },
  {
    "input_index": 6,
    "input": {
      "sdk_type": "ResultMessage",
      "fields": {
        "subtype": "success",
        "session_id": "synthetic-claude-session",
        "result": "Fixture complete.",
        "is_error": false
      }
    },
    "outputs": [
      {
        "type": "done",
        "session_id": "synthetic-claude-session",
        "result": "Fixture complete.",
        "final_text": "Fixture complete.",
        "is_error": false,
        "subtype": "success"
      }
    ]
  }
]

Codex JSON-RPC notifications

Several notifications. One tool result.

Text, tool starts and tool completion arrive as separate notifications. The adapter gathers them into events a chat can show.

What the adapter receives

  1. 01

    Agent-message delta

  2. 02

    Command item started

  3. 03

    Output chunks + item completed

  4. 04

    Turn completed

What the chat can show Saved output

  1. textA readable reply
    Checking the fixture.
  2. tool_useBash starts
    printf 'fixture-ok\n'
  3. tool_resultThe tool returns
    fixture-ok
    
  4. doneThe turn finishes
    Fixture complete.

The sample command is text in the fixture. It is never executed.

The useful difference

Wait for the complete result.

Two command-output chunks arrive before completion. They produce no chat event yet. When the command completes, the adapter combines the saved chunks into one tool result.

  1. Output chunk: “fixture-”No event yet · buffered
  2. Output chunk: “ok\n”No event yet · buffered
  3. Command item completedtool_result · “fixture-ok\n”

Recorded by calling the unchanged JSON-RPC notification translator directly. No app-server, command or HTTP stream was run.

Inspect the saved input and output

Selected records from this offline capture. Empty output means no event was emitted. The receipt defines the field selection; retained values are unchanged. Any /tmp/fixture-workspace path is synthetic.

[
  {
    "input_index": 2,
    "input": {
      "method": "item/agentMessage/delta",
      "params": {
        "threadId": "synthetic-codex-thread",
        "turnId": "synthetic-codex-turn",
        "itemId": "synthetic-commentary",
        "delta": "Checking the fixture."
      }
    },
    "outputs": [
      {
        "type": "text",
        "content": "Checking the fixture.",
        "accumulated": "Checking the fixture.",
        "phase": "commentary",
        "session_id": "synthetic-codex-thread"
      }
    ]
  },
  {
    "input_index": 4,
    "input": {
      "method": "item/started",
      "params": {
        "threadId": "synthetic-codex-thread",
        "turnId": "synthetic-codex-turn",
        "item": {
          "type": "commandExecution",
          "id": "synthetic-call-1",
          "command": "printf 'fixture-ok\\n'",
          "cwd": "/tmp/fixture-workspace"
        }
      }
    },
    "outputs": [
      {
        "type": "tool_use",
        "tool_use_id": "synthetic-call-1",
        "name": "Bash",
        "input": {
          "command": "printf 'fixture-ok\\n'",
          "cwd": "/tmp/fixture-workspace"
        },
        "session_id": "synthetic-codex-thread"
      }
    ]
  },
  {
    "input_index": 5,
    "input": {
      "method": "item/commandExecution/outputDelta",
      "params": {
        "threadId": "synthetic-codex-thread",
        "turnId": "synthetic-codex-turn",
        "itemId": "synthetic-call-1",
        "delta": "fixture-"
      }
    },
    "outputs": []
  },
  {
    "input_index": 6,
    "input": {
      "method": "item/commandExecution/outputDelta",
      "params": {
        "threadId": "synthetic-codex-thread",
        "turnId": "synthetic-codex-turn",
        "itemId": "synthetic-call-1",
        "delta": "ok\n"
      }
    },
    "outputs": []
  },
  {
    "input_index": 7,
    "input": {
      "method": "item/completed",
      "params": {
        "threadId": "synthetic-codex-thread",
        "turnId": "synthetic-codex-turn",
        "item": {
          "type": "commandExecution",
          "id": "synthetic-call-1",
          "exitCode": 0,
          "status": "success"
        }
      }
    },
    "outputs": [
      {
        "type": "tool_result",
        "tool_use_id": "synthetic-call-1",
        "content": "fixture-ok\n",
        "is_error": false,
        "session_id": "synthetic-codex-thread"
      }
    ]
  },
  {
    "input_index": 9,
    "input": {
      "method": "item/agentMessage/delta",
      "params": {
        "threadId": "synthetic-codex-thread",
        "turnId": "synthetic-codex-turn",
        "itemId": "synthetic-final",
        "delta": "Fixture "
      }
    },
    "outputs": [
      {
        "type": "text",
        "content": "Fixture ",
        "accumulated": "Fixture ",
        "phase": "final_answer",
        "session_id": "synthetic-codex-thread"
      }
    ]
  },
  {
    "input_index": 10,
    "input": {
      "method": "item/agentMessage/delta",
      "params": {
        "threadId": "synthetic-codex-thread",
        "turnId": "synthetic-codex-turn",
        "itemId": "synthetic-final",
        "delta": "complete."
      }
    },
    "outputs": [
      {
        "type": "text",
        "content": "complete.",
        "accumulated": "Fixture complete.",
        "phase": "final_answer",
        "session_id": "synthetic-codex-thread"
      }
    ]
  },
  {
    "input_index": 12,
    "input": {
      "method": "turn/completed",
      "params": {
        "threadId": "synthetic-codex-thread",
        "turnId": "synthetic-codex-turn",
        "turn": {
          "id": "synthetic-codex-turn",
          "status": "completed",
          "durationMs": 30
        }
      }
    },
    "outputs": [
      {
        "type": "done",
        "session_id": "synthetic-codex-thread",
        "result": "Fixture complete.",
        "final_text": "Fixture complete.",
        "is_error": false
      }
    ]
  }
]

OpenCode Message and part events

Keep the answer. Skip the echo.

OpenCode sends message parts as accumulated snapshots. The adapter remembers each part and emits only the new text.

What the adapter receives

  1. 01

    Assistant text part

  2. 02

    Tool part: running

  3. 03

    Tool part: completed

  4. 04

    Session idle

What the chat can show Saved output

  1. textA readable reply
    Checking the fixture.
  2. tool_useBash starts
    printf 'fixture-ok\n'
  3. tool_resultThe tool returns
    fixture-ok
    
  4. doneThe turn finishes
    Fixture complete.

The sample command is text in the fixture. It is never executed.

The useful difference

A repeated part adds nothing.

The final text part grows from “Fixture ” to “Fixture complete.” A later copy contains no new text, so the adapter emits nothing. The user’s prompt echo is also kept out of the reply.

  1. Part: “Fixture ”text · “Fixture ”
  2. Same part: “Fixture complete.”text · “complete.”
  3. Same part sent againNo event · nothing added

Recorded by calling the unchanged message-part translator directly. No event-bus connection, OpenCode server or HTTP stream was run.

Inspect the saved input and output

Selected records from this offline capture. Empty output means no event was emitted. The receipt defines the field selection; retained values are unchanged. Any /tmp/fixture-workspace path is synthetic.

[
  {
    "input_index": 2,
    "input": {
      "type": "message.part.updated",
      "properties": {
        "part": {
          "id": "synthetic-user-part",
          "sessionID": "synthetic-opencode-session",
          "messageID": "synthetic-user",
          "type": "text",
          "text": "Synthetic user prompt must not be streamed."
        }
      }
    },
    "outputs": []
  },
  {
    "input_index": 4,
    "input": {
      "type": "message.part.updated",
      "properties": {
        "part": {
          "id": "synthetic-commentary-part",
          "sessionID": "synthetic-opencode-session",
          "messageID": "synthetic-commentary",
          "type": "text",
          "text": "Checking the fixture."
        }
      }
    },
    "outputs": [
      {
        "type": "text",
        "content": "Checking the fixture.",
        "accumulated": "Checking the fixture.",
        "session_id": "synthetic-opencode-session"
      }
    ]
  },
  {
    "input_index": 5,
    "input": {
      "type": "message.part.updated",
      "properties": {
        "part": {
          "id": "synthetic-tool-part",
          "sessionID": "synthetic-opencode-session",
          "messageID": "synthetic-commentary",
          "type": "tool",
          "callID": "synthetic-call-1",
          "tool": "bash",
          "state": {
            "status": "pending",
            "input": {},
            "raw": ""
          }
        }
      }
    },
    "outputs": []
  },
  {
    "input_index": 6,
    "input": {
      "type": "message.part.updated",
      "properties": {
        "part": {
          "id": "synthetic-tool-part",
          "sessionID": "synthetic-opencode-session",
          "messageID": "synthetic-commentary",
          "type": "tool",
          "callID": "synthetic-call-1",
          "tool": "bash",
          "state": {
            "status": "running",
            "input": {
              "command": "printf 'fixture-ok\\n'"
            },
            "time": {
              "start": 1001
            }
          }
        }
      }
    },
    "outputs": [
      {
        "type": "tool_use",
        "tool_use_id": "synthetic-call-1",
        "name": "Bash",
        "input": {
          "command": "printf 'fixture-ok\\n'"
        },
        "session_id": "synthetic-opencode-session"
      }
    ]
  },
  {
    "input_index": 7,
    "input": {
      "type": "message.part.updated",
      "properties": {
        "part": {
          "id": "synthetic-tool-part",
          "sessionID": "synthetic-opencode-session",
          "messageID": "synthetic-commentary",
          "type": "tool",
          "callID": "synthetic-call-1",
          "tool": "bash",
          "state": {
            "status": "completed",
            "input": {
              "command": "printf 'fixture-ok\\n'"
            },
            "output": "fixture-ok\n",
            "title": "Synthetic fixture",
            "metadata": {},
            "time": {
              "start": 1001,
              "end": 1002
            }
          }
        }
      }
    },
    "outputs": [
      {
        "type": "tool_result",
        "tool_use_id": "synthetic-call-1",
        "content": "fixture-ok\n",
        "is_error": false,
        "session_id": "synthetic-opencode-session"
      }
    ]
  },
  {
    "input_index": 8,
    "input": {
      "type": "message.part.updated",
      "properties": {
        "part": {
          "id": "synthetic-final-part",
          "sessionID": "synthetic-opencode-session",
          "messageID": "synthetic-final",
          "type": "text",
          "text": "Fixture "
        }
      }
    },
    "outputs": [
      {
        "type": "text",
        "content": "Fixture ",
        "accumulated": "Fixture ",
        "session_id": "synthetic-opencode-session"
      }
    ]
  },
  {
    "input_index": 10,
    "input": {
      "type": "message.part.updated",
      "properties": {
        "part": {
          "id": "synthetic-final-part",
          "sessionID": "synthetic-opencode-session",
          "messageID": "synthetic-final",
          "type": "text",
          "text": "Fixture complete."
        }
      }
    },
    "outputs": [
      {
        "type": "text",
        "content": "complete.",
        "accumulated": "Fixture complete.",
        "session_id": "synthetic-opencode-session"
      }
    ]
  },
  {
    "input_index": 11,
    "input": {
      "type": "message.part.updated",
      "properties": {
        "part": {
          "id": "synthetic-final-part",
          "sessionID": "synthetic-opencode-session",
          "messageID": "synthetic-final",
          "type": "text",
          "text": "Fixture complete."
        }
      }
    },
    "outputs": []
  },
  {
    "input_index": 12,
    "input": {
      "type": "session.idle",
      "properties": {
        "sessionID": "synthetic-opencode-session"
      }
    },
    "outputs": [
      {
        "type": "done",
        "session_id": "synthetic-opencode-session",
        "result": "Fixture complete.",
        "is_error": false
      }
    ]
  },
  {
    "input_index": 13,
    "input": {
      "type": "session.idle",
      "properties": {
        "sessionID": "synthetic-opencode-session"
      }
    },
    "outputs": []
  }
]
Source versions and what was checked

On 15 October 2026, unchanged source code translated these new synthetic inputs offline. Separately, all 75 focused original tests passed: Claude 13, Codex 35 and OpenCode 27. Replaying the captures produced identical saved output.

  • Claudebf89abc1646bd12133a0f22bc2d51f3286813250
  • Codexe1553a508a7b6ac6cfdb34b1b51a3be45a91bf7e
  • OpenCode671de66ac4f32b09b2d8b36328030935edc3ac38

These checks establish the saved mappings shown here. They do not establish live inference, HTTP/SSE delivery or full client compatibility. The engines share an event vocabulary; optional fields and capabilities differ. Selecting an engine does not transfer conversation state.

Claude uses mocked SDK transport and the source’s unit-harness branch. Its translation code is unchanged. Cost, token, timing and context fields are omitted from this exhibit; it makes no performance comparison.

Read the concise source receipt

What this makes possible

Claude, Codex and OpenCode report their work differently. The engines translate those messages so the same chat can show text, tools and a finished answer.

Source information sheet

Evidence, implementation & history Implementation inspected

What I checked

New synthetic inputs were translated offline through unchanged Claude bf89abc, Codex e1553a5 and OpenCode 671de66 source. All 75 focused original tests passed separately. Claude used real SDK classes with mocked transport and its unit-harness branch. This is saved translation output, not live inference or a full HTTP/SSE client test.

Release anchor: panel-forge web-v7.83.0, web-v7.91.0; codex-engine v0.6.28; opencode-engine v1.0.0.

Research cut: 15 October 2026. Source areas below are from panel-forge + claude-engine + codex-engine + opencode-engine. Private conversation references are retained in the research ledger and are not published here.

Implementation decisions

Claude, Codex and OpenCode report their work differently. The engines translate those messages so the same chat can show text, tools and a finished answer.

Keep each upstream integration native to its runtime and normalize the client-facing event envelope at the integration boundary.

A path through the feature

  1. The consequence

    Keep each upstream integration native to its runtime and normalize the client-facing event envelope at the integration boundary.

Source areas

  • panel-forge/resources/js/composables/useClaudeEngineStream.ts
  • panel-forge/resources/js/composables/useCodexEngineStream.ts
  • panel-forge/resources/js/composables/useOpenCodeEngineStream.ts
  • claude-engine/claude_engine/invocation.py
  • claude-engine/tests/test_wire_contract.py
  • codex-engine/codex_engine/notifications.py
  • codex-engine/tests/test_notifications.py
  • opencode-engine/src/translator.ts
  • opencode-engine/tests/translator.test.ts

The saved translation exhibit uses new synthetic inputs and actual output from pinned, unchanged source. Its source receipt defines the selected fields and execution limits.

Recorded history

Dates describe the researched record, rather than today’s deployment.

  1. panel-forge web-v7.83.0, web-v7.91.0; codex-engine v0.6.28; opencode-engine v1.0.0