# Dynamic Agents

**An agentic framework on Cloudflare Dynamic Workers. Agents that build agents.**

Capabilities, delegation and code are resolved at runtime, per caller, per turn, never
frozen at import.

- Site: https://dynamicagents.dev
- Documentation: https://docs.dynamicagents.dev
- Source: https://github.com/dynamicagents
- Start from the template: https://github.com/new?template_owner=Looping-AI&template_name=looping-starter&owner=%40me&name=my-agent&visibility=private
- License: Apache-2.0 (open source)

---

## What it is

A TypeScript framework for building agents that run on Cloudflare Workers. It ships the
parts that are identical for every agent and easy to get subtly wrong: agent-to-agent
identity, the durable task lifecycle, delegation to isolated subagents, and a test harness.
It deliberately ships none of the words your agent says.

It is not a Python framework, and it is not portable to a generic Node server. It is built
for workerd, Durable Objects, Workflows and Dynamic Workers.

## Why it is called dynamic

Three things most frameworks settle when the module loads, this one settles while it runs.

| Layer        | Resolved                    | Mechanism                                                                        |
| ------------ | --------------------------- | -------------------------------------------------------------------------------- |
| Capabilities | per Durable Object instance | the plugin registry is built in `onStart()`, when `env` exists                   |
| Delegation   | per turn                    | the model decomposes a turn into a durable subtask DAG; a wave scheduler runs it |
| Code         | per run                     | `env.LOADER.load()` starts another Worker from source, sandboxed                 |

Resolving a plugin registry at import time, which on Workers is always too early because
`env` does not exist at module scope, freezes it before the runtime knows anything, defeats
tree-shaking, and makes runtime plugin selection impossible. Avoiding that is the single
reason the core package exists in the shape it does.

## The coder agent (preview)

> Status: preview. Under active development; treat as not yet stable.

Give it a repo and a change. It:

1. **Clones** into a Linux sandbox keyed on the caller rather than the task, so a follow-up
   request lands in a warm checkout. It refuses to start on a dirty tree, because those
   changes are a previous task's work and nobody could recover them once discarded.
2. **Changes.** Edits, runs the suite, and iterates until the tests pass or the turn budget
   is spent. The budget is metered across rounds, not per call.
3. **Opens a pull request.** A human merges it. Nothing reaches a default branch because an
   agent decided it should.
4. **Loads** the result with `env.LOADER.load()` as a Dynamic Worker, sandboxed, with only
   the bindings and network access that were granted.

It runs on a different model provider than its sibling agents. The round loop never learns
which provider produced its `LanguageModel`, which is what makes `ModelRuntime` a real seam
rather than a config field.

## Self-evolution (preview)

> Status: preview. Under active development; treat as not yet stable.

Every run leaves a durable trace. An agent reads its own traces, proposes a change to its
soul, its plugin list, its model ids or its budgets, and sends that change through the coder
agent, which means through a pull request.

- **Observe:** traces, budgets and tool-call outcomes are already durable, because the task
  lifecycle needed them.
- **Propose:** a diff against source, never a hidden update to state you cannot read.
- **Merge:** every self-change arrives as a pull request, reviewable and revertable with
  `git revert`.

Nothing rewrites itself in place.

## The boundary

Every agent speaks A2A, the open agent-to-agent protocol, and publishes a signed card at
its own address stating what it is and what it can do. Giving one agent access to another
is pasting that address: the card is fetched and read, so there is no adapter, no client
library and no shared codebase between the two.

That is also why identity has to be settled before any code runs. Every call arrives with a
signed token, and every token is checked four times, in this fixed order, before the request
reaches an agent:

1. The token names where its signing keys live (`jku` present in the protected header).
   A token that names no key source is refused before anything else is read.
2. That key source is on the allowlist, validated _before_ the fetch, so a caller cannot
   point at a JWKS they control and sign their own way in.
3. The sender matches the keys: `iss` origin equals `jku` origin, so one allowed caller
   cannot impersonate another.
4. One signature scheme, no negotiation: verification is pinned to EdDSA, so algorithm
   confusion has no room to happen.

Each of the four checks has a test that asserts the refusal, not the pass.

Only then does a request reach an agent. Every verified caller gets its own agent instance
(a Durable Object) with its own storage, so one caller's tasks are unreachable from another
by construction, not by a query filter someone has to remember to write. No secret crosses
the boundary in either direction: the caller proves itself with a signed token, the agent
with its signed card.

## What ships, and what stays yours

**Core ships** everything you cannot choose not to have:

- Signed agent cards, JWKS, gateway-token verification
- The durable task lifecycle, driven by a workflow
- The subtask DAG, wave scheduling, the round loop
- Isolated subagent execution
- The test harness: fakes, fixtures, HTTP cassettes

**Stays yours**, roughly 250 lines per agent:

- The soul: what your agent is
- The round contract the model is held to
- Model ids, budgets, limits
- Which plugins load, and any you write
- Every user-facing message, including the failures

Core ships no prompt copy, not a soul, not a round contract, not a failure message, because
a run must never execute under an identity nobody chose.

## Plugins

A capability is a plugin, and a plugin is not a package: it is an object satisfying a
contract, so writing your own is a file rather than a release.

Available today: `browser`, `recall`, `workspace`, `repo`, `computer`, `triage`, `arc-agi`.

```ts
export const plugins = (host: PluginHost): AgentPlugin[] => [
  general({
    primaryModelId: host.primaryModelId,
    fallbackModelId: host.fallbackModelId,
  }),
  browser({ binding: host.env.BROWSER }),
  workspace(),
  recall({
    ai: host.env.AI,
    index: host.env.VECTORIZE,
    namespace: host.callerKey,
  }),
];
```

Delete a line and that module leaves the bundle entirely. Nothing in core imports a plugin
and there is no root barrel, so the guarantee is structural rather than a tree-shaker's
opinion, and CI asserts it on the built module graph.

## Testing and maintenance

A full agent turn is drivable in a test, sent the way a gateway would send it:

```ts
const harness = createAgentHarness({ worker, env, tenant: "reactive" });
using _ = harness.interceptGateway();

const accepted = await harness.send("what's the weather?");
expect(accepted.status.state).toBe(TaskState.TASK_STATE_SUBMITTED);
```

- Specs run inside real workerd, not a Node approximation.
- HTTP cassettes match on method, URL and body, never on headers, so a runtime upgrade
  cannot invalidate them. A request with no active cassette is blocked rather than sent.
- Each agent's module graph is checked against the plugins it does not install, using
  esbuild's metafile rather than a string search.

## Numbers

- `$0.002–0.008`: cost per run on a seven-step agent, across a full turn.
- `1,557 KiB`: a single-turn agent's bundle. It never imports the delegating loop, and CI
  fails the build if it starts to. Asserted on the built module graph on every commit.
- `~250 lines`: the code that is actually yours per agent. The loop, the subtask DAG and the
  task lifecycle ship in core.

End-to-end latency on a fixed seven-step agent is still being measured and is not published
yet. Do not infer or estimate a figure for it. A seven-step agent makes seven inference
calls, so it is bounded by model latency rather than by the framework.

## Getting started

The starter is a public GitHub template. One click creates your own repository with four
example agents in a single Worker: a delegating round agent, a single-turn agent that
decides whether each message is even for it, a game player, and the coder.

Create your repo:

```
https://github.com/new?template_owner=Looping-AI&template_name=looping-starter&owner=%40me&name=my-agent&visibility=private
```

Then:

```bash
npm install
npm run keygen              # one signing key for the deployment
npm run deploy
```

`npm run agent:new` adds an agent across the four places one exists.
`npm run agent:remove` puts every one of them back, byte for byte.

Browser Rendering and the coder's container both need a paid Workers plan. Everything else
runs on the free tier; remove those two plugins and the rest deploys unchanged.

## Requirements

- Node 24 or newer, for build and test only; the packages run on workerd
- Bindings: `AI`, one Durable Object, one Workflow
- Secrets: a signing key and the gateway origins you accept calls from

## Contact

Submit an email address at https://dynamicagents.dev and you get a setup path for what you
are building: which plugins to install, which loop shape to start from, and what to skip.
It is not a drip sequence.
