lace-app-sdk
Quickstart — build a governed app from four files
An app is a manifest plus providers. Four small files make a governed, tenant-scoped application that survives restarts and goes through the same proof lanes as any builder-generated app.
Prerequisites
- Python 3.12+,
pip install lace-app-sdk(install). - Optionally,
pip install lace-app-sdk[runtime]if you want to runlace-app dev/lace-app testlocally outside the monorepo. - A LACE tenant + API key to publish — not needed to scaffold or run locally.
The four files
You will create app/manifest.py, app/data.py, app/tools.py, and app/agents.py. Each owns one surface.
1 — app/manifest.py — the manifest
The manifest is pure data — the platform projects it into catalog rows without importing your code.
from lace_app_sdk.manifest import LaceAppManifest
from app.data import TICKETS
MANIFEST = LaceAppManifest(
app_id="acme.field_intake",
display_name="Field Intake",
version="0.1.0",
data_collections=[TICKETS],
tool_providers=["app.tools:ToolProvider"],
agent_providers=["app.agents:AgentProvider"],
route_providers=["app.routes:RouteProvider"],
)
# write once so the publisher sees data_collections
from lace_app_sdk.manifest import write_app_manifest
write_app_manifest(MANIFEST, "lace_app_manifest.json")
Run python app/manifest.py once to emit lace_app_manifest.json (the publisher seals this file). See manifest reference and what gets installed.
2 — app/data.py — a typed collection
from lace_app_sdk.data import AppDataCollectionSchema, AppDataCapability, PhysicalStorageSpec
TICKETS = AppDataCollectionSchema(
app_id="acme.field_intake", collection_id="tickets", display_name="Tickets",
storage_backend="generic_app_data",
json_schema={"type": "object", "properties": {
"subject": {"type": "string"},
"priority": {"enum": ["low", "normal", "urgent"]},
}},
required_fields=["subject"], identity_fields=["subject"],
searchable_fields=["subject"],
physical_storage=PhysicalStorageSpec(capabilities=[AppDataCapability.FULL_TEXT_INDEX]),
)
Collections are tenant-scoped Postgres JSONB tables with an audit log and optional full-text index. Use AppDataService for CRUD — never an in-memory dict (see data collections).
3 — app/tools.py — a tool the agent can call
from lace_app_sdk.tools import tool
@tool(tool_id="lookup_ticket",
description_for_model="Returns the ticket row for a subject. Use when the user names a ticket.",
input_schema={"type": "object", "properties": {"subject": {"type": "string"}}})
def lookup_ticket(args: dict, context: dict) -> dict:
return {"ticket": {"subject": args["subject"], "priority": "urgent"}}
Side-effect: the decorator registers an AppToolDescriptor in the process-local AppToolRegistry keyed by (app_id, tool_id). The descriptor is pure data; core invokes the handler over HTTP against your sidecar.
4 — app/agents.py — an agent with an approval gate
from lace_app_sdk.agents import AgentDefinition, ToolCapability, ModelPolicy, InstructionProfile
AGENT = AgentDefinition(
agent_id="acme.field_intake.triage", tenant_id="default",
slug="intake-triage", name="Intake Triage",
description="Classifies tickets and drafts replies with citations.",
instructions=InstructionProfile(system="Prefer the KB. Cite every claim."),
model_policy=ModelPolicy(default_model_alias="anthropic/claude-sonnet-4"),
capabilities=[ToolCapability(tool_id="lookup_ticket"),
ToolCapability(tool_id="escalate", approval_required=True)],
)
class AgentProvider:
app_id = "acme.field_intake"
def agent_definitions(self, *, tenant_id, context=None): return [AGENT]
approval_required=True pauses the loop with waiting_approval before escalate executes — see agents & skills.
How it runs
- Scaffold:
lace-app create acme.field_intakeor start fromsrc/lace_app_sdk/examples/notes_app. - Dev:
lace-app dev— bootscompose.yaml, runs migrations, mounts routes at/apps/acme.field_intake/api/*. Trycurl localhost:8080/apps/acme.field_intake/api/notes. - Prove:
lace-app test— typecheck / build / route_smoke / tool / permission / app_data lanes (testing). - Publish:
lace-app push(CI) orlace-app deploy --cloud(direct) → immutableRelease, sidecar boots,lace-app open acme.field_intake.
Full runnable example
The SDK ships a complete app at src/lace_app_sdk/examples/notes_app/ (manifest, data, routes, agents) — copy it with lace-app create --template notes or read the files directly. The sidecar example apps/grid_ops_console/ shows a production sidecar with data seeding, an agent with KnowledgeCapability, and a federated UI.
Next: create a new app — UI / API / CLI or clone & local dev.