Ampcode Engineering Software Lab
Technical Deep Dive · Apr 2026

How Amp Solves
"Lost in the Middle"

Not as a bolt-on Gateway. Not as a separate Semantic Filter service. As an organic, built-in architectural property of the agent itself — through context isolation, subagent orchestration, and on-demand resource loading.

11
Built-in mechanisms
0
External infra required
Parallel context windows

Ampcode, brought to you by Engineering Software Lab (ESL)

← Swipe / Arrow keys to navigate →
The Problem

"Lost in the Middle" is a measurable failure mode

Liu et al. (arXiv 2023 / TACL 2024) showed that long-context models often retrieve best from the beginning or end of the prompt, while information placed in the middle is used less reliably.

Start
Beginning of context

In Liu et al.'s experiments, relevant information near the beginning was often retrieved more reliably.

Middle
Middle of context

The paper's key result is a pronounced dip when the answer-bearing document or key sits in the middle.

End
End of context

Many models rebound when the same information is moved near the end, producing the familiar U-shaped curve.

Two compounding sources of bloat

(1) Static tool-definition tax: Public GitHub MCP reports have shown dozens of tools and roughly tens of thousands of tokens depending on version and configuration. Add Slack, Jira, Sonar, and the starting context can get crowded fast.

(2) Dynamic execution noise: Every grep output, stack trace, and large file read accumulates. Long debugging sessions can end up dominated by history that is no longer useful.

The Common Answer

Gateway + Semantic Filter solves half the problem

The "MCP Gateway" architecture (LiteLLM, AWS AgentCore, etc.) addresses static tool bloat by exposing only N relevant tools per session. Necessary — but not sufficient.

✅ What Gateway/Filter solves

  • Static tool-definition bloat (the large GitHub MCP tool-list problem)
  • Tool catalog discovery at scale (1,000+ endpoints)
  • RBAC and audit at the tool boundary
  • Per-session relevance scoring of tool descriptions

❌ What it does NOT solve

  • Tool output pollution (10K-line log dumps)
  • Long search/grep chains accumulating in main context
  • Heavy reasoning passes that fill the window mid-task
  • Cross-repo / external-doc reads dragging large amounts of text into the main context
  • Multi-step refactors where intermediate state piles up
  • Session-level decay — context never shrinks

Insight: Filtering inputs is reactive. The real fix is preventing context from accumulating in the first place — through architectural isolation. That is the design Amp ships organically.

Design Philosophy

Amp's organic answer: Context Isolation as a primitive

Instead of one giant context window stuffed with everything, Amp orchestrates N disposable child contexts that return only their summary to the parent. The main thread stays lean by construction.

Traditional Agent (single context)

// Everything lives in one window
main_context = [
  system_prompt,
  all_tool_defs,
  AGENTS.md,
  read_file_1..N,
  grep_output_1..M,
  oracle_reasoning,
  user_turns,
]
// Search results, raw reads, and reasoning all accumulate in one place

Amp (orchestrated isolation)

main_context = [
  system_prompt,
  builtin_tools,
  AGENTS.md (scoped),
  user_turns + summaries
]
// delegates large work to focused tools/subagents
Task(...)
oracle(...)
finder(...)
librarian(...)
// Main thread stays smaller because children return distilled results
Mechanism 1 of 11

Task tool — full subagent with isolated window

The flagship primitive. Spawns a child agent with its own context window, its own tool budget, and its own model invocation loop. Returns only a final summary to the parent.

Technical contract

  • The parent passes a focused task brief and any context the child needs
  • The child runs in its own context window
  • No back-channel: parent cannot mid-course correct
  • No shared memory: child starts fresh rather than inheriting the whole thread
  • Output: result or summary back to the parent thread

Why it defeats Lost-in-the-Middle

A child agent can search, read, edit, and verify in its own window while the parent keeps only the task brief and the outcome.

Parallel fan-out lets Amp do independent work in parallel without stuffing every intermediate grep, file read, and failed attempt into the main thread.

// Real invocation pattern from Amp's tool schema
Task({
  description: "Convert all CSS to Tailwind",
  prompt: `Convert these 12 CSS files to Tailwind classes:
    [list]. Conventions in AGENTS.md. Run \`npm test\` to verify.
    Return: list of changed files + any failures.`
})
// → spawns isolated agent, returns summary only
Mechanism 2 of 11

Oracle — heavy reasoning in a separate context

A second-opinion advisor backed by GPT-5.4. It can read files, search code, and browse the web, then return focused advice instead of raw investigative output.

Use cases

  • Architecture review
  • Cross-file bug hunts
  • Refactor planning
  • Alternate viewpoint when stuck

Tools available inside oracle

ReadGrepglob web_searchread_web_page read_threadfind_thread

Oracle is useful for architecture review, code review, and tricky bugs that benefit from a stronger reasoning pass.

Why it helps

It keeps deep analysis off the main interaction path, so the parent thread gets the recommendation rather than the whole reasoning journey.

Tradeoff: Oracle is intentionally slower and more expensive than Amp's everyday coding path, so it is best used where the extra reasoning matters.

oracle({
  task: "Review the authentication architecture and find race conditions",
  files: ["src/auth/index.ts", "src/auth/jwt.ts", "src/auth/session.ts"],
  context: "We're seeing intermittent 401s under load"
})
// Oracle reads files in its own window, grep-searches related code,
// runs deep CoT reasoning, returns 1-2 page actionable analysis.
Mechanism 3 of 11

finder / codebase_search_agent — search as a subagent

"Where do we validate JWT headers?" no longer has to mean a long grep/read/grep chain in the main thread. Amp can delegate that search to a dedicated search subagent and bring back just the useful pointers.

Without finder (naive)

Grep("jwt")
Grep("verify")
Grep("validateToken")
Read(file_a)
Read(file_b)
Grep("middleware")
// The main thread accumulates search noise and dead ends

With finder

finder({
  query: "Find every place we verify
    JWT auth headers. Return file
    paths + line numbers."
})
// → search subagent runs the investigation internally
// → returns:
// src/auth/jwt.ts:42-58
// src/middleware/auth.ts:15
// src/api/routes.ts:233
// Main thread sees the answer, not the full search trail

Why this matters for Lost-in-the-Middle

Search is one of the easiest ways to flood a thread with low-value text. By making search itself a subagent, Amp can keep bulky intermediate results out of the main thread unless they are explicitly needed.

Mechanism 4 of 11

Librarian — external repos without local pollution

Reads public GitHub, private GitHub, Bitbucket Enterprise. Cross-repo research is one of the worst Lost-in-the-Middle offenders — Librarian quarantines it in its own subagent.

What it does in isolation

  • Searches code on default branch of any repo
  • Reads multiple files across multiple repos
  • Walks commit history when needed
  • Generates long-form architectural explanations
  • Returns one detailed answer to parent

Why it matters

Cross-repo research can easily swamp a coding thread with code that is not local to the task at hand.

Librarian keeps that investigation off to the side and brings back a focused explanation instead of raw repository spelunking.

Mechanism 5 of 11

Agent Skills — instructions loaded on demand

Skills are SKILL.md files registered with the agent. Only the name + description stay visible by default. The full body - workflows, scripts, references - loads only when invoked.

Anatomy of a skill

~/.agents/skills/cve-vulnerability-lookup/
├── SKILL.md          # body (lazy-loaded)
├── mcp.json          # bundled MCP server
├── scripts/
│   └── lookup.py
└── templates/
    └── report.md.j2

Frontmatter exposes name and description only. Everything else is dormant until skill("cve-vulnerability-lookup") is called.

The killer feature: MCP-in-skills

An MCP server bundled inside a skill (mcp.json) starts at launch but its tools stay hidden until the skill is loaded. This is Amp's organic answer to Gal's "Semantic Filter" — without a Gateway service.

  • Hide MCP tools until they are actually needed
  • Expose only the tools bundled with the invoked skill
  • Amp's own chrome-devtools example drops visible tool definitions from 26 tools / 17K tokens to 4 tools / 1.5K tokens

This lines up with the broader idea of pre-filtering tool exposure before the model sees a large catalog.

Mechanism 6 of 11

AGENTS.md with granular globs — scoped guidance

Project context lives in nested AGENTS.md files. The key feature is glob-scoped @-mentions: a referenced file only enters context if Amp has read a matching file.

# AGENTS.md (project root)

@docs/typescript-conventions.md
  globs: ["**/*.ts", "**/*.tsx"]
  # Only loaded if Amp reads a TypeScript file

@docs/backend-rules.md
  globs: ["server/**", "api/**"]
  # Only loaded for backend work

@docs/test-conventions.md
  globs: ["*.test.ts", "__tests__/*"]
  # Only loaded during testing tasks

Naive: load everything always

Without scoping, unrelated project guidance stays active all the time even when it has nothing to do with the task in front of the model.

Amp: glob-gated

Working on a Python script? TypeScript rules don't load. Editing tests? API conventions stay dormant. Context size scales with relevance, not with documentation volume.

Mechanism 7 of 11

Code Review Checks — per-module review subagents

Review prompts live in .agents/checks/*.md at the module level. Each check runs as its own subagent in parallel with its own context. Findings are aggregated.

File layout

repo/
├── .agents/checks/
│   └── api-standards.md      # global
├── payments/
│   └── .agents/checks/
│       └── pci-compliance.md # module
├── auth/
│   └── .agents/checks/
│       └── security.md       # module
└── frontend/
    └── .agents/checks/
        └── a11y.md           # module

Why this is Lost-in-the-Middle-proof

  • Each check = isolated subagent, isolated context
  • PCI rules never pollute the auth review's window
  • Checks can run in parallel without becoming one giant review prompt
  • Main thread receives only structured findings
  • Checks are scoped by directory, so local invariants stay local
Mechanism 8 of 11

Handoff — fresh thread with curated context

When a thread accumulates noise (failed attempts, dead-end traces), handoff analyzes the thread, drafts a prompt for a new one, and carries forward the relevant files for the next task.

Old Thread

Long thread
dead ends + stale history

⟶ handoff ⟶

New Thread

Fresh thread
drafted prompt + relevant files

Programmatic handoff

handoff({
  goal: "Continue refactoring the
    payments module — focus on
    extracting the Stripe adapter.",
  follow: true
})

Why it's organic

Handoff replaces summary-stacking with a user-reviewable fresh start. Amp drafts the next-thread prompt, shows it to you, and lets you edit it before sending.

Why it helps: you leave behind irrelevant history instead of carrying it forever in the same conversation.

Mechanisms 9, 10, 11

The supporting cast

9. Toolboxes

Replace MCP servers with simple executables in $AMP_TOOLBOX. Each script self-describes via stdout key-value pairs.

Why it helps: No JSON-RPC overhead, no boilerplate tool schemas. A 5-line bash script = a tool. Drastically smaller tool definitions.

10. Built-in tools first

Amp's built-in tools (Read, Grep, glob, Bash, Task, oracle, finder, librarian, ...) cover common coding workflows without needing an MCP server for everything.

The official guidance: "Use built-ins. Add MCP only when truly needed. Bundle MCPs in skills."

11. Permissions / RBAC

Tool-level allow/deny lists per workspace. Untrusted MCPs and skills can be sandboxed. amp permissions list.

Governance at the tool boundary is built into Amp instead of requiring a separate gateway layer.

Plus: read_thread, find_thread, ampdo skill, painter, mermaid…

Every Amp tool follows the same design rule: if a task can produce big output, give it its own context. The agent's job is to orchestrate small summaries, not swim in raw data.

Worked Example

Real task: "Audit auth code for race conditions across 3 services"

Naive single-context agent

Tool defs
File reads
Search noise
Reasoning
Thread history

Everything piles into one window.

The danger is not one specific number. It is that search trails, raw reads, and intermediate reasoning all compete with the actual task.

Amp orchestrated

Built-ins
Scoped guidance
Goal + summaries
Heavy work offloaded

Main thread stays closer to the actual goal.

Search, deep analysis, and remote-repo research can happen in dedicated tools/subagents, so the parent sees the result instead of every intermediate step.

The arithmetic of orchestration

Amp does not magically eliminate context limits. It changes where bulky work happens, so the main conversation can stay more focused while child contexts do the noisy investigation.

Side by Side

Gateway/Filter vs Amp's organic approach

Concern External Gateway + Semantic Filter Amp (organic, built-in)
Static tool bloat ✓ Filter to N relevant tools ✓ Skills hide MCP tools until loaded
Tool output pollution △ Not addressed by filtering alone ✓ Task / finder / oracle in own contexts
Long search chains △ Depends on the main agent's search behavior finder subagent returns summaries
Heavy reasoning passes △ Depends on the main agent's design oracle offloads deep analysis
Cross-repo research △ Can still expand the main context if done inline librarian quarantines it
Per-module guidance △ Not inherent to gateway/filter design ✓ Nested AGENTS.md + glob-gated @-refs
Code-review at scale △ Not inherent to gateway/filter design ✓ Per-module .agents/checks/*.md in parallel subagents
Session-level decay △ Does not reset conversation history by itself handoff resets with curated context
Infrastructure required Gateway service + Registry + Semantic index None — ships in the agent binary
Per-call latency Often adds an extra network/policy layer In-process
The Deeper Insight

Orchestration subsumes filtering

One useful way to think about the difference: filtering decides what the model sees, while orchestration also decides where bulky work happens.

Filtering picks what to load

Reactive. Operates on the static catalog. Once tools are loaded, you're back in the same window.

Isolation picks where work happens

Proactive. Operates on the execution dimension. Big work moves to disposable contexts, not the main one.

Composition wins

Amp does both. Skills + mcp.json filter what loads. Subagents control where work runs. In practice, that addresses more of the context problem than filtering alone.

Formalized

// Gateway/Filter approach
context(t) = system + filter(tools, query) + history(t)
// → history(t) grows monotonically → eventually saturates

// Amp orchestrated approach
main(t) = system + builtins + scoped_AGENTS + summaries(t)
where summaries(t) = Σ subagent_i.summary
  and Σ |summary| ≪ Σ |subagent_i.full_context|
// → main stays bounded; work scales horizontally
Why Amp Was Designed This Way

Four founding principles → one architecture

1. Unconstrained token usage

If tokens are cheap and quality is paramount, the right move is to spend more tokens in parallel disposable contexts, not to cram them into one window.

2. Always uses the best model

Different subagents can use different models. Oracle uses GPT-5 reasoning; main agent uses fast frontier coder. Each tool, the right model.

3. Raw model power

Don't try to RAG/compress your way around context limits — give the model a clean, focused window every time.

4. Built to evolve with new models

Longer context windows do not automatically fix middle-retrieval problems. As models scale, orchestration can still matter because it keeps work focused instead of merely making the window larger.

The principles directly imply the design. Context isolation isn't a feature Amp added — it's the consequence of taking these principles seriously.

Summary

The TL;DR

Gal Dahan's diagnosis (correct)

  • MCP tool bloat is real
  • Lost-in-the-Middle degrades agents
  • "Just remove tools" isn't the answer
  • Need intelligent orchestration

Amp's answer (organic)

  • Skills + mcp.json = filter on load
  • Subagents (Task/oracle/finder/librarian) = isolate execution
  • AGENTS.md globs + checks = scoped guidance
  • Handoff = session-level reset
  • All built-in. No Gateway. No registry service. No extra infra.

The bottom line

A Gateway treats Lost-in-the-Middle as an input filtering problem.
Amp treats it as an execution architecture problem.
The second framing addresses a broader part of the problem and ships inside the agent itself.

Your agent isn't dumb. It's just lost in the middle.
Amp's solution: never let it get there in the first place.

Ampcode Engineering Software Lab

Ampcode, brought to you by Engineering Software Lab (ESL)

1 / 18