App Runtime
An App is one package and one trust boundary containing any combination of UI, service, and job workloads.
App code is ordinary framework-neutral code. Lamarck standardizes the command boundary, durable APIs, runtime identity, and capability ceiling around it.
Package
apps/
└── personal-assistant/
├── .git/
├── manifest.json
├── package.json
├── package-lock.json
├── index.html
├── main.tsx
└── src/…
- The directory name is the App ID and must match
manifest.id. - The ID and named workload IDs match
^[a-z0-9][a-z0-9-]*$. - The package chooses its own language, framework, build tool, and internal process topology.
- Every declared command runs with the App package root as its working directory.
One package is the version, permission, and trust boundary. Splitting a UI and worker into separate Apps is appropriate only when they need independent permission ceilings or lifecycle.
Manifest
{
"manifestVersion": 1,
"id": "personal-assistant",
"name": "Personal Assistant",
"description": "Organizes personal requests, notes, and recurring assistant work.",
"runtime": {
"ui": {
"command": ["npm", "run", "start"],
"port": 3000
},
"services": {
"inbox-watcher": {
"command": ["npm", "run", "inbox-watcher"]
}
},
"jobs": {
"daily-review": {
"command": ["npm", "run", "daily-review"]
}
}
},
"permissions": {
"writes": {
"docs": ["reviews/"],
"tables": ["assistant_state"]
}
}
}
The parser is strict: unknown fields are rejected. The permissions.writes object and both of its permission arrays are required, and runtime must contain at least one actual workload.
| Field | Contract |
|---|---|
description | Required non-empty natural-language explanation of what the App is for. |
runtime.*.command | Non-empty argv array. The Host executes it directly; there is no shell interpolation. |
runtime.ui.port | Integer 1–65535; the UI listens on this Capsule-internal port. |
permissions.writes.docs | Extra writable D1 exact IDs or prefixes ending in /. |
permissions.writes.tables | Concrete D2 table names the App may mutate. Wildcards are invalid. |
runtime.agents is rejected in manifest v1. An agent-like process is a service when continuously supervised or a job when invoked to completion.
Workloads
| Kind | Lifecycle | Use it for |
|---|---|---|
ui | Viewer-activated, Host-supervised process with one internal port and stable Host origin. | Full-stack web UI, HTTP/WebSocket server, App-internal process tree. |
service | One continuously supervised instance per named entry; liveness is process liveness. | Workers, local queues, indexing loops, agent services. |
job | A new process per invocation; exit completes the run. | Event-triggered automation, scheduled ETL, bounded agent runs. |
Schedules, Event triggers, retry policy, concurrency, cancellation, timeout, and run history are mutable Host state. They do not belong in manifest.json.
Runtime identity and lifecycle
The Host derives a stable source identity before starting the command:
app:personal-assistant:ui
app:personal-assistant:service:inbox-watcher
app:personal-assistant:job:daily-review
A workload cannot claim another identity. When it writes a D0 Event, Guard injects the active identity as source. Job run IDs are execution records; they do not change the stable provenance string.
Archiving moves the entire package, including Git history, to .lamarck/archived-apps/. It leaves the active registry but remains recoverable. Existing Timeline Events and durable D1/D2 state are not rewritten.
System API
import { system } from "@lamarck/system";
const result = await system.query(
"SELECT * FROM assistant_state LIMIT 20"
);
await system.writeDoc(
"reviews/2026-07-15",
"# Daily review\n…"
);
await system.writeEvent({
type: "assistant.review.completed",
externalId: "2026-07-15",
startedAt: Date.now(),
payload: { document: "reviews/2026-07-15" }
});
| Method | Operation |
|---|---|
query(sql, params?) | Read relational data in data.db. SELECT/WITH only. |
resolveContentRef(ref) | Resolve referenced D0 content when full text is explicitly needed. |
mutate(sql, params?) | Run one INSERT, UPDATE, or DELETE on a permitted D2 table. |
transaction(statements) | Atomically execute a serializable list of permitted mutations. |
writeDoc / deleteDoc | Mutate D1 inside the App's Document grants. |
writeEvent(event) | Append a D0 Event. Omit source; the runtime injects it. |
Apps can query all relational data in data.db in v1. Read access is not granted table by table. Mutation remains limited to declared Tables and Document paths. DDL goes through the separate lamarck promote / demote structural workflow.
Sandbox and permissions
All workloads in one App share a logical Capsule and the same manifest permission ceiling. The sandbox filesystem is scratch or materialization, not authoritative storage.
| Available through capability | Not ambiently available |
|---|---|
| Read-only relational queries | data.db or system.db handle/path |
| Declared D1 and D2 mutations | Host filesystem or another App's scratch state |
| Host-mediated effects explicitly exposed to the App | Reusable credentials, devices, Host APIs, unrestricted network |
Because read queries may contain personal data, any future capability that can export results is itself an explicit effect. It is not implied by query access.