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:
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. 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. 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>).
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 wire protocol. Each Podium event is transformed into one or more client events with session-scoped sequence numbers and timestamps.
Turn Lifecycle
| Podium Message | Client Event | Description |
|---|
created / stream_start | turn_started | Agent begins processing |
update / stream_update | text_delta | Incremental text output |
complete / stream_end / stream_complete | turn_complete | Agent finishes processing |
error | turn_error | Agent encountered an error |
| Podium Message | Client Event | Description |
|---|
tool.call_start | tool_call_start | Tool invocation begins |
tool.call_delta | tool_call_delta | Streaming tool arguments |
tool.call | tool_call | Complete tool invocation with args |
tool.result | tool_result | Tool execution result |
tool.error | tool_error | Tool execution failed |
Interactive Events
| Podium Message | Client Event | Description |
|---|
tool.question_requested | question_requested | Agent needs user input |
tool.permission_requested | permission_requested | Agent needs permission for an action |
tool.approval_resolved | approval_resolved | Permission request resolved |
Thinking Events
| Podium Message | Client Event | Description |
|---|
thinking.start | thinking_start | Agent begins reasoning |
thinking.progress / thinking_update | thinking_progress | Incremental thinking text |
thinking.complete | thinking_complete | Reasoning phase complete |
Terminal Events
| Podium Message | Client Event | Description |
|---|
terminal.stream | terminal_stream | Terminal output data |
terminal.complete | terminal_complete | Command execution finished (with exit code) |
Sandbox Events
| Podium Message | Client Event | Description |
|---|
sandbox.provisioning | sandbox_provisioning | Sandbox environment being set up |
sandbox.init | sandbox_ready | Sandbox ready for use |
sandbox.removed | sandbox_removed | Sandbox has been torn down |
Session Lifecycle
| Podium Message | Client Event | Description |
|---|
terminating | session_state (state: terminated) | Agent shutting down |
terminated | session_state (state: terminated) | Agent process exited |
Usage Events
| Podium Message | Client Event | Description |
|---|
usage / usage.update | usage_update | Token counts, model, cost per turn |
context / usage.context | usage_context | Total 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 Message | Podium API |
|---|
list_files | GET /api/v1/instances/{id}/files?path={path}&depth={depth} |
read_file | GET /api/v1/instances/{id}/files/{path} |
file_history | GET /api/v1/instances/{id}/files/{path}/history |
file_at_iteration | GET /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:
| Parameter | Value |
|---|
| Failure threshold | 5 consecutive failures |
| Cooldown period | 30 seconds |
| Reset behavior | Half-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:
| Parameter | Value |
|---|
| Initial delay | 500ms |
| Backoff schedule | 500ms, 1s, 2s, 4s |
| Max retries | 3 |
| Jitter | Applied 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)