Agent Apps — Core Concepts
Agent Apps builds applications from skills. Every capability — middleware, directives, the agent, file I/O — is a skill with one interface: a name, frontmatter metadata, and a function (ctx, args?) => unknown. To change any behavior, place a replacement skill with the same name earlier in the search path.
Skill Formats
Markdown (.skill.md) — YAML frontmatter between --- fences + natural-language body. The body is a prompt executed by an AI agent. Directives (:arg[name], :env[VAR], :path[rel], :context[path], :config[key], :eval[expr], :skill[name], :tool[name], :inline[file], :script[lang]{src=file}) interpolate dynamic values before the agent sees the prompt.
Code (.skill.js / .skill.ts) — Exports frontmatter object + default async function(ctx, args). Returns a value to set the result. Use ctx.manager.invoke for all operations (file I/O, HTTP, shell) — don't import Node.js modules for capabilities the framework provides as skills. For capabilities the framework doesn't provide (e.g., parsing, image processing, templating), use npm packages — install dependencies with shell access when compiling, or expect them in package.json. The runtime is ESM — use await import() for dynamic imports, not require().
export const frontmatter = { name: 'x', metadata: { params: { type: 'object', properties: { path: { type: 'string' } } } } };
export default async function(ctx, { path }) {
const data = await ctx.manager.invoke('file-read', { path });
await ctx.manager.invoke('file-write', { path: 'out.json', content: JSON.stringify(data) });
return { result: data };
}
Frontmatter
Top-level keys: name, description, allowed-tools (glob patterns restricting which skills can be invoked — by agent or programmatically; generally only needed for markdown skills). Everything else under metadata::
params— JSON Schema for params. Defines the argument shape; tools without params accept no args.returns— JSON Schema for output. Forces structured agent responses viactx.manager.finish(value). Validated automatically; mismatches trigger retry.role—main(project entry point, authority root),middleware(pipeline participant onctx.envelope.target), orcontext(prompt content injected into agent tool docs).tags— string array for categorization.visibility—hiddenexcludes from listings.
All other metadata keys activate the middleware skill with that name. model: { temperature: 0.9 } triggers the model middleware with that value as args.
Context Object
Every skill receives ctx: args, locals.result, locals.config (cwd, workspace, paths, model), globals, locals (per-invocation scratchpad), nonlocals (inherited parent→child), locals.prompt ({ raw, expanded } for markdown, null for code), run.tool (name, description, params, returns, role, tags, visibility, allowedTools), run.middleware (fully resolved cascade metadata), envelope.target (self for normal skills, served context for middleware), run.signal (AbortSignal), run.interactive (true when running in a direct CLI session with a real TTY).
ctx.manager: next() — advance middleware chain (middleware calls await target.manager.next() where target = ctxTarget(ctx)). invoke(ref, args?, options?) — call a skill, returns result directly. finish(value) — set result, stop pipeline (permanent). fail(error) — throw and unwind. abort(reason?). inject(entries) — insert middleware entries after current position. refresh() — re-resolve tool against updated paths. get(path)/set(path, value) — dotted path access. log(event) — emit a log event. tail(opts?) — query recent log events. configure(opts?) — configure log settings.
Pipeline
Every invoke creates an isolated context and runs a full middleware pipeline: the cascade resolves metadata (authority → own keys → CLI overrides), each metadata key matching a tool becomes a pipeline entry, and the pipeline executes as a Koa-style onion. Errors propagate up — catch with try/catch around next().
Project Structure
A project has a main skill (role: main) — both entry point and configuration (no separate config file). Running with no args executes the main skill. Running with a skill name (e.g., agent-apps greet) runs that skill with the main skill's config inherited through the cascade.
The main skill's metadata configures the project: paths (search path array, e.g., [./skills]), workspace, model, web server, etc. These flow down to all child skills. Server middleware like web automatically keeps itself private via $self so it doesn't propagate. Per-skill middleware (session, state, auth, roles, schedule, event) also uses $self — each skill declares what it needs independently. For custom keys, use $private: [key] to prevent propagation, or $public: [key] to explicitly list which keys propagate (everything else stays private).
Skills are discovered lazily from the search path; first match wins. The workspace (default ./workspace) shadows source. Every skill resolves to an authority (nearest role: main ancestor). External dependencies have isolated authority. Skill files go in directories listed in the search path (typically ./skills/). The main skill is SKILL.md in the project root. A skill's name in frontmatter is authoritative — filenames are convention only.
Route middleware translates external events (HTTP, WebSocket, email, webhooks, timers) into invoke calls with transport-specific conventions. Some automatically set returns schemas and enrich prompts — check a harness's description before declaring your own returns.