Demo Design Choices¶
Chainlit human review is event-driven¶
Decision¶
A pending review is a persisted cl.Message with a cl.CustomElement.
on_message publishes it and returns. The element submits the complete batch
through callAction; the callback validates it and performs one Responses
continuation. A chained interrupt updates the same form, while a terminal
response completes the ledger and removes it.
Message metadata is authoritative for the model, Response, function calls, revision, and element ID. The browser sends only opaque references and answers.
Why not the alternatives?¶
Ask*Messagekeeps the handler waiting on its WebSocket. Navigation then needs task, reconnect, and cancellation bookkeeping. Our formeron_chat_endcancellation could also stop an unrelated stream and persist partial output.- Plain
cl.Actioncontrols use native callbacks but are not persisted thread elements in Chainlit 2.12, so history hydration cannot restore them.
A custom element provides the durable form, while callAction keeps the native
Chainlit endpoint and callback registry. No custom API route is needed.
Pros¶
- Navigation and reload restore reviews through normal history hydration.
- No waiter, resume task, timer, or HITL session cache is needed.
- Batch validation, persisted revisions, and a per-step lock reject incomplete, stale, or duplicate submissions.
- Ordinary responses can finish and persist after a thread switch.
Cons¶
- The form needs custom JSX; drafts and decisions are not a separate audit log.
- The lock is process-local; cross-worker submission needs durable coordination.
- Continuation and persistence are not transactional. Completing the ledger first favors replay safety over guaranteed final rendering.
- Running responses are not rebound to another WebSocket. Returning early may require a refresh to hydrate the completed message.
Chainlit 2.12 timestamp workaround¶
The official PostgreSQL layer hydrates createdAt in a format rejected by its
own update path. HitlWorkflow normalizes it before updating a restored ledger.
Otherwise the UI can look complete while PostgreSQL keeps a ghost pending
ledger that blocks the next turn. Recheck this workaround after upgrading
Chainlit.