I/D/E · flue-framework

Runtime Map

Summary

A source-pinned map of what Flue owns from agent file to deployed runtime, and where the rented model loop begins.

This series expands the hub post, Flue Under the Hood, into a source-level reading guide. The hub gives the architectural judgment: Flue owns the harness layer and rents the model loop. This chapter turns that claim into a map you can use while reading or changing the code.

The source pin for every claim in this chapter is withastro/flue@dbaa9effa305561c627c6836559f8a0cbce67875, the merge commit for PR #130. At that pin, packages/runtime/package.json says @flue/runtime is 0.5.3, and the model-loop dependencies are @mariozechner/pi-ai and @mariozechner/pi-agent-core.

Runtime Ownership Ledger

The first debugging move is to identify which runtime boundary owns the behavior before opening files.

Domain Word

In Flue, harness means the aggregate root for runtime capabilities around an agent: sessions, filesystem access, environment execution, sandbox adaptation, child sessions, tools, compaction, and deployed inspection routes.

The invariant is simple: user agent files declare work; the harness owns the runtime behavior that makes that work repeatable, inspectable, and deployable.

The twelve-factor pressure is build, release, run. A headless agent framework cannot stop at a library call. It has to create the same agent from source, wire it into a runtime, invoke it over a protocol, and inspect it after the process that started it is no longer the only thing holding the story.

The One-Page Map

FLUE RUNTIME MAP
.flue/agents/*.ts
 user handler declares intent

ctx.init(...)
 constructs a harness for this agent instance

Harness
 owns sessions, fs, env, shell, child-session creation

Session
 owns SessionHistory, active path, role/model/tool scoping
 pi-agent-core Agent
      pi-ai provider/model transport
 agent.ts built-in tool contracts
 sandbox.ts SessionEnv/SandboxApi adapters
 compaction.ts threshold + overflow recovery

Deployment surface
 flue-app.ts public OpenAPI and run routes
 admin-app.ts separately mounted inspection app
 CLI build plugins for Node and Cloudflare

That map is the spine. If you are debugging an agent file, you start at the top. If you are debugging provider payloads, you go to session.ts and runtime/providers.ts. If you are debugging command execution, you go to agent.ts, sandbox.ts, and the adapter for the target. If you are debugging how a caller finds an old run, you skip straight to runtime/flue-app.ts, run-store.ts, run-registry.ts, and handle-run-routes.ts.

The useful habit is to ask which boundary owns the behavior before opening files.

Agent Files Declare Intent

The user-authored agent file is intentionally thin. It is where the handler names the work, accepts payload, and calls ctx.init(...). The agent file should not become the place where session persistence, provider transport, run registry lookup, or sandbox mechanics leak in.

That is the first design bet. A framework becomes sticky when application code can stay small while the runtime absorbs the cross-cutting behavior. In Flue, that runtime begins at ctx.init(...).

Harness Is The First Runtime Boundary

packages/runtime/src/harness.ts defines Harness. It exposes session(), sessions.get(...), sessions.create(...), sessions.delete(...), fs, and shell(...). It also holds the in-process map of open Session objects.

The interesting detail is the storage key. When a session opens, the harness derives a storage key and an affinity key from the agent instance, agent name, and session name. That gives a session durable identity without making the user agent file manage persistence.

The harness also owns child session creation for tasks. When a task tool delegates work, the parent session does not fake a subagent inside the same transcript. The harness creates another Session with its own name, storage key, role, cwd override, task depth, and parent environment boundary.

Session Is The Core

packages/runtime/src/session.ts is the load-bearing file. It is where Flue turns stored history into model-visible messages, scopes roles and model overrides per call, constructs the rented Agent, synchronizes produced messages back into SessionHistory, and triggers compaction after turns.

This is the key ownership split:

BehaviorOwner
Active session tree and leafFlue SessionHistory
Provider model loop@mariozechner/pi-agent-core
Provider/model payload semantics@mariozechner/pi-ai
Role, model, thinking override precedenceFlue Session
Tool schema list and custom tool collision checksFlue Session and agent.ts
Compaction state transitionFlue Session and compaction.ts

The constructor makes the split visible. Flue prepares initialState with system prompt, model, tools, previous messages, and thinking level. It passes getApiKey, onPayload, toolExecution: 'parallel', and sessionId to new Agent(...). After the rented loop runs, Flue syncs messages, saves history, aggregates usage, emits events, and possibly compacts.

Tools Cross Into Runtime Reality

packages/runtime/src/agent.ts defines the model-visible tools: read, write, edit, bash, grep, glob, and task. The schema alone is not the contract. The contract is schema plus runtime behavior.

That is why packages/runtime/src/sandbox.ts matters. A model can request a command timeout through the bash tool, but the effect is only real if the tool passes timeout into SessionEnv.exec(...) and the sandbox adapter honors it. The pinned source deliberately pushes timeout both as a native provider hint and as an abort signal where possible.

This is the boundary behind the hub post’s PR #25 example. The bug was not “a bash option was missing.” The bug was that the model-visible schema promised a capability the runtime failed to enforce.

Compaction Is A Runtime Transition

packages/runtime/src/compaction.ts is not a prompt snippet. It has settings, token estimation, cut-point selection, message serialization, summary generation, split-turn handling, file-operation tracking, and usage aggregation.

The session calls it in two cases:

ModeTriggerRetry?
Thresholdprovider-reported assistant usage exceeds contextWindow - reserveTokensNo
OverflowisContextOverflow(...) detects provider context overflowYes

Overflow recovery proves why the session tree matters. The session removes the failed assistant leaf, saves the adjusted history, appends a compaction entry, rebuilds model-visible context, and continues the underlying agent. A flat transcript would make that recovery much harder to reason about.

Runtime Routes Make It A Framework

packages/runtime/src/runtime/flue-app.ts turns the harness into a service surface. At the pinned source it mounts:

GET  /openapi.json
POST /agents/:name/:id
GET  /runs/:runId
GET  /runs/:runId/events
GET  /runs/:runId/stream

That route set is the difference between “call a library” and “run a headless agent service.” A caller can invoke an agent by name and instance ID, then inspect by bare run ID later. The run registry is what removes the need for the caller to remember which agent instance owns a run.

admin-app.ts is separate. It exports admin() so a user can mount an inspection API deliberately, usually behind their own auth middleware. Do not blur public and admin routes in prose or diagrams.

Build Targets Are Runtime Shapes

The CLI build plugins complete the framework picture. packages/cli/src/lib/build.ts discovers agents, roles, optional app entry, and target configuration. The Node plugin generates a Hono server with in-memory registries and local session env support. The Cloudflare plugin generates a Worker entry, Durable Object classes, request-scoped registry/store factories, Workers AI provider registration, and Wrangler config output.

The source agent remains one thing. The runtime shape changes because the host changes which stores, bindings, registries, and sandbox adapters exist.

TargetWhat changesWhat should stay stable
NodeHono server, local env, in-memory run registryAgent handler, session language, public routes
CloudflareWorker entry, Durable Objects, bindings, request-scoped stores, Workers AI providerAgent handler, session language, public routes
Custom appUser owns app composition and middlewareflue() remains the public sub-app

The Eight Chapters

ChapterBoundaryReader outcome
01 Session Treesession history vs transcriptCan reason about active path, leaf, deletion, and task sessions.
02 Pi-ai Seamrented model loopCan change provider behavior without confusing ownership.
03 Compactioncontext as state transitionCan debug threshold compaction and overflow retry.
04 Tools/Sandboxschema vs runtime promiseCan verify tool behavior across adapters.
05 Run Registryheadless inspectionCan explain run lookup, events, streams, and admin routes.
06 Build Targetsgenerated runtime shapesCan predict Node vs Cloudflare differences.
07 Extending Fluesafe extension seamsCan add providers, tools, sandboxes, and MCP tools without seam drift.

What Breaks If This Boundary Drifts

DriftFailure
Agent files start owning persistenceEvery app invents its own session/run story.
Provider retention is called Flue memoryReplay becomes dependent on external hosted state instead of session history.
Runs and sessions collapse into one wordCallers cannot tell execution identity from conversation state.
Build targets are called examplesDeployment behavior stops being treated as a tested framework surface.
Admin and public APIs blurOperators accidentally expose inspection routes without their own auth boundary.

Verify In Source

  • packages/runtime/package.json: @flue/runtime is 0.5.3, and dependencies include @mariozechner/pi-ai and @mariozechner/pi-agent-core.
  • packages/runtime/src/harness.ts: Harness exposes sessions, fs, and shell(...), and creates child sessions through createTaskSession.
  • packages/runtime/src/session.ts: Session constructs new Agent(...), syncs messages back to SessionHistory, and triggers compaction.
  • packages/runtime/src/agent.ts and packages/runtime/src/sandbox.ts: built-in tool schemas cross into SessionEnv and SandboxApi.
  • packages/runtime/src/runtime/flue-app.ts: public route methods match the list above.
  • packages/cli/src/lib/build-plugin-node.ts and build-plugin-cloudflare.ts: generated entries configure the runtime before mounting flue().

References

Flue-framework Ch 1/8
  1. 1 Runtime Map 24m
  2. 2 Session Tree, Leaf, And Replay Safety 26m
  3. 3 The Pi-ai Seam 22m
  4. 4 Compaction As Failure Recovery 28m
  5. 5 Tool Contracts And Sandbox Reality 25m
  6. 6 Runs, Registries, Logs, And APIs 27m
  7. 7 Build Targets And Deployment Shape 26m
  8. 8 Extending Flue Safely 24m