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

lace-app-sdk

LaceAppManifest reference

LaceAppManifest declares everything your app provides. LACE discovers manifests and wires providers at bootstrap — no registration calls, no glue code.

pythonapp/manifest.py
from lace_app_sdk.manifest import LaceAppManifest, AppRuntimeUiDeclaration
from lace_app_sdk.data import AppDataCollectionSchema
from app.data import TICKETS

MANIFEST = LaceAppManifest(
    app_id="acme.field_intake",
    display_name="Field Intake",
    version="0.1.0",
    description="Field intake with triage agent.",
    data_collections=[TICKETS],
    # pure data — no import paths for handlers
    tools=[],  # populated by scanning @tool — see tool_modules below
    tool_modules=["app.tools"],
    route_providers=["app.routes:RouteProvider"],
    agent_providers=["app.agents:AgentProvider"],
    pipeline_providers=["app.pipelines:IntakePipelines"],
    runtime_ui=AppRuntimeUiDeclaration(
        module_id="field_intake.ui", module_version="1.0.0",
        entrypoints={"session_home": "./RuntimeView"}, isolation_mode="iframe"),
)
# emit the canonical JSON the publisher seals
from lace_app_sdk.manifest import write_app_manifest
write_app_manifest(MANIFEST, "lace_app_manifest.json")

What a manifest declares

FieldTypeWhat it owns
app_idstrStable id, e.g. acme.field_intake (used as sidecar mount prefix, collection scope, tool scope)
display_name / version / descriptionstrHuman metadata; version is informational — platform release id is rel_*
data_collectionslist[AppDataCollectionSchema]Typed, tenant-scoped collections (see data)
tools + tool_moduleslist[AppToolDeclaration] / list[str]Tool contracts (pure data) + modules to import so decorators run (tools)
route_providerslist[str]Dotted "app.routes:Provider" paths
agent_providers / agent_runtime_extensionslist[str]AppAgentProvider + extensions (agents)
pipeline_providerslist[str]AppPipelineProvider (pipelines)
runtime_uiAppRuntimeUiDeclarationModule Federation remote (routes & UI)
migration_providerstrAppData migrations
scheduler/schedule_specslist[AppScheduleSpecDeclaration]Cron schedules → app.route.invoke tasks
permission/policy/settings_providerslist[str]App-scoped policy and settings

Pure data — why the manifest carries no import paths for handlers

tools[] entries are data (tool_id, description_for_model, input_schema, policy) — no dotted refs — so the platform can mint lace.catalog_definitions rows without executing app code. tool_modules is the import sidecar's hint: it imports those modules in the app's own process so @tool decorators populate the registry. Without it, a manifest with tools only would 404 at invoke time. Same split as job_handler_modules.

Canonical JSON — what gets sealed

jsonlace_app_manifest.json
{
  "app_id": "acme.field_intake",
  "display_name": "Field Intake",
  "version": "0.1.0",
  "data_collections": [ ... ],
  "route_providers": ["app.routes:RouteProvider"],
  "agent_providers": ["app.agents:AgentProvider"],
  "tool_modules": ["app.tools"]
}

Call write_app_manifest(MANIFEST, "lace_app_manifest.json") (from lace_app_sdk.manifest) to emit the canonical file. The publisher (src/lace/builder/publisher.py) reads it — and also auto-derives collections from app/data.py as a fallback. The file is the blast-radius diff reviewers read before publish.

Declaration is enforcement

Because the manifest is the only way capabilities enter the runtime, an app cannot acquire a tool, route, collection, or schedule it did not declare. Reviewing the manifest diff is reviewing the blast radius.

Next: data collectionstoolsagents.