git:20260928.461df5e to git:20261019.d7f5063

115 added, 190 removed. Audit C to A.

---
name: agentproto-apps
- description:
- Operate and build Agentproto apps — AIP-53 app bundles (`defineApp().emit(dir)`,
- APP.md + agents/ + workflows/ + ui/) and their daemon lifecycle (app_install,
- app_apply, app_run, app_status, app_stop) plus the app-scoped durable data
- plane (app_data_read/write/list/migrate). Covers serving one with a UI
- (`agentproto app serve` → window.McpApp bridge → /mcp), app-scoped
- path-traversal-safe storage under the installed app dir, sequential
- scout-then-tailor-style app_run sequencing, and model/harness pass-through.
- Use when asking to "build an agent app", "wire an app UI / app_run", "how do
- apps persist data", "run an app's agents in order", or debugging an installed
- app (job-application-kit, …).
+ description: >-
+ Author, develop, and package an agentproto APP (AIP-53) — the real CLI
+ path: `agentproto app init` → edit `.agentproto/` (APP.md, agents/,
+ workflows/, ui/) → `agentproto app validate` → `agentproto app dev` /
+ `build` / `serve` → `agentproto app install` → `agentproto app pack`.
+ Covers the canonical file layout, APP.md frontmatter, the templates
+ (react-ts | vanilla | book | trame), validating all findings at once, and
+ shipping a verified .agentapp bundle. Use when asked to "build an agent
+ app", "scaffold/package an agentproto app", or debug why an app won't
+ validate. For the daemon install/run/data lifecycle that follows, see
+ the `ap-apps` skill.
metadata:
- tags: agentproto, app, aip53, udashboard, app_run, app_data, defineApp, app serve
+ tags: agentproto, app, aip53, authoring, scaffold, validate, app init, app dev, app pack
aip3:
- uses: ["agentproto"]
+ uses: ["agentproto", "ap-apps"]
---
- # Agentproto apps (AIP-53)
+ # Authoring an agentproto APP (AIP-53)
- A **packaged app** is a directory that ships one or more AIP-42 agents, the
- AIP-15 workflows they run, and (optionally) a single-file web UI, emitted under
- `<dir>/.agentproto/` by `defineApp().emit(dir)`. The daemon installs, applies,
- runs, and persists them. This skill is the app plane on top of the base
- `agentproto` skill (sessions/drivers live there).
+ An **app** is a directory whose `.agentproto/APP.md` (schema `app/v1`)
+ declares one or more AIP-42 agents, the AIP-15 workflows they run, and an
+ optional UI. You author that bundle with plain files inside `.agentproto/`
+ — there is no code builder or emit step, and no `agents/` folder at the app
+ root; everything the manifest references lives under `.agentproto/`. This
+ skill is the authoring plane; installing/running/data live in the `ap-apps`
+ skill.
- ## Anatomy — what an app dir contains
+ ## The path
```
- <appDir>/
- .agentproto/
- APP.md # frontmatter: id, agents[], workflows[], ui{path,tools}
- agents/<id>/AGENT.md # AIP-42 agent manifests
- workflows/<id>/WORKFLOW.md # AIP-15 workflow manifests
- ui/index.html # single-file dashboard (CSS+JS inline)
- data/ # default data dir (app_data_* writes here)
- <dataDir>/ # …or wherever `app_install {dataDir}` /
- # `app install --data-dir` pointed it
+ agentproto app init <template> [dir] # scaffold (react-ts | vanilla | book | trame)
+ # … edit .agentproto/APP.md, .agentproto/agents/, .agentproto/workflows/, ui/
+ agentproto app validate [dir] [--json] # all findings at once, exit 0 = sound
+ agentproto app dev <appDir> # Vite dev server + live McpApp bridge (ui/ projects)
+ agentproto app build <appDir> # build ui/ into .agentproto/ui/ (no-op for static UIs)
+ agentproto app serve [appDir] # serve the built/static .agentproto/ui/ standalone
+ agentproto app install <appDir> [args] # register with the daemon (validates) or remote source
+ agentproto app pack <appDir> [--release] [--out <path.agentapp>] # verified .agentapp
```
- `APP.md` frontmatter (`schema: app/v1`, `id`, `agents: [{id,path}]`,
- `workflows: [{id,path}]`, `ui: {path, title, tools: [...]}`, optional
- `data: {dir}` — the default data dir, relative to the app dir). The `ui.tools`
- array is an **allowlist** — `app_tool_call` REFUSES any tool id not in it, so
- when the UI calls a new daemon tool (e.g. `app_data_read`), add it there.
-
- > **Gotcha:** this repo's root `.gitignore` ignores `.agentproto/` by default
- > (it targets agent-scoped scratch). For a first-class app bundle the
- > `.agentproto/` dir IS the deliverable — `git add -f <appDir>/.agentproto` to
- > commit it.
-
- ## The app lifecycle (daemon verbs)
-
- - **`app_install {dir}`** — validates the app (`loadAppHandle`), cross-checks
- every WORKFLOW.md `tool` step id against the daemon's registered tools
- (reports ALL missing at once), and checks the agent adapter resolves. Upserts
- by `appId`. Re-installing refreshes the record.
- - **`app_apply {appId, scopeId?, dir?}`** — makes the app's capabilities
- available in a scope; installs first if `dir` given and not already installed;
- validates `requires` deps are applied. Idempotent.
- - **`app_run {appId, agents?, sequence?, prompt?, cwd?, model?, harness?, adapter?}`** —
- spawns one live session per agent under a fresh `appRunId`. `agents` runs them
- CONCURRENTLY. `sequence: [id, ...]` runs them ONE-AT-A-TIME in order (each
- awaited to a terminal session state before the next), for scout→tailor-style
- dependency chains — both under one appRunId. Pass `model`/`harness` (or
- `adapter`) to select the runner for every spawn.
- - **`app_status {appRunId}`** — returns the run's session descriptors + its
- workflow runs. As of the sequential-run work, `app_status` LAZILY reconciles:
- if the stored run status is `running` but every session is terminal, it
- reports `ended` (with a synthesized `endedAt`) — so concurrent runs are also
- observed to conclude instead of hanging forever at `running`.
- - **`app_stop {appRunId}`** — kills the run's sessions and marks it terminal.
- - **`app_list`** — installed apps with a run-history summary (includes
- `adapter`/`harness`/`model` when set on the run).
- - `app_uninstall` / `app_unapply` / `app_list_applied` / `app_catalog` /
- `app_skill_get` / `app_artifact_get` — installation/uninstall and catalog.
-
- **Terminal states:** run-level status is `running | stopped | ended | failed`.
- A session is terminal at its descriptor status ∈ `ended|exited|done|failed|
- stopped|killed`. The UI's "is it done" check should accept the run wrapper
- (`ended`/`done`/`endedAt`) OR any terminal session status — never only the
- wrapper, since a completed run may still read `running` until reconciliation.
-
- ## The app-scoped data plane (app_data_*)
-
- Generic `fs-*` tools are **workspace-rooted** — unsafe/incompatible for
- installed-app storage. Apps persist through the app-scoped surface, anchored to
- the installed app's **data dir** (`InstalledApp.dataDir`, default `<dir>/data`,
- persisted in `~/.agentproto/apps.json`) with **path-traversal protection**:
- `resolveAppDataPath` rejects absolute paths, drive-letter prefixes, and any
- `..`/symlink escape; every failure returns an MCP error envelope containing
- `traversal`.
-
- **Data dir ≠ source dir.** `app_install {dir, dataDir?}` (CLI: `agentproto app
- install <appDir> --data-dir <path>`) points the plane at a directory outside
- the source tree — that's where multi-GB generated output belongs. Precedence:
- explicit `dataDir` → the previously installed one (a bare re-install never
- moves data) → APP.md `data: {dir}` (relative to the app dir) → `<dir>/data`.
- `app_list` shows it. Resolution rule for an app-relative path:
-
- 1. resolves under `dataDir`;
- 2. under the default layout (`<dir>/data`) a leading `data/` is the legacy
- spelling and is dropped — `data/trips/x.json` ≡ `trips/x.json`;
- 3. if the path (or its top-level folder) exists only under the source `dir`
- (a pre-dataDir install), it resolves there — reads find it, writes update it
- in place, `app_data_list` merges both views. New paths land under `dataDir`.
-
- Address files at the new base (`trips/<id>/brief.json`, not
- `data/trips/...`). Reserved for later: a `store.sqlite` in `dataDir` behind an
- `app_data_query` tool — not there yet.
+ Template `trame` ships the most: agent step + gate workflow, stage-board UI,
+ `data/DATA.md`, `gates/`, `scripts/verify.mjs` wired as APP.md
+ `verify.command`. `vanilla`/`book` are the hand-written no-build-chain shape
+ (single-file `.agentproto/ui/index.html`, no root `package.json`); `react-ts`
+ adds a Vite + TanStack `ui/` source project.
- - **`app_data_read {appId, path}`** → `{appId, path, exists, content}` —
- `.json` paths parse `content`; others return raw text. `exists:false` when
- absent. Never leaks the host absolute path (returns the relative `path`).
- - **`app_data_write {appId, path, content}`** → `{appId, path, size}` —
- JSON-stringified (pretty) for `.json`; `{text}` otherwise. mkdir -p parent,
- atomic tmp+rename. Best-effort, never crashes.
- - **`app_data_list {appId, dir?}`** → `{appId, dir, entries:[{name,type,size}]}` —
- missing dir → empty entries (not an error).
- - **`app_data_migrate {appId, force?}`** → one-time import of a legacy data
- shape into the durable layout; idempotent via `data/state.json` (`force`
- re-runs). Reports `{migrated, jobCount, dossierCount, skippedFolders, alreadyMigrated?}`.
+ ## Layout that `app init` generates
- **Durable layout convention** (adapt to the app; the Job Application Kit uses,
- all relative to the data dir):
```
- <dataDir>/jobs/<jobId>.json
- <dataDir>/search/<searchRunId>.json
- <dataDir>/rankings/<rankingRunId>.json (+ rankings/latest.json for the list)
- <dataDir>/state.json
- <dataDir>/applications/<jobId>/{application.json, job.json, cv.json, cover.md, form-answers.md}
+ <appDir>/
+ .agentproto/
+ APP.md # manifest — schema: app/v1, id, agents[], workflows[], ui
+ agents/<id>/AGENT.md # AIP-42 agent manifest + system-prompt body
+ workflows/<id>/WORKFLOW.md # AIP-15 workflow manifest (steps, cost_class, …)
+ ui/index.html # static UI (vanilla/book/trame) — or ui/ source project
+ ui/ # Vite source project (react-ts template only)
+ data/ # durable app_data_* storage (trame ships data/DATA.md)
```
- Normalize legacy records on migrate: ensure `id` === `jobId` (both present) and
- derive `applyUrl` from `url` when missing. Durable files SURVIVE browser reload;
- localStorage is for harmless preferences only, never as the source of truth.
- ## Serving the UI (the bridge)
+ `APP.md` frontmatter real example (from `app init trame`):
- `agentproto app serve <appDir>` (or the equivalent in-process host) serves
- `<appDir>/.agentproto/ui/index.html` with a `window.McpApp` bridge whose
- `callTool` POSTs to the daemon's `/mcp` endpoint (same-origin JSON
- `/__agentproto/tool-call` route bridged by the serve host). The in-page JS
- dispatches through a wrapper:
- ```js
- window.McpApp.connect().then(bridge => {
- callTool = bridge.callTool
- // appData migrate → list → read, then render
- })
+ ```yaml
+ ---
+ schema: app/v1
+ id: my-app
+ name: My App
+ version: 0.1.0
+ description: >-
+ My App — scaffolded by `agentproto app init` (trame template)
+ agents:
+ - id: my-app-agent
+ path: .agentproto/agents/my-app-agent/AGENT.md
+ workflows:
+ - id: my-app-flow
+ path: .agentproto/workflows/my-app-flow/WORKFLOW.md
+ data:
+ dir: data
+ ui:
+ path: .agentproto/ui/index.html
+ title: My App
+ tools: [app_run, app_status, app_stop, app_list, app_data_read, app_data_write, app_data_list]
+ verify:
+ command: node scripts/verify.mjs
+ ---
```
- App UI tool calls go through `app_tool_call {appId, tool, args}` (enforced by
- the `ui.tools` allowlist), so every tool the UI uses must be allowlisted in
- APP.md. If the bridge is absent the UI should fall back to a standalone/mock
- mode instead of crashing.
- ### Display mode (the ⤢ toggle)
+ - Agent/workflow entries reference manifest files by **relative path**; the
+ folder names are free-form (the scaffold slug matches the `id`).
+ - `ui.tools` is an **allowlist** — `app_tool_call` refuses any tool id not
+ listed; extend it when the UI calls a new daemon tool.
+ - Optional `data: {dir}` sets the durable app_data_* base (default
+ `<appDir>/data`).
- The injected bridge also mounts a floating fullscreen toggle, placed clear of
- the host's chrome (`hostContext.safeAreaInsets`) and themed from
- `hostContext.theme`. It shows **only** for modes the host advertises in
- `hostContext.availableDisplayModes` — Claude Desktop advertises none and has
- its own native control, so nothing is drawn there; Codex advertises
- `fullscreen`. The bridge logs `[mcp-app] displayMode= … availableDisplayModes= …`
- plus the host-context key names, which is the first thing to read when a
- toggle doesn't appear.
+ Replace the scaffolded placeholders with the agent's real prompt, tool
+ allowlist, and the workflow's real steps; the APP.md prose and the
+ AGENT.md body stay stubs until you do. `app validate` reports every
+ finding in one pass.
- The controller is `window.McpApp.displayMode` (also on the object `connect()`
- resolves to): `get()`, `available()`, `request(mode)`, `onChange(cb)`,
- `mountToggle(el?)`. An app with its own header opts the floating button out
- and places it itself:
+ ## Minimal working example
- ```html
- <meta name="agentproto-display-toggle" content="none"> <!-- or "optimistic" -->
- <script>
- const app = await window.McpApp.connect() // connect({displayToggle:"none"}) also works
- app.displayMode.mountToggle(document.querySelector("header"))
- </script>
+ ```bash
+ agentproto app init vanilla notes-digest
+ cd notes-digest
+ # replace agents/notes-digest-assistant/AGENT.md prompt + workflows/
+ # notes-digest-flow/WORKFLOW.md steps with the real task; extend APP.md
+ # ui.tools for any new daemon tool the agents/UI need
+ agentproto app validate . # → "is a valid agentproto app", exit 0
+ agentproto app install . # → registers notes-digest in ~/.agentproto/apps.json
+ agentproto app pack . # → notes-digest-<version>.agentapp
```
- `optimistic` shows the button against a host that advertises nothing but may
- still honour the request, and hides it permanently on the first refusal.
- Restyle via `--agentproto-dm-bg/-fg/-border/-top/-right`.
-
- ## Recipe — build/wire an app UI against durable data (minimal)
+ Run it: static UIs (`vanilla`/`book`/`trame`): `agentproto app serve .`.
+ Vite UIs: `pnpm install`, then `agentproto app dev .` while iterating and
+ `agentproto app build .` before pack — `app pack --release` runs
+ `ui/`'s build itself when missing/stale and drops dev-only files from the
+ bundle.
- 1. Author the app bundle under `<appDir>/.agentproto/` (APP.md + agents +
- workflows + ui/index.html). Use `defineApp().emit(dir)` if authoring in TS.
- 2. `app_install {dir}` then `app_apply {appId}`.
- 3. In the UI, on bridge connect, call `app_data_migrate {appId}` once, then
- `app_data_list`/`app_data_read` to render datasets from disk (NOT
- localStorage). Allowlist `app_data_*` in APP.md `ui.tools`.
- 4. Wire user-facing model/harness pickers into `app_run` args (`model`,
- `harness`) — they're mirrored onto the run and observable via `app_list`/
- `app_status`.
- 5. For scout→tailor: call `app_run {sequence:["scout","tailor"], model, harness}`
- and poll `app_status` until `ended` (or any session terminal).
+ ## Gotchas
- ## Test an installed app (the smoke path)
+ - `agentproto app init`'s first argument is the **template**, the optional
+ second one the target dir: `agentproto app init trame my-app`.
+ - `app dev` requires `ui/package.json` with a `scripts.dev`; static UIs have
+ nothing to compile — use `app serve` (and `app build` is a no-op).
+ - This repo's root `.gitignore` ignores `.agentproto/` at any depth; for a
+ first-class app bundle that dir IS the deliverable —
+ `git add -f <appDir>/.agentproto` to commit it. The `react-ts` template's
+ `.gitignore` un-ignores its built `.agentproto/ui/` output.
+ - `app install` on a real path is idempotent and validates up front
+ (workflow `tool` ids against the daemon's table — ALL missing ids at once);
+ a git URL or `.agentapp` installs via the running daemon. `--data-dir`
+ points durable storage outside the source tree.
+ - Format gates bite: `cost_class` must be `trivial|metered|expensive` and
+ `APP.md`/`AGENT.md`/`WORKFLOW.md` each enforce
+ `schema: app/v1` on APP.md and `schema: agent/v1` on AGENT.md + id regexes — prefer the placeholder
+ fields over inventing new keys.
- 1. Serve it: `agentproto app serve <appDir>` and open the printed URL.
- 2. Fresh load runs `app_data_migrate` once → dashboard populates from
- `data/...` on disk — confirms recovery/migration (not a zero dashboard).
- 3. From devtools assert path-traversal is rejected:
- `callTool("app_tool_call",{appId,tool:"app_data_read",args:{path:"../etc/passwd"}})` → `traversal` error.
- 4. Assert the model/harness controls surface and that a Full Pipeline run
- reaches a terminal `ended` state (not stuck `running`).
- 5. Headless: `app_data_migrate`/`app_data_*` are covered by
- `packages/runtime/src/__tests__/app-data.test.ts`; `app_run` sequencing +
- terminal reconciliation by `app-tools.test.ts`. Run
- `pnpm --filter @agentproto/runtime test`.
+ ## Pointers
- ## Do / don't
+ - ap-apps — the installed-app lifecycle: app_install/app_run/app_status,
+ app_data_* persistence, app_tool_call — everything after install.
+ - ap-workflows — the workflow engine the bundled WORKFLOW.md files run as.
+ - extend-agentproto — publishing skills/adapters that the app might need.
- - **Do** persist via `app_data_*`; path-traversal it.
- - **Do** keep UI tool calls allowlisted in APP.md.
- - **Do** treat a run done when wrapper OR any session is terminal.
- - **Don't** treat `app_data_*` like generic fs-tools (they're app-scoped).
- - **Don't** use localStorage as the data source of truth.
- - **Don't** run `app_run` agents concurrently when the flow is dependency-ordered
- — use `sequence`.