Context
Durable queues introduced a second Vercel function for /api/internal/agent/continue. The staging fix in PR #509 preserves the current runtime contract by copying Junior app content and configured plugin package content into Nitro server output before Nitro Vercel clones per-route .func directories.
That fix is intentionally narrow and preserves existing behavior. Longer term, we should consider whether Junior should stop depending on runtime directory discovery inside serverless functions.
Problem
Junior currently discovers content by scanning filesystem paths at runtime, including:
app/SOUL.md, WORLD.md, DESCRIPTION.md, and reference markdown
app/plugins/**/plugin.yaml
app/skills/**/SKILL.md
- configured plugin package content under
node_modules/<package>/{plugin.yaml,plugins,skills}
That model works naturally in local dev and in a single server function. It gets brittle when a deployment platform splits routes into multiple function filesystems. Vercel Build Output treats each .func directory as the function filesystem boundary, so every function that can run Junior must contain or otherwise be able to load the same content graph.
Common framework patterns are more static:
- static
import/require/analyzable fs usage for file tracing
- framework include lists such as Next
outputFileTracingIncludes or Astro includeFiles
- Nitro
serverAssets accessed through useStorage("assets:...")
- Vite/Rollup raw imports or generated
import.meta.glob graphs
None of those is a drop-in replacement for the current dynamic scanner, but they point at a more explicit build-time content graph.
Proposal
Explore a compiled Junior content provider for Nitro/serverless builds:
- During
juniorNitro() build setup, discover app-local content and configured plugin package content once.
- Emit a virtual module or generated JSON/TS module describing the content graph.
- Load SOUL, plugin manifests, skill metadata, and skill bodies through a provider interface instead of scanning
process.cwd() paths in serverless runtime code.
- Use Nitro
serverAssets/useStorage for larger server-only payloads if lazy file-like reads are valuable, or raw/generated imports for small markdown/yaml payloads.
- Keep the existing filesystem provider for CLI, local dev, and non-Nitro usage where runtime scanning is the simplest contract.
This should make Vercel primary and queue functions load identical content without relying on a post-build directory copy for the long term.
Constraints
- Must support app-local content and configured npm plugin packages.
- Must preserve plugin manifest validation and plugin-owned skill boundaries.
- Must keep skills loadable by name and syncable into sandboxes.
- Must not expose SOUL, skills, plugin manifests, or config-only content as public assets.
- Must work in both the primary Nitro function and Vercel queue callback functions.
- Must not reintroduce source
apps/example/api/internal/agent/continue.ts routing.
- Should keep local dev behavior simple, ideally still filesystem-backed.
Non-goals
- Replacing the durable queue architecture.
- Moving the queue callback outside Nitro as the main fix.
- Requiring every app author to hand-maintain include lists for Junior core content.
- Making plugin package discovery less explicit or silently tolerant of missing package content.
Why not just use existing knobs?
serverAssets is promising, but Junior would need a storage-backed reader instead of direct filesystem scans.
- Raw imports and
import.meta.glob are good for static local files, but configured npm plugin packages still need a generated graph.
- Vercel or framework include lists help when a framework owns tracing, but Nitro Build Output functions are already emitted as
.func directories.
traceDeps helps dependency tracing, but it does not cover app-local app/* content by itself.
Acceptance criteria
- A proof of concept demonstrates SOUL, app skills, app plugins, and configured plugin package content loading through a compiled provider in Nitro/Vercel output.
- The proof covers both the primary server function and a route function created by
vercel.functionRules for the queue consumer.
- CI has an integration/build check that fails if either function cannot load the same Junior content graph.
- Runtime filesystem scanning remains available for CLI/local/non-Nitro environments or is replaced only after equivalent behavior is proven.
specs/plugin-runtime.md, specs/task-execution.md, or a more specific spec documents the selected provider boundary.
References
Context
Durable queues introduced a second Vercel function for
/api/internal/agent/continue. The staging fix in PR #509 preserves the current runtime contract by copying Junior app content and configured plugin package content into Nitro server output before Nitro Vercel clones per-route.funcdirectories.That fix is intentionally narrow and preserves existing behavior. Longer term, we should consider whether Junior should stop depending on runtime directory discovery inside serverless functions.
Problem
Junior currently discovers content by scanning filesystem paths at runtime, including:
app/SOUL.md,WORLD.md,DESCRIPTION.md, and reference markdownapp/plugins/**/plugin.yamlapp/skills/**/SKILL.mdnode_modules/<package>/{plugin.yaml,plugins,skills}That model works naturally in local dev and in a single server function. It gets brittle when a deployment platform splits routes into multiple function filesystems. Vercel Build Output treats each
.funcdirectory as the function filesystem boundary, so every function that can run Junior must contain or otherwise be able to load the same content graph.Common framework patterns are more static:
import/require/analyzablefsusage for file tracingoutputFileTracingIncludesor AstroincludeFilesserverAssetsaccessed throughuseStorage("assets:...")import.meta.globgraphsNone of those is a drop-in replacement for the current dynamic scanner, but they point at a more explicit build-time content graph.
Proposal
Explore a compiled Junior content provider for Nitro/serverless builds:
juniorNitro()build setup, discover app-local content and configured plugin package content once.process.cwd()paths in serverless runtime code.serverAssets/useStoragefor larger server-only payloads if lazy file-like reads are valuable, or raw/generated imports for small markdown/yaml payloads.This should make Vercel primary and queue functions load identical content without relying on a post-build directory copy for the long term.
Constraints
apps/example/api/internal/agent/continue.tsrouting.Non-goals
Why not just use existing knobs?
serverAssetsis promising, but Junior would need a storage-backed reader instead of direct filesystem scans.import.meta.globare good for static local files, but configured npm plugin packages still need a generated graph..funcdirectories.traceDepshelps dependency tracing, but it does not cover app-localapp/*content by itself.Acceptance criteria
vercel.functionRulesfor the queue consumer.specs/plugin-runtime.md,specs/task-execution.md, or a more specific spec documents the selected provider boundary.References
functionRules: https://nitro.build/deploy/providers/vercel