XBOS-X
X
BOS-X chat → app surface
ready
◢◤

Describe an app to build. The agent streams its work here; the live preview opens on the right when the build settles.

no preview yet

Your app preview will appear here once the build completes.

BOS-X — Architecture

A lean, self-owned stack that turns a chat message into a live web app: a buildless Surface talks to one Node Runner; the runner resolves an Engine to build the app and isolates untrusted execution in a managed Sandbox, routes every model call through a key-injection proxy so the sandbox never sees a provider key (Invariant 6), persists builds durably, and serves the live preview. State lives in an isolated Supabase.

The system at a glance

 SURFACE  (buildless SPA)        x.dev.bos.pro · Cloudflare Pages, also served by the runner
   built-apps rail │ chat + SSE transcript │ live preview iframe (sandboxed, no same-origin)
        │
        │   POST /api/run (SSE)   +   GET /api/* (apps · messages · models · health)
        ▼
 RUNNER  (one Node/TS service)   x-runner.bos.pro · durable CF tunnel  (laptop + Colima; VPS = BLK-12)
   server.ts → runTurn → engine registry → oma · aider · router-direct
        │     fallback chain: oma → aider → router-direct   (router-direct actually runs in deploy)
        │
        ├─ KEY-INJECTION PROXY  (Invariant 6):  injects the real router key — sandbox gets only a proxy URL + token, never a key
        │
        └─ durable builds:  runner/built-apps/{app_id}/  →  GET /apps/{app_id}/*  (survives sandbox reaping)
             │                            │                              │
             ▼                            ▼                              ▼
   SUPABASE (isolated)          MANAGED SANDBOX               MODEL ROUTER (read-only)
   gsuafvvzsdfkfybgfzjv         Daytona (wired default)       rundev.cloved.ai/v1
   apps · messages ·            build → preview               bos:* aliases · provider failover
   model_runs · engine_sessions egress allowlist + proxy      no /v1/models · no streaming
   RLS on · runner-only writes  URL only (Invariant 6)        (proxy de-streams)
          

Components

Surface

surface/

Buildless HTML/CSS/JS SPA: chat + SSE transcript + a sandboxed preview iframe (no allow-same-origin). Served by the runner and as a Cloudflare Pages copy at x.dev.bos.pro; its config.js pins BOSX_API_BASE → x-runner.bos.pro.

Runner

runner/src/server.ts

The one Node/TS service: every /api/* route, SSE framing, sandbox provisioning, durable build persistence, static serving.

Engine seam

runner/src/engines/*

IAgentEngine registry: oma (opencode, gated, default), aider (LiteLLM, git-diff), router-direct (one call + WRITE_FILE — runs in the live deploy). Fallback oma→aider→router-direct.

Key-injection proxy

runner/src/router/keyInjectionProxy.ts

Invariant 6. Strips inbound auth, injects the real router key, and de-streams (the router rejects stream:true). The sandbox only ever gets the proxy URL + a non-secret token.

Sandbox adapter

runner/src/sandbox/*

One branded SandboxedSpec; Daytona is the wired default, E2B an alternate adapter. The managed engines (oma/aider) run code only here; router-direct — the live fallback — writes its generated files in-process on the trusted runner, which then serves the durable preview at /apps/{app_id}/.

Model router

rundev.cloved.ai/v1

Read-only egress; bos:* aliases over provider failover. No /v1/models, no streaming (the proxy de-streams).

Supabase (isolated)

gsuafvvzsdfkfybgfzjv

apps · messages · model_runs · engine_sessions (the last is a stub). RLS enabled (forced on engine_sessions); the runner writes with the service-role key (host-checked, fail-closed).

Factory (offline)

factory/

Not in the request path: prebro_orchestrator.py (DAG build-waves) + proposal_debate.py / playwright-verify/goal_judge_debate.py (multi-model judge panels via callmodel.py).

Request flow — chat → live app

  1. The Surface POSTs {prompt, model, engine_ref?, session_id, app_id?} to /api/run and opens an SSE stream.
  2. The runner mints run-<id>, validates the model against a server-side allowlist, mints/reuses app-<id>, and emits meta{app_id}.
  3. It provisions a managed sandbox via sandboxedSpec() — which asserts Invariant 6: the sandbox env carries the proxy URL + a token, never a provider key.
  4. It persists the user turn to messages, loads any follow-up history + existing files, then runs the engine (fallback oma→aider→router-direct).
  5. The engine calls the model through the router and applies its file edits (router-direct parses WRITE_FILE blocks; aider derives them from git diff); every step streams to the Surface as engine_event.
  6. On success the files are persisted durably to runner/built-apps/{app_id}/ and the runner emits preview{url = /apps/{app_id}/} — a runner-served URL that survives the sandbox being torn down.
  7. The build is recorded in apps, the assistant turn in messages, and every attempt (success/fail) in model_runs; done{status} closes the stream.
  8. The Surface loads the preview URL into the sandboxed iframe (no allow-same-origin — generated apps are untrusted).

Invariants & trust boundaries

  • Invariant 6 — one egress authority. The runner holds the real model-router key; the sandbox never does. The key-injection proxy is the only hop that adds it.
  • One sandbox adapter. Managed engines (oma/aider) run code only in the sandbox and are fail-closed by default (the host-engine co-location guard) until exec is co-located into the sandbox; the live fallback (router-direct) makes one model call and writes the generated files on the trusted runner, which serves the durable preview at /apps/{app_id}/.
  • Engine ⊥ model ⊥ memory. The engine is a registry plugin, the model is the router's job, and cross-engine continuity is designed as a small summary-handoff (the store is still a stub).
  • Isolated state. Only the Supabase project gsuafvvzsdfkfybgfzjv; RLS is enabled (forced on engine_sessions) and every write is runner-only (service-role, host-checked).
  • Untrusted preview. The preview iframe deliberately omits allow-same-origin so a generated app can't read the parent/runner origin.

Status & honest gaps

  • The engine that actually runs in the live deploy is router-direct; oma/aider are registered but fail-closed by default (host-engine co-location guard) until sandbox exec co-location ships.
  • The runner is hosted on a laptop (Colima + the durable Cloudflare tunnel x-runner.bos.pro); a dedicated Prebro VPS is still pending (BLK-12).
  • engine_sessions exists in the schema but its store is a stub — cross-engine continuity is not yet wired.
  • There is no end-user auth yet: anon read is intentional; all writes are runner-only.
  • Canonical spec = memory/ARCHITECTURE.md (v2.0, “lean self-owned stack”), not the superseded root 01_ARCHITECTURE.md (v1.0).

Сравнение архитектур (RU)

bos-x — намеренно «тонкий» (lean) стек, который уже работает в проде. Ниже — сравнение с двумя эталонными архитектурами того же класса («чат → приложение в песочнице»): исследовательской modular-agentic-system и SaaS-рантаймом vbp-german runner-v2. Для каждой — схема-различие и таблица дельт.

bos-x ↔ modular-agentic-system

modular-agentic-system — это в основном проектная документация плюс три прототипа. Её суть — ядро с двумя реестрами: политика (приём → сессия → оркестратор → расчёт) живёт в крошечном ядре, которое никогда не видит подложку, а две независимые оси — харнесс (петля агента) и окружение (песочница) — подключаются по ref-строке через реестры. Ключевая дисциплина — непрозрачный EnvironmentHandle и CI-grep-гейт, который роняет сборку, если в ядро просочилось слово про подложку (container_id, dockerode, workspace_dir). Песочницы договариваются о возможностях (capability negotiation), а самый сложный момент — публикация порта превью — спрятан за единым контрактом exposePort(port)→url.

bos-x устроен проще: одна ось-реестр (движки oma/aider/router-direct), а песочница — единственный адаптер (Daytona подключён; E2B-адаптер есть, но не задействован), а не вторая ось-реестр. Границу гарантируют именованный (branded) тип SandboxedSpec и Инвариант 6 (прокси-инъекция ключа), а не CI-grep-гейт. Итог: bos-x — тоньше и уже работает в проде; modular-agentic-system — модульнее и строже к развязке слоёв, но почти не построена (реально запускается лишь sdk × docker).

 bos-x  (в проде)
 ─────────────────────────────
 Surface SPA (buildless)
   │
 Runner (один Node-сервис)
   ├ engine registry (1 ось):
   │     oma · aider · router-direct
   ├ key-injection proxy (Инв.6)
   └ sandbox adapter
         Daytona — один адаптер (E2B не подключён),
         НЕ реестр
 Supabase: apps·messages·model_runs·engine_sessions
 превью: durable /apps/{id}/ на runner
            
 modular-agentic-system  (эталон/прототип)
 ─────────────────────────────
 Studio UI (chat + превью)
   │
 Core-ядро (ТОЛЬКО политика)
   ├ registry ХАРНЕССОВ (ось 1)
   │     sdk · opencode · …
   └ registry ОКРУЖЕНИЙ (ось 2)
         docker · e2b · vercel …
   EnvironmentHandle (непрозрачный)
   exposePort(port)→url
   CI-grep-гейт против утечки подложки
 запускается лишь sdk × docker
            
Аспектbos-xmodular-agentic-systemΔ дельта
Оси подключения1 (движки)2 (харнесс + окружение)MAS модульнее
Песочницаодин адаптер (Daytona; E2B не подключён)реестр окружений по refMAS гибче, bos-x проще
Развязка слоёвSandboxedSpec + Инв.6непрозрачный handle + CI-grep-гейту MAS жёстче механически
Зрелостьв продедокументация + 3 прототипаbos-x реальнее
Секрет ключапрокси-инъекция (Инв.6): песочница ключа не видитсекрет инжектится в окружение (ProvisionSpec.env)у bos-x изоляция строже

bos-x ↔ vbp-german runner-v2

runner-v2 — полноценный многопользовательский SaaS-рантайм (Express 5, ~28 групп маршрутов). Песочница — облачная Daytona (одна на сессию, hibernate/wake). Состояние сессии двухуровневое: горячее в Redis + долговечное в Supabase с восстановлением после краша. Есть writer-lock (один писатель на проект), 4-канальная сборка контекста (ContextFrame) с аудитом и бюджетом токенов, цепочка BYOK (ключ пользователя → организации → платформы), именованные профили агентов и под-агенты с ограничением глубины (≤4), биллинг / права доступа / маркетплейс и «глубокая» проверка работоспособности. Провайдерский ключ резолвится на хосте и в песочницу не попадает — та же дисциплина, что у bos-x.

bos-x намеренно тоньше: один сервис, тонкий surface, слой движков (3 движка), прокси-инъекция ключа (Инв.6) как первоклассная граница, Daytona (E2B-адаптер не подключён) и Supabase с 4 таблицами. Чего у bos-x пока нет: облачного хостинга рантайма (сейчас ноутбук + Colima, VPS — BLK-12), восстановления сессий после краша (состояние не реализовано, stub), формального writer-lock, аудируемой сборки контекста, глубокой проверки работоспособности, биллинга. Итог: runner-v2 — операционно куда зрелее, но тяжелее (часть абстракций — заглушки: failover, контроль model-health, MCP — но deep-health реальна); bos-x — легче, честнее про «построено / заглушка» и с более чистой границей ключа.

 bos-x  (lean, в проде)
 ─────────────────────────────
 один Node-сервис, /api/*
 движки: oma · aider · router-direct
 key-injection proxy (Инв.6)
 sandbox: Daytona (E2B не подключён)
 сессии: не реализовано (engine_sessions=stub)
 единый писатель: не формализовано
 контекст: системный промпт
 health: /api/health
 хост: ноутбук + Colima (VPS=BLK-12)
 биллинга нет
            
 runner-v2  (SaaS-рантайм)
 ─────────────────────────────
 Express 5, ~28 групп маршрутов
 профили агентов + под-агенты (≤4)
 ключ на хосте (в песочницу не идёт)
 sandbox: Daytona облако, 1/сессия, hibernate
 сессии: Redis(горячо)+Supabase(durable)+recovery
 writer-lock (Redis, NX, TTL)
 ContextFrame: 4 канала, аудит, бюджет токенов
 health: deep (пингует Redis), BYOK-проверки
 хост: облако
 биллинг / права доступа / маркетплейс
            
Аспектbos-xrunner-v2Δ дельта
Хост рантайманоутбук + Colima (VPS=BLK-12)облакоrunner-v2 устойчивее
Состояние сессиине реализовано (stub)Redis + Supabase + recoveryrunner-v2 переживает сбой
Один-писательне формализованоwriter-lock (Redis NX TTL)заимствовать
Сборка контекстасистемный промптContextFrame: 4 канала + аудитзаимствовать
Граница ключапрокси-инъекция (Инв.6)резолв на хосте (в песочницу не идёт)оба изолируют ключ — разные механизмы
Объёмодин сервис, тонкийSaaS-плоскость (часть — заглушки)bos-x легче

Что заимствовать · где bos-x лучше / хуже

Заимствовать в bos-x (по приоритету)

Из runner-v2 (операционная зрелость) — то, что напрямую закрывает слабые места bos-x:

  1. Облачный хостинг рантайма + жизненный цикл песочницы на уровне сессии (одна Daytona-песочница на сессию, hibernate/wake, очистка по простою) — bos-x уже использует Daytona для песочниц, но сам рантайм живёт на ноутбуке (Colima); вынос рантайма в облако + посессионный жизненный цикл убирают зависимость от хрупкого ноутбука-хоста и закрывают BLK-12.
  2. Двухуровневое состояние сессии: Redis (горячо) + Supabase (durable) + восстановление после краша — сохраняется после перезапуска рантайма.
  3. writer-lock на проект (Redis, NX, TTL) — один писатель, без гонок (ложится на модель «строка apps = тред»).
  4. 4-канальный ContextFrame с аудитом и бюджетом токенов — инспектируемая и воспроизводимая сборка контекста.
  5. «Глубокая» проверка работоспособности, пингующая зависимости — честный сигнал вместо поверхностного /api/health.
  6. Цепочка BYOK (ключ пользователя → организации → платформы), резолв на хосте — расширяет границу ключа bos-x.

Из modular-agentic-system (модульность и строгость развязки):

  1. CI-grep-гейт против утечки подложки в ядро — механически защищает Инвариант 6 и границу песочницы.
  2. Флаги возможностей песочницы + ступенчатая деградация (publicPorts/snapshot/nativeGit) — одно ядро ведёт Daytona и E2B с мягкой деградацией.
  3. Контракт exposePort(port)→url, прокси спрятан в адаптере — сохраняет Daytona и E2B взаимозаменяемыми.
  4. Именованный контракт кросс-движковой непрерывности (производная идея, а не функция MAS) — решить, что переносится при смене engine_ref и в fallback-цепочке.

Где bos-x лучше

  • Тоньше и уже работает в проде — один связный сервис, без SaaS-разрастания (~28 групп маршрутов у runner-v2).
  • Единый egress-чокпойнт (Инвариант 6): весь модельный трафик идёт через один проксируемый шлюз — его проще аудировать.
  • Честность про «построено / заглушка» прямо на странице архитектуры — встроенный блок Status с открытым перечнем недоделок.

Где bos-x хуже

  • Хост — ноутбук + Colima против облака (VPS — BLK-12); облачный рантайм был бы устойчивее.
  • Сессии не реализованы (stub), без восстановления после сбоя (у runner-v2 — Redis+Supabase+recovery).
  • Нет формального writer-lock, аудируемой сборки контекста, глубокой проверки работоспособности и биллинга.

Текущее vs Идеальное + резюме (RU)

Где bos-x сейчас и какой должна быть «идеальная» модульная архитектура — сменный движок без потери контекста + доступ по ролям к сессиям, данным организации и памяти. Полное руководство для агентов: docs/TARGET_ARCHITECTURE.md. Легенда: ✅ есть · 🟡 частично/на ветке · ⬜ цель.

Сейчас (как построено) ↔ Идеал (как должно быть, «perfect»)

АспектСейчас — bos-xИдеал — perfect
Движок🟡 реестр + 3 движка (oma/aider/router-direct), fallback; в проде реально работает router-direct⬜ сменный движок за нормализованным контрактом EngineEvent; контракт load/resume
Контекст при смене движка🟡 lossy summary-handoff (DEC-004), не подключён — контекст теряется⬜ событийный журнал (event-sourced, T1) = единый источник истины; замена без потерь (на границе хода)
Исполнение кода🟡 router-direct пишет файлы на хосте; oma/aider fail-closed⬜ всё в песочнице (mode-1 exec-в-песочницу / mode-2); host-exec запрещён
Песочница🟡 один адаптер Daytona (E2B есть, не подключён)⬜ реестр окружений по ref; opaque handle; exposePort()→url
Хост рантайма🟡 ноутбук + Colima (BLK-12)⬜ облако (Daytona cloud, hibernate/wake)
Состояние сессий🟡 не реализовано (stub), без восстановления⬜ Redis-hot + Supabase-durable + recovery после краша
Доступ (RBAC)🟡 нет (single-tenant); готов на веткеscopedDb reference-monitor; capability-vector SSOT; effective = ∩; fail-closed
Кросс-сессии / организация🟡 не разграниченыsession.read_own/workspace/org; supervisor — по workspace
Память (memory)⬜ не разделенаscopedMemory: namespace по identity, 3 уровня, фильтр на retrieval
Ключ / секреты✅ прокси-инъекция (Инв.6) — песочница ключа не видит✅+⬜ то же + OBO-делегирование (sub/act.sub), секреты файлом / из vault
Health / failover🟡 /api/health поверхностный; 1 маршрут модели⬜ deep-health (пингует зависимости); провайдер-failover

Резюме (RU): diff · дилеммы · исследования · тесты

① Главный diff — что менять (по приоритету)

  • Облако вместо ноутбука (Daytona cloud) → убирает падения x-runner (BLK-12).
  • Событийный журнал (event-sourced) вместо summary → смена движка / краш без потери контекста.
  • Подключить RBAC (scopedDb) + RLS → доступ по ролям к сессиям / орг / памяти.
  • Память как разграниченный ресурс (scopedMemory, namespace по identity).
  • Реестр окружений + CI grep-гейт → настоящая двух-осевая модульность.

② Сложные выборы / дилеммы (D1–D13)

  • Непрерывность: summary (дёшево, с потерями) vs событийный журнал (без потерь, дороже) → журнал.
  • Протокол движка: ACP (pre-1.0) vs in-proc SDK vs HTTP-SSE → нормализовать в EngineEvent, ACP на краю.
  • Доступ: capability-vector (RBAC) vs ReBAC-граф → вектор сейчас, ReBAC при шеринге сессий / иерархии.
  • Enforcement: scopedDb vs RLS → оба (scopedDb — шлюз; RLS — бэкап для не-service-role путей).
  • Режим движка: исполнение всегда в песочнице, не на хосте (mode-1/2).
  • Память: per-user / workspace / org — по умолчанию узко.

③ Области для исследования

  • Доказать «без потерь» при свапе движка (тесты §2.3) — пока не реализовано.
  • Зрелость ACP session/resume (в проде есть баги → леджер как бэкап).
  • Порог перехода на ReBAC (когда одного вектора возможностей уже мало).
  • Tiering и анти-bleed памяти (namespace по identity, фильтр на retrieval).
  • Событие capability_degraded при fallback (смена поведения под одним run-id).
  • Резолвер BYOK user→org→platform (подтверждён частично).

④ Как проверить (тесты)

  • Свап движка в середине сессии → новый движок видит прошлые ходы + файлы + решения.
  • Краш-resume: убить рантайм → сессия восстанавливается из durable.
  • manager не читает чужую сессию → 403 + 0 строк (нет IDOR).
  • viewer не вызывает агента → 403 + composer выключен.
  • Кросс-workspace чтение → пусто (не чужие строки).
  • Запрос памяти из workspace A → только A; namespace B → пусто.
  • Обход scopedDb (from('sessions')) → CI grep-линт роняет сборку.