# App Structure & app.yaml
URL: /docs/building-apps/app-structure
Markdown: /docs/building-apps/app-structure.md
Description: How a Rome App source tree is laid out, and what the app.yaml manifest declares.

A Rome App is a source tree with one manifest at the root: `app.yaml`. The
manifest declares the app identity and lists the pieces Rome should load. Files
that are not listed are just files; they are not registered as actions, agents,
skills, hooks, APIs, databases, or web surfaces.

## Source Tree [#source-tree]

The default scaffold has this shape:

```text
<app-root>/
├── app.yaml
├── README.md
├── .gitignore
├── package.json
├── pnpm-workspace.yaml
├── tsconfig.json
├── components.json
├── drizzle.config.ts
├── .rome_store/
│   └── rome_store.yaml
└── src/
    ├── actions/<name>/
    │   ├── action.yaml
    │   └── index.ts
    ├── agents/<name>.yaml
    ├── api/index.ts
    ├── assets/icon.svg
    ├── db/
    │   ├── schema.ts
    │   ├── migrations/
    │   └── repositories/
    ├── skills/
    └── web/
        ├── App.tsx
        ├── components/
        ├── lib/
        └── styles.css
```

Only `app.yaml` is required for the app model. The rest depends on which
capabilities the app actually ships. The scaffold does not create a
`src/hooks/` directory and leaves `src/skills/` empty — add
`src/hooks/<name>/` and `src/skills/<name>/SKILL.md` (plus their manifest
entries) when the app needs them.

* `README.md` is user-facing App Store listing copy.
* `.gitignore` keeps `node_modules/`, `dist/`, and `.rome/` out of git.
* `package.json`, `pnpm-workspace.yaml`, and `tsconfig.json` define the app
  workspace and build.
* `components.json` is used by the scaffolded web UI component setup.
* `drizzle.config.ts` is needed only when the app uses a database.
* `.rome_store/rome_store.yaml` contains store-side metadata used when
  publishing; it is separate from the runtime manifest.
* `src/assets/icon.svg` is usually referenced by `app.yaml` as `icon:
  assets/icon.svg`.

Delete unused scaffold files together with their manifest entries. For example,
if the app has no database, remove `src/db/`, `drizzle.config.ts`, DB scripts
and dependencies, and the `db:` block.

## Build Output [#build-output]

Most source apps set:

```yaml
appRoot: dist
```

With that layout, source files live under `src/`, and `pnpm build` writes the
runtime app into `dist/`. Rome loads declared artifacts from the effective app
root, so after build these manifest paths resolve under `dist/`:

```yaml
web:
  manifest: web/manifest.json

api:
  entry: api/index

actions:
  - actions/hello
```

Do not edit `dist/` by hand. Source-mode install rebuilds and repacks the app.

## Manifest [#manifest]

`app.yaml` is strict. Unknown fields are rejected. New apps use
`formatVersion: 2`; version 1 remains readable only for legacy bundles. Strict
does not mean minimal, though: the schema also accepts a small set of optional
store-listing fields (`author`, `long_description`, `screenshots`, `category`,
`homepage`, `repository`).

A typical app looks like this:

```yaml
formatVersion: 2
id: invoice-tracker
name: Invoice Tracker
version: 0.1.0
description: Track invoices, payments, and follow-ups.
icon: assets/icon.svg
appRoot: dist

web:
  manifest: web/manifest.json
  displayName: Invoice Tracker
  navLabel: Invoices

api:
  entry: api/index

actions:
  - actions/create-invoice
  - actions/sync-payments

agents:
  - agents/invoice-assistant.yaml

skills:
  - skills/invoice-triage

hooks:
  - hooks/channel-message

components:
  - invoice-card

db:
  migrations: db/migrations
  tablePrefix: invoice_tracker
```

### Identity Fields [#identity-fields]

| Field           | Required | Notes                                                                                                                                                                                                |
| --------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `formatVersion` | yes      | Use `2` for new apps. Version 1 is read only for legacy compatibility.                                                                                                                               |
| `id`            | yes      | The machine id: lowercase letters, numbers, hyphens, starts with a letter, max 64 chars. `core` and `self` are reserved. Used in install state, paths, URLs, artifact ids, and default table naming. |
| `version`       | yes      | Valid SemVer, such as `0.1.0`.                                                                                                                                                                       |
| `description`   | yes      | Short app summary used by Rome and store surfaces.                                                                                                                                                   |
| `name`          | no       | Human display name. Use Title Case words, not the kebab-case `id`.                                                                                                                                   |
| `icon`          | no       | Path relative to `appRoot`, usually `assets/icon.svg` after build.                                                                                                                                   |
| `appRoot`       | no       | Effective runtime root. Built apps normally use `dist`. Required in practice for any app built with the `rome` CLI — the build fails without it.                                                     |

### Capability Fields [#capability-fields]

Each capability block is optional. Add only what the app uses.

| Field                      | Purpose                                                                                                                                    |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `web`                      | React web surface mounted by the dashboard. `manifest` points to the built web manifest; `displayName` and `navLabel` control host labels. |
| `api`                      | App-owned HTTP API. `entry` defaults to `api/index` when omitted.                                                                          |
| `actions`                  | Action directories, each with `action.yaml` and `index.ts`.                                                                                |
| `agents`                   | App-owned agent YAML files.                                                                                                                |
| `skills`                   | Skill directories, each usually containing `SKILL.md`.                                                                                     |
| `hooks`                    | Runtime hook directories.                                                                                                                  |
| `components`               | Inline component ids the web bundle registers and may render in chat.                                                                      |
| `db`                       | Per-app SQLite migrations and table prefix.                                                                                                |
| `suggestedChannelBindings` | Optional setup hints that suggest routing a channel to one of the app's agents.                                                            |

`actions`, `agents`, `skills`, and `hooks` entries are normally plain paths:

```yaml
actions:
  - actions/hello
```

### Artifact Names and References [#artifact-names-and-references]

Agent, action, and skill definitions declare an app-local `name` matching
`^[a-z][a-z0-9_-]{0,63}$`. A definition name never contains `:`. Rome combines
the manifest App ID and local name into the runtime identity:

```text
<app-id>:<local-name>
```

References and runtime calls always use that canonical ID, including references
to an artifact owned by the same app. For an app with `id: invoice-tracker`, an
action definition might say `name: create_invoice`, while an Agent allowlist,
Routine target, or `runAction` call uses `invoice-tracker:create_invoice`.

```yaml
# src/agents/invoice-assistant.yaml
name: invoice-assistant
actions:
  - invoice-tracker:create_invoice
allowedSubagents:
  - assistant:explore
```

Never write a bare artifact reference or `self:<local-name>`. Store listing IDs
such as `@publisher/invoice-tracker` are also not runtime artifact namespaces;
the manifest App ID remains the owner.

Manifest entries can use object form for compatibility metadata:

```yaml
actions:
  - path: actions/create-invoice
    publicName: create_invoice
```

In format version 2, `publicName`, when present, must match the definition's
local name. `aliases` and `legacyPath` exist for legacy bundles and do not create
new bare-name behavior for version 2 apps.

### API Options [#api-options]

By default, app API requests require a session. App web UIs call the dashboard
surface at `/api/apps/<appId>/...` through `fetchAppApi`. Public callbacks and
third-party webhooks should use `/api/app-api/<appId>/...`; those public
subpaths can opt out with `api.noAuth`:

```yaml
api:
  entry: api/index
  noAuth:
    - /webhooks/stripe
    - /public/*
```

* `noAuth: true` opens every public app-api subpath.
* `noAuth: ["/path", "/prefix/*"]` opens only those public subpaths.
* Omit it for normal dashboard-only APIs reached by `fetchAppApi`.

`api.relayWebhook` is for apps that consume the core webhook relay. Most apps do
not need it.

### Database Options [#database-options]

The `db:` block enables app-owned SQLite migrations:

```yaml
db:
  migrations: db/migrations
  tablePrefix: invoice_tracker
```

`tablePrefix` must be a SQL-safe lowercase identifier: lowercase letters,
numbers, and underscores, starting with a letter. If omitted, Rome uses the app
id as the prefix; when the app id contains hyphens, set `tablePrefix`
explicitly with underscores. Real app tables are namespaced so they do not
collide with system tables or other apps.

## Validation Rules [#validation-rules]

Rome validates the manifest at pack/install time.

* Unknown manifest fields fail validation.
* `formatVersion`, `id`, `version`, and `description` are required.
* `id` must be a valid app id.
* Format version 2 action, agent, and skill names must be valid local names.
* Format version 2 Agent `actions` and `allowedSubagents` entries must use
  canonical `<app-id>:<local-name>` references (except the special `"*"`
  action allowlist).
* `version` must be valid SemVer.
* Paths listed in the manifest must exist in the effective app root after build.
* `api.noAuth` path entries must start with `/`.
* `db.tablePrefix`, when set, must match the table-prefix rule.

The main rule is simple: the manifest and filesystem must agree. When you add a
capability, add both its files and its manifest entry. When you remove a
capability, remove both.

## Next [#next]

* Use [Quickstart](/docs/building-apps/quickstart) for the scaffold and install
  flow.
* Use [Capabilities](/docs/building-apps/capabilities) for action, agent, API,
  database, and web UI implementation details.
* Use [Design Guidelines](/docs/building-apps/design-guidelines) for product,
  mobile, icon, and README rules.