Breaking
Advertisement
AI

TinyFish Breaks Down Agentic Search Architecture for Production AI Agents

Quick Read

An AI search agent can work in a demo but fail under production workloads when retrieval returns incomplete evidence, retries repeat unsuccessful actions, or execution exceeds time and cost limits. TinyFish, which provides web infrastructure for AI agents, has published two technical guides addressing these challenges. The first defines a production agentic search architecture, while the second shares lessons from building a deep research system. Together, they examine how an autonomous search agent can plan retrieval, evaluate evidence, handle failures, and manage the boundary between model reasoning and deterministic execution. TL;DR TinyFish introduced its agentic search architecture and key engineering lessons from building deep research systems. Built on four web primitives (TinySearch, TinyFetch, TinyBrowser, and TinyAgent), the system operates via a seven-phase control loop around the base model. On OpenBenchmarks, TinyFish achieved a 79.0% completion rate on coding tasks while delivering the lowest median task cost ($0.050) and token footprint, highlighting the importance of rigid control loops, failure-specific retries, and unified reasoning over standalone planners. What is Agentic Search Architecture? Agentic search architecture enables an AI agent to plan retrieval, access sources, evaluate evidence, and adapt its approach as new information becomes available. The model provides reasoning, while the surrounding application manages state, tool execution, evidence handling, runtime policies, and output validation. Together, these components allow an agent to handle incomplete results, recoverable failures, and changing information requirements. TinyFish’s architecture draws foundational concepts from a February 2026 architecture preprint, which decouples reasoning from execution through typed tool interfaces. The paper establishes essential system guardrails, including detailed tracing alongside strict caps on tokens, execution time, overall cost, and tool invocations. How does a production search run work? TinyFish splits a run into 7 phases: Define the task: Turn the goal into an “evidence contract”: required fields, sources, freshness and completion rules. Plan retrieval: Separate independent needs from dependent ones. Dependent queries wait for earlier answers. Pick the first route: Choose a tool based on what the system knows about the evidence. Discover, select, retrieve: Treat search results as candidates until the page content arrives. Evaluate: Label evidence as sufficient, incomplete, conflicting, inaccessible or budget exhausted. Recover or escalate: Revise the query, retry, change sources or change access method. Stop and validate: Return completed, incomplete, failed, cancelled or human review required. Phase 2 addresses a known failure. ChainRAG (ACL 2025) calls it the ‘lost-in-retrieval’ problem, where key entities disappear during multi-hop question decomposition. Phase 6 draws on Search-R1, which demonstrates how models can learn to generate additional search queries during reasoning when earlier retrieval is insufficient. Across 7 QA datasets, its learned multi-turn search beat RAG baselines by 41% (Qwen2.5-7B) and 20% (Qwen2.5-3B). How should a search agent route tools? Routing should follow the information gap, not a fixed ladder. Escalation is not always the fix. Weak results need a better query. Conflicting sources need stronger evidence. A heavier tool fixes neither. Retries should be based on the failure type and remain within defined limits. Transient errors may justify repeating a request, while incomplete or conflicting evidence requires a different approach. Browser retries must also account for actions already completed to avoid unintended consequences, such as submitting the same form twice. <iframe id="mtp-tfas-frame" title="How a production search agent works" loading="lazy" style="width:100%;height:600px;border:0;display:block;background:transparent;" scrolling="no" srcdoc="<!doctype html> <title>Agentic Search Explainer</title> html,body{margin:0!important;padding:0!important;background:transparent!important} #mtp-tfas{--bg:#111;--card:#161616;--line:#2c2c2c;--g:#76B900;--t:#eaeaea;--m:#8f8f8f;--amb:#e0a03a;--red:#e05a4a; max-width:880px;margin:0 auto;background:var(--bg)!important;color:var(--t)!important;border:1px solid var(--line)!important;border-radius:14px; font:15px/1.45 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;overflow:hidden;box-sizing:border-box} #mtp-tfas *{box-sizing:border-box} #mtp-tfas hr,#mtp-tfas p:empty,#mtp-tfas del,#mtp-tfas s{display:none!important} #mtp-tfas .hd{padding:18px 22px 4px;display:flex;align-items:baseline;gap:12px;flex-wrap:wrap} #mtp-tfas .kick{color:var(--g)!important;font-size:12px;letter-spacing:.12em;text-transform:uppercase;font-weight:700} #mtp-tfas h3{margin:0!important;font-size:20px;color:#fff!important;line-height:1.25;flex-basis:100%} #mtp-tfas .tabs{display:flex;gap:6px;padding:12px 22px 0;border-bottom:1px solid var(--line);overflow-x:auto} #mtp-tfas .tab{background:transparent!important;border:1px solid var(--line)!important;border-bottom:none!important;color:var(--m)!important; padding:9px 14px;border-radius:9px 9px 0 0;cursor:pointer;font:600 13px system-ui,sans-serif;white-space:nowrap} #mtp-tfas .tab.on{background:var(--card)!important;color:var(--g)!important;border-color:var(--g)!important} #mtp-tfas .pane{display:none;padding:14px 22px 16px;background:var(--card)!important} #mtp-tfas .pane.on{display:block;animation:tfIn .35s ease} @keyframes tfIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}} #mtp-tfas .chips{display:flex;flex-wrap:wrap;gap:6px;justify-content:center} #mtp-tfas .chip{background:#1d1d1d!important;border:1px solid var(--line)!important;color:var(--t)!important;padding:7px 12px;border-radius:20px;cursor:pointer;font:600 13px system-ui,sans-serif} #mtp-tfas .chip.on{background:var(--g)!important;color:#111!important;border-color:var(--g)!important} #mtp-tfas svg{display:block;width:100%;max-width:700px;height:auto;margin:6px auto 0} #mtp-tfas svg text{font-family:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif} #mtp-tfas .cap{text-align:center;color:var(--m)!important;font-size:13px;margin:4px 0 0;min-height:19px} #mtp-tfas .cap b{color:var(--g)!important} #mtp-tfas .cap a,#mtp-tfas a{color:var(--g)!important} #mtp-tfas .ring{stroke-dasharray:6 10;animation:tfFlow 1.2s linear infinite} @keyframes tfFlow{to{stroke-dashoffset:-16}} #mtp-tfas .wire{stroke-dasharray:7 7;animation:tfFlow .7s linear infinite} #mtp-tfas .ft{display:flex;justify-content:space-between;gap:10px;padding:10px 22px;border-top:1px solid var(--line);font-size:12px;color:var(--m)!important;background:var(--bg)!important;flex-wrap:wrap} #mtp-tfas .ft .brand{color:var(--g)!important;font-weight:700} @media (max-width:640px){ #mtp-tfas .hd{padding:14px 14px 2px}#mtp-tfas .tabs{padding:10px 14px 0}#mtp-tfas .pane{padding:12px 10px 14px} #mtp-tfas h3{font-size:17px}#mtp-tfas .tab{padding:8px 10px;font-size:12px}#mtp-tfas .chip{padding:6px 10px;font-size:12px}#mtp-tfas .ft{padding:10px 14px} } <div id="mtp-tfas"> <div class="hd"><span class="kick">Explainer · Agentic search</span><h3>How a production search agent works</h3></div> <div class="tabs" role="tablist"> <button class="tab on" data-t="p1">The loop</button> <button class="tab" data-t="p2">Tool routing</button> <button class="tab" data-t="p3">Deep research</button> <button class="tab" data-t="p4">Benchmark</button> </div> <div class="pane on" id="p1"> <div class="chips" id="c1"> <button class="chip" data-s="clean">Clean run</button> <button class="chip on" data-s="weak">Weak results</button> <button class="chip" data-s="budget">Budget hit</button> </div> <p class="cap" id="k1"></p> </div> <div class="pane" id="p2"> <div class="chips" id="c2"> <button class="chip on" data-r="0">Source unknown</button> <button class="chip" data-r="1">URL known</button> <button class="chip" data-r="2">Code drives the page</button> <button class="chip" data-r="3">Multi-page goal</button> <button class="chip" data-r="4">Weak or conflicting</button> </div> <p class="cap" id="k2"></p> </div> <div class="pane" id="p3"> <div class="chips" id="c3"> <button class="chip on" data-v="0">v1 Planner</button> <button class="chip" data-v="1">v2 Subagents</button> <button class="chip" data-v="2">v3 Tool calls</button> <button class="chip" data-v="3">Source weight</button> </div> <p class="cap" id="k3"></p> </div> <div class="pane" id="p4"> <div class="chips" id="c4"> <button class="chip on" data-b="sf">Search + fetch</button> <button class="chip" data-b="so">Search only</button> </div> <p class="cap">Median task cost vs grounded completion, 100 tasks, gpt-5.6-sol. Source: <a href=" target=" rel="noopener">OpenBenchmarks</a>, 26 Sept 2026.</p> </div> <div class="ft"><span>Sources: <a href=" target=" rel="noopener">architecture</a> · <a href=" target=" rel="noopener">deep research</a> · <a href=" target=" rel="noopener">OpenBenchmarks</a></span><span class="brand">Built by Marktechpost</span></div> </div> (function(){ var R=document.getElementById('mtp-tfas'),NS=' var G='#76B900',LN='#2c2c2c',BX='#1d1d1d',TX='#eaeaea',MU='#8f8f8f',AM='#e0a03a',RD='#e05a4a'; function $(s){return R.querySelector(s)}function $$(s){return R.querySelectorAll(s)} function el(t,a,p){var e=document.createElementNS(NS,t);for(var k in a)e.setAttribute(k,a[k]);if(p)p.appendChild(e);return e} function tx(p,x,y,s,a){var o={x:x,y:y,fill:TX,'font-size':15,'text-anchor':'middle','dominant-baseline':'middle'};for(var k in a)o[k]=a[k];var t=el('text',o,p);t.textContent=s;return t} function clear(s){while(s.firstChild)s.removeChild(s.firstChild)} function boost(sv,f){if(R.offsetWidth>=560)return;sv.querySelectorAll('text').forEach(function(t){if(t.dataset.b)return;t.dataset.b=1;t.setAttribute('font-size',(+t.getAttribute('font-size')*f).toFixed(1))})} function post(){try{parent.postMessage({mtpTfasH:R.offsetHeight+40},'*')}catch(e){}} var active='p1'; $$('.tab').forEach(function(t){t.onclick=function(){$$('.tab').forEach(function(x){x.classList.remove('on')});$$('.pane').forEach(function(x){x.classList.remove('on')});t.classList.add('on');active=t.dataset.t;$('#'+active).classList.add('on');if(active==='p4')drawScatter(true);setTimeout(post,60)}}); function chips(id,attr,fn){$$('#'+id+' .chip').forEach(function(c){c.onclick=function(){$$('#'+id+' .chip').forEach(function(x){x.classList.remove('on')});c.classList.add('on');fn(c.dataset[attr])}})} function arrowDefs(s,id,col){var d=el('defs',{},s);var m=el('marker',{id:id,viewBox:'0 0 10 10',refX:9,refY:5,markerWidth:7,markerHeight:7,orient:'auto-start-reverse'},d);el('path',{d:'M0 0L10 5L0 10z',fill:col},m)} /* ---------- TAB 1: the loop ---------- */ var s1=$('#s1'),C={x:330,y:180,r:115}; var N={goal:[80,52],plan:[200,52],route:[330,65],retrieve:[445,180],evaluate:[330,295],recover:[215,180],validate:[545,295],partial:[110,295]}; var LB={goal:'Goal',plan:'Plan',route:'Route',retrieve:'Retrieve',evaluate:'Evaluate',recover:'Recover',validate:'Validate',partial:'Partial'}; var ANG={route:-90,retrieve:0,evaluate:90,recover:180}; var box={},packet,bud,budT,center1,center2,termTxt; function build1(){clear(s1);arrowDefs(s1,'a1',LN); el('circle',{cx:C.x,cy:C.y,r:C.r,fill:'none',stroke:'#3a3a3a','stroke-width':2.5,class:'ring'},s1); [['goal','plan'],['plan','route']].forEach(function(p){var a=N[p[0]],b=N[p[1]];el('line',{x1:a[0]+52,y1:a[1],x2:b[0]-54,y2:b[1]>a[1]?b[1]:a[1],stroke:LN,'stroke-width':2,'marker-end':'url(#a1)'},s1)}); el('line',{x1:382,y1:295,x2:491,y2:295,stroke:LN,'stroke-width':2,'marker-end':'url(#a1)'},s1); el('line',{x1:278,y1:295,x2:164,y2:295,stroke:LN,'stroke-width':2,'marker-end':'url(#a1)'},s1); for(var k in N){var g=el('g',{},s1),p=N[k];var r=el('rect',{x:p[0]-52,y:p[1]-19,width:104,height:38,rx:10,fill:BX,stroke:LN,'stroke-width':1.5},g); var t=tx(g,p[0],p[1]+1,LB[k],{'font-size':15,'font-weight':600});box[k]={r:r,t:t}} center1=tx(s1,C.x,C.y-10,'',{'font-size':22,'font-weight':800,fill:'#fff'}); center2=tx(s1,C.x,C.y+18,'',{'font-size':14,fill:MU}); tx(s1,430,24,'Task budget',{'font-size':12,fill:MU,'text-anchor':'start'}); el('rect',{x:430,y:34,width:180,height:10,rx:5,fill:'#222'},s1); bud=el('rect',{x:430,y:34,width:0,height:10,rx:5,fill:G},s1); budT=tx(s1,612,24,'0%',{'font-size':12,fill:TX,'text-anchor':'end'}); tx(s1,330,340,'time · tokens · tool calls · retries · cost',{'font-size':11,fill:'#5c5c5c'}); packet=el('circle',{r:8,fill:G,stroke:'#111','stroke-width':2},s1); el('circle',{r:16,fill:'none',stroke:G,'stroke-opacity':.35,'stroke-width':2,id:'pkh'},s1);boost(s1,1.3)} var SC={ clean:{steps:[['goal'],['plan'],['route'],['retrieve'],['evaluate','Sufficient',G],['validate','Completed',G]],cap:0.42,txt:'Evidence meets the contract on the first pass. <b>Stop and validate.</b>'}, weak:{steps:[['goal'],['plan'],['route'],['retrieve'],['evaluate','Incomplete',AM],['recover','Rewrite query',AM],['route'],['retrieve'],['evaluate','Sufficient',G],['validate','Completed',G]],cap:0.74,txt:'A gap sends the run back around the loop with a <b>changed</b> query.'}, budget:{steps:[['goal'],['plan'],['route'],['retrieve'],['evaluate','Incomplete',AM],['recover','Retry, bounded',AM],['route'],['retrieve'],['evaluate','Budget exhausted',RD],['partial','Partial + review',AM]],cap:1,txt:'At the boundary the agent <b>stops honestly</b>, not with a fluent guess.'}}; function seg(a,b){var pts=[],i; if(ANG[a]!==undefined&&ANG[b]!==undefined){var a0=ANG[a],a1=ANG[b];if(a1<=a0)a1+=360;for(i=0;i<=40;i++){var an=(a0+(a1-a0)*i/40)*Math.PI/180;pts.push([C.x+C.r*Math.cos(an),C.y+C.r*Math.sin(an)])}} else{var p=N[a],q=N[b];for(i=0;i<=30;i++)pts.push([p[0]+(q[0]-p[0])*i/30,p[1]+(q[1]-p[1])*i/30])} return pts} var run1=null; function play1(name){var sc=SC[name];$('#k1').innerHTML=sc.txt; var path=[],marks=[];for(var i=0;i0){var sg=seg(sc.steps[i-1][0],sc.steps[i][0]);sg.shift();path=path.concat(sg)}else path.push(N[sc.steps[0][0]].slice());marks.push(path.length-1)} var L=[0];for(i=1;i100)dt=16; if(wait>0){wait-=dt;if(wait<=0&&ending){reset();return}}else dist=Math.min(tot,dist+dt*speed); var d=dist; var nxt=done+1;if(nxt=L[marks[nxt]]){done=nxt;dist=d=L[marks[nxt]];wait=hold;if(done===marks.length-1){wait=1900;ending=true}var st=sc.steps[done],b=box[st[0]]; b.r.setAttribute('stroke',st[2]||G);b.r.setAttribute('fill',st[2]===RD?'#2a1412':st[2]===AM?'#2a2111':'#1b2410'); center1.textContent=LB[st[0]];center2.textContent=st[1]||'';center2.setAttribute('fill',st[2]||MU); if(st[1]&&(st[0]==='validate'||st[0]==='partial')){b.t.textContent=st[1];b.t.setAttribute('fill',st[2])} } var j=0;while(j<L.length-1&&L[j+1]L[j]?(d-L[j])/(L[j+1]-L[j]):0;f=Math.max(0,Math.min(1,f||0)); var p=j=.99?RD:fr>.6?AM:G);budT.textContent=Math.round(fr*100)+'%'}}} build1();chips('c1','s',play1);play1('weak'); /* ---------- TAB 2: routing ---------- */ var s2=$('#s2'),lanes=[],wire,qmark,needT,rw; var LA=[['Search','find candidate sources','srch'],['Fetch','clean page content','doc'],['Browser','your code drives it','win'],['Web Agent','goal in, JSON out (Mako)','bot']]; function icon(g,k,x,y){var st={fill:'none',stroke:'currentColor','stroke-width':2.2,'stroke-linecap':'round'}; if(k==='srch'){el('circle',Object.assign({cx:x-3,cy:y-3,r:8},st),g);el('line',Object.assign({x1:x+3,y1:y+3,x2:x+9,y2:y+9},st),g)} if(k==='doc'){el('rect',Object.assign({x:x-8,y:y-10,width:16,height:20,rx:2},st),g);[ -4,0,4].forEach(function(o){el('line',Object.assign({x1:x-4,y1:y+o,x2:x+4,y2:y+o},st),g)})} if(k==='win'){el('rect',Object.assign({x:x-11,y:y-9,width:22,height:18,rx:2},st),g);el('line',Object.assign({x1:x-11,y1:y-4,x2:x+11,y2:y-4},st),g)} if(k==='bot'){el('rect',Object.assign({x:x-9,y:y-6,width:18,height:14,rx:4},st),g);el('line',Object.assign({x1:x,y1:y-6,x2:x,y2:y-11},st),g);el('circle',{cx:x-4,cy:y+1,r:1.8,fill:'currentColor'},g);el('circle',{cx:x+4,cy:y+1,r:1.8,fill:'currentColor'},g)}} function build2(){clear(s2);arrowDefs(s2,'a2',G); tx(s2,110,40,'Evidence need',{'font-size':12,fill:MU}); el('circle',{cx:110,cy:150,r:52,fill:'#1b2410',stroke:G,'stroke-width':2},s2); qmark=tx(s2,110,150,'?',{'font-size':40,'font-weight':800,fill:G}); needT=tx(s2,110,222,'',{'font-size':13,fill:TX}); wire=el('path',{fill:'none',stroke:G,'stroke-width':2.5,class:'wire','marker-end':'url(#a2)'},s2); LA.forEach(function(l,i){var y=42+i*70,g=el('g',{style:'color:'+MU},s2); var r=el('rect',{x:380,y:y-24,width:240,height:52,rx:11,fill:BX,stroke:LN,'stroke-width':1.5},g);icon(g,l[2],410,y+2); var t=tx(g,436,y-6,l[0],{'font-size':17,'font-weight':700,'text-anchor':'start'});tx(g,436,y+13,l[1],{'font-size':12,fill:MU,'text-anchor':'start'}); lanes.push({g:g,r:r,t:t,y:y})}); rw=el('g',{opacity:0},s2);el('rect',{x:30,y:262,width:160,height:44,rx:10,fill:'#2a2111',stroke:AM,'stroke-width':1.5},rw); tx(rw,110,284,'Rewrite query',{'font-size':15,'font-weight':700,fill:AM});boost(s2,1.22)} var RT=[['no URL yet',0,'Discover before paying to inspect or operate pages.'],['have a URL',1,'Skip search. It cannot add discovery value here.'],['scripted clicks',2,'Deterministic navigation stays in your code.'],['many pages',3,'Keeps the goal and run state while the path changes.'],['bad evidence',-1,'<b>No escalation.</b> A heavier tool will not fix a weak query.']]; function route(i){i=+i;var r=RT[i];needT.textContent=r[0];$('#k2').innerHTML=r[2]; lanes.forEach(function(l,k){var on=k===r[1];l.r.setAttribute('stroke',on?G:LN);l.r.setAttribute('fill',on?'#1b2410':BX);l.g.setAttribute('style','color:'+(on?G:MU)+';opacity:'+(r[1]=0){var y=lanes[r[1]].y;wire.setAttribute('d','M164 150 C 270 150, 270 '+y+', 374 '+y);wire.setAttribute('opacity',1);rw.setAttribute('opacity',0);qmark.textContent='?'} else{wire.setAttribute('d','M110 204 L 110 256');rw.setAttribute('opacity',1);qmark.textContent='!'} wire.animate&&wire.animate([{opacity:0},{opacity:1}],{duration:400})} build2();chips('c2','r',route);route(0); /* ---------- TAB 3: deep research ---------- */ var s3=$('#s3'),dots=[],spawn=null,stage=0,last3=0; function tagPill(x,y,s,c){var w=s.length*7.4+26;el('rect',{x:x-w/2,y:y-14,width:w,height:28,rx:14,fill:c==G?'#1b2410':c==RD?'#2a1412':'#2a2111',stroke:c,'stroke-width':1.5},s3);tx(s3,x,y+1,s,{'font-size':13,'font-weight':700,fill:c})} function bx(x,y,w,h,label,col,fs){el('rect',{x:x,y:y,width:w,height:h,rx:11,fill:BX,stroke:col||LN,'stroke-width':1.6},s3);if(label)tx(s3,x+w/2,y+h/2,label,{'font-size':fs||15,'font-weight':700})} var weight=0; function v3(v){v=+v;stage=v;clear(s3);dots=[];arrowDefs(s3,'a3',LN); if(v===0){bx(30,50,240,200,'',LN);tx(s3,150,72,'Orchestrator',{'font-size':15,'font-weight':700}); var bars=[['structured state',G],['chat history','#5b8def'],['past reports','#5b8def'],['findings','#5b8def'],['user intent','#5b8def']]; bars.forEach(function(b,i){var y=96+i*30;el('rect',{x:50,y:y,width:200,height:22,rx:5,fill:b[1],'fill-opacity':.18,stroke:b[1],'stroke-opacity':.6},s3);tx(s3,62,y+11,b[0],{'font-size':12,'text-anchor':'start',fill:TX})}); el('line',{x1:340,y1:40,x2:340,y2:260,stroke:RD,'stroke-width':2,'stroke-dasharray':'6 6'},s3);tx(s3,340,30,'boundary',{'font-size':11,fill:RD}); bx(420,110,190,80,'',LN);tx(s3,515,140,'Planner',{'font-size':16,'font-weight':700});tx(s3,515,164,'fresh LLM call',{'font-size':12,fill:MU}); tagPill(515,236,'Context lost',RD); spawn=function(){var i=Math.floor(Math.random()*5),y=107+i*30;dots.push({x:250,y:y,tx:420,ty:150,c:i?'#5b8def':G,die:i?340:null,v:2.4})}; $('#k3').innerHTML='Only structured state crossed. Follow-ups like "focus only on pricing risks" drifted.'} if(v===1){bx(30,110,170,80,'Orchestrator',RD);var ys=[50,150,250]; ys.forEach(function(y,i){bx(430,y-26,180,52,'Subagent '+(i+1),LN,14);el('line',{x1:200,y1:150,x2:430,y2:y,stroke:LN,'stroke-width':1.5},s3)}); tagPill(115,240,'Latency ↑ Cost ↑',RD); spawn=function(){var y=ys[Math.floor(Math.random()*3)],out=Math.random()<.5;dots.push(out?{x:200,y:150,tx:430,ty:y,c:MU,v:4}:{x:430,y:y,tx:200,ty:150,c:AM,v:4})}; $('#k3').innerHTML='1 subagent per query. The orchestrator kept dispatching, waiting and merging.'} if(v===2){bx(30,100,190,100,'',G);tx(s3,125,135,'Orchestrator',{'font-size':16,'font-weight':700});tx(s3,125,160,'plan + decide next',{'font-size':12,fill:G}); ['Search','Fetch','Browse'].forEach(function(n,i){var y=60+i*60;bx(290,y-20,110,40,n,LN,14);el('line',{x1:220,y1:150,x2:290,y2:y,stroke:LN,'stroke-width':1.5},s3);el('line',{x1:400,y1:y,x2:460,y2:120,stroke:LN,'stroke-width':1.5},s3)}); bx(460,70,150,100,'',G);tx(s3,535,106,'Rank + extract',{'font-size':14,'font-weight':700});tx(s3,535,128,'plain code',{'font-size':12,fill:G}); el('path',{d:'M535 170 C 535 250, 125 260, 125 204','fill':'none',stroke:G,'stroke-width':1.5,'stroke-dasharray':'4 5','marker-end':'url(#a3)'},s3); tx(s3,330,262,'ranked sources + extracted claims',{'font-size':12,fill:G}); tagPill(330,20,'Context ↓ Latency ↓ Cost ↓',G); spawn=function(){var i=Math.floor(Math.random()*3),y=60+i*60;dots.push({x:400,y:y,tx:460,ty:120,c:MU,v:2.2,r:6,then:1})}; $('#k3').innerHTML='Deterministic steps became tool calls. The orchestrator only sees the distilled result.'} if(v===3){spawn=null; var w=el('g',{},s3),gl=[['Goal: product sentiment',0],['Goal: unemployment rate',1]]; gl.forEach(function(g,i){var x=i?330:30,on=weight===i,rg=el('g',{style:'cursor:pointer'},w);el('rect',{x:x,y:10,width:280,height:34,rx:17,fill:on?G:BX,stroke:on?G:LN},rg);tx(rg,x+140,28,g[0],{'font-size':13,'font-weight':700,fill:on?'#111':TX});rg.onclick=function(){weight=i;v3(3)}}); tx(s3,170,272,'Social posts',{'font-size':14,'font-weight':700});tx(s3,480,272,'Government report',{'font-size':14,'font-weight':700}); var soc=[[110,110],[160,90],[210,120],[130,170],[185,165],[235,180],[95,215],[150,225],[215,225]]; soc.forEach(function(p){var c=el('circle',{cx:p[0],cy:p[1],r:6,fill:weight===0?G:'#3a3a3a'},s3);c.animate&&c.animate([{r:6},{r:weight===0?20:6}],{duration:700,fill:'forwards',easing:'ease-out'});if(weight===0)c.setAttribute('r',20)}); var gv=el('circle',{cx:480,cy:160,r:weight===1?78:16,fill:weight===1?G:'#3a3a3a','fill-opacity':.9},s3);gv.animate&&gv.animate([{r:16},{r:weight===1?78:16}],{duration:700,easing:'ease-out'}); $('#k3').innerHTML='Same sources, different weight. <b>Rank by the research objective</b>, not one quality score.'} boost(s3,1.25);setTimeout(post,60)} function tick3(ts){if(spawn&&ts-last3>(stage===1?110:260)){spawn();last3=ts} for(var i=dots.length-1;i>=0;i--){var d=dots[i];if(!d.e){d.e=el('circle',{r:d.r||5,fill:d.c,cx:d.x,cy:d.y},s3)} var dx=d.tx-d.x,dy=d.ty-d.y,l=Math.hypot(dx,dy); if(d.die&&d.x>=d.die-2){d.e.setAttribute('fill',RD);d.fade=(d.fade||1)-.06;d.e.setAttribute('opacity',d.fade);if(d.fade<=0){d.e.remove();dots.splice(i,1)}continue} if(l<d.v){if(d.then){d.e.remove();dots.splice(i,1);dots.push({x:535,y:170,tx:125,ty:204,c:G,v:3.2,r:3.5,curve:1});continue}d.e.remove();dots.splice(i,1);continue} d.x+=dx/l*d.v;d.y+=dy/l*d.v;d.e.setAttribute('cx',d.x);d.e.setAttribute('cy',d.y)}} chips('c3','v',v3);v3(0); /* ---------- TAB 4: scatter ---------- */ var s4=$('#s4'),D={sf:[['TinyFish',0.050,79.0],['Exa deep',0.127,83.0],['Perplexity',0.083,77.7],['Firecrawl',0.082,76.0]], so:[['TinyFish',0.035,59.3],['Exa fast',0.103,66.3],['Perplexity',0.059,77.3],['Firecrawl',0.059,70.3]]}; var X0=80,X1=610,Y0=290,Y1=30,pts={},cur='sf'; function sx(v){return X0+(v/0.14)*(X1-X0)}function sy(v){return Y0-((v-50)/40)*(Y0-Y1)} function build4(){clear(s4); for(var c=0;c<=0.14001;c+=0.02){var x=sx(c);el('line',{x1:x,y1:Y1,x2:x,y2:Y0,stroke:'#222'},s4);tx(s4,x,Y0+18,'$'+c.toFixed(2),{'font-size':12,fill:MU})} for(var v=50;v=1)anim4=null}} build4();chips('c4','b',function(b){cur=b;drawScatter(false)});drawScatter(true); function loop(ts){if(active==='p1'&&run1)run1.tick(ts);if(active==='p3')tick3(ts);if(anim4)anim4(ts); if(pts.TinyFish)pts.TinyFish.ring.setAttribute('r',15+3*Math.sin(ts/220));requestAnimationFrame(loop)} requestAnimationFrame(loop); window.addEventListener('load',post);window.addEventListener('resize',post);setTimeout(post,300); })(); "> window.addEventListener("message",function(e){if(e.data&&e.data.mtpTfasH){var f=document.getElementById("mtp-tfas-frame");if(f)f.style.height=e.data.mtpTfasH+"px";}}); What did TinyFish learn building deep research? The deep research agent searches, browses, and generates comprehensive, cited reports. Most engineering challenges centered around context management, system orchestration, and product design. Lesson #1: Removing the separate planner Initial iterations used a standalone planner called fresh each time. Only structured state crossed into it. On follow-up queries like “focus only on pricing risks in Southeast Asia,” the agent drifted back to broad market research. TinyFish subsequently moved planning into the orchestrator’s own unified reasoning loop. Lesson #2: Removing subagents: TinyFish initially assigned a separate subagent to each search query, increasing context-switching overhead, latency, and cost. The team later replaced these subagents with tool calls for repeatable tasks such as ranking search results, classifying sources, and extracting evidence. This reduced the information passed back to the orchestrator, allowing it to focus on research planning and deciding what to investigate next. Lesson #3- Matching researchers: TinyFish initially optimized its research system for source coverage, but users placed greater importance on the quality and relevance of supporting evidence. The team adjusted source ranking to account for the research objective and added controls for prioritizing, excluding, or restricting domains. It also developed an internal evaluation harness using an LLM judge to assess reports for relevance, evidence strength, citation trust, and source diversity. How do cost and latency add up? TinyFish budgets the full task, not single calls: Task latency = planning + search + retrieval + evaluation + retries + interactive steps + queueing. Task cost = tokens + tool calls + page or browser usage + retries + infrastructure. SearchAgent-X (ICLR 2026) shows why. Exact retrieval added overhead, and coarse retrieval forced extra reasoning. Its fixes reached up to 3.4x throughput and 5x lower latency versus tested baselines. How does TinyFish compare on search and fetch? OpenBenchmarks fixes the model (gpt-5.6-sol), 100 tasks and budgets, and varies only the provider. Scores are means of 3 runs. Source: OpenBenchmarks board, updated 26 Sept 2026. Tasks cover enterprise documentation only. OpenBenchmarks also reports search-only results separately. In that configuration, TinyFish achieved 59.3% task completion, compared with 77.3% for Perplexity, 70.3% for Firecrawl, and 66.3% for Exa Fast. These results should not be directly compared with the search-and-fetch scores because the available retrieval capabilities differ. Key Takeaways Control the whole trajectory, not 1 search call. Route by information gap; escalate only when access fails. Keep planning and execution in 1 reasoning loop. Move deterministic steps into tool calls, not subagents. Rank sources by research objective, not one quality score. Read the architecture guide, the deep research lessons and the TinyFish docs. Thanks to the Tinyfish team for the thought leadership / resources for this article. This article is sponsored by TinyFish. The post TinyFish Breaks Down Agentic Search Architecture for Production AI Agents appeared first on MarkTechPost.

An AI search agent can work in a demo but fail under production workloads when retrieval returns incomplete evidence, retries repeat unsuccessful actions, or execution exceeds time and cost limits. TinyFish, which provides web infrastructure for AI agents, has published two technical guides addressing these challenges. The first defines a production agentic search architecture, while the second shares lessons from building a deep research system. Together, they examine how an autonomous search agent can plan retrieval, evaluate evidence, handle failures, and manage the boundary between model reasoning and deterministic execution. 

TL;DR

TinyFish introduced its agentic search architecture and key engineering lessons from building deep research systems. Built on four web primitives (TinySearch, TinyFetch, TinyBrowser, and TinyAgent), the system operates via a seven-phase control loop around the base model. On OpenBenchmarks, TinyFish achieved a 79.0% completion rate on coding tasks while delivering the lowest median task cost ($0.050) and token footprint, highlighting the importance of rigid control loops, failure-specific retries, and unified reasoning over standalone planners.

What is Agentic Search Architecture?

Agentic search architecture enables an AI agent to plan retrieval, access sources, evaluate evidence, and adapt its approach as new information becomes available. The model provides reasoning, while the surrounding application manages state, tool execution, evidence handling, runtime policies, and output validation. Together, these components allow an agent to handle incomplete results, recoverable failures, and changing information requirements.

TinyFish’s architecture draws foundational concepts from a February 2026 architecture preprint, which decouples reasoning from execution through typed tool interfaces. The paper establishes essential system guardrails, including detailed tracing alongside strict caps on tokens, execution time, overall cost, and tool invocations.

How does a production search run work?

TinyFish splits a run into 7 phases:

  1. Define the task: Turn the goal into an “evidence contract”: required fields, sources, freshness and completion rules.
  2. Plan retrieval: Separate independent needs from dependent ones. Dependent queries wait for earlier answers.
  3. Pick the first route: Choose a tool based on what the system knows about the evidence.
  4. Discover, select, retrieve: Treat search results as candidates until the page content arrives.
  5. Evaluate: Label evidence as sufficient, incomplete, conflicting, inaccessible or budget exhausted.
  6. Recover or escalate: Revise the query, retry, change sources or change access method.
  7. Stop and validate: Return completed, incomplete, failed, cancelled or human review required.

Phase 2 addresses a known failure. ChainRAG (ACL 2025) calls it the ‘lost-in-retrieval’ problem, where key entities disappear during multi-hop question decomposition. Phase 6 draws on Search-R1, which demonstrates how models can learn to generate additional search queries during reasoning when earlier retrieval is insufficient. Across 7 QA datasets, its learned multi-turn search beat RAG baselines by 41% (Qwen2.5-7B) and 20% (Qwen2.5-3B).

How should a search agent route tools?

Routing should follow the information gap, not a fixed ladder.

Escalation is not always the fix. Weak results need a better query. Conflicting sources need stronger evidence. A heavier tool fixes neither.

Retries should be based on the failure type and remain within defined limits. Transient errors may justify repeating a request, while incomplete or conflicting evidence requires a different approach. Browser retries must also account for actions already completed to avoid unintended consequences, such as submitting the same form twice.

<iframe id="mtp-tfas-frame" title="How a production search agent works" loading="lazy" style="width:100%;height:600px;border:0;display:block;background:transparent;" scrolling="no" srcdoc="<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Agentic Search Explainer</title>
<style>
html,body{margin:0!important;padding:0!important;background:transparent!important}
#mtp-tfas{–bg:#111;–card:#161616;–line:#2c2c2c;–g:#76B900;–t:#eaeaea;–m:#8f8f8f;–amb:#e0a03a;–red:#e05a4a;
max-width:880px;margin:0 auto;background:var(–bg)!important;color:var(–t)!important;border:1px solid var(–line)!important;border-radius:14px;
font:15px/1.45 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;overflow:hidden;box-sizing:border-box}
#mtp-tfas *{box-sizing:border-box}
#mtp-tfas hr,#mtp-tfas p:empty,#mtp-tfas del,#mtp-tfas s{display:none!important}
#mtp-tfas .hd{padding:18px 22px 4px;display:flex;align-items:baseline;gap:12px;flex-wrap:wrap}
#mtp-tfas .kick{color:var(–g)!important;font-size:12px;letter-spacing:.12em;text-transform:uppercase;font-weight:700}
#mtp-tfas h3{margin:0!important;font-size:20px;color:#fff!important;line-height:1.25;flex-basis:100%}
#mtp-tfas .tabs{display:flex;gap:6px;padding:12px 22px 0;border-bottom:1px solid var(–line);overflow-x:auto}
#mtp-tfas .tab{background:transparent!important;border:1px solid var(–line)!important;border-bottom:none!important;color:var(–m)!important;
padding:9px 14px;border-radius:9px 9px 0 0;cursor:pointer;font:600 13px system-ui,sans-serif;white-space:nowrap}
#mtp-tfas .tab.on{background:var(–card)!important;color:var(–g)!important;border-color:var(–g)!important}
#mtp-tfas .pane{display:none;padding:14px 22px 16px;background:var(–card)!important}
#mtp-tfas .pane.on{display:block;animation:tfIn .35s ease}
@keyframes tfIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
#mtp-tfas .chips{display:flex;flex-wrap:wrap;gap:6px;justify-content:center}
#mtp-tfas .chip{background:#1d1d1d!important;border:1px solid var(–line)!important;color:var(–t)!important;padding:7px 12px;border-radius:20px;cursor:pointer;font:600 13px system-ui,sans-serif}
#mtp-tfas .chip.on{background:var(–g)!important;color:#111!important;border-color:var(–g)!important}
#mtp-tfas svg{display:block;width:100%;max-width:700px;height:auto;margin:6px auto 0}
#mtp-tfas svg text{font-family:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif}
#mtp-tfas .cap{text-align:center;color:var(–m)!important;font-size:13px;margin:4px 0 0;min-height:19px}
#mtp-tfas .cap b{color:var(–g)!important}
#mtp-tfas .cap a,#mtp-tfas a{color:var(–g)!important}
#mtp-tfas .ring{stroke-dasharray:6 10;animation:tfFlow 1.2s linear infinite}
@keyframes tfFlow{to{stroke-dashoffset:-16}}
#mtp-tfas .wire{stroke-dasharray:7 7;animation:tfFlow .7s linear infinite}
#mtp-tfas .ft{display:flex;justify-content:space-between;gap:10px;padding:10px 22px;border-top:1px solid var(–line);font-size:12px;color:var(–m)!important;background:var(–bg)!important;flex-wrap:wrap}
#mtp-tfas .ft .brand{color:var(–g)!important;font-weight:700}
@media (max-width:640px){
#mtp-tfas .hd{padding:14px 14px 2px}#mtp-tfas .tabs{padding:10px 14px 0}#mtp-tfas .pane{padding:12px 10px 14px}
#mtp-tfas h3{font-size:17px}#mtp-tfas .tab{padding:8px 10px;font-size:12px}#mtp-tfas .chip{padding:6px 10px;font-size:12px}#mtp-tfas .ft{padding:10px 14px}
}
</style></head><body>
<div id="mtp-tfas">
<div class="hd"><span class="kick">Explainer · Agentic search</span><h3>How a production search agent works</h3></div>
<div class="tabs" role="tablist">
<button class="tab on" data-t="p1">The loop</button>
<button class="tab" data-t="p2">Tool routing</button>
<button class="tab" data-t="p3">Deep research</button>
<button class="tab" data-t="p4">Benchmark</button>
</div>

<div class="pane on" id="p1">
<div class="chips" id="c1">
<button class="chip" data-s="clean">Clean run</button>
<button class="chip on" data-s="weak">Weak results</button>
<button class="chip" data-s="budget">Budget hit</button>
</div>
<svg id="s1" viewBox="0 0 640 350" aria-label="Agentic search control loop"></svg>
<p class="cap" id="k1"></p>
</div>

<div class="pane" id="p2">
<div class="chips" id="c2">
<button class="chip on" data-r="0">Source unknown</button>
<button class="chip" data-r="1">URL known</button>
<button class="chip" data-r="2">Code drives the page</button>
<button class="chip" data-r="3">Multi-page goal</button>
<button class="chip" data-r="4">Weak or conflicting</button>
</div>
<svg id="s2" viewBox="0 0 640 320" aria-label="Tool routing"></svg>
<p class="cap" id="k2"></p>
</div>

<div class="pane" id="p3">
<div class="chips" id="c3">
<button class="chip on" data-v="0">v1 Planner</button>
<button class="chip" data-v="1">v2 Subagents</button>
<button class="chip" data-v="2">v3 Tool calls</button>
<button class="chip" data-v="3">Source weight</button>
</div>
<svg id="s3" viewBox="0 0 640 300" aria-label="Deep research architecture versions"></svg>
<p class="cap" id="k3"></p>
</div>

<div class="pane" id="p4">
<div class="chips" id="c4">
<button class="chip on" data-b="sf">Search + fetch</button>
<button class="chip" data-b="so">Search only</button>
</div>
<svg id="s4" viewBox="0 0 640 340" aria-label="Completion versus cost scatter"></svg>
<p class="cap">Median task cost vs grounded completion, 100 tasks, gpt-5.6-sol. Source: <a href=" target="_blank" rel="noopener">OpenBenchmarks</a>, 26 Sept 2026.</p>
</div>

<div class="ft"><span>Sources: <a href=" target="_blank" rel="noopener">architecture</a> · <a href=" target="_blank" rel="noopener">deep research</a> · <a href=" target="_blank" rel="noopener">OpenBenchmarks</a></span><span class="brand">Built by Marktechpost</span></div>
</div>
<script>
(function(){
var R=document.getElementById('mtp-tfas'),NS='
var G='#76B900',LN='#2c2c2c',BX='#1d1d1d',TX='#eaeaea',MU='#8f8f8f',AM='#e0a03a',RD='#e05a4a';
function $(s){return R.querySelector(s)}function $$(s){return R.querySelectorAll(s)}
function el(t,a,p){var e=document.createElementNS(NS,t);for(var k in a)e.setAttribute(k,a[k]);if(p)p.appendChild(e);return e}
function tx(p,x,y,s,a){var o={x:x,y:y,fill:TX,'font-size':15,'text-anchor':'middle','dominant-baseline':'middle'};for(var k in a)o[k]=a[k];var t=el('text',o,p);t.textContent=s;return t}
function clear(s){while(s.firstChild)s.removeChild(s.firstChild)}
function boost(sv,f){if(R.offsetWidth>=560)return;sv.querySelectorAll('text').forEach(function(t){if(t.dataset.b)return;t.dataset.b=1;t.setAttribute('font-size',(+t.getAttribute('font-size')*f).toFixed(1))})}
function post(){try{parent.postMessage({mtpTfasH:R.offsetHeight+40},'*')}catch(e){}}
var active='p1';
$$('.tab').forEach(function(t){t.onclick=function(){$$('.tab').forEach(function(x){x.classList.remove('on')});$$('.pane').forEach(function(x){x.classList.remove('on')});t.classList.add('on');active=t.dataset.t;$('#'+active).classList.add('on');if(active==='p4')drawScatter(true);setTimeout(post,60)}});
function chips(id,attr,fn){$$('#'+id+' .chip').forEach(function(c){c.onclick=function(){$$('#'+id+' .chip').forEach(function(x){x.classList.remove('on')});c.classList.add('on');fn(c.dataset[attr])}})}
function arrowDefs(s,id,col){var d=el('defs',{},s);var m=el('marker',{id:id,viewBox:'0 0 10 10',refX:9,refY:5,markerWidth:7,markerHeight:7,orient:'auto-start-reverse'},d);el('path',{d:'M0 0L10 5L0 10z',fill:col},m)}

/* ———- TAB 1: the loop ———- */
var s1=$('#s1'),C={x:330,y:180,r:115};
var N={goal:[80,52],plan:[200,52],route:[330,65],retrieve:[445,180],evaluate:[330,295],recover:[215,180],validate:[545,295],partial:[110,295]};
var LB={goal:'Goal',plan:'Plan',route:'Route',retrieve:'Retrieve',evaluate:'Evaluate',recover:'Recover',validate:'Validate',partial:'Partial'};
var ANG={route:-90,retrieve:0,evaluate:90,recover:180};
var box={},packet,bud,budT,center1,center2,termTxt;
function build1(){clear(s1);arrowDefs(s1,'a1',LN);
el('circle',{cx:C.x,cy:C.y,r:C.r,fill:'none',stroke:'#3a3a3a','stroke-width':2.5,class:'ring'},s1);
[['goal','plan'],['plan','route']].forEach(function(p){var a=N[p[0]],b=N[p[1]];el('line',{x1:a[0]+52,y1:a[1],x2:b[0]-54,y2:b[1]>a[1]?b[1]:a[1],stroke:LN,'stroke-width':2,'marker-end':'url(#a1)'},s1)});
el('line',{x1:382,y1:295,x2:491,y2:295,stroke:LN,'stroke-width':2,'marker-end':'url(#a1)'},s1);
el('line',{x1:278,y1:295,x2:164,y2:295,stroke:LN,'stroke-width':2,'marker-end':'url(#a1)'},s1);
for(var k in N){var g=el('g',{},s1),p=N[k];var r=el('rect',{x:p[0]-52,y:p[1]-19,width:104,height:38,rx:10,fill:BX,stroke:LN,'stroke-width':1.5},g);
var t=tx(g,p[0],p[1]+1,LB[k],{'font-size':15,'font-weight':600});box[k]={r:r,t:t}}
center1=tx(s1,C.x,C.y-10,'',{'font-size':22,'font-weight':800,fill:'#fff'});
center2=tx(s1,C.x,C.y+18,'',{'font-size':14,fill:MU});
tx(s1,430,24,'Task budget',{'font-size':12,fill:MU,'text-anchor':'start'});
el('rect',{x:430,y:34,width:180,height:10,rx:5,fill:'#222'},s1);
bud=el('rect',{x:430,y:34,width:0,height:10,rx:5,fill:G},s1);
budT=tx(s1,612,24,'0%',{'font-size':12,fill:TX,'text-anchor':'end'});
tx(s1,330,340,'time · tokens · tool calls · retries · cost',{'font-size':11,fill:'#5c5c5c'});
packet=el('circle',{r:8,fill:G,stroke:'#111','stroke-width':2},s1);
el('circle',{r:16,fill:'none',stroke:G,'stroke-opacity':.35,'stroke-width':2,id:'pkh'},s1);boost(s1,1.3)}
var SC={
clean:{steps:[['goal'],['plan'],['route'],['retrieve'],['evaluate','Sufficient',G],['validate','Completed',G]],cap:0.42,txt:'Evidence meets the contract on the first pass. <b>Stop and validate.</b>'},
weak:{steps:[['goal'],['plan'],['route'],['retrieve'],['evaluate','Incomplete',AM],['recover','Rewrite query',AM],['route'],['retrieve'],['evaluate','Sufficient',G],['validate','Completed',G]],cap:0.74,txt:'A gap sends the run back around the loop with a <b>changed</b> query.'},
budget:{steps:[['goal'],['plan'],['route'],['retrieve'],['evaluate','Incomplete',AM],['recover','Retry, bounded',AM],['route'],['retrieve'],['evaluate','Budget exhausted',RD],['partial','Partial + review',AM]],cap:1,txt:'At the boundary the agent <b>stops honestly</b>, not with a fluent guess.'}};
function seg(a,b){var pts=[],i;
if(ANG[a]!==undefined&&ANG[b]!==undefined){var a0=ANG[a],a1=ANG[b];if(a1<=a0)a1+=360;for(i=0;i<=40;i++){var an=(a0+(a1-a0)*i/40)*Math.PI/180;pts.push([C.x+C.r*Math.cos(an),C.y+C.r*Math.sin(an)])}}
else{var p=N[a],q=N[b];for(i=0;i<=30;i++)pts.push([p[0]+(q[0]-p[0])*i/30,p[1]+(q[1]-p[1])*i/30])}
return pts}
var run1=null;
function play1(name){var sc=SC[name];$('#k1').innerHTML=sc.txt;
var path=[],marks=[];for(var i=0;i<sc.steps.length;i++){if(i>0){var sg=seg(sc.steps[i-1][0],sc.steps[i][0]);sg.shift();path=path.concat(sg)}else path.push(N[sc.steps[0][0]].slice());marks.push(path.length-1)}
var L=[0];for(i=1;i<path.length;i++)L.push(L[i-1]+Math.hypot(path[i][0]-path[i-1][0],path[i][1]-path[i-1][1]));var tot=L[L.length-1];
for(var k in box){box[k].r.setAttribute('stroke',LN);box[k].r.setAttribute('fill',BX);box[k].t.textContent=LB[k];box[k].t.setAttribute('fill',TX)}
var speed=0.16,hold=650,done=-1,pk=$('#pkh'),dist=0,prev=null,wait=0,ending=false;
function reset(){dist=0;done=-1;wait=0;ending=false;for(var k in box){box[k].r.setAttribute('stroke',LN);box[k].r.setAttribute('fill',BX);box[k].t.textContent=LB[k];box[k].t.setAttribute('fill',TX)}center1.textContent='';center2.textContent=''}
reset();
run1={tick:function(ts){if(prev===null)prev=ts;var dt=ts-prev;prev=ts;if(dt>100)dt=16;
if(wait>0){wait-=dt;if(wait<=0&&ending){reset();return}}else dist=Math.min(tot,dist+dt*speed);
var d=dist;
var nxt=done+1;if(nxt<marks.length&&d>=L[marks[nxt]]){done=nxt;dist=d=L[marks[nxt]];wait=hold;if(done===marks.length-1){wait=1900;ending=true}var st=sc.steps[done],b=box[st[0]];
b.r.setAttribute('stroke',st[2]||G);b.r.setAttribute('fill',st[2]===RD?'#2a1412':st[2]===AM?'#2a2111':'#1b2410');
center1.textContent=LB[st[0]];center2.textContent=st[1]||'';center2.setAttribute('fill',st[2]||MU);
if(st[1]&&(st[0]==='validate'||st[0]==='partial')){b.t.textContent=st[1];b.t.setAttribute('fill',st[2])}
}
var j=0;while(j<L.length-1&&L[j+1]<d)j++;var f=L[j+1]>L[j]?(d-L[j])/(L[j+1]-L[j]):0;f=Math.max(0,Math.min(1,f||0));
var p=j<path.length-1?[path[j][0]+(path[j+1][0]-path[j][0])*f,path[j][1]+(path[j+1][1]-path[j][1])*f]:path[path.length-1];
packet.setAttribute('cx',p[0]);packet.setAttribute('cy',p[1]);pk.setAttribute('cx',p[0]);pk.setAttribute('cy',p[1]);pk.setAttribute('r',12+4*Math.sin(ts/150));
var fr=d/tot*sc.cap;bud.setAttribute('width',180*fr);bud.setAttribute('fill',fr>=.99?RD:fr>.6?AM:G);budT.textContent=Math.round(fr*100)+'%'}}}
build1();chips('c1','s',play1);play1('weak');

/* ———- TAB 2: routing ———- */
var s2=$('#s2'),lanes=[],wire,qmark,needT,rw;
var LA=[['Search','find candidate sources','srch'],['Fetch','clean page content','doc'],['Browser','your code drives it','win'],['Web Agent','goal in, JSON out (Mako)','bot']];
function icon(g,k,x,y){var st={fill:'none',stroke:'currentColor','stroke-width':2.2,'stroke-linecap':'round'};
if(k==='srch'){el('circle',Object.assign({cx:x-3,cy:y-3,r:8},st),g);el('line',Object.assign({x1:x+3,y1:y+3,x2:x+9,y2:y+9},st),g)}
if(k==='doc'){el('rect',Object.assign({x:x-8,y:y-10,width:16,height:20,rx:2},st),g);[ -4,0,4].forEach(function(o){el('line',Object.assign({x1:x-4,y1:y+o,x2:x+4,y2:y+o},st),g)})}
if(k==='win'){el('rect',Object.assign({x:x-11,y:y-9,width:22,height:18,rx:2},st),g);el('line',Object.assign({x1:x-11,y1:y-4,x2:x+11,y2:y-4},st),g)}
if(k==='bot'){el('rect',Object.assign({x:x-9,y:y-6,width:18,height:14,rx:4},st),g);el('line',Object.assign({x1:x,y1:y-6,x2:x,y2:y-11},st),g);el('circle',{cx:x-4,cy:y+1,r:1.8,fill:'currentColor'},g);el('circle',{cx:x+4,cy:y+1,r:1.8,fill:'currentColor'},g)}}
function build2(){clear(s2);arrowDefs(s2,'a2',G);
tx(s2,110,40,'Evidence need',{'font-size':12,fill:MU});
el('circle',{cx:110,cy:150,r:52,fill:'#1b2410',stroke:G,'stroke-width':2},s2);
qmark=tx(s2,110,150,'?',{'font-size':40,'font-weight':800,fill:G});
needT=tx(s2,110,222,'',{'font-size':13,fill:TX});
wire=el('path',{fill:'none',stroke:G,'stroke-width':2.5,class:'wire','marker-end':'url(#a2)'},s2);
LA.forEach(function(l,i){var y=42+i*70,g=el('g',{style:'color:'+MU},s2);
var r=el('rect',{x:380,y:y-24,width:240,height:52,rx:11,fill:BX,stroke:LN,'stroke-width':1.5},g);icon(g,l[2],410,y+2);
var t=tx(g,436,y-6,l[0],{'font-size':17,'font-weight':700,'text-anchor':'start'});tx(g,436,y+13,l[1],{'font-size':12,fill:MU,'text-anchor':'start'});
lanes.push({g:g,r:r,t:t,y:y})});
rw=el('g',{opacity:0},s2);el('rect',{x:30,y:262,width:160,height:44,rx:10,fill:'#2a2111',stroke:AM,'stroke-width':1.5},rw);
tx(rw,110,284,'Rewrite query',{'font-size':15,'font-weight':700,fill:AM});boost(s2,1.22)}
var RT=[['no URL yet',0,'Discover before paying to inspect or operate pages.'],['have a URL',1,'Skip search. It cannot add discovery value here.'],['scripted clicks',2,'Deterministic navigation stays in your code.'],['many pages',3,'Keeps the goal and run state while the path changes.'],['bad evidence',-1,'<b>No escalation.</b> A heavier tool will not fix a weak query.']];
function route(i){i=+i;var r=RT[i];needT.textContent=r[0];$('#k2').innerHTML=r[2];
lanes.forEach(function(l,k){var on=k===r[1];l.r.setAttribute('stroke',on?G:LN);l.r.setAttribute('fill',on?'#1b2410':BX);l.g.setAttribute('style','color:'+(on?G:MU)+';opacity:'+(r[1]<0?.35:on?1:.55));l.t.setAttribute('fill',on?'#fff':TX)});
if(r[1]>=0){var y=lanes[r[1]].y;wire.setAttribute('d','M164 150 C 270 150, 270 '+y+', 374 '+y);wire.setAttribute('opacity',1);rw.setAttribute('opacity',0);qmark.textContent='?'}
else{wire.setAttribute('d','M110 204 L 110 256');rw.setAttribute('opacity',1);qmark.textContent='!'}
wire.animate&&wire.animate([{opacity:0},{opacity:1}],{duration:400})}
build2();chips('c2','r',route);route(0);

/* ———- TAB 3: deep research ———- */
var s3=$('#s3'),dots=[],spawn=null,stage=0,last3=0;
function tagPill(x,y,s,c){var w=s.length*7.4+26;el('rect',{x:x-w/2,y:y-14,width:w,height:28,rx:14,fill:c==G?'#1b2410':c==RD?'#2a1412':'#2a2111',stroke:c,'stroke-width':1.5},s3);tx(s3,x,y+1,s,{'font-size':13,'font-weight':700,fill:c})}
function bx(x,y,w,h,label,col,fs){el('rect',{x:x,y:y,width:w,height:h,rx:11,fill:BX,stroke:col||LN,'stroke-width':1.6},s3);if(label)tx(s3,x+w/2,y+h/2,label,{'font-size':fs||15,'font-weight':700})}
var weight=0;
function v3(v){v=+v;stage=v;clear(s3);dots=[];arrowDefs(s3,'a3',LN);
if(v===0){bx(30,50,240,200,'',LN);tx(s3,150,72,'Orchestrator',{'font-size':15,'font-weight':700});
var bars=[['structured state',G],['chat history','#5b8def'],['past reports','#5b8def'],['findings','#5b8def'],['user intent','#5b8def']];
bars.forEach(function(b,i){var y=96+i*30;el('rect',{x:50,y:y,width:200,height:22,rx:5,fill:b[1],'fill-opacity':.18,stroke:b[1],'stroke-opacity':.6},s3);tx(s3,62,y+11,b[0],{'font-size':12,'text-anchor':'start',fill:TX})});
el('line',{x1:340,y1:40,x2:340,y2:260,stroke:RD,'stroke-width':2,'stroke-dasharray':'6 6'},s3);tx(s3,340,30,'boundary',{'font-size':11,fill:RD});
bx(420,110,190,80,'',LN);tx(s3,515,140,'Planner',{'font-size':16,'font-weight':700});tx(s3,515,164,'fresh LLM call',{'font-size':12,fill:MU});
tagPill(515,236,'Context lost',RD);
spawn=function(){var i=Math.floor(Math.random()*5),y=107+i*30;dots.push({x:250,y:y,tx:420,ty:150,c:i?'#5b8def':G,die:i?340:null,v:2.4})};
$('#k3').innerHTML='Only structured state crossed. Follow-ups like "focus only on pricing risks" drifted.'}
if(v===1){bx(30,110,170,80,'Orchestrator',RD);var ys=[50,150,250];
ys.forEach(function(y,i){bx(430,y-26,180,52,'Subagent '+(i+1),LN,14);el('line',{x1:200,y1:150,x2:430,y2:y,stroke:LN,'stroke-width':1.5},s3)});
tagPill(115,240,'Latency ↑ Cost ↑',RD);
spawn=function(){var y=ys[Math.floor(Math.random()*3)],out=Math.random()<.5;dots.push(out?{x:200,y:150,tx:430,ty:y,c:MU,v:4}:{x:430,y:y,tx:200,ty:150,c:AM,v:4})};
$('#k3').innerHTML='1 subagent per query. The orchestrator kept dispatching, waiting and merging.'}
if(v===2){bx(30,100,190,100,'',G);tx(s3,125,135,'Orchestrator',{'font-size':16,'font-weight':700});tx(s3,125,160,'plan + decide next',{'font-size':12,fill:G});
['Search','Fetch','Browse'].forEach(function(n,i){var y=60+i*60;bx(290,y-20,110,40,n,LN,14);el('line',{x1:220,y1:150,x2:290,y2:y,stroke:LN,'stroke-width':1.5},s3);el('line',{x1:400,y1:y,x2:460,y2:120,stroke:LN,'stroke-width':1.5},s3)});
bx(460,70,150,100,'',G);tx(s3,535,106,'Rank + extract',{'font-size':14,'font-weight':700});tx(s3,535,128,'plain code',{'font-size':12,fill:G});
el('path',{d:'M535 170 C 535 250, 125 260, 125 204','fill':'none',stroke:G,'stroke-width':1.5,'stroke-dasharray':'4 5','marker-end':'url(#a3)'},s3);
tx(s3,330,262,'ranked sources + extracted claims',{'font-size':12,fill:G});
tagPill(330,20,'Context ↓ Latency ↓ Cost ↓',G);
spawn=function(){var i=Math.floor(Math.random()*3),y=60+i*60;dots.push({x:400,y:y,tx:460,ty:120,c:MU,v:2.2,r:6,then:1})};
$('#k3').innerHTML='Deterministic steps became tool calls. The orchestrator only sees the distilled result.'}
if(v===3){spawn=null;
var w=el('g',{},s3),gl=[['Goal: product sentiment',0],['Goal: unemployment rate',1]];
gl.forEach(function(g,i){var x=i?330:30,on=weight===i,rg=el('g',{style:'cursor:pointer'},w);el('rect',{x:x,y:10,width:280,height:34,rx:17,fill:on?G:BX,stroke:on?G:LN},rg);tx(rg,x+140,28,g[0],{'font-size':13,'font-weight':700,fill:on?'#111':TX});rg.onclick=function(){weight=i;v3(3)}});
tx(s3,170,272,'Social posts',{'font-size':14,'font-weight':700});tx(s3,480,272,'Government report',{'font-size':14,'font-weight':700});
var soc=[[110,110],[160,90],[210,120],[130,170],[185,165],[235,180],[95,215],[150,225],[215,225]];
soc.forEach(function(p){var c=el('circle',{cx:p[0],cy:p[1],r:6,fill:weight===0?G:'#3a3a3a'},s3);c.animate&&c.animate([{r:6},{r:weight===0?20:6}],{duration:700,fill:'forwards',easing:'ease-out'});if(weight===0)c.setAttribute('r',20)});
var gv=el('circle',{cx:480,cy:160,r:weight===1?78:16,fill:weight===1?G:'#3a3a3a','fill-opacity':.9},s3);gv.animate&&gv.animate([{r:16},{r:weight===1?78:16}],{duration:700,easing:'ease-out'});
$('#k3').innerHTML='Same sources, different weight. <b>Rank by the research objective</b>, not one quality score.'}
boost(s3,1.25);setTimeout(post,60)}
function tick3(ts){if(spawn&&ts-last3>(stage===1?110:260)){spawn();last3=ts}
for(var i=dots.length-1;i>=0;i–){var d=dots[i];if(!d.e){d.e=el('circle',{r:d.r||5,fill:d.c,cx:d.x,cy:d.y},s3)}
var dx=d.tx-d.x,dy=d.ty-d.y,l=Math.hypot(dx,dy);
if(d.die&&d.x>=d.die-2){d.e.setAttribute('fill',RD);d.fade=(d.fade||1)-.06;d.e.setAttribute('opacity',d.fade);if(d.fade<=0){d.e.remove();dots.splice(i,1)}continue}
if(l<d.v){if(d.then){d.e.remove();dots.splice(i,1);dots.push({x:535,y:170,tx:125,ty:204,c:G,v:3.2,r:3.5,curve:1});continue}d.e.remove();dots.splice(i,1);continue}
d.x+=dx/l*d.v;d.y+=dy/l*d.v;d.e.setAttribute('cx',d.x);d.e.setAttribute('cy',d.y)}}
chips('c3','v',v3);v3(0);

/* ———- TAB 4: scatter ———- */
var s4=$('#s4'),D={sf:[['TinyFish',0.050,79.0],['Exa deep',0.127,83.0],['Perplexity',0.083,77.7],['Firecrawl',0.082,76.0]],
so:[['TinyFish',0.035,59.3],['Exa fast',0.103,66.3],['Perplexity',0.059,77.3],['Firecrawl',0.059,70.3]]};
var X0=80,X1=610,Y0=290,Y1=30,pts={},cur='sf';
function sx(v){return X0+(v/0.14)*(X1-X0)}function sy(v){return Y0-((v-50)/40)*(Y0-Y1)}
function build4(){clear(s4);
for(var c=0;c<=0.14001;c+=0.02){var x=sx(c);el('line',{x1:x,y1:Y1,x2:x,y2:Y0,stroke:'#222'},s4);tx(s4,x,Y0+18,'$'+c.toFixed(2),{'font-size':12,fill:MU})}
for(var v=50;v<=90;v+=10){var y=sy(v);el('line',{x1:X0,y1:y,x2:X1,y2:y,stroke:'#222'},s4);tx(s4,X0-10,y,v+'%',{'font-size':12,fill:MU,'text-anchor':'end'})}
tx(s4,(X0+X1)/2,Y0+40,'Median task cost (lower is better)',{'font-size':12,fill:MU});
tx(s4,24,(Y0+Y1)/2,'Task completion',{'font-size':12,fill:MU,transform:'rotate(-90 24 '+(Y0+Y1)/2+')'});
tx(s4,X0+12,Y1+14,' better’,{‘font-size’:13,’font-weight’:700,fill:G,’text-anchor’:’start’});
D.sf.forEach(function(d){var tf=d[0]===’TinyFish’,g=el(‘g’,{},s4);
var ring=el(‘circle’,{r:tf?17:0,fill:’none’,stroke:G,’stroke-opacity’:.4,’stroke-width’:2},g);
var c=el(‘circle’,{r:tf?10:8,fill:tf?G:’#6a6a6a’,stroke:’#111′,’stroke-width’:2},g);
var t=tx(g,0,0,”,{‘font-size’:13,’font-weight’:700,fill:tf?G:TX,’text-anchor’:’start’});
pts[d[0]]={g:g,c:c,t:t,ring:ring,x:sx(d[1]),y:sy(d[2])}});
pts[‘Exa fast’]=pts[‘Exa deep’];boost(s4,1.2)}
var anim4=null,OFF={sf:{‘TinyFish’:[-16,-20,’end’],’Exa deep’:[-14,-18,’end’],’Perplexity’:[14,-12,’start’],’Firecrawl’:[14,16,’start’]},
so:{‘TinyFish’:[16,0,’start’],’Exa fast’:[-14,0,’end’],’Perplexity’:[14,0,’start’],’Firecrawl’:[14,0,’start’]}};
function drawScatter(fromZero){var data=D[cur],start={},t0=null;
data.forEach(function(d){var p=pts[d[0]];start[d[0]]=fromZero?{x:sx(0),y:Y0}:{x:p.x,y:p.y};p.tx=sx(d[1]);p.ty=sy(d[2]);
p.t.textContent=d[0]+’ ‘+d[2].toFixed(1)+’% · $’+d[1].toFixed(3)});
anim4=function(ts){if(!t0)t0=ts;var f=Math.min(1,(ts-t0)/900),e=1-Math.pow(1-f,3);
data.forEach(function(d){var p=pts[d[0]],s=start[d[0]];p.x=s.x+(p.tx-s.x)*e;p.y=s.y+(p.ty-s.y)*e;p.g.setAttribute(‘transform’,’translate(‘+p.x+’,’+p.y+’)’);
var o=OFF[cur][d[0]];p.t.setAttribute(‘x’,o[0]);p.t.setAttribute(‘y’,o[1]);p.t.setAttribute(‘text-anchor’,o[2])});
if(f>=1)anim4=null}}
build4();chips(‘c4′,’b’,function(b){cur=b;drawScatter(false)});drawScatter(true);

function loop(ts){if(active===’p1’&&run1)run1.tick(ts);if(active===’p3′)tick3(ts);if(anim4)anim4(ts);
if(pts.TinyFish)pts.TinyFish.ring.setAttribute(‘r’,15+3*Math.sin(ts/220));requestAnimationFrame(loop)}
requestAnimationFrame(loop);
window.addEventListener(‘load’,post);window.addEventListener(‘resize’,post);setTimeout(post,300);
})();
</script>
</body></html>
“>

window.addEventListener(“message”,function(e){if(e.data&&e.data.mtpTfasH){var f=document.getElementById(“mtp-tfas-frame”);if(f)f.style.height=e.data.mtpTfasH+”px”;}});

What did TinyFish learn building deep research?

The deep research agent searches, browses, and generates comprehensive, cited reports. Most engineering challenges centered around context management, system orchestration, and product design.

Lesson #1: Removing the separate planner

Initial iterations used a standalone planner called fresh each time. Only structured state crossed into it. On follow-up queries like “focus only on pricing risks in Southeast Asia,” the agent drifted back to broad market research. TinyFish subsequently moved planning into the orchestrator’s own unified reasoning loop.

Lesson #2: Removing subagents:

TinyFish initially assigned a separate subagent to each search query, increasing context-switching overhead, latency, and cost. The team later replaced these subagents with tool calls for repeatable tasks such as ranking search results, classifying sources, and extracting evidence. This reduced the information passed back to the orchestrator, allowing it to focus on research planning and deciding what to investigate next.

Lesson #3- Matching researchers:

TinyFish initially optimized its research system for source coverage, but users placed greater importance on the quality and relevance of supporting evidence. The team adjusted source ranking to account for the research objective and added controls for prioritizing, excluding, or restricting domains. It also developed an internal evaluation harness using an LLM judge to assess reports for relevance, evidence strength, citation trust, and source diversity.

How do cost and latency add up?

TinyFish budgets the full task, not single calls:

  • Task latency = planning + search + retrieval + evaluation + retries + interactive steps + queueing.
  • Task cost = tokens + tool calls + page or browser usage + retries + infrastructure.

SearchAgent-X (ICLR 2026) shows why. Exact retrieval added overhead, and coarse retrieval forced extra reasoning. Its fixes reached up to 3.4x throughput and 5x lower latency versus tested baselines.

How does TinyFish compare on search and fetch?

OpenBenchmarks fixes the model (gpt-5.6-sol), 100 tasks and budgets, and varies only the provider. Scores are means of 3 runs.

Source: OpenBenchmarks board, updated 26 Sept 2026. Tasks cover enterprise documentation only.

OpenBenchmarks also reports search-only results separately. In that configuration, TinyFish achieved 59.3% task completion, compared with 77.3% for Perplexity, 70.3% for Firecrawl, and 66.3% for Exa Fast. These results should not be directly compared with the search-and-fetch scores because the available retrieval capabilities differ.

Key Takeaways

  • Control the whole trajectory, not 1 search call.
  • Route by information gap; escalate only when access fails.
  • Keep planning and execution in 1 reasoning loop.
  • Move deterministic steps into tool calls, not subagents.
  • Rank sources by research objective, not one quality score.

Read the architecture guide, the deep research lessons and the TinyFish docs.

Thanks to the Tinyfish team for the thought leadership / resources for this article. This article is sponsored by TinyFish.

The post TinyFish Breaks Down Agentic Search Architecture for Production AI Agents appeared first on MarkTechPost.

Source: MarkTechPost

Advertisement