Skip to main content
A channel piece can let participants answer the question tool inside the conversation. Add questions to the piece’s channel definition, next to adapter and identity. The piece draws and reads platform controls, and its hooks return what FrogBot should remember. FrogBot keeps the question’s record, decides which interactions are out of date, checks who is answering, records the answer once, retries a failed update, and continues the agent in the same conversation. A channel conversation is answered only in its channel. The admin chat shows it read-only, and HTTP callers get a channel-chat error.

Where the tool is offered

FrogBot offers the question tool to an agent in a channel conversation only when the piece defines questions and supports({ thread }) does not return false. Use supports to turn questions off where a platform surface cannot show controls. If supports throws, FrogBot logs the error and does not offer questions for that turn.

The question record

FrogBot keeps one record per question call. Every hook after render receives it as question:
Hooks return what FrogBot saves. render returns the first messages and state, and updated and settled may return a change:
Returned messages replace the whole list, so return [...question.messages, next] to add a message, or [next] to replace them all. Returned state replaces the stored state; leave it out to keep it. FrogBot saves the change while it holds the question’s lock, so two fast interactions never overwrite each other. Keep nothing a later hook needs anywhere else. FrogBot passes the record to every hook, on every process and after a restart. A message ID stays linked to its call after it leaves messages, so a click on it still reaches the question and gets stale.

Define the hooks

This piece posts one message per question on a forum-style platform, where participants answer by replying /answer 2:
A reply sent before the current question was posted answers an earlier question, so parse returns stale for it. updated never reads interaction, so the same code works when FrogBot retries it. settled closes every message of the call. updated, settled, rejected, denied, and stale share these arguments: call, client, question, req, and thread. render may cover only some calls. FrogBot renders the rest once the posted calls are answered, which lets a piece ask several calls one at a time. An entry that covers several calls gives each call its own record with the same messages. If render throws, FrogBot posts the questions again with the next message in the conversation. Each call’s input is the validated question input: questions, each with header, question, options, multiple, and custom. Answers use the question tool’s output shape, with each option’s exact label. outcome is { output } for an answer and { dismissed: true } for a dismissal.

Parse interactions

interaction is one of: Keep parse free of side effects. FrogBot may call it more than once for the same event: first to find the question an event belongs to, then again after it has checked access and locked the question. settled is true when the question has already been answered; classify the interaction the same way, and FrogBot sends a match to stale. Return one of: A thread reply that returns anything other than ignore is consumed and is not sent to the agent as a new message.

Out-of-date interactions

FrogBot calls stale instead of recording anything when:
  • The question has already been answered or dismissed.
  • A click targets a message that is not the question’s current message. FrogBot checks this before calling parse, so don’t compare message IDs yourself.
  • A form submission comes from a message that is not the current message. FrogBot uses the submission’s related message when the platform reports one, and otherwise the form locator.
  • parse returns { kind: 'stale' }.
A piece that edits one message in place, such as a card that steps through a question set, keeps one message ID for every step, so the message check can’t tell steps apart. Put the step in each control’s ID and return stale from parse when it does not match question.state. stale can run before FrogBot identifies the participant, so don’t rely on req.user in it.

Advance a question

When parse returns partial, FrogBot saves the returned state with revision + 1, marks the question as still posting, and calls updated with the interaction and the updated question. When updated returns, FrogBot saves its change and clears the mark. A piece without updated gets the saved state and nothing else. Throw from updated only when the current question isn’t visible yet, so that a retry would fix what people see. FrogBot then logs the error and runs updated again as a job, up to 10 times with exponential backoff starting at 5 seconds. The job has no interaction, so updated must be able to post or redraw the current question from question alone. Failures of responses that belong to one interaction, such as opening a form or posting a private confirmation, aren’t fixed by a retry: catch them, log them with req.frogbot.logger.error, and return normally. While the question is still posting, an interaction that parse matches to it gets rejected with the reason “The next question is still posting — try again in a moment.” Nothing is recorded against the question people can’t see yet, and a held reply does not reach the agent. Each held interaction also queues the retry again, so a question recovers after a restart or after the retries run out. A retry that finds the question answered, or at a different revision, does nothing.

Close a question

After an answer or dismissal is recorded, FrogBot calls settled with outcome and actor, the participant who answered, including their channel identity. Replace the controls on every entry in question.messages, not only the current one. settled can return a change, and later hooks see it in question. Use it for anything a late click needs, such as the answer to redraw on the card in stale. If settled throws, FrogBot logs the error and closes the question without a change. When the platform call fails but there is still something to save, catch the error and return the change. If an answer was recorded but its question was not closed, for example because the process stopped during settled, the next interaction calls settled again with actor: null and the stored outcome, then calls stale.

Who can answer

FrogBot resolves the participant with the piece’s identity hook and runs the agent’s access check, the same checks as for a new message. A participant who fails gets denied, and the question stays open. An answer is only accepted for the conversation the question was posted in.

Forms opened by the piece

When a piece opens a platform form itself, the submission may have no link to the question’s message. Store a locator for the current message in the form’s metadata, and FrogBot finds the question from it:
FrogBot reads the locator from privateMetadata on the submission. A form opened from a message that is no longer current submits to stale. The locator only finds the question; FrogBot still checks the participant’s access and that the question belongs to that conversation.

Question records

FrogBot stores question records in the configured KV store: the call’s input, the messages, the revision, and the piece’s state. A record expires 30 days after its last change. After an answer, it is kept for 24 hours so late clicks get stale. A card whose record has expired stops responding; the next message in the conversation posts the question again.