-
Notifications
You must be signed in to change notification settings - Fork 1
Core
Edge adaptation of the upstream agent loop, agent registry, and turn lifecycle.
Upstream reference: Core
The core subsystem provides the foundational agent infrastructure that every composition builds on:
-
AgentRegistry (
ctx.agents) — creates, resumes, and disposes agents. Each agent owns a session, an inbox (next-turn / next-step message queues), and a scoped cordis context. The disposer is a capability — only the creator can tear down the agent. -
AgentLoop — the concrete driver implementing the
Agentcontract. Processes inbox messages through the step pipeline: pre-step validation → model request → streaming → tool execution → post-step. Transitions betweenidleandrunningstatus. -
Agent events — scope-filtered dispatch:
agent/created,agent/disposed,agent/pre-step,agent/request,agent/turn-stopping,agent/status, and inbox events. -
Inbox management — two ordered message lists (
next-turnandnext-step) with append, prepend, replace, remove, clear, and splice operations, all recorded as durableagent/inbox/splicedevents.
Both plugins are installed as-is with zero modification. Agent creation, session binding, inbox management, step pipeline, model streaming, and tool dispatch are entirely upstream code.
Upstream's agent loop runs in the same process as the caller — a turn starts when the caller sends a message and ends when the agent goes idle. Edge adds a transport layer around this because the caller (browser) is remote:
-
activeTurnsmap — tracks which sessions have running turns in the DO. Prevents concurrent turns on the same session across interleaved HTTP requests. -
claimAndOpenAgent()— atomically claims the session slot and opens/resumes an agent before accepting the prompt. Ensures no two requests own the same session simultaneously. -
runAgentTurn()— wraps the upstream turn in a flush-then-publish pipeline: listens tosession/event, flushes to DO SQL, then publishes the event over WebSocket. Binds the shell backend for the turn duration. -
Admission control —
createDurablePromptAdmittergates prompt admission behind a persistence flush, ensuring the user message is durable before the model sees it.
The upstream agent loop itself runs unchanged inside this wrapper — it doesn't know it's being observed and published over a network.
When a turn starts, Edge broadcasts host/session-status { running: true } over WebSocket. When it ends, { running: false }. Upstream reads agent.status directly from memory; Edge translates this into network frames.
Edge fully supports both upstream message modes, end to end:
| Mode | When | UI trigger | Backend path |
|---|---|---|---|
| queue | Agent idle | User sends message while no turn is running |
session.prompt(mode:'queue') → agent.send() → next-turn inbox → processed when turn starts |
| steer | Agent running | User sends message while a turn is active |
session.prompt(mode:'steer') → agent.steer() → next-step inbox → processed at next step boundary |
The client's composer policy automatically selects the mode: resolve(running, gesture, steeringAvailable) — if the agent is running and steering is available, the message steers; otherwise it queues.
Queue management: updateQueue() lets the client edit queued message text, remove a pending message, or promote it to steer. Edge broadcasts session/queue frames on every inbox change and at reconnect baseline so the client always shows the current pending state.
Investigate steer mode UI behavior. (#114) Backend supports both queue and steer modes. The client composer policy auto-selects based on agent running state. However, the UI does not show a visible mode switch or steer indicator when the agent is running — needs investigation to confirm whether steer is transparently active or if a UI gap exists.
- Agent creation, session binding, and disposal lifecycle
- Inbox management and message queue semantics
- Step pipeline (pre-step → request → streaming → tool execution → post-step)
- Scope-filtered event dispatch
- Model request preparation and retry policy
- Turn-stopping negotiation (
agent/turn-stoppingwaterfall)
Edge adds three costs on top of upstream's turn:
- Claim check: one Map lookup per prompt to verify no concurrent turn — O(1), sub-microsecond.
- Flush barrier: each event is flushed to DO SQL before WebSocket publish. This adds a synchronous SQL write per event (~1ms). Upstream flushes at checkpoint boundaries; Edge flushes per-event for stronger durability guarantees.
-
Shell binding:
EdgeShellBindings.bind()associates the Computer VFS workspace with the agent for the turn duration — one Map insertion, released infinally.
A single Durable Object instance (named 'owner') manages all workspaces and sessions — mirroring upstream's single-process architecture. The activeTurns map ensures at most one turn runs per session, while different sessions can interleave within the same DO. Isolation comes from application-level scoping (cordis agent scopes, VFS workspace paths), not from separate runtime instances.
When a session already has a live agent (from a previous turn that hasn't been disposed), openAgentForTurn reuses it — no agent creation cost. Cold sessions (after DO restart) pay the agent resume cost: load session header + event log from DO SQL + rebuild in-memory state.
| Component | Category | Edge Code |
|---|---|---|
| AgentRegistry | Reuse | One ctx.plugin() call |
| AgentLoop | Reuse | One ctx.plugin() call |
| Turn lifecycle wrapper | Bridge |
runAgentTurn() + claimAndOpenAgent()
|
| Status broadcast | Bridge |
host/session-status WebSocket frames |
Key observation: The core agent infrastructure runs entirely upstream code. Edge wraps each turn in a claim → flush → publish pipeline to bridge the network gap between the DO and the browser. The agent loop doesn't know it's running on Cloudflare — it sees the same cordis context, the same session, and the same tool registry as upstream.
The current single-DO architecture mirrors upstream's single-process design but inherits Cloudflare-specific ceilings:
- Memory: 128 MB per DO shared by all active sessions — limits the number of concurrent turns with large context windows.
- SQL storage: 10 GB per DO for all session event logs — long-lived deployments with many sessions will eventually hit this.
- Concurrency: DO is single-threaded — multiple sessions interleave but don't truly parallelize.
- Subagent isolation: agents within the same DO share memory and can't be resource-isolated.
The natural evolution is session-per-DO: each session gets its own Durable Object with independent memory, storage, and lifecycle. This requires:
- A router DO (or the entry Worker) managing workspace → session → DO-id mapping.
- Layered VFS: shared agent-level storage (workspace code, config) accessible across session DOs, plus session-local storage (temp files, spill, tool output) isolated per DO. The shared layer could use R2 or a dedicated workspace DO.
- Subagent via DO fork: child agents spawned as independent DOs with their own memory budget, communicating results back to the parent via DO-to-DO fetch.
- Home
- Architecture
- Core & Scope
- Session & Persistence
- Model & Context
-
Execution & Tools
- Tools
- Bash
- Subprocess 🚫
- PTY Session 🚫
- Background Jobs 🚫
- Filesystem
- LSP Navigation 🚫
- Code Runtime 🚫
-
Web Access
⚠️ -
Skills
⚠️ - Workflow 🚫
- Subagent 🚫
-
Policy & Interaction
- Goal
- Approval 🚫
- Permission Presets 🚫
-
Sandbox
⚠️ - Plan Mode 🚫
- User Interaction 🚫
- Commands 🚫
- Schedule 🚫
- Message Feedback 🚫
- Platform & Access
- Development
- 首页
- 架构
- 核心与作用域
- 会话与持久化
- 模型与上下文
- 执行与工具
- 策略与交互
- 平台与接入
- 开发