Podium Integration

Podium is the agent orchestration platform that manages compute instances, agent lifecycles, and message routing. Diminuendo connects to Podium as an upstream service — creating compute instances for coding agents, establishing WebSocket connections to stream events, and proxying file operations to agent workspaces.

PodiumClient

The PodiumClient is an Effect service (Context.Tag) that encapsulates all communication with the Podium coordinator. It provides five core operations:
export class PodiumClient extends Context.Tag("PodiumClient")<PodiumClient, {
  readonly createInstance: (params: {
    agentType: string
    agentId?: string
    secrets?: Record<string, string>
    environment?: Record<string, string>
  }) => Effect.Effect<{ instanceId: string; deploymentId: string }, PodiumConnectionError>

  readonly connect: (instanceId: string) => Effect.Effect<PodiumConnection, PodiumConnectionError>

  readonly isAlive: (instanceId: string) => Effect.Effect<boolean>

  readonly stopInstance: (instanceId: string) => Effect.Effect<void, PodiumConnectionError>

  readonly listFiles: (instanceId: string, path?: string, depth?: number) => Effect.Effect<FileEntry[], PodiumConnectionError>
  readonly readFile: (instanceId: string, path: string) => Effect.Effect<FileContent, PodiumConnectionError>
  readonly fileHistory: (instanceId: string, path: string) => Effect.Effect<IterationMeta[], PodiumConnectionError>
  readonly fileAtIteration: (instanceId: string, path: string, iteration: number) => Effect.Effect<FileContent, PodiumConnectionError>
}>() {}
The PodiumClientLive layer reads PODIUM_URL and PODIUM_API_KEY from the application config and provides the concrete implementation.

Instance Lifecycle

The lifecycle of an agent instance follows a predictable pattern:
1

Create Instance

When a user starts a session (or an inactive session is reactivated), the gateway calls createInstance with the agent type, deployment descriptor, secrets, and environment variables.
POST /api/v1/instances
{
  "deployment_id": "coding-agent:1.0.0@local",
  "agent_id": "optional-agent-id",
  "secrets": { ... },
  "environment": { ... }
}
→ { "instance_id": "inst-abc123", "deployment_id": "..." }
The deployment ID is constructed as {agentType}:1.0.0@local from the session’s agent type.
2

Connect via WebSocket

After instance creation, the gateway establishes a WebSocket connection to the instance’s event stream:
ws://{PODIUM_URL}/api/v1/instances/{instanceId}/connect
This returns a PodiumConnection object with methods to send messages, access the event stream, and close the connection.
3

Stream Events and Send Messages

The connection remains open for the duration of the session. Messages from the user are sent via sendMessage(content). Events from the agent arrive through the events stream (Stream.Stream<PodiumEvent>).
4

Stop Instance

When a session is deactivated or the gateway shuts down, the instance is stopped:
DELETE /api/v1/instances/{instanceId}
A 404 response is treated as success (the instance may have already been reclaimed).

PodiumConnection

The PodiumConnection interface represents an active WebSocket connection to a Podium instance:
export interface PodiumConnection {
  readonly instanceId: string
  readonly sendMessage: (content: string, metadata?: Record<string, unknown>) =>
    Effect.Effect<void, PodiumConnectionError>
  readonly sendRaw: (data: string) =>
    Effect.Effect<void, PodiumConnectionError>
  readonly events: Stream.Stream<PodiumEvent, PodiumConnectionError>
  readonly close: Effect.Effect<void>
}
Messages are sent as JSON with a process_message envelope:
{
  "type": "process_message",
  "content": {
    "text": "Fix the authentication bug in auth.ts",
    ...metadata
  }
}

PodiumEvent

Events arriving from Podium have a simple structure:
export interface PodiumEvent {
  readonly messageType: string
  readonly content?: Record<string, unknown>
  readonly agentId?: string
}
The messageType field determines the event category. The content object carries event-specific data. The gateway’s PodiumEventMapper translates these raw events into the typed client-facing protocol.

Event Mapping

The PodiumEventMapper (src/domain/PodiumEventMapper.ts) is a pure function that maps Podium’s 30+ message types to Diminuendo’s 51-event wire protocol. Each Podium event is transformed into one or more client events with session-scoped sequence numbers and timestamps.

Turn Lifecycle

Podium MessageClient EventDescription
created / stream_startturn_startedAgent begins processing
update / stream_updatetext_deltaIncremental text output
complete / stream_end / stream_completeturn_completeAgent finishes processing
errorturn_errorAgent encountered an error

Tool Events

Podium MessageClient EventDescription
tool.call_starttool_call_startTool invocation begins
tool.call_deltatool_call_deltaStreaming tool arguments
tool.calltool_callComplete tool invocation with args
tool.resulttool_resultTool execution result
tool.errortool_errorTool execution failed

Interactive Events

Podium MessageClient EventDescription
tool.question_requestedquestion_requestedAgent needs user input
tool.permission_requestedpermission_requestedAgent needs permission for an action
tool.approval_resolvedapproval_resolvedPermission request resolved

Thinking Events

Podium MessageClient EventDescription
thinking.startthinking_startAgent begins reasoning
thinking.progress / thinking_updatethinking_progressIncremental thinking text
thinking.completethinking_completeReasoning phase complete

Terminal Events

Podium MessageClient EventDescription
terminal.streamterminal_streamTerminal output data
terminal.completeterminal_completeCommand execution finished (with exit code)

Sandbox Events

Podium MessageClient EventDescription
sandbox.provisioningsandbox_provisioningSandbox environment being set up
sandbox.initsandbox_readySandbox ready for use
sandbox.removedsandbox_removedSandbox has been torn down

Session Lifecycle

Podium MessageClient EventDescription
terminatingsession_state (state: terminated)Agent shutting down
terminatedsession_state (state: terminated)Agent process exited

Usage Events

Podium MessageClient EventDescription
usage / usage.updateusage_updateToken counts, model, cost per turn
context / usage.contextusage_contextTotal token usage and context window
The usage_update event carries model, provider, input_tokens, output_tokens, cached_tokens, and cost_micro_dollars. The usage_context event carries total_tokens, max_tokens, and percent_used.
Any Podium event that does not match a known message type but contains text content is treated as a text_delta fallback. Events with no matching type and no content are silently dropped (mapped to an empty array).

File Operations

File access messages from clients (list_files, read_file, file_history, file_at_iteration) are proxied through to Podium’s REST file API:
Client MessagePodium API
list_filesGET /api/v1/instances/{id}/files?path={path}&depth={depth}
read_fileGET /api/v1/instances/{id}/files/{path}
file_historyGET /api/v1/instances/{id}/files/{path}/history
file_at_iterationGET /api/v1/instances/{id}/files/{path}/at/{iteration}
All file API calls use a 15-second timeout and propagate PodiumConnectionError on failure.

Resilience

The PodiumClient is wrapped with resilience patterns to handle transient failures:

Circuit Breaker

The createInstance method is protected by a circuit breaker with the following parameters:
ParameterValue
Failure threshold5 consecutive failures
Cooldown period30 seconds
Reset behaviorHalf-open after cooldown; first success closes the breaker
When the circuit breaker is open, createInstance calls fail immediately with a PodiumConnectionError rather than attempting the HTTP request.

Exponential Retry

Failed requests are retried with exponential backoff and jitter:
ParameterValue
Initial delay500ms
Backoff schedule500ms, 1s, 2s, 4s
Max retries3
JitterApplied to each delay

Connection Deduplication

The gateway prevents parallel createInstance calls for the same session. If session A is already in the activating state (meaning a createInstance call is in flight), a second activation request for the same session will wait for the first call to complete rather than issuing a duplicate request to Podium.

Health Probe

The isAlive(instanceId) method checks whether a Podium instance is still running:
isAlive: (instanceId) => Effect.tryPromise({
  try: async () => {
    const res = await fetch(`${podiumUrl}/api/v1/instances/${instanceId}`, {
      headers: authHeaders,
      signal: AbortSignal.timeout(5000),
    })
    return res.ok
  },
  catch: () => false,
})
This 5-second timeout probe is used by:
  • The /health endpoint to assess Podium availability
  • The stale session recovery mechanism to determine whether a session’s Podium instance survived a gateway restart (it never does — Podium connections do not survive process death)