Skip to main content
An installed agent answers in threads: a reply chain or a ticket in a provider your integration bridges. A ConversationBinding is a governed projection from one external conversation to one Session. An interaction binding turns “a message arrived in this thread” into “drive this Session”; a delivery binding is outbound-only. A binding maps (ExternalInstallation, canonical conversation key) to exactly one Session and the SurfaceScope it lives in. Its projection policy explicitly selects event classes. question, result, and failure are durable projections; progress is an ephemeral live hint. The binding stores only projection message IDs, a reconciliation cursor, and the last projected canonical sequence, never a transcript. The Session event log remains the single source of truth; provider delivery is a downstream projection of that log.

The canonical conversation key

Providers model threads differently; Checkfu normalizes them into one key with three kinds: Only a thread carries a thread_external_id; the shape is enforced. Your provider adapter is responsible for producing the canonical key from the provider’s own thread identifiers.

Resolve, don’t create

On the live ingress path you call one endpoint per inbound message: resolve. It creates the Session on the first message of a conversation and returns the existing binding on every message after.
You do not pass a session. Resolve derives and admits the installed Session for you, and forbids a caller-supplied session_id, agent_deployment_id, agent_deployment_revision_number, version_policy, acted_as, harness, model_routing_profile_key, sandbox_profile_key, mounts, or caused_by. It returns a ConversationBinding at 200, carrying the session_id to drive. Because resolve runs a full installed admission, it can return admission errors like 422 model.no_eligible_model, the same failures a Session create would.

Racing first messages converge

Two people can hit send in the same fresh thread at the same instant. Without care, that is two Sessions for one conversation. Checkfu makes them converge, and the mechanism is worth understanding because it is not a caller idempotency key. The Session ID is derived deterministically from the Workspace, the ExternalInstallation, and the complete canonical conversation key. Both racers therefore select the same Session before either can mint a competing one, and a unique binding name claims the conversation. The first resolver freezes the installed admission and the initial requester; every later caller, including a different person, receives the existing binding and does not rewrite Session identity or mounts. The ordering is crash-safe: a crash can leave an unbound Session that the next request completes, but never an active binding to a Session that was not durably initialized.
An optional Idempotency-Key still gives you HTTP response replay, but it is not what makes two authors converge; the deterministic Session ID is. You get convergence even when the two messages carry different idempotency keys.
The message that lands a moment later is covered too. A user.message authored while the Session’s first Run is still queued or provisioning is accepted, appended to the log, and ordered into the next Run. Checkfu does not refuse it with 409 runtime.invalid_transition merely because the sandbox has not finished starting. A fast follow-up in a live thread therefore needs no client-side retry logic.

Shared threads authorize each author

One conversation is one Session with one frozen service identity. A shared thread has many human authors, and each is checked on their own. The author who wins the race becomes the initial requester. After that, every inbound message asserts its own authored_by Principal, and Checkfu rechecks that author’s PermissionAssignments: a turn-starting user.message requires invoke on the Agent and the surface; driving an in-flight Run requires steer. The binding is not a blanket pass: it routes messages to a Session, and the Session’s governance still applies to each author. Your backend authenticates its own users and asserts each author as the authored_by Principal; Checkfu never authenticates end users itself.

The administrative create

POST /v1/conversation-bindings (without /resolve) attaches an existing Session (one you already hold the session_id for) to another governed projection and returns 201.
  • An installed Session may gain another matching interaction or delivery projection.
  • A direct API Session may gain only a delivery binding. The request must name the active agent_installation_id that governs the exact installation and surface.
  • A delivery binding never admits provider input. ConversationIngest fails closed if its canonical conversation resolves to one.
This is how one Session remains one event log while appearing in several external destinations. Your provider credential remains in your own integration; neither the Session nor the harness receives it.

Next steps

Add an agent to your app

Placement, binding, and driving a thread, end to end.

Events

Drive the Session the binding returned, and follow its log.