Skip to main content
Formation’s generic connector exposes a durable application feed. The agent continues working in Temporal when an application disconnects. Postgres owns both the materialized public conversation and its ordered event journal. Read the design rationale for the philosophy, privacy boundaries, and ELI5 explanation.

Opt in per connection

New and existing connections default to buffered answers. In the operator UI, open an agent’s connection and enable Stream answers, or use the existing operator session/API-token authorization with:
Slack and Teams connections also accept progress_enabled (boolean) and scope (all, chats, or direct) in the same request. Progress is independent of answer streaming and preserves existing enabled behavior by default. Scope applies to both features; final replies are unaffected. Omitted options retain their saved values, so existing clients remain compatible. These advanced options are not accepted for other connector types. The response contains connector.streaming_enabled, connector.progress_enabled, connector.streaming_scope, and status (active or saved_restart_required). Only operators with access to the owning agent can change its Slack, Telegram, or generic installation. Settings survive credential rotation and connection edits. Disabling applies to new turns after reload; it does not cancel an in-flight turn. A restart is required when reload fails. Opt-in enables the public-answer handoff and incremental inference. With it off, existing model requests and whole-message delivery remain buffered. Generic snapshot/replay still provides canonical messages and authorized progress; webhook payloads and whole-response delivery do not change.

Native messaging

Slack uses chat.startStream, chat.appendStream, and chat.stopStream with that installation’s bot credentials (chat:write). The validated final first part updates the same Slack message; further parts use normal delivery. Drafts are provisional and may be corrected. Public-channel streams include the requesting user and installation’s workspace as recipient context. Telegram uses sendMessageDraft in private chats only. It sends plain-text previews, then the existing formatted sendMessage path persists the final answer. Group replies stay buffered. No draft stop/cancellation button is exposed. Both transports coalesce updates to approximately one every three seconds, with a final flush. If a draft exceeds the provider’s limit or its API rejects an update, further previews stop and the canonical reply path remains authoritative. Failed model attempts remove known Slack previews or clear Telegram drafts best-effort; retries do not append a different generation onto the old preview. Native APIs do not provide the SSE journal’s replay guarantees. Formation reserves creation and append before sending and retains known message IDs across worker restarts. It never blindly repeats an uncertain Slack creation or append. If Slack accepts a creation but its response or the local ID commit is lost, a partial preview can remain alongside the eventual answer; Slack expires the stream after ten minutes. Telegram drafts expire automatically. Lost ownership prevents cleanup by the old worker. Operators should test with a dedicated connection before enabling this on an existing conversation. The native transport’s saved preview text follows the configured run-ledger body retention window after its run becomes terminal. Active and recent runs retain that text; a zero window keeps it. Delivery identity and uncertain-send reservation metadata remain available for reconciliation until the run is deleted. Official API contracts: Slack streaming and Telegram drafts.

Authentication and commands

Use the generic connector’s existing X-Mesh-Install, X-Mesh-Timestamp, and X-Mesh-Signature headers. Sign the exact POST body with the installation’s secret using the normal v1:in construction. The body binds the operation, conversation, actor, and replay cursor. Keep signing secrets in the application’s backend; never ship them to a browser or mobile app. The application backend derives actor_id from its authenticated user. It must not accept a client-supplied actor or installation as authority. Formation checks that this is a private conversation for that actor, in that installation and agent’s perspective. Mixed or unverifiable audiences do not expose a feed. Current source disclosure is rechecked for snapshots and each replay page, including previously delivered progress. Disabled agents and rotated or removed connections stop active subscriptions. Send messages through POST /generic/events with stable message_id values for idempotency. Subscribing does not submit a command, cancel a run, approve a tool, or start a model call. This protocol does not add cancellation, attachment, or approval command APIs; applications must not advertise those capabilities based on streaming support alone.

Snapshot

POST /generic/conversations/snapshot:
The response is an application snapshot:
The sequence and state come from one repeatable database snapshot. Start replay after that sequence. An unknown or inaccessible conversation may return an empty snapshot; it never exposes another actor’s state. A new message becomes visible when durable intake captures it into the conversation graph.

Subscription and replay

POST /generic/conversations/stream:
The response uses Content-Type: text/event-stream, Cache-Control: private, no-store, and X-Accel-Buffering: no. Disable buffering in any application proxy. Use a streaming HTTP client rather than a short JSON-request timeout.
id is the decimal sequence, never the event UUID. Numbers stay within the JavaScript safe-integer range. If sending Last-Event-ID, it must exactly match after_sequence in the newly signed request. The server does not trust an unsigned header to change the read scope or cursor. The current event types are: A streaming message carries status: streaming, a text part with streaming: true, its runId, and an attemptId. A new model attempt replaces the draft with an empty message.upsert and a different attempt ID. Never concatenate text from different attempts. A validated canonical reply replaces the draft at the same message ID and marks it complete. An interrupted or failed terminal run marks any remaining draft failed. Draft text is provisional; replacing it cannot undo what a person has already seen. Incoming IDs are message:<message_id>, replies are reply:<parent_message_id>, and approved progress has its own progress:<event_id>. Messages are ordered by their first appearance; replacing a draft does not move it to the bottom. Run states are queued, running, waiting_input, completed, failed, and cancelled. Socket EOF does not mean a run completed. Preserve the last successfully applied sequence and reconnect with a fresh signature. Replay can include later messages and runs in the same conversation after a run completes. Subscriptions last at most two minutes, with comment heartbeats every fifteen seconds and five-second write deadlines. A process admits at most 128 concurrent subscriptions, with a default of 16 per installation (configure MESH_GENERIC_MAX_STREAMS_PER_INSTALL). Slots are released when subscriptions close. Reconnect with bounded jittered backoff after transport failure or 429/503; authentication and authorization errors require fixing access. An application backend must tie its proxy subscription to the client’s connection, while keeping command execution independent of that connection. The journal retains roughly 4,096 events per conversation, pruning in batches of 256. A cursor below the retained floor or ahead of the current watermark receives HTTP 410 before streaming starts, without an SSE control frame. On HTTP 410, fetch a new signed snapshot, replace local state, and reconnect with that snapshot’s sequence; do not retry the expired cursor. If retention overtakes an open subscriber, the server emits event: resnapshot with data: {} and closes. This is a transport control event, not an application event: fetch a new snapshot and replace local state before resuming. Never advance the cursor across a gap or feed this control frame into a domain-event reducer.

What streams

The engine offers begin_answer on eligible application turns. The agent calls it alone once work and verification are finished. Its normal tool receipt makes the handoff recoverable; the next inference has no tools and emits public answer text. This adds an explicit model decision boundary rather than exposing private assistant commentary that may precede a tool call. If a model answers directly without the handoff, the validated answer still arrives as a complete upsert. OpenRouter, OpenAI-compatible Chat Completions, and the Responses adapter decode incrementally through the existing preparation, routing, audit, retry, and usage paths. Native reasoning fields and tool arguments never become answer deltas. Provider truncation is an error even when the transport returns EOF cleanly. Final usage remains provider-reported, including usage received before failure. Control-envelope continuations and unsupported text tool protocols retain final validation. The incremental reply guard holds text from the first brace, angle bracket, or backtick until final validation, so a split JSON envelope, think tag, or code fence cannot leak control syntax. Ordinary prose before that boundary can stream; code and structured answers settle through the canonical upsert. Approved report_progress notices also appear in the feed. Raw tool execution logs, reasoning, arguments, and internal activity labels remain private.

Storage and deployment

The forward migration installs a projection and journal plus transactional hooks for conversation messages, run status, and authorized progress. It backfills existing generic conversation messages and run states. Test migration time on a representative database before upgrading a large installation. Streaming writes use the existing execution ownership fence and current source policy in the publication transaction. Public state and event sequence commit atomically. Concurrent writers serialize on the conversation projection row; rolled-back writes cannot leave a cursor hole. Readers poll committed pages at 250 ms intervals. This works across API replicas and with Formation’s transaction-pool database configuration without persistent LISTEN connections or a new broker. Replay pages contain at most 128 events and normally at most 256 KiB (a larger single canonical message is returned alone). A slow consumer reconnects from durable history. The retained snapshot and provenance remain durable after event pruning. Future notification fan-out can reduce read traffic without changing the replay contract.