1:1 mentoring with Big Tech AI engineers
01
8 questions

Fundamentals & Architecture

What an agent actually is, when a loop earns its cost, and how to pick between ReAct, Planner-Executor, and multi-agent.

Asked at
Q1
What's the difference between a chatbot and an agent?
AgentsArchitectureTool Use

Asked at Anthropic · OpenAI · Sierra

open question
How to Answer

"A chatbot is a single LLM call — input in, text out, stateless. An agent is an LLM inside a loop. The loop gives it tools, memory, and the ability to take actions in the world. The agent decides what to do next based on observations. The key difference is autonomy — an agent can reason, act, observe, and iterate until a task is complete. The 'agent' is actually the while-loop your code runs around the LLM, not the LLM itself."

What the loop actually looks like

Same model in both rows. The only difference is what your code does with the model's output — return it, or feed it back in.

Chatbot vs agent — the difference is the loop, not the model
CHATBOT — ONE PASS USER LLM · 1 CALL no tools · no memory TEXT OUT stateless — forgets you AGENT — LOOP UNTIL DONE USER LLM · DECIDE next action, each iteration sees memory + tool results TOOL CALL API · DB · search · code ANSWER or final action MEMORY / STATE persists across steps 1 · action (tool + args) 2 · observation (result) — repeat 3 · when the model says done: emit final answer re-read every iteration — this is what makes step N aware of step N−1 chatbot: 1 call · ~600ms · ~$0.001   |   agent: 3–8 calls · seconds · cents — you pay for autonomy in latency and tokens

Side-by-side, the way an interviewer wants it

DimensionChatbotAgent
Control flowFixed by your code: one call, text outChosen by the model each iteration — it decides the next step
StateStateless (or raw chat history re-sent)Working memory carried across steps
ToolsNone, or retrieval onlyCalls APIs, databases, code — can write to the world
Latency / cost~600ms, ~$0.001 per question3–8 loop iterations, seconds, cents per task
Failure modeWrong answer — user reads it and moves onWrong action — refund issued, email sent. Needs guardrails
Ship it when…Q&A, FAQ deflection, draftingMulti-step tasks with side effects
REAL SYSTEM

A support copilot runs both, tiered. The FAQ tier is a chatbot — one call, ~400 tokens, ~600ms, ~$0.001/question — it deflects “what’s your refund policy?”. The resolution tier is an agent: for “refund my last order” it loops lookup_order → check_policy → issue_refund → send_confirmation — ~5 iterations, ~6K tokens, ~8s, ~$0.03 — but it closes the ticket end-to-end. Same base model; the wrapper decides which one it is. Routing between the two tiers is where most of the money is saved.

FOLLOW-UP TRAP

“So is RAG an agent?” — No. Vanilla RAG is a fixed pipeline: retrieve → stuff → generate, same path every time. It becomes agentic when the model decides whether to retrieve, what to retrieve, and when to stop. Quick litmus test: if your control flow lives in Python if statements, it’s a pipeline; if it lives in the model’s next-token distribution, it’s an agent.

Q2
When would you NOT use an agent? When is a simple RAG pipeline enough?
AgentsRAGArchitectureCost Optimization

Asked at Anthropic · Google · Glean

open question
How to Answer

"If the task is single-turn retrieval + generation — user asks a question, you find the answer in docs — RAG is cheaper, faster, and more predictable. I'd reach for agents only when:

  • (1)the task requires multiple steps
  • (2)it needs tool use (write operations, calculations, API calls), or
  • (3)the solution path is not known upfront and requires reasoning. An agent adds 3-10x the cost and latency of RAG. The tradeoff is autonomy vs predictability."

The three gates

Ask them in order and stop at the first yes. Most “we need an agent” requests fail all three — they are a retrieval problem wearing a loop.

Three gates — any single yes means you need an agent
ASK IN ORDER — THE FIRST YES ENDS THE QUESTION TASK MULTI-STEP? more than one hop WRITE ACTIONS? refunds, emails, tickets PATH UNKNOWN? steps depend on findings RAG is enough no no no yes yes yes AGENT loop · tools · memory SAME 1,000 REQUESTS — WHAT AUTONOMY COSTS RAG $2 · 0.8s Agent — 5 iterations, 3 tool calls $10 · 8s the bill is the small part — you are really trading predictability for autonomy

What you give up when you add the loop

DimensionRAG pipelineAgent
Control flowFixed in your code: retrieve → stuff → generateChosen by the model at every step
LatencyOne round trip, p50 ≈ 0.8s3–8 round trips, seconds
Cost1× baseline3–10× — history is re-sent every iteration
ReproducibilitySame input, same path, every timePath varies per run — you debug traces, not code
Failure modeWrong answer; the user reads it and moves onWrong action; the refund is already issued
Eval effortRetrieval hit rate + answer accuracyOutcome and trajectory scoring, per task type

The middle ground most teams skip

Between the two there is a pipeline that keeps its fixed shape but lets the model make one bounded decision:

  • Conditional retrieval — the model decides whether to search before answering. One extra call, no loop.
  • Query rewriting — one rewrite pass before retrieval; fixes most “retrieval missed it” complaints for ~$0.0002.
  • Bounded re-query — if the top chunk scores below threshold, retrieve once more with a different query. Hard cap of two.
  • Read-only tools — a loop with lookups but no writes. You get multi-step reasoning with none of the blast radius.
REAL SYSTEM

An internal docs assistant handles ~60K questions/month. Classification showed 82% are single-hop lookups — those run as plain RAG at ~$0.002 and ~0.8s. The remaining 18% (“compare our Q2 and Q3 policy and tell me what changed for contractors”) route to an agent at ~$0.04 and ~7s. Running everything through the agent would have cost ~$2.4K/month for ~$430 of actual work, and pushed p50 latency from 0.8s to 7s for the 82% who never needed it.

FOLLOW-UP TRAP

“Retrieval keeps missing the answer — won’t an agent fix that?” — No. An agent that searches a broken index just pays 5× to fail more slowly, and now it fails non-deterministically so you cannot reproduce the bug. Fix chunking, add hybrid keyword + vector search, and measure recall@5 first. Reach for the loop when recall is good and the task still needs several steps — not when retrieval is bad.

Q3
How do you decide between ReAct, Planner-Executor, and Multi-Agent?
ReActPlanningMulti-AgentArchitecture

Asked at OpenAI · Microsoft · Sierra

open question
How to Answer

"Decision tree: ReAct when the task is exploratory and the path isn't known upfront (research, diagnosis). Planner-Executor when the task has clear phases and I want auditability — the plan is a human-readable artifact I can approve before execution. Great for compliance-heavy workflows. Multi-Agent only when there are genuinely separable domains of expertise — e.g., a researcher who searches the web and an analyst who runs SQL shouldn't share a context window. Multi-agent is a tool, not a default — it adds coordination overhead and debugging complexity."

The three shapes, side by side

The pattern is not a taste question — it follows from whether the path is known upfront and whether anyone must approve it.

Three control-flow shapes — pick the one the task already has
REACT — EXPLORE LLM · DECIDE next step from last result TOOL CALL one per step act observe → decide again 3 to 15 steps — you find out at runtime PLANNER-EXECUTOR — APPROVE FIRST PLAN human-readable artifact HUMAN APPROVES EXECUTE step 1 → 2 → 3, no re-planning the plan is the audit artifact MULTI-AGENT — SPLIT CONTEXTS ORCHESTRATOR RESEARCHER web search own context ANALYST SQL own context two context windows that never mix you pay in handoffs and debugging WHAT TRIGGERS EACH → path unknown: research, diagnosis → phased work you must sign off → expertise that must stay separate start at ReAct, add a planner when you need auditability, split into agents only when contexts genuinely conflict

Choose on the failure you can live with

PatternReach for it whenWhat it costsHow it fails
ReActThe path is discovered as you go — diagnosis, research, triageUnbounded steps; latency and cost you cannot quote in advanceWanders: re-calls tools, never converges, stops at the iteration cap
Planner-ExecutorPhases are known and a human must sign off before anything runsOne extra planning call, plus human wait time in the middleA wrong plan is executed faithfully — the executor never questions it
Multi-AgentGenuinely separate expertise whose contexts should not mixN× the tokens, plus handoff serialization and cross-agent tracingFacts get lost in handoffs; the orchestrator cannot arbitrate a disagreement

Why the plan artifact is the whole point

Planner-Executor wins compliance reviews because the plan is inspectable before anything happens — a diff a human can approve, not a trace they read afterwards:

{
  "task": "close out the Q3 vendor invoice dispute",
  "plan": [
    { "step": 1, "tool": "fetch_invoices", "args": { "vendor_id": "V-8841" },
      "reads": ["billing.invoices"], "writes": [] },
    { "step": 2, "tool": "diff_against_contract", "args": { "contract_id": "C-119" },
      "reads": ["legal.contracts"], "writes": [] },
    { "step": 3, "tool": "open_credit_memo", "args": { "amount_cap_usd": 5000 },
      "writes": ["billing.credit_memos"], "requires_approval": true }
  ],
  "approval": { "because": "step 3 moves money", "approver_role": "ap_manager" }
}

Every step declares what it reads and writes, so the approval gate is derived rather than remembered. ReAct cannot produce this document: step 3 does not exist until step 2 returns.

REAL SYSTEM

An incident-triage system started as three agents — logs, metrics, deploy history — behind an orchestrator. Median resolution burned ~31K tokens and ~48s, and 1 run in 5 lost a fact across a handoff: the log agent found the bad deploy, the orchestrator’s summary dropped the timestamp. One ReAct agent holding all three tools took it to ~12K tokens and ~19s, with lost-context failures near zero. Multi-agent came back for one case: the customer-data agent, which had to run under a different service account.

FOLLOW-UP TRAP

“More specialists must be better — why not five agents?” — Because every handoff is a lossy summary. One agent passes full observations from step N to step N+1; a handoff passes only what the orchestrator wrote down. Split when contexts must not mix — different credentials, different data residency — not because the org chart has specialists.

Q4
Your agent is taking 15 tool calls to complete a task that should take 3. What do you do?
Tool UseDebuggingAgentsOptimization

Asked at Sierra · Cursor · Anthropic

open question
How to Answer

"I'd diagnose in this order:

  • (1)Check tool descriptions — are they ambiguous? If the agent can't tell which tool to use, it tries them all. Fix the descriptions.
  • (2)Check if it's re-calling the same tool — add deduplication.
  • (3)Check context bloat — after 10 calls, the context is so long the model loses track. Add observation summarization.
  • (4)Consider a planner step — if the agent is wandering, a plan upfront constrains the path.
  • (5)Check the system prompt — add explicit guidance like 'you should need at most 5 tool calls for this type of task.'"

Read the trace before you touch the prompt

Fifteen calls is not one bug. Pull ten traces and count what each call contributed — the shape of the waste names the fix.

The same task, traced twice — twelve of fifteen calls did no work
BEFORE — 15 CALLS, 3 OF THEM USEFUL 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 1–2 real work 3–7 wrong tool, retried 8 real work 9–14 same call, same args 15 answer AFTER — 3 CALLS, IDENTICAL OUTCOME 1 2 3 dedup on (tool, args) · rewritten descriptions · 5-call budget in the prompt an agent that wanders is almost always a description problem, not a reasoning problem

Five shapes of waste and what each one means

What you see in the traceRoot causeFix
Same tool, same arguments, three timesNo dedup — the answer is in context but buriedCache by (tool, sorted(args)); reuse the result
Cycles through four similar-sounding toolsDescriptions overlap; it is guessing between near-tiesAdd a “not for X — use Y” clause to each
Calls get vaguer after step 8Context bloat — raw output crowded out the goalSummarize observations to 2–3 lines; re-state the goal
Never takes the same path twiceNo plan — every step is an independent decisionPlan first, execute it, re-plan only on failure
Only ever stops at the iteration capNo stop condition the model can reachState the budget in the prompt and cap it in code

Two guards, both in your code

def step(state, call):
    sig = (call.tool, json.dumps(call.args, sort_keys=True))

    if sig in state.seen:                       # never pay twice for one question
        return Obs(state.seen[sig], note="identical args — reusing the earlier result")

    if state.calls >= HARD_CAP:                 # a cap the model cannot argue with
        return Obs(None, note="tool budget spent — answer from what you have")

    state.seen[sig] = execute(call)
    state.calls += 1
    return summarize(state.seen[sig], max_lines=3)

The note matters as much as the guard — told only “no result”, the model retries with a cosmetic argument change.

REAL SYSTEM

A billing-support agent averaged 14.6 tool calls on a 3-call task. Traces showed 61% were exact duplicates and 22% were get_account vs fetch_customer ping-pong — descriptions differing by one adjective. Dedup, merged descriptions, and a stated 5-call budget took it to 3.4 calls, p50 9.0s → 2.1s, $0.21 → $0.05 per task. Success went up, 68% to 94%: each extra call was another chance to derail, not to succeed.

FOLLOW-UP TRAP

“Just cap it at 3 calls — problem solved?” — A cap alone turns a slow success into a fast failure: the agent hits the wall mid-task and answers from partial data, which is worse because nothing looks broken. Cap and fix the cause — dedup so the budget is not spent on repeats, and make the cap a graceful exit that names what it could not finish.

Q5
When do you fine-tune instead of adding retrieval or changing the prompt?
Fine-TuningRAGPrompt EngineeringArchitecture

Asked at OpenAI · Databricks · Scale AI

open question
How to Answer

“Three levers, and they fix different failures. Prompting fixes instruction-following — the model can do the task, it just isn’t doing it your way. Retrieval fixes missing knowledge — the model doesn’t know your data, or your data changes daily. Fine-tuning fixes consistency — the model knows how, but drifts on format or tone across thousands of calls, or you need a smaller model to hit a latency and cost target.

The order matters: prompt, then retrieval, then fine-tune. Most teams reach for fine-tuning first because it sounds like the serious answer, spend six weeks building a dataset, and find out the real problem was a vague system prompt.

In production they combine. Fine-tune for shape, retrieve for facts. What I never do is fine-tune facts into a model, because then every fact change is a retraining job.”

Match the lever to the failure

Three levers, three different failures — in the order you should try them
Try them in this order 1 · Prompting fixes: not following instructions fixes: wrong shape on a good day cost: an afternoon reversible: instantly 2 · Retrieval fixes: doesn’t know your data fixes: facts change daily cost: days, plus an index to run reversible: reindex 3 · Fine-tuning fixes: tone/format drift at scale fixes: frontier latency and price cost: weeks, 2–10K labelled rows reversible: retrain None of the three add reasoning the base model does not have If the model cannot do the task once, by hand, with the answer sitting in front of it — that is a model choice, not a tuning problem. Most teams skip to 3 because it sounds serious, then find the bug was in 1.

The symptom tells you which one you need

The mistake is almost always reaching one rung too high.

SymptomWhat teams reach forWhat actually fixes it
Output format wandersfine-tuneschema-constrained decoding plus two examples
Answers are a week stalefine-tuneretrieval with a freshness filter
Cites documents that don’t existfine-tune on the docsretrieval with the id checked against the index
Tone is off-brand 1 reply in 5more prompt rulesfine-tune on 2–5K approved replies
p95 is 4s on a frontier modelcachingdistil to a small fine-tuned model
Cannot do the domain reasoning at allfine-tunea stronger base model — tuning does not add reasoning

What a fine-tune actually buys

The honest case for fine-tuning is rarely quality. It is holding quality while the model gets small enough to be fast and cheap.

Frontier model, prompted8B model, fine-tuned
Format compliance~71%~98%
p95 latency3.4s0.9s
Cost / 1K replies$4.10$0.38
Time to change behaviourminutesa retrain cycle
Facts come fromretrievalretrieval — still
REAL SYSTEM

A support-reply system had a tone problem: about one reply in five read wrong for the brand, and no amount of prompt rules held it. We fine-tuned an 8B model on ~12K human-approved replies — format compliance went ~71% → ~98%, p95 3.4s → 0.9s, and cost per thousand replies $4.10 → $0.38. Every fact in those replies still came from retrieval, so a pricing change is a reindex, not a retrain.

FOLLOW-UP TRAP

“Why not fine-tune on your docs and drop RAG entirely?” — because you retrain on every document change, you cannot cite a source, you cannot apply per-user permissions at retrieval time, and you cannot delete a fact when a customer asks you to. Fine-tuning teaches shape; retrieval supplies facts. A model with the facts baked in is confidently wrong the day the facts move.

Q6
Walk me through your RAG pipeline. What chunk size and overlap, and why?
RAGEmbeddingsRetrievalArchitecture

Asked at Sierra · Databricks · Glean

open question
How to Answer

“Six stages — chunk, embed, index, retrieve, rerank, generate. The interesting decisions are at the two ends.

Chunking: I start at about 512 tokens with 10–15% overlap for prose, but I don’t chunk structured documents on a token count at all. I chunk them on their own boundaries — a section, a whole table, a function. The failure everyone hits once is splitting a table so that neither half means anything.

Retrieval: dense alone misses exact strings — part numbers, error codes, names. So hybrid. BM25 and vector search in parallel, fused with reciprocal rank fusion, then a cross-encoder rerank over the top 50 down to the five that go in the prompt.

And the number I actually track isn’t similarity score, it’s whether the chunk containing the answer made it into the context. I measure recall on a labelled set before I tune anything else.”

Where quality is lost

Six stages — but quality is lost in three of them
The pipeline, left to right chunk embed index retrieve rerank generate Chunking loses it first a table split in half means nothing a claim without its qualifier inverts fix: chunk on the document’s own boundaries, not on a token count Dense retrieval loses exact terms embeddings blur part numbers, error codes and proper nouns together fix: BM25 and vector in parallel, fused with reciprocal rank fusion The prompt loses the answer score order is not usefulness order a long context buries its middle fix: cross-encoder rerank, then cut to the five that fit The metric that matters is not similarity score — it is whether the answer-bearing chunk is in the context at all.

Chunk on the boundary, not on the number

512 tokens is a default for prose and wrong for everything else.

ContentChunk onSizeWhy
Policies, prose docsheading + paragraph~512 tok, 64 overlapkeeps a claim with its qualifier
API referenceone endpointwhatever it ishalf an endpoint is worse than none
Tables, spreadsheetsthe whole table + captionwholea split row loses its header row
Codefunction or classwholea signature without its body retrieves nothing
Transcriptsturn windows6–10 turnspronouns need their antecedent in scope

Retrieve wide, rerank narrow

Each stage buys recall; the rerank spends a little of it back to buy prompt space.

StageReturnsAdded p50Answer chunk present
BM25 only5012 ms71%
Vector only5028 ms78%
Hybrid, fused with RRF5034 ms91%
+ cross-encoder → top 55170 ms88%

The last row gives up three points of recall and cuts prompt tokens roughly tenfold. That is the trade, and it is usually worth taking.

REAL SYSTEM

An enterprise policy corpus — ~240K chunks, heavy on tables. Recall@5 sat at ~61% and the agent’s answers were right ~68% of the time. Two changes: stop splitting tables (re-chunk on document structure) and add BM25 alongside the vector search with a cross-encoder rerank. Recall@5 went to ~88% and answer accuracy to ~84%, with ~200 ms added to p50. No prompt or model change was involved.

FOLLOW-UP TRAP

“Context windows are huge now — why not skip retrieval and paste everything in?” — cost scales linearly with tokens on every single call, attention degrades in the middle of a long context, and you lose the two things retrieval gives you for free: a citation, and a place to enforce per-user permissions. Retrieval is an access-control boundary, not only a cost trick.

Q7
How do you get reliable structured output? What happens when the JSON doesn't parse?
Structured OutputReliabilityTool UseProduction

Asked at OpenAI · Stripe · Amazon

open question
How to Answer

“First, don’t ask for JSON in prose and hope. Use the provider’s constrained decoding — attach a schema to the request so invalid tokens are masked at sampling time and the output parses by construction.

That buys you syntactic validity, not semantic validity. The model will happily return a perfectly-shaped object with a date in the wrong format, an enum value it invented, or line items that don’t add up to the total it also gave you. So there are two layers: constrained decoding for shape, then my own validator for meaning.

When validation fails, I retry exactly once with the validation error echoed back — ‘currency must be one of USD, EUR, GBP; you sent US Dollars’. That fixes most of them. If the second attempt fails, it goes to a fallback path or a human. It does not loop.”

Two gates, two different failures

Constrained decoding gets you shape. It does not get you meaning.
Two gates, two different failures Gate 1 — shape schema-constrained decoding: invalid tokens masked at sampling time catches: unparseable JSON, missing required fields, wrong types Gate 2 — meaning your validator, after it parses catches: invented enums, wrong units, totals that don’t reconcile, ids that don’t exist, arrays quietly cut short parses Fail → retry once with the error echoed back → then stop “field currency must be one of USD, EUR, GBP — you sent ‘US Dollars’” · a second failure falls back to a human; it does not loop

What still goes wrong after the JSON parses

FailureWhat it looks likeGuard
Invented enum"status": "in-progress-ish"literal enum in the schema; reject and echo the allowed values
Ambiguous unit"amount": 1250 — cents or dollars?require an explicit currency and minor-unit field
Doesn’t reconcileline items sum to 1180, total says 1250compute the total yourself; never trust the model’s arithmetic
Hallucinated idan order_id that has never existedresolve every id against the system of record before acting on it
Silent truncation3 of 47 line items, and it parsescheck the stop reason, not just whether it parsed

The retry that works

One repair attempt, carrying the specific error. Not a loop, and not a bare retry of the same prompt — that just resamples the same mistake.

def extract(doc, attempt=1):
    resp = client.messages.create(model=MODEL, tools=[INVOICE_SCHEMA],
                                  messages=build(doc))
    if resp.stop_reason == "max_tokens":
        raise Truncated(doc.id)          # parses fine, is missing rows

    obj = resp.content[0].input          # shape already guaranteed
    errors = check_business_rules(obj)   # enums, units, totals, ids
    if not errors:
        return obj
    if attempt == 2:
        return escalate(doc, errors)     # human queue; do not keep looping
    return extract(doc, attempt + 1, repair_hint=render(errors))
REAL SYSTEM

Invoice extraction, ~40K documents a month. Moving from “respond in JSON” to schema-constrained decoding took hard parse failures from ~4.2% to zero. The business-rule validator then caught another ~6.1% that parsed perfectly — mostly invented currency codes and totals that didn’t match the line items. One repair retry cleared ~81% of those, leaving ~1.2% for a human. The truncation check was added after a month of invoices that were quietly missing their last rows.

FOLLOW-UP TRAP

“If decoding is constrained, why validate at all?” — the grammar only guarantees the shape you asked for. Its most dangerous failure is the one that looks like success: hit the token limit mid-array and you can still get a well-formed object with three of forty-seven rows. It parses, it validates against a loose schema, and it is wrong. Check the stop reason on every call.

Q8
When would you not use an agent framework and write the loop yourself?
ArchitectureAgentsTool UseProduction

Asked at Anthropic · Cursor · Sierra

open question
How to Answer

“The agent loop is about forty lines — call the model, if it asked for tools run them, append the results, call again, stop on a final answer or a step cap. That’s not the hard part, and adopting a framework doesn’t remove the hard part.

What a framework does give you is the machinery around the loop: retries, streaming, tracing, checkpointing, human-in-the-loop pauses, durable state. Writing all of that yourself is real work, and that’s the honest argument for using one.

What it costs you is a layer between you and the model. When tool selection goes wrong you need to see the exact request that went over the wire, and that’s harder through three abstractions — especially when a version bump quietly edits the prompt underneath you.

So: own the loop and own the prompt, adopt libraries for the plumbing. If a framework won’t show me what the model was actually sent, that’s disqualifying.”

What is actually in the loop

The loop is about forty lines. The machinery around it is the real work.
What is actually in the loop 1 call model messages + tool schemas 2 tool calls? none → return the answer 3 execute your timeouts, your retries 4 append results go back as messages Step cap of 8. Exceeding it raises — it is a bug to investigate, not a condition to retry. Everything hard lives in step 3 and in the prompt that shapes step 1.

Decide per concern, not per framework

This is not an all-or-nothing choice, and treating it as one is how teams end up rewriting a workflow engine badly.

ConcernDefaultWhy
Loop control and step capownforty lines, and it is where your policy lives
System prompt, tool descriptionsownthis is the product — no upgrade should edit it
Retries, backoff, rate limitsadoptsolved, boring, and easy to get subtly wrong
Tracing and spansadoptyou want OpenTelemetry, not a bespoke logger
Durable state, resume after crashadopta real workflow engine beats a status column
Human-in-the-loop pausesplitadopt the mechanism, own the policy that triggers it

The loop, in full

def run(messages, tools, max_steps=8):
    for _ in range(max_steps):
        reply = model.call(messages, tools=tools)   # your prompt, your schemas
        messages.append(reply)
        if not reply.tool_calls:
            return reply.text
        for call in reply.tool_calls:
            messages.append(execute(call))          # timeouts and retries here
    raise StepBudgetExceeded(max_steps)             # never spin forever
REAL SYSTEM

A support agent on an off-the-shelf framework was picking the wrong tool about 9% of the time and nobody could say why — the framework was injecting its own tool-choice preamble ahead of ours. Dropping to a hand-written loop (~60 lines) made the request inspectable, and the actual fix was one sentence in a tool description. Selection errors fell to ~2%. Retries, tracing and the workflow store all stayed on libraries; the loop and the prompt came in-house.

FOLLOW-UP TRAP

“So frameworks are bad?” — no. The bad version is adopting one so you don’t have to understand the loop. If you can’t draw what goes over the wire on each step, the framework is a liability the first time production misbehaves. Teams that understand the loop use frameworks well, and switch off the parts that fight them.