Skip to content
LACE
  • v0.1 Current
  • Python
  • TypeScript Soon

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 run lace-app dev / lace-app test locally 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.

pythonapp/manifest.py
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

pythonapp/data.py
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

pythonapp/tools.py
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

pythonapp/agents.py
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

  1. Scaffold: lace-app create acme.field_intake or start from src/lace_app_sdk/examples/notes_app.
  2. Dev: lace-app dev — boots compose.yaml, runs migrations, mounts routes at /apps/acme.field_intake/api/*. Try curl localhost:8080/apps/acme.field_intake/api/notes.
  3. Prove: lace-app test — typecheck / build / route_smoke / tool / permission / app_data lanes (testing).
  4. Publish: lace-app push (CI) or lace-app deploy --cloud (direct) → immutable Release, 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.