MindCloud · private B2B platform
Cloudy
A platform-wide voice and chat assistant, and a workflow builder you talk to. I architected it end to end.
Hands-free over WebRTC, from anywhere in the product. The model decides when to take its turn.
Same question, typed
Same thread, same tools, same answer
The typed lens is not a fallback. It is the same assistant, reached a different way.
One conversation
Speaking and typing are two ways into one thread, not two products. Switching between them mid-conversation continues it, and the same result comes back either way, rendered for whichever surface you are on.
The browser makes the WebRTC offer; the product's own server relays it, attaches the provider key in process, and hands back only the answer. The model, the voice and the system prompt are chosen server-side.
One session hosts both a push-to-talk commit and hands-free semantic detection, switched mid-session without renegotiating the connection, and the always-on microphone is acquired only after the server echoes the new mode back.
Releasing the button commits the captured audio but does not ask for a reply. The client waits for the server to confirm the buffer actually held something, and an empty buffer disarms the pending request, so a silent press gets no answer. Hands-free works the other way round: there the turn boundary is the server's to detect.
Vague acknowledgements are banned in the prompt. A small state machine drives the orb, a working line and busy dots instead, and it ranks "the answer is still being spoken" above "a tool is running", because the transcript finishes before the audio does.
Cutting in mirrors locally and the delegated read keeps going, because interrupting to refine a question should not discard the answer to it.
Tearing a session down advances one counter, and each asynchronous read captures it before its first await and checks it again before it renders or replies, so an answer arriving after you started a new conversation is dropped instead of spoken into it. Work that already committed on the far side, a confirmed build or a completed account switch, is deliberately allowed to finish; only its reply is suppressed.
The model gets a small object assembled field by field on the server, with model-authored text length-capped and unrecognized fields dropped. The rendered answer goes to the browser and never re-enters the model's context.
The list of places the assistant may send you is composed server-side from the signed-in user's permissions, and it is fixed for the session on purpose, because that list is exactly where an injected instruction would try to land.
Instead of rewriting the prompt, each turn carries a small note about where you are, deduplicated against the last one, and the model is told the newest one supersedes the rest.
Candidate destinations are filtered by the same validators that run when you actually click, rather than a second copy of the access rules, and anything the model emits is checked again on the way out and dropped if you cannot reach it.
A build or edit request never runs on arrival. The browser mints its own random approval token, snapshots the exact request, and sends nothing back to the model until you approve or decline, by tap or by voice. The approval is checked against that snapshot, outside the model, and can be spent once, so a model that says it already has the go-ahead changes nothing.
One invalid field rejects the whole batch with no writes applied, and the model has to re-emit the corrected set in a single call.
A step configuration whose reference to an earlier step could not be resolved is reported as a failure, not a success, and the half-broken configuration is never applied. An agent loop treats success as terminal: calling it done would leave the orchestrator convinced the field was set, with the broken reference quietly dropped.
Where a field draws from a live list, a channel or a folder or a mailbox, a value is accepted only if it appears in options actually loaded for that field, through the step's own connected account. Anything else is rejected with the real values quoted back, so the model corrects itself in one turn instead of writing a plausible id that fails at run time. Free-text fields are not constrained this way.
The cheap grounding checks read the editor's own snapshot and deliberately let a thin one through, because inventing rejections would block legitimate work on a fresh draft. The one path that actually persists re-reads the draft and rebuilds the field schema from the live catalog, so the write is grounded even when the snapshot has drifted.