Skip to main content
All posts
Agents3 min read

The Question State Machine

How pending, answered, expired, and cancelled states keep the Question flow safe and understandable.

Editorial illustration for The Question State Machine

The Question primitive only works if its state machine is strict.

Humans will tap stale notifications. Agents will retry requests. Mobile devices will deliver duplicate actions. Expiry sweeps may run near the same time as an answer. A room-scope question might receive multiple taps in the same second.

The protocol needs to turn that messy reality into one clear outcome.

The Four States

The full state machine is:

pending -> answered
pending -> expired
pending -> cancelled

The terminal states are:

answered
expired
cancelled

There is no transition out of a terminal state. No endpoint should resurrect a question, change an answer, or convert an expired question into an answered one after the deadline.

Pending

pending means the question is open.

The system has persisted it, enqueued or delivered the Ping, and is waiting for a valid answer, cancellation, or expiry. During pending, the app can show answer buttons, the notification can show actions, and the asker can long-poll the wait endpoint.

Pending is the only state where an answer is accepted.

Answered

answered means one valid responder chose one valid option.

For direct scope, the responder must be the target user. For room scope, the responder must be an eligible room member. For v1, the first valid room answer wins.

The server must record the chosen option, responder identity, and timestamp atomically. The responder identity must come from authentication, never from the request body.

Expired

expired means the deadline passed with no valid answer.

This is a real result, not an absence of result. The asker needs to distinguish "human chose no" from "human did not answer." Those outcomes drive different workflows.

An expiry sweep should safely flip overdue pending rows to expired. It should be idempotent and safe to run repeatedly.

Cancelled

cancelled means the asker withdrew the question before it was answered or expired.

Cancellation is useful when the workflow changes. Maybe the deploy was cancelled from another system. Maybe the agent found a safer path. Maybe the question was sent to the wrong room.

After cancellation, notification actions become no-ops and waiters receive a cancelled result.

Race Handling

The hard part is not the diagram. It is concurrency.

The answer operation should be an atomic conditional update:

update questions
set state = 'answered', answer = ...
where id = ? and state = 'pending'

Only one caller can win. Everyone else receives the already-resolved state.

Expiry and cancellation use the same pattern. They only transition rows that are still pending. If an answer committed first, the sweep skips it. If cancellation committed first, the answer path returns cancelled.

Human Experience

Race handling should not punish the human.

If a person taps a stale notification, they should see that the question was already answered, expired, or cancelled. That is not an error in the human sense. It is just the state of the workflow.

The app should update the in-app cell and, where possible, the notification state so the stale action does not keep inviting taps.

Agent Experience

The agent should get the same terminal envelope from every result path.

Long-poll wait, fetch, list, and outgoing-webhook events should all represent the same final state. That consistency lets SDKs stay simple.

The Rule

Every Question has exactly one terminal story.

It was answered by this responder with this option, expired without an answer, or was cancelled by the asker. The protocol should make that story impossible to confuse.

Mahdi Salmanzade

The Ping that cuts through.

Keep reading

Product

Share a Point, Not a Trail

A custom Ping or Question can now carry one deliberately chosen place, with a quiet feed preview, a useful map sheet, and no background location trail.

3 min read
Product

Urgent Is Not Confirmation

Urgent now controls time-sensitive delivery; confirmation asks for a recorded human response. Either can stand alone, and a Ping can carry both when the moment needs both.

3 min read