There’s a version of the “AI agent” story where the agent is a clever prompt wrapper that calls one API and hands back a response. That version works. It also plateaus quickly.
The hard knowledge-work problems — “what’s the current price of X across these three sites,” “which models are on the intelligence-price frontier right now,” “what did we learn about this customer last month” — require more than one tool. They require search to find where to look, a browser to reach what search can’t render, a code sandbox to compute on what was found without trusting the model’s arithmetic, and memory to make the next run better than the first.
Perplexity’s Computer agent, which combines exactly these four tools, measured 87% reduction in task time and 94% cost reduction versus human-plus-search-assistant. The gains come from the combination. None of the four alone gets you there.
This post walks through a live run of the same four-tool loop, wired inside an AgentCore Runtime, executed on 2026-07-12.
Why each tool is in the loop
The four tools aren’t interchangeable. Each closes a specific gap the others leave open.
Web Search is fast and indexed. It tells the agent where to look. But web search results are stale by hours or days, can’t execute JavaScript, and can’t reach anything behind a login. It’s the orientation pass, not the extraction pass.
Browser reaches live pages — rendered JS, lazy-loaded content, dynamic prices, anything behind a session cookie. But browsers are slow and frequently blocked by bot protection. Best Buy loads prices only after the viewport scrolls past the product card (IntersectionObserver — the browser has to scroll and wait). Costco returns HTTP 403 to managed Chromium entirely. The browser is the deep pass, not the wide pass.
Code Interpreter computes on what the first two tools found. Without it, the model narrates a number it inferred rather than calculated. With it, the agent pushes a dataset into a managed sandbox, runs the arithmetic there, and reads back the result. The computation is verifiable. The agent can’t hallucinate a Pareto frontier — either it computed one or it didn’t.
Memory makes the system non-amnesiac. Every run without memory starts from zero. Every run with memory can recall what was found last time, what changed, and what pattern the agent itself observed. Without memory, the knowledge worker is a commodity query engine. With it, it builds a knowledge base.
The combination is the product. The infrastructure is fixed. The prompt is the product surface.
The prescriptive TASK block
The loop doesn’t ask the model to decide what to do next. The task prompt specifies the plan explicitly:
TASK: What models are currently on the Pareto frontier of intelligence vs price
in the LLM landscape?
TOOLS: search, compute, memory
OUTPUT: json with frontier list, cheapest_on_frontier, smartest_on_frontier,
snapshot_date
MEMORY: persist result keyed by snapshot_date
CONSTRAINTS: do not call live AA API (use s3 snapshot path); no browser needed
The TOOLS field controls which of the four steps actually run. CONSTRAINTS let you skip a slow or blocked step without changing the code. The entrypoint parses this block, not the LLM — the LLM handles the task, not the routing.
This is the key architectural choice: the prescriptive prompt is more auditable and cheaper than a ReAct loop where the model decides tool order at inference time. It trades flexibility for predictability. For knowledge-work tasks where the sequence is known in advance, it’s the right trade.
The live trace — 2026-07-12
The live trace from this run:
[
{ "step": "web_search",
"query": "What models are on the Pareto frontier of intelligence vs price?",
"hits": 3 },
{ "step": "fetch_aa",
"rows": 572,
"bytes": 18734,
"source": "s3_snapshot (latest)",
"snapshot_date": "2026-07-11" },
{ "step": "ci_session_start",
"session_id": "01KXA90GQR47SC36SQ4W7Y18SE" },
{ "step": "ci_compute",
"exec_time_s": 1.23,
"frontier_size": 15,
"priced_cohort": 371 },
{ "step": "ci_session_stop" },
{ "step": "memory_persist",
"ok": true }
]
Six steps. No LLM calls between them — the entrypoint ran them sequentially per the parsed TASK block.
Step 1 — Search (3 hits). The web search returned real indexed results: Digital Applied’s Q2 2026 efficient-frontier analysis, an AI/ML API blog post, an LLM selection guide. These gave the agent the framing — “which models are Pareto-dominant across cost, quality, and speed” — and confirmed that Gemma 3n E4B Instruct was showing up as the value anchor at the low end.
Step 2 — Fetch snapshot. The Artificial Analysis Data API rate-limits to 10 requests per 24 hours. Running a live fetch on every invocation would exhaust the quota and couple the compute path to network availability. The architecture decouples ingest from compute: a scheduled job writes dated snapshots to S3 (model-analysis-snapshots-<account-id>/snapshots/YYYY-MM-DD/), and the compute path reads from there. The snapshot of 2026-07-11 had 572 models; 371 had a blended price listed.
Steps 3–5 — Code Interpreter. The dataset (18,734 bytes) was pushed into the managed sandbox via writeFiles. The pareto_efficient() computation ran in the sandbox — the same function used in Group E1, now called from within a live Runtime invocation. Execution time: 1.23 seconds. The sandbox ran Python 3.12.13, pandas 2.3.1, numpy 1.26.4, pre-installed — no install step needed.
The frontier came back as 15 models, the priced cohort was 371, and the Code Interpreter session stopped cleanly. No sandbox resource persists after StopCodeInterpreterSession.
Step 6 — Memory persist. The result was written to AgentCore Memory under actor_id = "acx-analyst", keyed by session analyst-compute-2026-07-11. The next invocation that asks the same question will retrieve “Pareto frontier as of 2026-07-11: 15 models, cheapest: Gemma 3n E4B Instruct at $0.025/M, smartest: Claude Fable 5” rather than re-running steps 2–5.
What the computation returned
The computation returned these results:
frontier_size: 15
priced_cohort: 371 / 572 models had blended price
snapshot_date: 2026-07-11
computed_at: 2026-07-12T04:22:48Z
cheapest_on_frontier: Gemma 3n E4B Instruct @ $0.025 / M tokens
smartest_on_frontier: Claude Fable 5 (Adaptive Reasoning, Max Effort, Opus 4.8 Fallback)
Frontier members:
Claude Fable 5 (Adaptive Reasoning, Max Effort, Opus 4.8 Fallback)
Claude Opus 4.8 (Adaptive Reasoning, Max Effort)
DeepSeek V4 Flash (Reasoning, Max Effort)
GLM-5.2 (max)
GPT-5.6 Luna / Sol / Terra (max)
Gemma 3n E4B Instruct
Grok 4.5 (high)
MiMo-V2-Flash (Feb 2026)
MiniMax-M3
Muse Spark 1.1 (xhigh)
Qwen3.5 4B (Reasoning)
Qwen3.5 9B (Reasoning)
Sarvam 30B (high)
This is the intelligence-price Pareto frontier: every model on this list is one where no cheaper model achieves equal-or-higher intelligence. Everything off the list is dominated — there exists at least one cheaper option with the same or better benchmark score.
The result is dated, computed, and traceable to a specific S3 snapshot. If you want to know what moved since last week, run it again against a newer snapshot and diff the two memory records.
The observability picture from inside Runtime
Group E1 established that a standalone Code Interpreter call emits CloudWatch metrics but no distributed span — metrics are a property of the tool. The E4 run adds the Runtime wrapper, and now the trace tree is complete.
The Phase 2 E1 results showed what the span tree looks like for a Runtime-hosted Code Interpreter invocation:
| Span | Duration |
|---|---|
| POST /invocations (runtime root) | ~3,170 ms |
| StartCodeInterpreterSession | ~1,271 ms |
| InvokeCodeInterpreter (writeFiles) | ~77 ms |
| InvokeCodeInterpreter (executeCode) | ~1,414 ms |
| StopCodeInterpreterSession | ~146 ms |
| CreateEvent (memory persist) | ~3 ms |
The distributed trace (spans) is a property of Runtime. The metrics (invocation counts, latency, errors by operation) are a property of the tool. You need both layers to fully observe the loop — metrics tell you how often each tool step ran and how fast; the span tree tells you where the time went within a single invocation and which step failed.
The browser step — when it runs and when it doesn’t
The E4 task didn’t need the browser: the data was in S3, and the search results were structured enough to orient the computation. Browser was disabled via TOOLS: search, compute, memory in the TASK block.
In a different task — say, “what is the current price of a 65-inch Samsung QLED on BestBuy.com” — the trace would look like:
web_search → "best buy 65 inch samsung qled price 2026" (orientation)
browse → bestbuy.com/site/65-samsung-qled (extraction)
→ page.goto() → page.mouse.wheel(0, 800) → wait 1500ms (scroll triggers lazy-load)
→ body.innerText price regex → ["$735.99", "$469.99"]
memory → persist prices keyed by date + URL
The scroll-and-wait pattern (800px scroll, 1.5s pause) is what makes Best Buy’s lazy-loaded prices appear in the DOM. Without it, the IntersectionObserver never fires and the price element doesn’t exist in body.innerText. This pattern is in the browser module and runs on every navigation — it’s not a special case.
Some sites block managed Chromium at the connection layer. Costco returns a Protocol error (HTTP/2) that never reaches the page-load stage. When that happens, browse_and_extract sets blocked: true and the loop continues without browser results — degrading gracefully rather than failing the whole task.
What makes this architecture different from a ReAct loop
A standard ReAct agent asks the LLM to decide what to do next after every tool call. This is flexible: the agent can react to what it found and change course. It’s also expensive (extra inference per step) and less auditable (the routing decision isn’t inspectable without reading the model’s reasoning trace).
The prescriptive loop makes a different trade: the TASK block specifies tool order at call time, and the entrypoint executes it. The model handles the task content — what to search for, what to extract from the browser, how to interpret the compute result — but not the routing. The routing is deterministic and logged in the trace as explicit steps.
This matters for production: you can write a test that asserts trace[0].step == "web_search" and trace[2].step == "ci_compute" and fail the deployment if those steps run out of order. That’s the Group G evaluation trajectory check — behavioral assertions on the tool sequence, not just the final output. Post #12 covers that in detail.
What to build on top of this
The knowledge worker as built is a single-invocation loop. A few natural extensions:
Scheduled ingest + on-demand compute. The E4 architecture already decouples these: the S3 snapshot is written by a separate job, and mode: analyst reads from it. Adding a daily EventBridge trigger for ingest and a weekly scheduled compute run would make the frontier tracking fully autonomous.
Parallel browse. browse_multiple(urls) already runs sequentially. For tasks that need 3–5 pages, a ThreadPoolExecutor wrapper would cut wall-clock time proportionally. The browser sessions are independent; there’s no ordering constraint between them.
Metadata-tagged memory. The E4 memory persist writes a flat string. Post #16 covers tagging the memory event with {category: "frontier", snapshot_date: "2026-07-11"} so you can filter recalls by date range rather than scanning all semantic matches.
The infrastructure is the same. The prompt is the product surface. Adding a new task type is a new TASK block, not a new deployment.
Part of a series working through Amazon Bedrock AgentCore by building on it. Start with The AgentCore Map for the full picture. The primitives behind this loop: AgentCore Runtime, AgentCore Memory, AgentCore Code Interpreter, and How AgentCore Gateway Turns Any API Into an Agent Tool.