For AI agents: the complete documentation index is available at https://scriptedalchemy.github.io/agent-bundle/llms.txt, the full documentation bundle is available at https://scriptedalchemy.github.io/agent-bundle/llms-full.txt, and this page is available as Markdown at https://scriptedalchemy.github.io/agent-bundle/guide/concepts/architecture.md.
Skip to main content
  • English
  • Compiler architecture

    agent-bundle compiles one project into native host plugins. This page describes the compiler as the code works today: three layers of data that later stages read, the modules that produce and consume each layer, and the artifact manifest those stages leave behind. It is the documentation slice of #592: the three layers from step 2, and the v2 authoritative manifest from step 3 (PR #604).

    Paths below are relative to packages/agent-bundle/src/ unless a package name is spelled out. Type names are the ones the compiler exports. There is no ApplicationIR, ProjectionIR, or ArtifactIR type yet.

    The output layout this page describes is the composite plugin root: one directory at the artifact output, with targets selecting which host projections appear inside it (#555 Wave 1, PR #578). There is no artifact/<host>/ partition and no plugin target.

    The three layers

    src/ conventions + agent-bundle.config.ts
            │
            ▼
    config/discover.ts
      + routes/graph.ts  (compileRouteGraph)
            │
            ▼
    DiscoveredProject
      (skills, commands, rules, state, routeGraph, …)
            │
            ▼
    config/normalize.ts  →  NormalizedPlugin     ← Application layer
            │
            ▼
    adapters/*.ts  plan()
      + adapters/capabilities/*.json
            │
            ▼
    TargetArtifactPlan  (one per selected host)
            │
            ▼
    build/compose.ts  (composeProjections)       ← Projection layer
            │
            ▼
    CompositePlan  (entries, hookEntries, identity)
            │
            ▼
    build/build.ts + build/emit.ts + build/compile-stages.ts
            │
            ▼
    composite plugin root
      + agent-bundle.manifest.json
      + agent-bundle.compile-evidence.json
            │
            ▼
    build/compiler.ts + build/external-policy.ts / AB6005
      + build/validate-artifact*.ts / AB6005 (residual), AB6039
      / prepack AB7014, AB7015                   ← Artifact layer

    A reader who wants the filesystem conventions those arrows start from should read Project structure. A reader who wants the bytes those arrows end in should read Targets and artifacts.

    Application layer

    Discovery runs once per build, then normalization freezes a host-independent model. Two structures share that job today; they are not yet one IR.

    What discovery produces

    config/discover.ts walks the project and returns a DiscoveredProject:

    CollectionSourceParser
    skillssrc/skills/<name>/SKILL.md or a rendered-skill module, or explicit skills globsconfig/skill.ts
    commandssrc/commands/*.mdconfig/command.ts
    rulessrc/rules/*.mdcconfig/rule.ts
    statesrc/state.ts (opt out with state: false)config/state-extract.ts
    assets / payloadsassets/ or configured assets / payloadconfig/discover.ts
    routeGraphconventional executable modulesroutes/graph.ts

    compileRouteGraph in routes/graph.ts is the executable-route compiler. It globs these roots (and no others):

    src/layout.{ts,tsx}
    src/mcp/*/layout.{ts,tsx}
    src/mcp/*/{tools,resources,prompts,apps}/*.{ts,tsx}
    src/events/*/*.{ts,tsx}
    src/events/stop.{ts,tsx}
    src/providers/*.{ts,tsx}
    src/cli/**/*.{ts,tsx}
    src/scripts/**/*.{ts,tsx,jsx}

    The frozen result is a CompiledRouteGraph (routes/types.ts):

    • servers[]: CompiledServerSurface (generated / custom / command / remote / conflict), each holding CompiledAgentRoute rows of kind tool, resource, prompt, or app.
    • cli?: CompiledCliSurface (generated / conventional / conflict) with CompiledCliCommand rows whose argv grammar comes from literal config.inputJsonSchema or JSON input.
    • events[]: CompiledAgentRoute rows of kind event-route.
    • scripts[]: kind script.
    • providers[]: CompiledProvider (not a route kind; they wrap execution).
    • layouts?: CompiledLayout (root or server scope). Event routes and browser Apps are never wrapped.
    • contracts?: route-local config.inputJsonSchema metadata, identified by contract:<module>#inputJsonSchema.
    • digest: sha256 over project-relative identity and declared metadata.
    • diagnostics: route-graph errors (AB48xx and AB494x).

    Each CompiledAgentRoute carries id, kind, source, conventional provenance, a statically extracted config (or emptyRouteConfig), an optional bounded inputSchema, optional contract (the shared RouteContract id), and, for MCP routes, serverId. Event routes also carry event (CanonicalAgentEvent from routes/public.ts).

    Skills, commands, rules, hooks declared in config, handwritten MCP entries, and native host hook documents are not in this graph. They stay on DiscoveredProject / NormalizedPlugin beside it.

    What normalization hands forward

    config/normalize.ts consumes DiscoveredProject plus the loaded config and emits a NormalizedPlugin (core/types.ts). That is the object every later planner reads. It does not retain the CompiledRouteGraph; PreparedProject (dev/project-service.ts) carries model and routeGraph as sibling fields. The plugin holds:

    • identity (metadata) and the selected targets (NormalizedTarget[], sorted unique names);
    • skills, commands, rules, hooks, nativeHooks, mcpServers, mcpApps, scripts, assets, payloads;
    • providers and layouts copied off the route graph;
    • runtime.node (the generated-executable floor);
    • optional state, notices, packageBuild, marketplace, host extensions.

    Config-declared hooks and src/events/** routes both land in NormalizedPlugin.hooks. The event-route branch sets eventRoute and an id such as hook:event-route:tool-before (/ in the canonical event replaced by -); the config branch does not. Native host hook documents (Claude hooks/hooks.json sources, and so on) are a third collection, nativeHooks.

    config/validate.ts runs against the same discovered project. It does not rediscover files; it judges what discovery already found.

    What the next layer receives

    Adapters and build/compose.ts receive the whole NormalizedPlugin. They do not call compileRouteGraph again. Shared compile stages (build/entries.ts, build/cli-bins.ts, build/mcp-apps.ts) also read that model: generated MCP entries, routed CLI bins, scripts, and Apps are planned from it once, then attributed to the composite identity.

    Projection layer

    A projection is one host's representation of the same NormalizedPlugin. The config key is still targets. After the composite root landed, that key selects which projections are planned into the one output directory. It does not name the artifact, the runtime, the distribution form, or the application.

    Where targets enter

    1. agent-bundle.config.ts targets, or CLI --target. Omit both and adapters/registry.ts admits only the default host, portable.
    2. config/normalize.ts stores the selected names on NormalizedPlugin.targets.
    3. build/compose.ts planComposite calls sortedProjections(model.targets.map((t) => t.name)) and runs registry.get(name).plan(model) only for those names.
    4. plugin is not a target. targets: ['plugin'] and --target plugin are AB4100.
    5. inspect --target is a display filter on api.ts inspect(): it keeps only that name in the printed plans (unknown name is AB7004). It does not change selectedTargets or the composite identity. inspect --bundler still composes the full selection.

    adapters/composite-layout.ts owns the selection helpers: sortedProjections, projectionIdentity (sorted names joined by +, for example claude+codex), and hookWrapperPath.

    Declaration-level targets on a command, rule, hook, or script still mean "this component reaches these hosts." A component is included when that set intersects the selection. A command or rule scoped to a subset of the selected hosts but sitting in a directory another selected host discovers conventionally is AB4105. Skills always ship to every selected host (skill.targets is the selection); a per-host Skill frontmatter extension that changes bytes is AB4103.

    Who plans a projection

    Each built-in adapter implements TargetAdapter.plan(model) and returns a TargetArtifactPlan (adapters/types.ts): entries (write or copy), optional hookEntries, and diagnostics.

    HostPlannerCapability table
    ampampAdapter.plan in adapters/amp.tsadapters/capabilities/amp-0.0.0-20260907001852-gf348fed.json
    claudeplanClaudeArtifacts in adapters/claude.tsadapters/capabilities/claude-2.1.260.json
    codexplanCodexArtifacts in adapters/codex.tsadapters/capabilities/codex-0.147.0.json
    cursorplanCursorArtifacts in adapters/cursor.tsadapters/capabilities/cursor-2026-08-28.json
    portableportableAdapter.plan in adapters/portable.tsadapters/capabilities/portable-1.0.0.json

    The capability JSON files are the host evidence the registry and the generated Hosts page read: observed versions, manifest paths, install surfaces, path tokens, MCP transports, and component support. Adapters consult them for what a host can express; compose.ts consults registry.hostsComponent and registry.artifactLayout when judging AB4105, and registry.noticeDelivery when intersecting notice-delivery advertisements across the selection. The shared MCP entries and every host's hook wrappers are wired from that intersection so they advertise the same routes.

    TargetRegistry (adapters/registry.ts) is the only place adapters are registered. createDefaultRegistry registers portable as the default, then codex, claude, cursor, and amp. Registration order is not target precedence: planComposite sorts the selection (sortedProjections) before planning it, and the composite identity is the sorted name join. There is no plugin adapter.

    amp is the one built-in host whose plugin is a generated directory rather than a set of declarative documents. Its plan writes an index.js PluginAPI factory under .amp/plugins/{plugin}/ that registers Skills and event callbacks through the API, and its MCP configuration is skill-scoped: a canonical server needs exactly one Amp Skill, and a compiler-owned local server is refused because the pinned contract documents no plugin-root placeholder to run it from. The per-surface evidence stays on the generated Hosts page; the author-facing selection map is Targets and capability map.

    What compose hands to artifact assembly

    composeProjections (build/compose.ts) merges the selected plans:

    • Byte-identical entries at one path are kept once. Different bytes at one path are AB4103 (projections compared in host-name order, paths in path order).
    • install/surface.ts installSurfaceEntries is merged as a synthetic owner (INSTALL.md for any built-in host; install.mjs when cursor or portable is selected).
    • Hook entries are concatenated; they are not merged by path. A hook that reaches more than one selected host compiles one wrapper per host (hooks/<name>.<host>.mjs); a single-host hook keeps hooks/<name>.mjs (hookWrapperPath).
    • Codex and Cursor hook/MCP documents live beside their manifests (.codex-plugin/hooks.json, .cursor-plugin/mcp.json, …). When another selected host has a Hook or MCP server that reaches the conventional hooks/hooks.json / .mcp.json / mcp.json path, those projections emit an empty shield document so folder discovery cannot load that file.

    The CompositePlan the build stages carries entries, hookEntries, selected, identity, projections (one planned host each), cliBin (true when any selected host admits the routed CLI), and noticeDelivery. AB4106 is judged earlier, on the model, in config/validate.ts: a selection that mixes an advanced-registry adapter with any other target cannot share one root.

    Compiled MCP entries, scripts, routed CLI bins, and MCP Apps are not re-planned per host. build/build.ts compiles them once with target: composite.identity and writes them at the root.

    Artifact layer

    Artifact assembly consumes the CompositePlan plus the compiled surfaces and writes one tree at artifactRoot (CLI default artifact/; output.distPath or --output moves it).

    Writers

    WriterModuleWhat it writes
    Plan entriesbuild/emit.ts emitPlanEntriesHost documents, skills, commands, rules, assets, INSTALL.md / install.mjs
    Scripts, hooks, MCP entries, CLI binsbuild/rslib.ts compileRslibSurfaces via build/entries.ts, build/cli-bins.tsscripts/*.mjs, hooks/*.mjs, mcp/mcp-<server>-<hash>.mjs, bin/<plugin>.mjs (+ -flight.mjs workers)
    MCP Appsbuild/mcp-apps.ts compileMcpAppsmcp-apps/<name>.html (inlined into the generated server as well)
    Compile evidencebuild/compile-evidence.ts createCompileEvidenceRecordagent-bundle.compile-evidence.json (one assets[] entry per compiled file)
    Manifestbuild/build.ts manifestFor + build/emit.ts writeManifestagent-bundle.manifest.json
    Publishbuild/emit.ts publishArtifactAtomic rename of the stage directory onto outputRoot

    build/compile-stages.ts orders the work: MCP Apps first (the MCP entries embed their HTML), then the node surfaces.

    Validators

    CheckModuleCode
    Canonical manifest parse + file digest matchbuild/validate-artifact.tsAB60xx family; parse errors throw from parseArtifactManifest
    Host-pack, package-build dist, and MCP App view compilations keep only permitted externalsbuild/compiler.ts, build/external-policy.tsAB6005
    Compile evidence record vs manifest bundle filesbuild/compile-evidence.ts compileEvidenceDiagnosticsAB6039
    Emitted modules the compiler could not see: an expression import() anywhere, a literal import the build was told to ignore (rspackIgnore/webpackIgnore) in a compiled module, and the imports and syntax of JavaScript the framework did not compile or a tools hatch may have rewrittenbuild/validate-artifact-modules.tsAB6005 (residual forms)
    Skills / hooks / MCP documents match the selected hostsbuild/validate-artifact-skills.ts, build/validate-artifact-hooks.ts (executables.hooks[], AB6018), build/validate-artifact-mcp.tsper-surface AB60xx
    Artifact ownership of the output rootbuild/validate-artifact.tsAB6014
    Packed tarball contains every manifest file and no unused install depsbuild/pack-inventory.tsAB7010, AB7014, AB7015

    AB6005 reads compile evidence for every host-pack, package-build dist, and MCP App view compilation. Each ExternalModule that Rspack kept must be a Node built-in, pnpapi, or an emitted sibling; any other external fails. That is the self-containment rule enforced by build/compiler.ts and build/external-policy.ts, alongside the generated-plugin compiler profile (build/rslib.ts composeEntryLibConfig: autoExternal: false, bundle: true, no author-facing externals).

    AB7014 / AB7015 are the prepack gate: a package.json install-time dependency must be justified by prebuilt runtimeDependencies, a packed declaration reference, or a consumer install script (AB7014), and a consumer's npm must be able to fetch it (AB7015). A package inlined into a bundle is not used; compile evidence records the dist bundles named in the diagnostic.

    Composite root

    Built with targets: ['amp', 'claude', 'codex', 'cursor', 'portable'], the host-test example's root is:

    artifact/
    ├── .amp/plugins/<name>/{index.js,skills/,hooks/}
    ├── .agents/plugins/marketplace.json
    ├── .claude-plugin/{plugin.json,marketplace.json}
    ├── .codex-plugin/{plugin.json,hooks.json,mcp.json}
    ├── .cursor-plugin/{plugin.json,marketplace.json,hooks.json,mcp.json}
    ├── .mcp.json
    ├── plugin.json, mcp.json
    ├── hooks/hooks.json
    ├── hooks/<hook>.mjs
    ├── hooks/<hook>.{claude,codex,cursor}.mjs
    ├── hooks/hooks-flight.mjs
    ├── mcp/mcp-<server>-<hash>.mjs (+ -flight)
    ├── bin/<plugin>.mjs, bin/<plugin>-flight.mjs
    ├── skills/, scripts/, commands/, rules/, assets/, mcp-apps/
    ├── INSTALL.md, install.mjs
    ├── agent-bundle.manifest.json
    └── agent-bundle.compile-evidence.json

    Targets and artifacts is the output contract: which host reads which document, the hook-wrapper naming rule, and AB4103 / AB4105 / AB4106. This page does not repeat that table.

    Execution kernel and the Agent Document

    The compiler pipeline above stops at files on disk. Request time is a separate stack. The request kernel that does exist is runAgentRequest (packages/rsc-runtime/src/agent-request.ts, imported as @agent-bundle/runtime). Generated MCP routes, event routes, and rendered CLI commands call it. Config-declared hooks bypass it: generated wrappers decode the host envelope and invoke the author's function directly, with no providers, layout, or Agent Document. Native hook documents are host JSON that adapters validate and merge; they are not compiled wrappers and do not enter the kernel.

    SurfaceEntryRequest path
    Generated MCP stdiomcp-entry.ts (lifecycle only: stdout guard, signals, shutdown) then the generated entry from build/entry-shell.tsmcp-server-runtime.ts registers routes, runs runAgentRequest, renders through a warm Flight worker, and lowers tool results with attachMcpStructuredContent
    Event routesthe same generated MCP server (createEventRuntimeServer in events/ipc.ts)events/project.ts canonicalizes the host envelope and projects the document (projectEventDocument) through runAgentRequest
    Config-declared hooksgenerated hooks/<name>.mjs from adapters/hook-contract.tshost-native codec in the wrapper; does not call runAgentRequest
    Native hook documentsadapter-declared host JSON (nativeHooks)validated and merged into the host hook document; no wrapper, no kernel
    Routed CLIcli-entry.ts runGeneratedCliEntrycommand tree from the route graph; rendered .tsx commands use runAgentRequest. CLI document types live in cli-entry.ts and do not import @agent-bundle/runtime
    Browser Appshost ui/* bridge (Workbench McpAppFrameRelay, serve-app HOST_SCRIPT)the App is a view over a tool result, not a second route graph

    mcp-entry.ts is the stdio process shell. It does not construct request context. mcp-server-runtime.ts does: identity, lineage, providers, runAgentRequest, layout (for document routes), and the MCP projection.

    The Agent Document is the canonical rendered-route response. The type lives in packages/rsc-runtime/src/agent-document.ts and is imported as @agent-bundle/runtime:

    • version (currently 1)
    • status: success | represented-error | failed
    • root: a tree of Agent.* nodes (result, text, markdown, context, json, progress, image, audio, resource, error)
    • optional value (JSON-serializable structured result)

    MCP tool routes lower that document to CallToolResult content plus structuredContent. Resource and prompt routes return their validated protocol result from document.value. CLI human/JSON modes render the same document through the CLI output contract. The Workbench inspects it via packages/workbench/src/runtime/agent-document-client.ts. Raw CallToolResult is an interop boundary, not the authoring model.

    Context values are Observed<T> (@agent-bundle/runtime): state is available (with source native | receipt | derived) or unavailable (with a reason). The kernel does not invent a host or session identity the host did not supply.

    agent-bundle.manifest.json today

    The writer is build/build.ts manifestFor. It assembles an ArtifactManifest (build/manifest.ts; TypeDoc: ArtifactManifest) from manifestProjections, manifestBins / manifestHooks / manifestMcpServers / manifestScripts, manifestDistribution, and build/manifest-routes.ts artifactRoutesFor. writeManifest serializes it with assembleArtifactManifest: canonical stableJson plus a trailing newline. parseArtifactManifest rejects any other byte sequence, duplicate keys, unknown keys, missing keys, unsorted arrays, and a file entry that names the manifest itself. manifestVersion is 5. The public reader is build/manifest-file.ts readArtifactManifest: ok, missing (ENOENT only), or invalid.

    The schema is closed in TypeScript (requireExactKeys in build/manifest.ts) and shipped as JSON Schema (packages/agent-bundle/schemas/agent-bundle.manifest.schema.json), exported as artifactManifestSchema and validateArtifactManifestSchema from build/manifest-schema.ts via api.ts / index.ts. The parser remains the stricter authority: it also checks cross-references, canonical bytes, and sort order. Host plugin.json / hooks.json / mcp.json schemas under adapters/schemas/** and Agent Skills frontmatter under schemas/** are a different contract; the website Hosts / Events pages are generated from adapters/capabilities/*.json and docs/diagnostics.md, not from this manifest.

    Every section and field is on Artifact manifest. This page keeps only the outline.

    Sections the writer emits

    The document has two parts with two version numbers. The public contract (manifestVersion: 6) is what consumers read. The compiler record (compiler, recordVersion: 1) is the operational account of the run, producer, project digests, provenance, adapter revisions, pinned schemas, validation, and changes without bumping manifestVersion; consumer readers never need it.

    SectionWhat it records
    manifestVersion5
    applicationHost-independent identity: id, name, version, optional description
    files[]Every emitted file except the manifest (path, bytes, sha256, kind, optional mode)
    runtime{ node } from NormalizedPlugin.runtime
    projections[]One row per selected host. host is the selected name; builtInHost is the shipped adapter identity (absent for an advanced-registry adapter); documents (plugin?, marketplace?, mcp?, hooks?, entry?) point at the derived host documents; optional marketplace.name. Renamed from targets[].
    routesCompiled route graph: servers[], events[], scripts[], cli?, providers[], layouts[], contracts[], digest
    executablesProcesses the root can start: bins[], hooks[] (kind is config | event-route), mcpServers[] (entry, apps[]), scripts[]
    distributionchannels (local always; npm when packaged) and optional install pointers
    compilerrecordVersion: 1; producer ({ name: 'agent-bundle', version }); project (configPath, configDigest, modelDigest, revision, sourceInputs[], optional packageName / packageVersion); provenance[] (one { path, sourceInputs[] } per files[] row); adapters[] (one { host, adapterRevision, observedVersion, schemas[] } per projection); agentSkills (specification, sourceRevision, schemaSha256); validation ({ source, artifact, projections[] }, every status is 'passed')

    Who reads each field

    Production readers call readArtifactManifest or parseArtifactManifest. Tests are omitted except the installed-host harness.

    ReaderFields usedPurpose
    build/validate-artifact.tsall of themRe-parse the on-disk bytes, match files[] digests and modes, check projections[] and compiler.adapters[] (adapter revision, observed version, and builtInHost identity, AB6010) against the registry, pin compiler.agentSkills and runtime.node. There is no separate coherence pass: the host documents and the manifest are written from the same compiled plans in one build, and every document is a digest-pinned files[] row
    build/compiler.ts, build/external-policy.tsCompileResult.externals for host packs, package-build dist, and MCP App viewsAB6005 compile-evidence self-containment
    build/compile-evidence.tsfiles[] (bundle rows)AB6039: re-check the persisted compile evidence record against the file table without parsing JavaScript
    build/validate-artifact-modules.tsfiles[] (.js/.mjs rows) plus package-build dist; bundle rows the compile evidence record proves are lexed and their literal imports held to the recorded externalsAB6005 residual walk: expression import(), ignored (rspackIgnore) literal imports, uncompiled or hatch-rewritten modules
    build/validate-artifact-skills.tsprojections[] (manifestTargets) plus the treeSkill documents vs selected hosts
    build/validate-artifact-hooks.tsexecutables.hooks[], projections[]AB6018: hook rows vs host hook documents
    build/validate-artifact-mcp.tsprojections[], MCP filesMCP documents vs selected hosts
    build/pack-inventory.tsfiles[], projections[], application.version, compiler.project.packageVersionPacked tarball must contain every manifest file (AB7010); digest drift; release-version agreement; AB7014 / AB7015
    eval/artifact.tsfiles[], projections[], runtimeevalTargetDigests: one digest per projections[].host over the whole file table (same bytes, distinct identity)
    dev/artifacts/artifact-inspection-service.tsfiles[], compiler.project, compiler.provenance[], application, distribution, projections[], routes, executablesWorkbench artifact inspector: one tree from files[], labeled once per projection; the application tree through dev/artifacts/application-explorer.ts (ArtifactInspection.application), a thin projection rather than the manifest's raw shape
    dev/artifacts/artifact-executables.tsexecutables.scripts[]Host-scoped script rows (replaces artifact-script-catalog.ts)
    services/hook-service.tsexecutables.hooks[]hooks list / simulate
    dev/playground/hook-playground-service.tsexecutables.hooks[] (via hook-service)Workbench hook playground
    install/identity.ts readBundleIdentityapplication, projections[]install, uninstall, and doctor: identity from application; projection by builtInHost === host (not the selected host name); plugin document from documents.plugin (must exist); marketplace name from marketplace.name (claude / codex). AB7001 when any of that is missing or invalid. <from>/<host> is never probed.
    serve-app, mcp list / invoke / runexecutables.mcpServers[], projections[]build/manifest-projection.ts resolves the server; --target is optional when one MCP-capable projection hosts it
    inspect --jsonoutput.manifestinspectManifestOutput: projections and executables summary when a built manifest exists
    test/installed.tsprojections[], executables.hooks[]Installed-host proofs pick the shipped adapter by builtInHost
    index.tsre-exports the types, parse / serialize / assemble / read, and the schemaPublic API

    There is no portable install / doctor host (BundleIdentityHost is amp | claude | codex | cursor). doctor covers claude, codex, and cursor; Amp has no lifecycle probe (DoctorHost excludes it).

    What step 3 landed

    #592 step 3 made this file the compiled application index (manifestVersion 2). These numbered items from the former gap list now live on the manifest, except (5):

    1. Route graph. routes records servers, tools, resources, prompts, Apps, CLI commands, event routes, providers, layouts, contracts, and digest.
    2. MCP / CLI / App inventory. executables.mcpServers[] records each server (entry, apps[]); executables.bins[] records the routed CLI.
    3. Hook semantics. executables.hooks[].kind is config or event-route. Native commands an author writes into a host hooks document are not rows. agent-bundle.hooks.json is gone.
    4. Install / doctor identity. readBundleIdentity reads application and the projection whose builtInHost matches the host.
    5. Capability view. Deliberately not recorded. Notice-delivery intersection is derived at build time in build/compose.ts compositeNoticeDelivery; inspect omittedFeatures still comes from the model. Recording that intersection is later #592 work.
    6. Executables as first-class rows. executables.bins[], hooks[], mcpServers[], and scripts[].
    7. Distribution metadata. distribution.channels and optional distribution.install.

    The field contract is Artifact manifest.

    Where the code diverges today

    #592 names the splits this page must not paper over. Paths only; no fixes.

    Discovery is not one graph. compileRouteGraph covers executable routes. Skills, commands, rules, config hooks, native hooks, and handwritten MCP entries are parallel collections on DiscoveredProject / NormalizedPlugin. A later consumer that needs "every surface" still walks several fields.

    Five planners, one merge. planClaudeArtifacts, planCodexArtifacts, planCursorArtifacts, portableAdapter.plan, and ampAdapter.plan each walk the whole model and emit host documents. compose.ts merges bytes; it does not replace those planners with a single project(application, capabilities) function.

    Event execution. Conventional src/events/** routes support plain .ts handlers, rendered .tsx handlers, and explicit lazy .view.tsx modules. Host-native documents remain adapter inputs. Providers resolve lazily through the request context.

    MCP and CLI. A colocated <tool>.cli.ts projects the tool without a second operation. Flag metadata is literal config.inputJsonSchema; runtime schemas are loaded by compiled executables. Inspection does not interpret schema imports or alias chains.

    Browser bridge was forked. One host-side relay now lives in web-host/browser/frame-relay.ts (McpAppFrameRelay) and is mounted by the Workbench, serve-app, dev /web/<server>/<app>, and the artifact's <plugin> web page (#564 landed); Apps use the typed client from agent-bundle/app (#594 landed).

    Filesystem probing next to the manifest. install, uninstall, and doctor read identity through install/identity.ts readBundleIdentity (application, projections[].builtInHost, documents.plugin, marketplace name). They do not probe <from>/<host> or a host plugin.json to find the root. Eval still hashes the whole file table once per projections[].host (evalTargetDigests) rather than recording a per-projection file view. The Workbench inspector rebuilds one tree from files[] and labels it once per projections[] row, reading executables from executables rather than path prefixes.

    Per-target leftovers in shared surfaces. Shared MCP / CLI / App compile uses composite.identity, but hook wrappers still bake one target. dev/artifacts/artifact-executables.ts expands executables.scripts[] into one execution row per host that lists the script. artifact-inspection-service.ts labels the same tree once per projections[] host. Notice delivery is the intersection of every selected host (compose.ts compositeNoticeDelivery), applied to every shared MCP entry, and is not recorded on the manifest.

    Capability selection is narrow. Event routes can declare capability rows such as requires: ['events.toolBefore.deny']; the compiler selects supporting hosts from the pinned capability tables. Other components still use targets, which remains the event-route escape hatch when behavior is genuinely host-specific.

    Roadmap

    #592 is the umbrella. This page documents the three layers (step 2) and the v2 manifest (step 3). The implementation slices under it, each with its own issue:

    IssueSlice
    #555Composite plugin root; targets as projection selection (Wave 1 landed as PR #578)
    #593Resolve local schema imports into one route contract (landed)
    #596One operation, idiomatic MCP + CLI projections
    #595Preflight / lazy providers on the event-route kernel
    #594Generated typed App client + shared bridge
    #564Production web surface on that same bridge

    Step 3 landed the authoritative agent-bundle.manifest.json as version 2; subsequent executable, distribution, and route-contract additions advanced the current contract to manifestVersion 6. The field contract is Artifact manifest. #592 stays open until the remaining acceptance criteria on that issue land.

    Next