Skip to content

File-authored Agents

A file-authored agent keeps its declarative behavior in a folder and compiles that folder into an immutable, content-addressed Agent Artifact. Production execution binds the artifact to a Runtime Revision and pins that exact pair to the conversation thread.

The runnable example is in apps/examples/file-agent-chat. It uses gpt-4.1-mini and exercises the generated Node server through kuralle chat.

agent/
instructions.md # required
agent.json # serializable identity, model, and limit fields
tools/**/*.ts
flows/**/*.flow.json # or *.ts
policies.ts
skills/<name>/SKILL.md
skills/<name>/references/**
references/**
workspace/**
subagents/<id>/**

Those nine names are the complete slot set (ROOT_SLOTS in @kuralle-agents/build). An unknown slot fails the build, as do symlinks, case-fold collisions, malformed exports or skills, recognizable credentials, and quota violations. policies.ts must export at least one policy phase — input, output, tool, refine, or validate.

Tool, flow, and policy modules are parsed during discovery and statically imported only by the generated runtime bundle; the control plane never executes uploaded TypeScript.

flows/*.flow.json files are declarative FlowDefinition graphs. The compiler validates each one strictly and embeds it inline in the artifact — a bad flow fails the build and names the dotted issue path. TypeScript modules under flows/ still compile to capability references resolved from the host runtime.

references/** becomes a read-only /references mount. workspace/** is copied exactly once into the thread-private /workspace mount — Node keys that directory by tenant, thread, and agent, while a Cloudflare thread Durable Object owns its own SQLite workspace. Artifact metadata is kept outside the visible mount, and model writes stay disabled unless the host opts in. Skill bodies and resources remain progressively loaded content.

From the repository root:

Terminal window
bun packages/cli/src/cli.ts build \
--agent apps/examples/file-agent-chat/agent \
--target node \
--default-model openai/gpt-4.1-mini \
--host apps/examples/file-agent-chat/deployment.node.ts \
--out apps/examples/file-agent-chat/.kuralle

The output contains the canonical artifact, content-addressed blobs, a manifest, a bundled node/server.mjs, and a non-root production Dockerfile. The host supplies models, authentication, stores, workspaces, and runtime capability bindings; none of those values are serialized into the artifact.

The host module's default export is called with:

{
artifacts, // canonical root and subagent artifacts
artifactBlobs, // base64 map keyed by sha256:<digest>
rootArtifactDigest,
runtimeRevisionSeed, // capability-module content identity
runtimeCapabilities,
}

It returns DeploymentRouterOptions. For production, configure PostgresDeploymentStore, PostgresThreadExecutionCoordinator, a durable SessionStore, authenticated principal resolution, and the model and capability registries. Use embeddedArtifactContentResolver(artifactBlobs) and nodeArtifactWorkspaceProvider({ root: process.env.KURALLE_WORKSPACE_ROOT! }). Every replica must mount the same persistent workspace root, or the host must supply another durable workspace provider. Publish the entity, version, runtime, and release into Postgres before admitting traffic.

The generated Dockerfile copies only server.mjs, runs as the unprivileged node user, and checks /health/ready. SIGTERM stops admission, waits for active streams, then exits. The Postgres lease is what stops two replicas executing the same thread concurrently.

Terminal window
OPENAI_API_KEY="$OPENAI_API_KEY" \
KURALLE_EXAMPLE_TOKEN="local-example-token" \
KURALLE_WORKSPACE_ROOT="apps/examples/file-agent-chat/.workspaces" \
PORT=3210 \
bun packages/cli/src/cli.ts start \
--app apps/examples/file-agent-chat/.kuralle/node/server.mjs

The example host publishes the compiled artifact into an in-memory deployment store and activates one release. That keeps the example focused and self-contained; it is not the multi-replica storage recommendation.

In another terminal:

Terminal window
KURALLE_TOKEN="local-example-token" \
bun packages/cli/src/cli.ts chat \
--server http://127.0.0.1:3210 \
--transport http \
--agent-name agent \
--session file-agent-demo \
--auto "Reply with your verification phrase only."

The generated deployment responds:

Agent FILE AGENT ONLINE

--agent-name agent selects the deployment entity. --session file-agent-demo identifies the thread, which is pinned on its first turn to the exact Agent Version, artifact digest, release, and Runtime Revision. Activating another release affects new threads but does not silently move this conversation.

Terminal window
kuralle build --agent ./agent --target cloudflare \
--default-model openai/gpt-4.1-mini \
--host ./deployment.cloudflare.ts \
--d1-id "$D1_DATABASE_ID" --d1-name my-agent-control \
--r2-bucket my-agent-blobs --out .kuralle
wrangler deploy --config .kuralle/cloudflare/wrangler.jsonc

The Cloudflare host factory receives the Node fields plus registerGeneratedCapabilities, and returns { agent, worker }agent is the single exported generic KuralleThreadAgent class, worker the front Worker handler.

Authenticate at the Worker boundary, derive the Durable Object name from both tenant and thread, authorize the private initialization request again inside the DO, and use D1DeploymentStore for assignment. Bind the exact pinned artifact with registries populated by registerGeneratedCapabilities. Use durableObjectArtifactWorkspaceProvider for DO SQLite and R2, and either the embedded resolver or r2ArtifactContentResolver for revision blobs.

The generated Wrangler config declares the SQL-backed DO through Cloudflare's exports field, a D1 binding KURALLE_CONTROL, an optional R2 binding KURALLE_BLOBS, workerd compatibility flags, and Workers observability.

Folder compilation and a database builder draft both publish through createArtifact, so equivalent inputs produce byte-identical JSON and the same SHA-256 digest. A release allocation must total 10,000 basis points and pair each Agent Version with a compatible Runtime Revision.

  • Activate a release to change assignment for new threads.
  • Keep old capability versions in the deployed runtime while any pinned thread can still reference them.
  • Roll back new-thread traffic by activating a prior release. Existing thread pins do not move.
  • Treat Worker or container rollback separately from release rollback. A code rollback is unsafe if it removes a runtime revision a pin still names.
  • Cloudflare DO SQLite migrations are forward operations. Test migration and Worker rollback combinations before a production rollout.

Every runtime span carries tenant, entity and version, artifact digest, release, runtime revision, environment and branch, and config and secret generations. Conversation audit events are persisted in the dedicated audit store and retain an inline crash-safe copy — audit is not inferred from traces.

Alert on readiness failures, lease renewal failures, binding or content verification failures, stream errors, DO initialization conflicts, D1 and R2 errors, and drain timeouts. Logs must not contain prompts, workspace bytes, tool arguments, tokens, or resolved secrets by default. Retain the build manifest, artifact JSON, runtime bundle digest, migration tag, and release record for every deployment.

Replace the example's in-memory deployment/session stores and process-local lease with Postgres implementations before running multiple replicas. Keep schema changes in your application's existing migration workflow. See Agent Definitions in Your Database for Postgres, Prisma, Drizzle, Neon, and Cloudflare control-plane patterns.