The Chat Loop
A single message from you can become many model passes. The chat loop (server/ai.js) orchestrates that: build the prompt, stream the model, parse and execute tools, feed results back, repeat until the model is done.
The system prompt
Before streaming,buildSystemPrompt() assembles a large prompt from:
- The AI’s name and personality (from config).
- How to customize the OS — themes, surfaces, files.
- The app-vs-real-code rubric — when to build a BOR surface vs. write a real project (and to ask when the platform is ambiguous).
- The surface-choice rubric — app vs. shortcut vs. just answering.
- The design gate — call
get_ux_ui_strict_guidelinesbefore the first surface. - Tool docs —
buildToolDocs()renders every registered tool. - The filesystem snapshot, time/locale, and system configuration.
- The artifact policy — where AI-created files go (
.bor/…). - The active theme and the existing surfaces list.
The pass loop
- Tools marked
feedsBack: true(most of them) cause another pass: the model reads the result and continues. <say>(stream-only) and<attempt_completion>do not feed back — they end the turn.- A safety ceiling,
BOR_CHAT_MAX_PASSES, prevents runaway loops.
One tool per stream
After the first feedback tool in a stream, the runtime aborts the provider stream. This kills a failure mode where a weaker model hallucinates the<tool_results> block inline and “runs” many tools in one stream (replaying entire web pages, looping). One real tool runs; the loop feeds its result back cleanly on the next pass.
Open-tag continuation
If a stream ends mid-tag (e.g. the model was cut off inside a<write_file>), the runtime asks the model to continue from the exact next character — up to a few continuation attempts — so a long file completes instead of being dropped.
Completion & human-in-the-loop
<attempt_completion><result>…</result></attempt_completion>— the model signals it’s done. The result is shown to you; the turn ends. The model must not call tools after it.<wait_for_human_input>— the model needs something only you can give (a profile choice, an OTP, a CAPTCHA, credentials, a manual click). It states what it needs and ends the turn, waiting for your reply. It must not call more tools while waiting.
Tool-result discipline
The model is told to inspect tool results before acting, not restate them. Afterexecute_command, web_search, file tools, browser actions, or surface tools, it waits for the result pass, reads it, and only then decides to continue, complete, or wait. This is why BOR doesn’t claim “I’m on the page” before a screenshot proves it.
Tuning
| Variable | Effect |
|---|---|
BOR_CHAT_MAX_PASSES | Max tool-use passes per turn (safety ceiling). |
BOR_CHAT_STREAM_ATTEMPTS | Stream retry attempts on failure. |
BOR_TOOL_MAX_ATTEMPTS | Per-tool retry attempts on transient failure. |
BOR_STREAM_IDLE_TIMEOUT_MS | Idle timeout before a stalled stream is retried. |
If BOR ever feels like it’s stalling, the first thing to audit is these constants and the hard caps in the request path — not the model.
Streaming to the bubble
As tools execute, the loop emits SSE events:tag_start (a tool began), tag_delta (streaming content), say_delta (spoken text), and the final result event per tool. The presence turns these into the live cards.