Skip to content

ADR-0033: Worker bundling for YAML agent definitions and Markdown prompts

ADR-0033: Worker bundling for YAML agent definitions and Markdown prompts

Section titled “ADR-0033: Worker bundling for YAML agent definitions and Markdown prompts”

Status: Accepted Date: 2026-05-02 Extends: ADR-0031 (YAML agent definition format) Related:

  • ADR-0014 (Cloudflare Workers as the runtime)

ADR-0031 committed agents to YAML files on disk with system prompts in companion Markdown files. It deferred the worker bundling question:

The Worker has no fs at runtime. YAML files and prompt files are bundled at build time (esbuild text loader, wrangler text_blobs, or raw import of .yaml strings — to be locked in a future commit when the scenario lands).

That commit is now happening. The Phase 1 demo scenario (Order Triage + Refund Decision + Communication) lands three YAML agents and three Markdown prompts in apps/worker/agents/. The worker needs to load them at startup; runtime fs access doesn’t exist on Workers, so the files must be inlined into the bundle.

Use Wrangler’s built-in [[rules]] configuration with type = "Text" for .yaml, .yml, and .md extensions. Static imports inside the worker (import yaml from './agents/triage.yaml') yield the file contents as a UTF-8 string at runtime.

Three options were considered:

(a) Wrangler [[rules]] for Text — chosen. Wrangler’s bundler natively supports declaring custom file extensions as text imports. Adds two [[rules]] blocks to wrangler.toml. No custom plugins, no custom build step, no extra tooling.

(b) Vite-style ?raw query suffix. Standard with Vite/Rollup but not supported by Wrangler’s built-in esbuild bundler. This was my initial lean, corrected before scaffolding when I verified against the Cloudflare bundling docs. Listing it for transparency.

(c) Custom esbuild plugin or Custom Builds escape hatch. Wrangler supports running a custom build before its own. Overkill: we have no transformations to apply, just text inlining, which (a) handles natively.

wrangler.toml
[[rules]]
type = "Text"
globs = ["**/*.yaml", "**/*.yml"]
fallthrough = true
[[rules]]
type = "Text"
globs = ["**/*.md"]
fallthrough = true
// worker code
import triageYaml from './agents/triage.yaml';
// triageYaml is a string at runtime — the YAML source
import triagePrompt from './agents/prompts/triage.md';
// triagePrompt is a string at runtime — the markdown source

TypeScript needs ambient module declarations to recognize these imports. They live in apps/worker/src/agent-files.d.ts:

declare module '*.yaml' {
const content: string;
export default content;
}
declare module '*.yml' {
const content: string;
export default content;
}
declare module '*.md' {
const content: string;
export default content;
}

The @agent-platform/agent-loader package’s loadAgentFromString primitive accepts a YAML source as a string. The worker code feeds bundled YAML text into it. For system prompt resolution ({ file: ./prompts/x.md } references), the worker supplies a custom FileReader that maps prompt paths to bundled markdown strings — a small in-process lookup, not a real filesystem access.

// Worker assembly
import triageYaml from '../agents/triage.yaml';
import triagePrompt from '../agents/prompts/triage.md';
const PROMPT_REGISTRY: Record<string, string> = {
'./prompts/triage.md': triagePrompt,
// ... other prompts
};
const reader: FileReader = async (path) => {
const text = PROMPT_REGISTRY[path];
if (text === undefined) throw new Error(`prompt not bundled: ${path}`);
return text;
};
const triage = await loadAgentFromString(triageYaml, { reader, source: 'agents/triage.yaml' });

This wiring lands in a follow-up commit (the assembleRuntime rewire). This ADR locks the bundling decision; the wiring is implementation.

Becomes easy:

  • Agent YAML and prompt Markdown ship in the worker bundle automatically. No fetch, no R2, no extra binding.
  • Diff-friendly file layout: each agent is its own YAML, each prompt is its own Markdown file.
  • Build-time validation possible: a future test or pre-deploy check can call loadAgentFromString against bundled YAML and fail the build on schema errors.

Becomes hard / accepted tradeoffs:

  • Bundle size grows with each YAML/prompt. Phase 1’s three agents add ~10KB total — negligible. Hundreds of agents would matter; that’s the trigger to migrate to runtime loading from R2 or D1.
  • Hot reload requires redeploy. Agent edits ship as code. Acceptable for v1; multi-tenant operator-uploaded agents will need a different path (deferred per ADR-0031).
  • Wrangler’s built-in bundler is now load-bearing. A future migration to Vite plugin or Custom Builds is bounded but not free.
  • Doesn’t address agent registries / runtime loading. That’s a future ADR when multi-tenant lands.
  • Doesn’t address worker-host integration with the loader. The wiring (replacing the hardcoded assembleRuntime) is a follow-up commit.
  • Doesn’t add bundling for non-text artifacts. WASM, binary fixtures, etc. would need their own decisions.
  • Agent count grows beyond ~50 in the bundle: bundle size starts mattering; consider runtime loading from R2 or D1.
  • Operator-authored agents arrive: build-time bundling can’t ship user content; runtime loading becomes mandatory.
  • Wrangler bundler changes break this: esbuild updates have broken patterns before; the [[rules]] config is the supported path, but if it ever becomes unsupported, Custom Builds is the escape hatch.

Shipped 2026-05-02 alongside the Scenario B agent files:

  • apps/worker/wrangler.toml: [[rules]] blocks for .yaml, .yml, .md
  • apps/worker/src/agent-files.d.ts: ambient module declarations for TypeScript
  • apps/worker/agents/{triage,refund-decision,communication}.yaml: agent definitions
  • apps/worker/agents/prompts/{triage,refund-decision,communication}.md: system prompts
  • apps/worker/src/agents-yaml.test.ts: load-and-validate test for all three YAMLs

The actual assembleRuntime rewire to use these files instead of the hardcoded research-assistant agent lands in a follow-up commit.