captured against @flue/runtime 1.0 beta · @cloudflare/think 0.15 · bedrock agentcore 0.4 · july 2026
As a bilingual, one of the most effective ways I learned a new language was by comparison. Pick an expression in the new language, find the closest one in a language I already know, and hold them side by side. The nuance lives in the gap.
I do the same thing with tech. When someone introduces a new framework, my first question is compared to what? Then I build the same small thing in both, the new one and the one I'm familiar with and trust, step by step, until I can see where they agree and where they differ. The difference between two implementations is where the learning is.
A while back I stumbled onto this tutorial by Craig Dennis, one of Cloudflare's most gifted and fun developer educators, about Cloudflare's new agent harness, Think. I'd been building personal agents on Flue, a third party OSS framework, on top of Cloudflare Workers and Durable Objects. That was already a "compared to what?" moment. And while I was in it, I threw a third harness into the ring: AWS Bedrock AgentCore + Strands. That gave the comparison an AWS vs Cloudflare axis too. Was it going to be an interesting weekend? Absolutely. And it was.
The example I picked is deliberately generic. A John Doe customer support crew: a triage classifier, a resolver that drafts a reply, and an escalation reviewer with a veto. Same three agents, same hardcoded knowledge base, same Claude model behind the same request. Three implementations. What changed was everything around the LLM call: how you declare an agent, where subagents run, how memory works, how you handle retries, how you trace what happened, and what you pay.
If you're learning how an agent harness is wired up, or weighing a move between Cloudflare and AWS in either direction, this is a good starting point.
This is the writeup. Full code for all three implementations lives on GitHub: fawzy-tat/agent-harness-benchmarking. Enjoy.
Fig. 01 · three vendors, one crew
The layer sheet
Before we open a shop, look at the plumbing. Cloudflare's own launch post frames it as a stack:
Framework (Flue) → Harness (Pi) → Runtime (Agents SDK) → Platform (Workers).
Everything in the benchmark maps onto those four rows, but not every column fills every row. Flue is the only one with a clean framework and harness split. Think collapses framework and harness into one class you extend, which is why the Think box spans two rows.
The AWS column has a twist worth knowing. AgentCore actually ships two hosting products under the same umbrella. AgentCore Harness is a config-only chatbot builder: you supply prompts and settings, AWS runs the loop for you, but there's no place to put your own code. That doesn't fit a multi-agent crew. AgentCore Runtime is the other product: it hosts your container and lets you bring any framework (Strands, LangGraph, Google ADK, or raw Python). This benchmark uses Runtime + Strands, which is why AgentCore Harness sits in the diagram as a dashed alternative and the Strands arrow bypasses it on the right, connecting straight down to AgentCore Runtime.
The Cloudflare column is where the interesting overlap happens. Flue and Think are two different harnesses at the same layer, both plugged into Cloudflare's own Agents SDK. AgentCore's stack is separate all the way down.
The runtime split
The deepest line in the whole comparison isn't framework style or how you write tools. It's the compute unit each product runs on. Cloudflare's Agents SDK runs the harness inside a Durable Object. AgentCore runs it inside a per session microVM. Everything else follows.
Same LLM. Same product. Very different compute bill. Cloudflare puts browser facing HTTP, session storage, and the harness loop into one Durable Object. AgentCore splits them. An API Gateway route lands on a BFF Lambda that signs a SigV4 request, then invokes the microVM where the agent actually runs. The extra hop in Fig. 03 is the accent blue arrow.
Two harnesses on one runtime
Zoom into the Cloudflare column of Fig. 02. There are two names in the harness row. Pi is the harness Flue wraps. It was written by the Astro team, on the same base as OpenClaw. Think is Cloudflare's own harness, named after the class you extend. Both harnesses run the same agent loop (tool call ping pong, prompt assembly, sub agent coordination) against the same Durable Object runtime. Cloudflare shipped a competing harness on top of their own SDK. Now both compete for the same reader's attention.
The rest of this piece is variations on that theme. Flue's take vs. Think's take vs. AgentCore's take, one dimension at a time.
How it feels to write · describe, extend, decorate
The smallest possible echo agent, three ways. Read Listing 01 by column, not by row. Each column is a complete working file.
// .flue/agents/echo.ts
export default defineAgent(() => ({
model: 'anthropic/claude-sonnet-5',
instructions: 'Echo back.',
}));// worker/index.ts
export class Echo extends Think<Env> {
getModel() { return env.MODEL; }
getSystemPrompt() { return 'Echo back.'; }
}# agents/echo/src/main.py
app = BedrockAgentCoreApp()
agent = Agent(
model='us.anthropic.claude-sonnet-4-6',
system_prompt='Echo back.',
)
@app.entrypoint
def invoke(payload): return agent(payload['prompt'])Cloudflare's own launch posts frame the split as "describe what the agent knows" (Flue) vs. "extend a class, override the methods you care about" (Think). AgentCore adds a third approach: "bring your own code, we host the container". There is no base class to extend, no factory to configure. You write Python that constructs a Strands.Agent and hand the container's /invocations HTTP surface to AWS.
None of the three is objectively better. Flue costs the fewest tokens to type. Think gives you the most override points close together. AgentCore trades the familiar "my code lives inside the harness" feel for the freedom to bring any framework (Strands, LangGraph, Google ADK, or raw code) as long as it responds on port 8080.
Subagents · three shapes
The Support Crew has three agents. The interesting question is not "does each harness let you delegate?" All three do. It's "where do the child agents actually run?"
Flue's profiles share the parent's Durable Object. One isolate, one SQLite, one hibernation clock. A subagent tool calling getDurableObjectIdentity() reads the parent's customer and ticket ids for free.
Think's subagents are their own DO classes. That means three new_sqlite_classes entries in wrangler.jsonc, three isolates, three hibernation clocks. Identity threads down through this.parentPath, a root first ancestor chain of { className, name } objects. The emphasized edge on Fig. 05 is the DO to DO RPC hop that Flue doesn't have.
AgentCore's Strands has no SubAgent class in the JS SDK. You pass an Agent into another agent's tools array and it auto wraps via Agent.asTool(). Everything shares the microVM process, so identity is a plain closure.
Side effects · tools, Actions, and the retry line
Now the retries. Every crew has a submit_triage tool that writes the classification to durable storage. If a turn is retried (browser reloaded, Worker restarted, microVM crashed), will that write happen twice?
| dimension | Flue | Think | AgentCore |
|---|---|---|---|
| shape | defineTool({ name, input, run }) | getTools() vs. getActions() | @tool Python fn in the container |
| on retry | your problem | Actions cache the finished result | your problem |
| ledger | none | durable, keyed by e.g. ticket:${cust}~${tkt} | none |
| if it runs twice | you write twice | Think replays the cached result | you write twice |
Only one harness ships a first class building block for this. Think splits its API two ways. getTools() is for pure reads (search the KB, list prior tickets). getActions() is for external side effects (write the triage row, notify the escalation queue). Actions carry an idempotency key and a durable ledger. The second call with the same key returns the first call's result without running the side effect again.
ticket:${customerId}~${ticketId}. The next call with the same key returns the same result the first call produced, no matter how many times the caller retries. HTTP APIs use this to make POST safe under retry. Think brings the same discipline into the agent loop.Delivery, auth, memory · same story, three schemes
These three concerns live at the same distance from the agent loop. Outside it, but important.
Turn API
How does one user message become one LLM call and one response? Each harness has a different wrapper.
Fig. 06 · how a turn goes in
Flue's HTTP wrapper is built in. Append ?wait=result to any agent endpoint. The framework holds the connection until the turn finishes, then returns { result: { text } }. Think ships a programmatic API, runTurn({mode: 'wait'}), that you call from your own Worker route. AgentCore is also programmatic, but through boto3.invoke_agent_runtime(...) and only via SigV4. The SDK cannot pass a Cognito JWT, so an OAuth inbound Runtime must be called with raw HTTPS from the browser.
Authorization header derived from your access key, secret, region, service, and the request bytes. boto3 handles it for you. Browsers can't (there's nowhere to hide the secret). That's why any OAuth fronted AgentCore endpoint is invoked as raw HTTPS with a bearer JWT, not through the AWS SDK.Auth
On Cloudflare, auth rides on Worker bindings. The Worker calls your provider via a bearer that the AI Gateway swaps at the edge (see BYOK below). On AWS, AgentCore Identity handles both directions. Cognito for inbound user tokens, an OAuth vault for outbound tool calls to Google, GitHub, Slack.
Memory
Both Cloudflare harnesses split memory the same way. A per session transcript in the DO's SQLite (managed by the harness for you), and a cross customer index in KV. AgentCore ships a separate Memory service with a proper long term extraction strategy.
| scope | Flue | Think | AgentCore |
|---|---|---|---|
| per session | DO SQLite (auto) | DO SQLite (auto) | AgentCore Memory events |
| cross session | KV MEMORY by customer:${id} | KV MEMORY by customer:${id} | SEMANTIC strategy · actorId=customerId |
| gotcha to know | none | idempotency keys read this.name | runtimeSessionId must be ≥33 chars · SEMANTIC memory took about 40 minutes to become queryable · manual flush() per turn |
cacheRead, cacheWrite. Cached tokens after turn 1 are priced at about ten percent of fresh input, but often twenty times the count. If you roll your own cost math, trust usage.cost.total. A naive input × 3 + output × 15 undercounts the real invoice by about half.anthropic.claude-* id. You get an "on demand throughput isn't supported" error. Prefix with us. (or global.) to route via a cross region inference profile. aws bedrock list-inference-profiles lists the currently active ones.Observability · three opposite wirings
When the crew misclassifies a ticket, where do you look? Each harness has a different answer. And the wiring effort differs by more than an order of magnitude.
The one sentence worth memorizing:
Flue's bus model makes the easy things trivial; Think's per class method model makes the flexible things possible.
Flue exposes an observe() event bus. Every LLM call, tool call, and subagent handoff is emitted once. Any number of subscribers can pick it up. Wiring OTel, Braintrust, Sentry, and an SSE run view for the UI took about six hundred lines. Adding a fifth sink is one observe() call more.
Think exposes per DO lifecycle hooks. Override beforeToolCall, onStepFinish, afterToolCall, onChatResponse on the class itself. Each subagent's hooks fire on its own DO. Same call, different this.name. If you want one trace tree across parent and children, you thread the trace context yourself through the DO to DO RPC boundary.
AgentCore hosted agents are instrumented for you. Every model call, tool call, memory operation, and reasoning step lands on the CloudWatch GenAI dashboard without a single line of instrumentation code. The one time cost is toggling CloudWatch Transaction Search on for the account (spans take about 10 minutes to appear the first time). Wiring Braintrust on top for cross provider parity was about 30 lines. A twentieth of the Flue count.
Reach for X when…
Fig. 08 · the picker
Reach for Flue when you want the shortest path from "describe an agent" to "hit an HTTP endpoint from a React app." Flue also ships prebuilt Channels for Slack, GitHub, Linear, and Discord. The Node runtime target means you're not married to Cloudflare. The same declarative agent runs as long lived Node, a container, or a GitHub Action.
Reach for Think when you want deep Cloudflare integration. Durable Actions with the idempotency ledger, MCP auto merge, workspace filesystem and browser tools, per DO scheduled tasks. Think is heavier to write, but the building blocks it adds don't exist anywhere else.
Reach for AgentCore when you're on AWS anyway. You want observability for free, need microVM level isolation for long running work (up to 8 hour sessions), or need a framework that isn't TypeScript. Strands, LangGraph, Google ADK, or your own Python. Pay for the extra Lambda hop with a much richer managed platform.
Gotchas on the way
No harness picks itself. Each one bites in a different place.
| gotcha | where it bites |
|---|---|
us. model id prefix | AgentCore. Newer Anthropic Claude ids on Bedrock require a cross region inference profile. |
runtimeSessionId ≥ 33 chars | AgentCore. A naive customer~ticket id is usually too short. Pad with a UUID. |
| gateway autodetect + reasoning models | Flue. Pi force sends legacy max_tokens on gateway.ai.cloudflare.com, which reasoning tiers reject. The fix needs a whole third registered provider. |
| NodejsFunction ESM + aws sdk v3 | AgentCore BFF. The SDK's internal require('node:https') doesn't survive being bundled into ESM. Use OutputFormat.CJS. |
no_handle_cross_request_promise_resolution flag | Think. Needed if your SSE subscribers live in a module global map. Hooks fire from a different request context than the one that registered the subscriber. |
| prompt cache undercount | All three. Anthropic reports four token counts. Rolling your own input × 3 + output × 15 undercounts the invoice by half after turn 1. |
Closing
Three sets of scars from three different opinions about the same problem. If you're on Cloudflare and haven't decided yet, Flue and Think are one pnpm add apart. Port between them the day one shape earns its keep over the other. If you're on AWS, AgentCore is the only game in town today, and the free observability alone will pay you back on the first outage.
When you pick next time, you'll know what you're picking.
