DocsDevelopers

Anatomy of an app

The files of an app repo, what each one owns, and the small contract between an app and the host that runs it.

An app is one git repo. It stays small: the schema, the pages, the hooks, its own spec. Everything generic comes from locosdk.

The repo#

<app>/
  CLAUDE.md         two lines: the kit, then the app's own spec
  agent/            the app's spec, read by every agent, wins over the kit
  schema.ts         collections, fields, rules: the single source of truth
  server.ts         createApp({ schema, app, pages }), hooks, routes, crons, then serve()
  loco.config.ts    the identity (name, color, logo, lang), review topics, deploy: false
  CHANGELOG.md      Unreleased, then v<n> sections: what the app's users read
  src/              main.tsx, data.ts, collections/<name>/, pages
  tests/
  bunfig.toml       the test preload: fast password hashing in tests only
  data/             gitignored, your data only: app.db, uploaded files, backups
  .loco/            ignores itself: the builders' workspace, review cards, shots
fileownsrule
schema.tsevery collection, field, index, view, rule and auth optiona change here is applied at the next boot, see Schema as code
server.tshooks, custom routes, crons, mail, MCPplain TypeScript in the app's process
loco.config.tsthe identity, review topics, deploy: falseplain data only: no import, no expression. It is read as text and rewritten in place by the portal
CHANGELOG.mdwhat the users read in the what's new modalone line per visible change, see Changelog and releases
agent/the app's own spec: what it is for, its screens, its decisionsupdated before the code it describes

CLAUDE.md and the agent kit#

CLAUDE.md holds two lines:

@node_modules/locosdk/agent/index.md
@agent/index.md

The first line loads the agent kit that ships inside locosdk: conduct, code rules, and one page per sdk surface. The kit is versioned with the sdk, so the docs an agent reads always match the API it calls. The second line loads the app's own spec, which wins on conflict. More: The agent kit.

The data folder#

data/ is gitignored and holds your data, nothing else. The agents that change the app cannot read or write it: their sandbox closes the folder.

pathwhat
data/app.dbthe SQLite database, WAL mode, PocketBase's layout
data/storage/uploaded files, <collectionId>/<recordId>/<file>, unless the app stores files on S3
data/backups/backups on disk, unless the app keeps them on S3

.loco/ is the workspace of whoever builds the app, kept out of git by its own .gitignore:

pathwhat
.loco/review.db, .loco/review/<id>/review cards, their threads, their images and clips
.loco/shots/shots of the app taken by golo shot

The contract with the host#

The host that runs an app asks two things of it:

  1. Listen on the port in PORT. app.serve() does it, and uses 8090 when PORT is not set.
  2. Answer GET /api/health with 200. createApp mounts it.

Nothing else. In production an app binds the loopback interface only, and the server's front door passes it its own visitors. See Security.

One process#

  • One Bun process serves the API, the pages (Bun's HTML imports, hot reload in dev) and the database.
  • Hooks and crons run in that process as plain TypeScript. No VM, no sandbox, no sleep between requests.
  • A restore of a backup ends the process on purpose. The host starts it again on the restored data, in about one second.
  • A second createApp on the same data folder (a hot reload) retires the first one: its crons stop and its database closes.

How an app gets locosdk#

The dependency is a snapshot of committed sdk code, installed as a normal package. Every agent run and every start of the app brings it up to the newest snapshot first, so a dev app always runs the current sdk.

This page as markdown