Platform Guide
App Builder
Describe an application in plain language and the builder plans it, constructs it in a sandbox, proves it, and publishes it as a versioned, isolated sidecar — with the same contracts a senior engineer would hand-write.
The builder emits code against the public lace-app-sdk and never against core internals. Generated software stays reviewable because identity, data access, policy, limits, and audit are platform capabilities that the generated code calls. See enterprise vibe coding.
The build lifecycle — intent → published app
Intent (your prompt)
│ ──▶ Architect (lace.builder.architect_model) ──▶ Plan (AppDefinitionIR)
│ renders app-type templates, app-builder clone instructions, and AppDefinitionIR
▼
Scaffold ──▶ Sandbox workspace (lace.builder.workspace.BuilderWorkspaceManager, isolated filesystem)
│
├─▶ Validate ──▶ import-boundary scan, schema checks, dependency graph
├─▶ Proof lanes ──▶ typecheck / build / route_smoke / tool / permission / app_data / isolation
├─▶ Seal ──▶ package (lace.builder.package) → image + manifest snapshot
└─▶ Publish ──▶ BuilderPublisher → Release row (immutable, content-addressed)
│
└─▶ SidecarActivator → isolated sidecar at /apps/<app_id>/api/*, /ui/manifest.json
│
└─▶ day-two: prompt an edit OR clone + lace-app dev/push/rollback
Generated apps go through the same lifecycle as hand-written ones. There is no shortcut for code that came from a model. The builder service is src/lace/builder/service.py, the workspace manager is src/lace/builder/workspace.py, and sealing and publishing are src/lace/builder/publisher.py and src/lace/builder/app_sidecar_activation.py.
The plan — what you approve before code is written
The architect does not jump to files. It first renders a plan — an AppDefinitionIR that names the app, its data collections (with indexes and constraints), tools, agent definitions (with capabilities and approval gates), routes, pipelines, and the proof lanes it will run. You review the plan and the builder only scaffolds once you approve. Templates for common app types live in src/lace/builder/templates.py and starter_scaffolds.py.
# What you typed
"An operations console: browse work orders by site/status, ask the Grid Dispatch
agent operations questions, and have it text the on-call tech when you escalate."
# What the architect returns — a plan, not code yet
{
"app_id": "alpenglow.grid_ops_console",
"data_collections": [{"collection_id": "work_orders", "indexes": ["site", "status"]}],
"routes": [{"path": "/work-orders", "methods": ["GET"]}],
"agents": [{"agent_id": "grid_dispatch", "capabilities": ["lookup_work_order", "notify_tech"]}],
"proof_lanes": ["typecheck", "build", "route_smoke", "tool", "permission"]
}
Sandboxed construction
Construction happens in an isolated workspace on the builder host (see lace.builder.workspace + lace.builder.app_shell). The workspace is namespaced per builder session (stored in BuilderSessionStore, Postgres-backed). The builder writes files, runs the toolchain, and proves the result there — the host install is never touched by the generated code until publish.
Specialist sub-agents (lace.builder.specialist_agents) can be invoked for app-type-specific scaffolding (for example grid_ops_console).
Proof lanes — the same gate hand-written apps go through
Generated apps go through the same proof harness as hand-written ones. There is no shortcut for code that came from a model. See testing & proof lanes for the lane table (typecheck, build, route_smoke, tool, permission, app_data, isolation). A lane failure refuses to seal. The builder surfaces the diagnostic and you (or the architect) fix the source.
Publish and versions
On green, the publisher (lace.builder.publisher.BuilderPublisher) builds the sidecar image, pins its digest in LaceApp.yaml, and creates an immutable Release row. The sidecar activator (lace.builder.app_sidecar_activation) boots the release in its own isolated sidecar — routes at /apps/<app_id>/api/*, UI at /ui/manifest.json. Rollback is one command: re-activate the prior release's image.
Day two — iterate like a real app
Two modes, same manifest:
| Mode | How | Good for |
|---|---|---|
| Prompt an edit | Open the app in Builder, describe the change, approve the incremental plan. The builder re-enters the sandboxed lifecycle. | Product tweaks, new field, new agent capability, copy changes. |
| Clone and code | lace-app clone <app_id> → lace-app dev → edit app/ → lace-app test → lace-app push (or deploy --cloud). | Deep logic, custom tools, pipelines, migrations, UI bundles. |
lace-app clone alpenglow.grid_ops_console # → ./grid_ops_console
cd grid_ops_console && lace-app dev # local sidecar at localhost:8080
# edit app/routes.py, app/agents.py, app/data.py
lace-app test # proof lanes locally
lace-app push # CI → seal → Release rel_…
lace-app releases list alpenglow.grid_ops_console
lace-app releases rollback alpenglow.grid_ops_console rel_0Ck8aX9q
Starter apps and examples
The quickest way to evaluate the Builder's output is to clone a promoted sidecar: apps/grid_ops_console (fleet operations + Grid Dispatch agent), apps/research_notes_v3, src/lace_app_sdk/examples/notes_app (minimal SDK tutorial). Each is a real sidecar with a main.py using create_sidecar_app_from_manifest plus a lace_app_manifest.json and LaceApp.yaml.
Governance does not bend for generated code
- Data stays tenant-scoped through
AppDataService. No in-memory list reaches production. - Tool dispatch is policy-checked before execution.
approval_requiredstill pauses the loop. - Logs, traces, and per-call model attribution are emitted the same way they are for hand-written apps (observability, governance).
- Import-boundary scan rejects any generated file that imports
lace.apps.*directly. The SDK boundary is enforced at seal time.
Publishing details: publishing & releases · Product surface: App Builder