Agent harness بنائیں: loop اور اس سے نکلنے کے پانچ راستے
پندرہ لائن کا loop، پھر سات جان بوجھ کر خرابیوں کے ساتھ؛ ایک runaway جو سخت cap سے 77 گنا مہنگا پڑا۔
اس صفحے پر
شروع ایماندار حصے سے کریں، کیونکہ کوئی اور یہ نہیں کہے گا: “harness” jargon ہے، standard نہیں۔ کوئی specification نہیں، کوئی committee نہیں، کوئی reference definition نہیں۔ یہ chapter جن چار papers کا حوالہ دیتا ہے — ReAct,1 CoALA,2 SWE-bench اور vLLM — ان کے abstracts میں یہ لفظ ایک بار بھی نہیں آتا۔ اس چیز کی سب سے زیادہ download ہونے والی implementation، Vercel کا ai package جس کے ماہانہ 89.4 million downloads ہیں، بھی اسے استعمال نہیں کرتی: version 7.0.93 کے ساتھ shipped type declarations کے 397 KB میں string harness صفر بار آتی ہے۔3 ایک جگہ جہاں یہ لفظ واقعی load-bearing ہے، وہاں اس کا مطلب کچھ اور ہے۔ SWE-bench اپنے README میں “harness” پانچ بار کہتا ہے، ہمیشہ evaluation harness کے طور پر — containerised scaffold جو patch apply کرتا ہے اور tests چلاتا ہے — اور اس کا Python module لفظی طور پر swebench.harness.run_evaluation ہے۔4
تو دو مختلف چیزیں ایک نام share کرتی ہیں۔ evaluation harness agent کو ساکن رکھتا ہے اور اسے score کرتا ہے۔ agent harness وہ program ہے جو agent کو چلاتا ہے: یہ model کو call کرتا ہے، model جو مانگتا ہے اسے execute کرتا ہے، فیصلہ کرتا ہے کہ کب رکنا ہے، اور درمیان میں state رکھتا ہے۔ یہ chapter دوسرا harness بناتا ہے، TypeScript کی دو سو لائنوں سے کم میں، بالکل بغیر framework کے۔
loop خود پندرہ لائنوں کا ہے اور پہلی کوشش میں کام کرتا ہے۔ اس کے بعد کی ہر چیز اس سے نکلنے کا ایک طریقہ ہے۔
تفصیلات دکھائیں
اس chapter کو پچھلے chapters سے کیا چاہیے۔
- Chapter 14 client کے لیے: deadlines، status triage، cancellation، idempotency keys، اور mock provider technique جو یہاں دوبارہ استعمال ہوئی ہے۔
- Chapter 16 arithmetic کے لیے: input tokens conversation کے square کے ساتھ بڑھتے ہیں، اور نیچے استعمال ہونے والی rates وہی ہیں جو وہاں 6 September 2026 کو پڑھی گئیں۔
- Chapter 18 tool catalogue کے لیے: ایک schema جو model دیکھتا ہے، ایک endpoint جو وہ کبھی نہیں دیکھتا، اور یہ قاعدہ کہ errors exceptions نہیں بلکہ context ہیں۔
- Chapter 22 اس loop کے لیے جو یہ inherit کرتا ہے، اور “agent” کی دو published definitions کے لیے جو ایک دوسرے سے disagree کرتی ہیں۔
یہاں tensors نہیں۔ یہ course کا دوسرا dependency hub ہے: Chapters 24، 25، 29 اور 30 نیچے والی file پر چلتے ہیں، اور 26 سے 28 اس پر build کرتے ہیں جس تک یہ پہنچ سکتی ہے۔
ایک provider جسے آپ script کر سکتے ہیں
اس حصے کا لنک: ایک provider جسے آپ script کر سکتے ہیںChapter 14 کسی real provider کے خلاف نہیں لکھا جا سکتا تھا، کیونکہ آپ کسی provider سے chosen moment پر 429 نہیں مانگ سکتے۔ اس chapter کو وہی مسئلہ ایک مختلف شکل میں ہے: آپ real model سے demand اور reproducibly یہ نہیں کہہ سکتے کہ runaway ہو جائے، یا لگاتار دو بار identical tool request کرے۔
اس لیے پہلا program ایک scripted provider ہے: chat completions API کی شکل کا endpoint جس کا reply turn index اور tools کے اب تک واپس کیے گئے results کا function ہے۔ یہ real byte-pair encoder سے tokens count کرتا ہے، اس لیے نیچے والا money decoration نہیں بلکہ arithmetic ہے۔
const SCRIPTS = {
// A well-behaved task: list, read, answer.
plan: (t) =>
t === 0 ? asks(call("c1", "list_files", {}))
: t === 1 ? asks(call("c2", "read_file", { path: "errors.log" }))
: text("errors.log mentions a timeout: worker 7 timed out after 30000 ms."),
// Never declares itself done.
runaway: (t) => asks(call(`c${t}`, "list_files", {})),
// Guesses a file name, then corrects itself IF it was told what happened.
recover: (t, all) =>
t === 0 ? asks(call("c1", "read_file", { path: "timeout.log" }))
: /Call list_files/.test(all)
? (t === 1 ? asks(call("c2", "list_files", {}))
: t === 2 ? asks(call("c3", "read_file", { path: "errors.log" }))
: text("errors.log mentions a timeout."))
: text("I could not read the file, so I do not know."),
};
const turn = messages.filter((m) => m.role === "assistant").length;
const toolText = messages.filter((m) => m.role === "tool").map((m) => m.content).join("\n");
const message = SCRIPTS[scenario](turn, toolText);دو lines design اٹھاتی ہیں۔ turn index conversation سے derived ہے، variable میں held نہیں، اس لیے provider stateless ہے اور run کو kill کر کے اس کے خلاف resume کیا جا سکتا ہے۔ اور recover فیصلہ کرنے سے پہلے tool results پڑھتا ہے: scripted model جو اپنا transcript پڑھتا ہے، یہ measure کرنے کے لیے minimum ہے کہ harness نے اسے کچھ پڑھنے کے قابل دیا بھی یا نہیں۔
catalogue Chapter 18 کا ہے، تین files پر چار tools: list_files، read_file، delete_file — needsApproval marked — اور scan_archive، جو جان بوجھ کر slow ہے۔
وہ loop جو کام کرتا ہے
اس حصے کا لنک: وہ loop جو کام کرتا ہےیہ پوری idea ہے، ان تمام parts سے پہلے جو اسے survivable بناتے ہیں۔
while (true) {
const reply = await callModel(base, messages, tools, signal);
messages.push(reply.message);
const calls = reply.message.tool_calls ?? [];
if (!calls.length) return reply.message.content;
for (const c of calls) {
const tool = byName.get(c.function.name);
const result = await tool.run(JSON.parse(c.function.arguments));
messages.push({ role: "tool", tool_call_id: c.id, name: c.function.name, content: result });
}
}اسے scripted provider کی طرف point کریں اور یہ بالکل وہی کرتا ہے جو دکھتا ہے:
plan, cap 20 turns=3 tools=2 in=815 out=70 cost=$0.002470 ms=89 status=completed
answer: "errors.log mentions a timeout: worker 7 timed out after 30000 ms."
per-turn prompt tokens: 204, 269, 342تین turns، دو tool executions، ایک US cent کا چوتھائی حصہ۔ آخری line نوٹ کریں: 204، 269، 342۔ ہر turn اپنے سے پہلے سب کچھ دوبارہ بھیجتا ہے، یعنی Chapter 16 کا quadratic bill ایسی جگہ آ گیا جہاں کسی نے کچھ type بھی نہیں کیا۔ اس chapter کا باقی حصہ یہ ہے کہ جب وہ line بڑھنا بند نہیں کرتی تو کیا ہوتا ہے۔
Break one: وہ task جو کبھی ختم نہیں ہوتا
اس حصے کا لنک: Break one: وہ task جو کبھی ختم نہیں ہوتااسی loop کو runaway script کی طرف point کریں — ایک model جو ہر single turn پر tool مانگتا ہے اور کبھی prose emit نہیں کرتا — اور marked return کبھی fire نہیں ہوتا۔ کوئی اور exit نہیں۔ program چلتا رہتا ہے جب تک process مر نہ جائے یا credit card۔
fix ایک line ہے، literature جس first control کی recommendation دیتی ہے وہی،5 اور آخرکار سب اسے لکھتے ہیں۔ جو تقریباً کوئی نہیں کرتا، وہ یہ measure کرنا ہے کہ اس کی value کیا ہے:
| turn cap | model calls | input tokens | cost |
|---|---|---|---|
| 8 | 8 | 3,431 | $0.009070 |
| 20 | 20 | 16,259 | $0.038038 |
| 50 | 50 | 88,649 | $0.191098 |
| 100 | 100 | 337,299 | $0.702198 |
آخری دو rows کو ساتھ پڑھیں۔ cap کو 50 سے 100 double کرنے سے cost double نہیں ہوئی؛ یہ 3.7 گنا ہو گئی۔ input tokens 88,649 سے 337,299 ہو گئے، 3.8 کا factor، کیونکہ turn اپنے ساتھ ہر previous turn اٹھاتا ہے اور total ہے۔ turn cap linear dial نہیں۔ یہ آپ کے worst case کے square root پر dial ہے، اسی لیے 20 سے 100 “صرف safe رہنے کے لیے” بڑھانا ایسا decision ہے جس کی قیمت پہلے لگانی چاہیے۔
Break two: turns پر cap money پر cap نہیں
اس حصے کا لنک: Break two: turns پر cap money پر cap نہیںturn cap کا مسئلہ یہ ہے کہ turn کی fixed price نہیں ہوتی۔ short transcript پر twenty turns اوپر $0.038 کے تھے۔ 200-tool catalogue، retrieved document set اور history کے چالیس messages کے ساتھ twenty turns اس سے hundreds of times cost کرتے ہیں، اور cap کو پتا نہیں چلتا۔ operator جس چیز کو bound کرنا چاہتا ہے وہ bill ہے۔
اس لیے loop money count کرتا ہے، Chapter 16 کے computeCost کو وہاں پڑھی گئی rates کے خلاف استعمال کرتے ہوئے — $2.00 per million input tokens اور $12.00 per million output، اس model کے لیے جسے پورے course میں price کیا گیا ہے:
const PRICE_IN = 2.0 / 1e6, PRICE_OUT = 12.0 / 1e6;
export const cost = (u: Usage) => u.prompt_tokens * PRICE_IN + u.completion_tokens * PRICE_OUT;
// at the top of every iteration, before asking the model anything:
if (state.turns >= opts.limits.maxTurns) return stop("max_turns_exceeded", { type: "max_turns" });
if (state.costUsd >= opts.limits.maxBudgetUsd) return stop("budget_exceeded", { type: "max_budget" });
// ...and once the reply is back, before anything else happens with it:
state.costUsd += cost(reply.usage);وہی runaway script، کوئی turn cap بالکل نہیں، تین budgets:
| budget | turns reached | actually spent |
|---|---|---|
| $0.01 | 9 | $0.010780 |
| $0.05 | 24 | $0.051790 |
| $0.20 | 52 | $0.205398 |
دو چیزوں کو نام دینا ضروری ہے۔ پہلی، budget ہر بار different number of turns خریدتا ہے، اور یہی point ہے: یہ اس چیز کو bound کر رہا ہے جس کی operator کو پروا ہے، اور turn count کو وہاں گرنے دے رہا ہے جہاں transcript اسے رکھتا ہے۔ دوسری، ہر row overshoot کرتی ہے۔ budget $0.010 تھا اور $0.010780 spent ہوا، کیونکہ check turn سے پہلے چلتا ہے اور turn کی price ختم ہونے تک معلوم نہیں ہوتی۔ آپ spend کو exactly bound نہیں کر سکتے؛ آپ اسے ایک turn کی cost کے اندر bound کر سکتے ہیں۔ interface میں یہی کہیں، pretend نہ کریں، اور check call سے پہلے رکھیں تاکہ overshoot ایک turn ہو، دو نہیں۔
loop سے نکلنے کے پانچ راستے، ایک نہیں
اس حصے کا لنک: loop سے نکلنے کے پانچ راستے، ایک نہیںاب loop کے تین exits ہیں، اور باقی chapter کی shape visible ہے۔ production run exactly پانچ ways میں سے ایک میں end ہوتا ہے، اور یہ ایک دوسرے کی variations نہیں:
| how it ends | who decided | what the caller should do |
|---|---|---|
| the model stopped asking | model | answer پڑھیں |
| turn cap | آپ نے، پہلے سے | cap بڑھائیں، یا partial result accept کریں |
| budget exhausted | آپ نے، پہلے سے | مزید money approve کریں، یا partial result accept کریں |
| an error you cannot retry | provider یا tool | deployment fix کریں؛ Chapter 14 کا triage فیصلہ کرتا ہے |
| a human intervened | ایک person | verdict کا wait کریں، پھر resume کریں |
ان سب کو ایک boolean میں collapse کرنا اس file کی سب سے common design mistake ہے، اور یہ ایک specific way میں expensive ہے: پانچ میں سے تین resumable ہیں اور دو نہیں۔ جس agent نے اپنا turn cap hit کیا، اس کے پاس valid transcript، real partial result اور next step ہے؛ جس agent کو 401 ملا، اس کے پاس ان میں سے کچھ نہیں۔ اس لیے harness reason کو data کے طور پر record کرتا ہے:
export type RunStatus =
| "running" | "completed" | "failed"
| "max_turns_exceeded" | "budget_exceeded" | "interrupted";
export type Interruption =
| { type: "approval"; callId: string; toolName: string; args: unknown }
| { type: "max_turns" } | { type: "max_budget" }
| { type: "cancelled"; reason: string };Break three: ایک tool fail ہوتا ہے
اس حصے کا لنک: Break three: ایک tool fail ہوتا ہےChapter 18 ایک claim پر ختم ہوا تھا جس کے ساتھ number نہیں تھا: tool کا error raise کرنے کے بجائے tool result کے طور پر model کو واپس دیں، اور model عموماً خود کو fix کر لیتا ہے۔ یہ رہا number۔
ایک failure، تین policies۔ scripted model ایک ایسی file guess کرتا ہے جو exist نہیں کرتی؛ tool no such file: timeout.log. Call list_files to see what exists. throw کرتا ہے
| what the harness does with the error | turns | tool runs | cost | what the user got |
|---|---|---|---|---|
| اسے loop سے باہر throw کرتا ہے | 1 | 1 | $0.000756 | stack trace |
Error: the tool failed. return کرتا ہے | 2 | 1 | $0.001462 | “I could not read the file, so I do not know.” |
| جو actually ہوا وہ return کرتا ہے | 4 | 3 | $0.003550 | “errors.log mentions a timeout.” |
تیسری row پہلی سے 4.7 times cost کرتی ہے اور وہی واحد ہے جو question کا answer دیتی ہے۔ اور دوسری row interesting ہے، کیونکہ یہی زیادہ تر codebases actually کرتی ہیں: error caught ہوا، loop survived، model کو بتایا گیا کہ کچھ fail ہوا، کیا fail ہوا نہیں، اور اس نے شائستگی سے give up کر دیا۔ rows two اور three کا فرق error handling نہیں۔ یہ reader کے لیے لکھی گئی ایک sentence ہے۔
لہٰذا harness thrown tool کو data سمجھتا ہے، اور wording کو policy بناتا ہے:
} catch (err: any) {
if (signal.aborted) return stop("interrupted", { type: "cancelled", reason: String(signal.reason) });
if (opts.toolErrorsAreFatal) { state.error = err.message; return stop("failed"); }
result = (opts.toolErrorText ?? ((e: Error) => `Error: ${e.message}`))(err);
}Chapter 18 نے دوسری side کے بارے میں بھی warn کیا تھا، اور اس کی بھی price ہے۔ loop کو ایسے tool کی طرف point کریں جو ایسی وجہ سے fail ہوتا ہے جسے کوئی message fix نہیں کر سکتا — ایسی read جسے process perform کرنے کی اجازت نہیں — اور model اسے ہمیشہ retry کرتا رہتا ہے:
read a file the process may not open turns=12 toolruns=11 in=7,079 cost=$0.018622
status=max_turns_exceeded answer=""ایک ایسی call کی eleven identical executions جو succeed نہیں کر سکتی، اس run کی cost سے 5.2 times جس نے fixable error سے recover کیا، اور آخر میں کچھ نہیں۔ Errors context ہیں؛ permanent error ایسا context ہے جو باقی run کو poison کر دیتا ہے۔ distinction Chapter 14 کی status triage ہے جو ایک layer اوپر move ہوئی ہے: ایسا error جس پر model act کر سکتا ہے transcript میں واپس جاتا ہے، اور ایسا error جس پر وہ act نہیں کر سکتا run کو reason کے ساتھ stop کرنا چاہیے۔ آج turn cap ہی آپ اور دوسرے case کے درمیان کھڑا ہے، جو floor ہے fix نہیں۔
Break four: وہی call، دو بار
اس حصے کا لنک: Break four: وہی call، دو باراب وہ failure جسے اکثر لوگ assume کرتے ہیں کہ ہو ہی نہیں سکتا۔ Models خود کو repeat کرتے ہیں۔ کسی بھی loop کو کافی دیر چلائیں اور آپ identical tool کو identical arguments کے ساتھ دو consecutive turns پر دیکھیں گے۔
اسی task کے baseline کے خلاف measured، repeat کے بغیر:
| turns | tool runs | cost | |
|---|---|---|---|
| task، no repeat | 2 | 1 | $0.001396 |
| وہی task، ایک call repeated | 3 | 2 | $0.002446 |
| repeated، read-only tools پر result cache کے ساتھ | 3 | 1 | $0.002446 |
duplicated call نے $0.001050 extra cost کیا، 75 % increase، اور یہ حصہ لوگوں کو surprise کرتا ہے: result cache کرنے سے اس میں سے کچھ بھی recover نہیں ہوا۔ Deduplication نے tool execution بچائی، turn نہیں، کیونکہ جب تک آپ کا code repeat notice کرتا ہے model کو asking کے لیے already pay کیا جا چکا ہوتا ہے۔ saving real ہے جب tool slow، rate-limited، یا per-call billed ہو — اور اس line item پر zero ہے جو بڑھی۔
اس سے worse version ہے۔ یہی cache ایسے tool پر apply کریں جو write کرتا ہے، اور second call silently نہیں ہوتی:
naive cache on every tool 3 turns, 1 tool run, files deleted: ["access.log"]
cache only on read-only tools 3 turns, 2 tool runs, files deleted: ["access.log","access.log"]ان میں سے کون correct ہے؟ کوئی بھی نہیں، knowably۔ protocol کہتا ہے یہ دو calls ہیں: یہ دو different tool_call_id values carry کرتی ہیں۔ arguments کہتے ہیں شاید یہ ایک ہوں۔ ایسا harness جو argument strings compare کر کے decide کرتا ہے ایک دن two identical, intended charges میں سے second کو swallow کر جائے گا — اور Chapter 14 already اس واحد mechanism کا نام دے چکا ہے جو اسے honestly resolve کرتا ہے، یعنی idempotency key جو logical operation کے لیے اس layer سے generate ہوتی ہے جو جانتی ہے کہ operation ہے کیا۔ جب تک tool کے پاس وہ نہ ہو، defensible default اوپر والا read-only gate ہے: reads cache کریں، writes execute کریں، اور write کی اپنی idempotency کو باقی handle کرنے دیں۔
if (opts.dedupe && (tool.readOnly || opts.dedupeAll) && seen.has(signature)) {
state.messages.push({ role: "tool", tool_call_id: c.id, name: c.function.name, content: seen.get(signature)! });
continue;
}Break five: یہ کچھ delete کرتا ہے
اس حصے کا لنک: Break five: یہ کچھ delete کرتا ہےdestructive script files list کرتی ہے اور پھر ایک ایسی file delete کرنے کو کہتی ہے جس کا task نے کبھی mention نہیں کیا۔ loop میں اب تک کچھ بھی اسے روکنے والا نہیں تھا۔
needsApproval marked tool نہ fail ہوتا ہے نہ proceed کرتا ہے۔ یہ run stop کرتا ہے اور control return کرتا ہے، ہر چیز کے ساتھ جو ایک person کو decide کرنے کے لیے چاہیے:
if (tool.needsApproval && !state.approved.includes(c.id)) {
trace(state.runId, "approval_required", { toolName: tool.name, args: c.function.arguments, callId: c.id });
return stop("interrupted", { type: "approval", callId: c.id, toolName: tool.name, args: JSON.parse(c.function.arguments) });
}stopped at turn 2: interrupted / approval -> delete_file({"path":"access.log"})
files deleted so far: []
approve -> total turns=3 deleted=["access.log"] "Deleted access.log to free space."
reject -> total turns=3 deleted=[] "I did not delete anything: you declined the deletion."یہ پورا mechanism ہے، اور وجہ کہ یہ callback کے بجائے return ہے اگلا section ہے: stop اور verdict کے درمیان process شاید exist ہی نہ کرے۔
لیکن پہلے، وہ measurement جس کی کسی کو توقع نہیں۔ rejection result کی absence نہیں — transcript میں tool_call_id سے keyed slot ہے اور اس میں کچھ نہ کچھ جانا ہے۔ same rejection کو دو بار run کریں، صرف یہ بدلتے ہوئے کہ وہ چیز کیا کہتی ہے:
rejected with a reason deleted=[] the agent then told the user:
"I did not delete anything: you declined the deletion."
rejected with nothing deleted=[] the agent then told the user:
"Deleted access.log to free space."دونوں runs میں کچھ delete نہیں ہوا، اور second میں user کو بتایا گیا کہ ہو گیا۔ permission system perfect کام کیا؛ report جھوٹ ہے۔ یہ tool-error table جیسا ہی mechanism ہے، مگر ایسی جگہ پہنچا جہاں matter کہیں زیادہ ہے — human نے no کہا، action correctly blocked ہوا، اور agent کی summary reality سے contradict کرتی ہے کیونکہ refusal کبھی وہاں written نہیں ہوئی جہاں model پڑھتا ہے۔ اس سے نکلنے والا rule short ہے: آپ کا code tool call کے بارے میں جو بھی decide کرے، decision کو transcript میں words میں لکھیں۔ Chapter 30 security side سے اس پر واپس آتا ہے، جہاں یہ audit trail اور fiction کا فرق ہے۔
Break six: process مر جاتا ہے
اس حصے کا لنک: Break six: process مر جاتا ہےapproval میں minutes یا hours لگتے ہیں۔ deploy میں seconds۔ اگر run کسی HTTP request کے اندر local variable میں رہتا ہے، تو ہر restart lost run ہے اور ہر approval race ہے۔
اس لیے run closure نہیں۔ یہ plain serialisable object ہے — messages، turn count، cost، status، interruption، approved call ids کی list — اور loop اس پر pure function ہے۔ یہی single constraint persistence کو one-line concern بناتا ہے:
export const save = (s: RunState, dir: string) => writeFileSync(`${dir}/${s.runId}.json`, JSON.stringify(s));
export const load = (dir: string, runId: string) => JSON.parse(readFileSync(`${dir}/${runId}.json`, "utf8"));correctness question saving نہیں۔ یہ ہے کہ واپس آتے وقت کیا ہوتا ہے، اور naive answer آپ سے double-charge کراتا ہے۔ اگر process اس کے بعد مر گیا کہ model نے tool مانگا مگر result written ہونے سے پہلے، تو resume اگر model کو دوبارہ call کر کے start کرے تو ایسے turn کے لیے pay کرتا ہے جو اس کے پاس already ہے — اور اگر tools دوبارہ run کر کے start کرے تو write twice perform کرتا ہے۔
fix یہ ہے کہ loop شروع میں transcript سے پوچھے کہ outstanding کیا ہے:
export function pending(state: RunState): ToolCall[] {
const answered = new Set(state.messages.filter((m) => m.role === "tool").map((m) => m.tool_call_id));
const last = state.messages.at(-1);
if (last?.role !== "assistant") return [];
return (last.tool_calls ?? []).filter((c) => !answered.has(c.id));
}ہر iteration پہلے pending drain کرتی ہے اور model سے تب ہی پوچھتی ہے جب کچھ outstanding نہ ہو۔ Resume normal path جیسا ہی code path بن جاتا ہے، اور approval بھی — approved call simply pending call ہے جو اب run کرنے کی allowed ہے۔ process کو mid-task kill کریں اور restart کریں:
process died after turn 2. tool runs so far: list_files, read_file:errors.log
restored from disk: turns=2 cost=$0.001570 messages=6 status=running
resumed and finished: turns=3 cost=$0.002470 status=completed
tool runs across BOTH processes: list_files, read_file:errors.logایسے task کے لیے two processes میں two tool executions جسے two ہی چاہیے، اور final cost اس run جیسی identical ہے جو کبھی crash نہیں ہوا۔ Cost restart کے across accumulate ہوتی ہے کیونکہ یہ state میں تھی، variable میں نہیں۔
Break seven: تین minutes کی خاموشی
اس حصے کا لنک: Break seven: تین minutes کی خاموشیscan_archive یہاں three seconds لیتا ہے اور production میں three minutes لینے والے tool کی نمائندگی کرتا ہے۔ چلتے وقت دو چیزیں missing ہیں: user کو اندازہ نہیں کہ کچھ ہو رہا ہے، اور Stop button کچھ نہیں کرتا۔
دونوں کی fix ایک ہی ہے، اور یہ Chapter 14 کا AbortSignal ایک level deeper push کیا گیا ہے۔ signal صرف fetch کے لیے نہیں — اسے tool کے اندر pass کیا جاتا ہے، اور well-written tool اسے honour کرتا ہے:
result = await tool.run(JSON.parse(c.function.arguments), {
signal,
progress: (label) => { trace(state.runId, "tool_progress", { toolName: tool.name, label }); opts.onProgress?.(label); },
});progress: scanned 200 of 1200 files (t+506 ms)
progress: scanned 400 of 1200 files (t+1007 ms)
no cancellation: stopped after 3,015 ms, status=completed
user presses Stop at 1.2 s: stopped after 1,202 ms, status=interrupted, reason="user pressed Stop"click سے stop تک two milliseconds، کیونکہ tool کے اندر sleep اسی signal کو listen کرتی ہے جسے fetch کرتی ہے۔ اسے صرف fetch میں thread کریں اور identical Stop button three seconds wait کرتا ہے — tool کی length — اور run “cancels” تب ہوتا ہے جب جس work کو cancel کر رہا تھا وہ already finish ہو چکا ہوتا ہے۔ Cancellation جو all the way down plumb نہیں ہوئی، ایک spinner ہے جو صحیح word کہتا ہے۔
trace، اور یہ log کیوں نہیں
اس حصے کا لنک: trace، اور یہ log کیوں نہیںharness ہر event پر ایک line emit کرتا ہے، اور vocabulary اتنی small ہے کہ memorise کی جا سکتی ہے: turn، tool_start، tool_progress، tool_result، approval_required، run_stopped۔
{"runId":"n1","type":"turn","turn":1,"prompt_tokens":204,"completion_tokens":23,"total_tokens":227,"costUsd":0.000684,"finish":"tool_calls"}
{"runId":"n1","type":"tool_start","toolName":"list_files","args":"{}","callId":"c1"}
{"runId":"n1","type":"tool_result","toolName":"list_files","ms":1,"ok":true}
{"runId":"n1","type":"turn","turn":2,"prompt_tokens":269,"completion_tokens":29,"total_tokens":298,"costUsd":0.00157,"finish":"tool_calls"}
{"runId":"n1","type":"approval_required","toolName":"delete_file","args":"{\"path\":\"access.log\"}","callId":"c2"}
{"runId":"n1","type":"run_stopped","status":"interrupted","reason":"approval","turns":2,"costUsd":0.00157}تین properties اسے logging کے بجائے trace بناتی ہیں۔ ہر line run id carry کرتی ہے، اس لیے ایسا run جو تین processes اور دو دن span کرے ایک query ہے۔ ہر turn line اپنے token counts اور running cost carry کرتی ہے، اس لیے “اس run پر forty dollars کیوں لگے” بعد میں answerable ہے، صرف theory میں reproducible نہیں۔ اور run_stopped reason carry کرتا ہے، وہ field جو support ticket کو one-line answer میں بدل دیتا ہے: budget پر رکا ہوا agent اور crashed agent باہر سے identical لگتے ہیں اور opposite responses require کرتے ہیں۔
latency کی arithmetic
اس حصے کا لنک: latency کی arithmeticChapter 13 نے آپ کے own hardware پر time to first token measure کیا۔ Chapter 14 نے اسے socket کے through measure کیا۔ agent اسے multiply کرتا ہے، اور multiplier ایک ایسا number ہے جو کسی نے choose نہیں کیا:
وہی three-turn task، صرف provider کی latency بدلتے ہوئے:
| provider latency per turn | wall clock, 3 turns |
|---|---|
| 0 ms | 15 ms |
| 200 ms | 615 ms |
| 800 ms | 2,413 ms |
harness خود three-turn run میں fifteen milliseconds contribute کرتا ہے۔ باقی سب ہے، ایسے number سے multiplied جس پر آپ control نہیں رکھتے — serving scheduler کے اندر set ہوتا ہے جو آپ کی request کو strangers کی requests کے ساتھ batch کر رہا ہے6 — اور model choose کرتا ہے۔ اسی لیے Chapter 14 کی streaming یہاں chat سے زیادہ matter کرتی ہے اور کم help کرتی ہے: آپ final turn stream کر سکتے ہیں، اور اس سے پہلے کے four turns silence ہیں جب تک harness progress emit نہ کرے۔ یہی اوپر والے tool_progress event کی پوری argument بھی ہے — agent میں feedback کی honest unit token نہیں، step ہے۔
وہی harness، port کے پیچھے real model
اس حصے کا لنک: وہی harness، port کے پیچھے real modelاوپر کی ہر چیز scripted provider کے خلاف چلی، جو harness prove کرتی ہے اور models کے بارے میں کچھ prove نہیں کرتی۔ تو ایک line بدلیں — Chapter 14 کا seam، LLM_BASE_URL — اور identical code کو local Qwen2.5-0.5B-Instruct کی طرف point کریں، same four tools کے ساتھ۔ same three files پر six tasks:
turns=2 tools=1 wall= 15,260ms Which file mentions a timeout? -> "The file timeout.txt does not exist..."
turns=2 tools=1 wall= 13,037ms How many files are in the directory? -> "There are three files..."
turns=2 tools=1 wall= 10,121ms Read notes.txt and tell me what it says. -> "Remember to rotate your logs."
turns=2 tools=2 wall= 21,290ms List the files and then read each one.
turns=2 tools=1 wall= 10,698ms Which file is the largest? -> "The largest file is access.log."
turns=2 tools=1 wall= 12,490ms Is there a file about rotating logs?
TOTAL turns=12 toolruns=7 wall=82,896ms mean turn=6,908msتین findings، اور third ہی اس section کی وجہ ہے۔
ہر single task exactly two turns میں finish ہوا۔ turn cap کبھی fire نہیں ہوا، budget کبھی fire نہیں ہوا، اور loop کا only exit model کا prose produce کرنا تھا۔ half-billion-parameter model iterate نہیں کرتا؛ یہ اپنی second breath پر answer دے دیتا ہے، چاہے اس کے پاس جو چاہیے ہو یا نہ ہو۔ turn count model کی property ہے، آپ کے loop کی نہیں۔
mean turn نے 6,908 milliseconds لیے، اس لیے اوپر والی latency table toy نہیں: اس size پر hypothetical eight-turn run screen پر کچھ بھی نہ ہونے کے ساتھ تقریباً ایک minute wall clock ہے۔
اور answers غلط ہیں۔ largest file errors.log ہے؛ model نے files list کیں، انہیں کبھی read نہیں کیا، اور پھر بھی ایک کا نام لے دیا۔ first task نے file name guess کیا، بتایا گیا کہ وہ exist نہیں کرتی، اور conclude کر دیا۔ harness نے all six runs میں flawlessly execute کیا۔ harness agent کو governable بناتا ہے، correct نہیں — Chapter 29 یہ معلوم کرنے کا طریقہ ہے کہ کون سا، اور Chapter 30 یہ کہ جب کسی نے نہ کیا تو cost کیا ہے۔
Subagents، یہاں named اور later charged
اس حصے کا لنک: Subagents، یہاں named اور later chargedcatalogue میں ایک tool اپنے پیچھے another run رکھ سکتا ہے۔ interface Chapter 18 کا ہے — schema اور endpoint — اور پورا agent اس کے پیچھے fit ہو جاتا ہے کیونکہ interface narrow ہے:
const research: Tool = {
name: "research",
description: "Investigate one question and return a short summary.",
parameters: { type: "object", properties: { question: { type: "string" } }, required: ["question"] },
readOnly: true,
async run(args, ctx) {
const child = newRun(RESEARCH_SYSTEM, args.question); // its own transcript
const out = await run(child, researchTools, { base, limits: { maxTurns: 6, maxBudgetUsd: 0.05 }, signal: ctx.signal });
return out.output ?? "no result";
},
};ان ten lines میں تین چیزیں already right ہیں اور تینوں اوپر کیے گئے decisions کے consequences ہیں: child کے پاس اپنی window ہے، اس لیے parent کے transcript کو summary ملتی ہے، وہ سب کچھ نہیں جو child نے read کیا؛ اس کے پاس اپنی limits ہیں، اس لیے runaway child parent کا budget spend نہیں کر سکتا؛ اور یہ signal inherit کرتا ہے، اس لیے ایک Stop tree cancel کر دیتا ہے۔ clean window point کیوں ہے side effect نہیں، یہ Chapter 24 ہے؛ five orchestration patterns — prompt chaining، routing، parallelisation، orchestrator-workers، evaluator-optimiser — اور handoff Chapter 25 ہیں۔
frameworks کہاں ہیں، اور اس course نے ایک بھی کیوں استعمال نہیں کیا
اس حصے کا لنک: frameworks کہاں ہیں، اور اس course نے ایک بھی کیوں استعمال نہیں کیااوپر کی کسی بھی بات کو libraries کے خلاف argument نہیں سمجھنا چاہیے۔ 7 September 2026 کو measured، 29 August کو ختم ہونے والے month کے لیے:7
| package | downloads that month | what it gives you |
|---|---|---|
ai (Vercel AI SDK) | 89,385,860 | ToolLoopAgent، stopWhen، tool approval، step hooks |
@anthropic-ai/claude-agent-sdk | 41,558,352 | Claude Code harness بطور library: loop، sessions، hooks، permissions، subagents8 |
@langchain/langgraph | 12,812,815 | loop بطور explicit state graph |
langchain | 11,359,058 | chains، agents، integrations |
@openai/agents | 6,093,155 | agents، handoffs، guardrails |
@mastra/core | 5,914,502 | agents، workflows، memory |
یہ course ان میں سے کسی ایک کو teach کرنے کے بجائے loop ہاتھ سے کیوں لکھتا ہے، وجہ implied نہیں بلکہ declared ہے، اور measurable ہے۔ 7 September 2026 تک کے twelve months میں، ai نے 945 versions publish کیے اور major 5 سے major 7 پر move کیا، اور اس کی agent class ابھی بھی Experimental_Agent کے طور پر exported ہے؛ langchain نے اسی window میں 132 versions publish کیے؛ @openai/agents نے 83 publish کیے اور اپنی first release کے پندرہ months بعد بھی 0.x پر ہے۔7 ان APIs میں سے کسی کے خلاف لکھی گئی chapter ایک season میں stale ہو جاتی ہے، اور یہ one thirty-three languages میں publish ہوتی ہے، اس لیے ہر re-edition پوری translation کو cost کرتی ہے۔ ان سب کے نیچے جو ہے وہ move نہیں کرتا: loop، stopping rule، catalogue، executor، کچھ state۔
اور reference implementation اس chapter سے اس part پر agree کرتی ہے جو matter کرتا ہے۔ ai version 7.0.93 میں loop کا exit number نہیں — یہ stopWhen ہے، predicates کی list، جن میں step count صرف ایک ہے:3
type StopCondition<TOOLS extends ToolSet> = (options: { steps: Array<StepResult<TOOLS>> }) => PromiseLike<boolean> | boolean;
declare function isStepCount(stepCount: number): StopCondition<any, any>; // exported as stepCountIsاس loop کی most-used implementation میں stopping plural ہے، اسی reason سے جس سے اوپر کی hundred and ninety-six lines میں plural ہے۔
یہ آگے کہاں جاتا ہے
اس حصے کا لنک: یہ آگے کہاں جاتا ہےاب آپ کے پاس harness ہے: loop، catalogue، executor، نکلنے کے پانچ راستے، persisted run، signal جو tools تک پہنچتا ہے، اور trace جس کی ہر line پر run id ہے۔ Chapters 24، 25، 29 اور 30 اس file پر build کرتے ہیں، اور 26 سے 28 اس پر جس تک یہ پہنچ سکتی ہے۔
اس کا ایک problem باقی ہے، اور اوپر کی measurements پورے راستے اسی کی طرف point کر رہی تھیں۔ runaway table کو ایک بار پھر دیکھیں: eight turns پر 3,431 input tokens، hundred پر 337,299۔ working run دیکھیں: 204، 269، 342۔ ہر turn پورا transcript resend کرتا ہے، اس لیے agent کا context اپنی own history سے fill ہو جاتا ہے — اور model long window کے far end کو near end سے worse use کرتا ہے، اسی لیے turn five پر اچھا agent turn forty پر confused ہو جاتا ہے۔
turn cap اسے fix نہیں کرتا۔ یہ صرف آپ کو اس منظر کی payment سے روکتا ہے۔ اسے fix کرنے والی چیز ہر single turn پر decide کرنا ہے کہ کون سے tokens window کے deserve ہیں: کیا compact کرنا ہے، کیا agent کے fetch کر سکنے والے note میں باہر move کرنا ہے، clean window والے subagent کو کیا hand کرنا ہے، اور کون سی tool definitions اپنے permanent tax کے قابل ہیں۔ Chapter 24 measure کرتا ہے کہ window actually کہاں جاتی ہے — اور surprise یہ ہے کہ conversation نہیں۔
Sources and method
اس حصے کا لنک: Sources and methodاس chapter میں ہر number اوپر described two servers سے آیا، Node 22 پر loopback interface کے through: scripted provider جو o200k_base encoding سے tokens count کرتا ہے، اور Qwen/Qwen2.5-0.5B-Instruct same shape کے endpoint کے پیچھے، greedy decoding، CPU پر۔ Costs measured token counts سے compute کیے گئے، ان rates پر جو Chapter 16 نے 6 September 2026 کو read کیے — $2.00 per million input tokens اور $12.00 per million output — اور اس chapter میں کوئی request paid endpoint تک نہیں گئی۔ local model کے answers small model کے answers ہیں؛ انہیں loop کے بارے میں evidence سمجھیں، جو دونوں ways میں identical ہے، نہ کہ current models کیا کرتے ہیں اس کا benchmark۔
حوالہ جات
اس حصے کا لنک: حوالہ جات-
Yao, S., Zhao, J., Yu, D., Du, N., Shafran, I., Narasimhan, K. and Cao, Y. ReAct: Synergizing Reasoning and Acting in Language Models. arXiv:2210.03629 (2022). reasoning traces اور actions کی interleaving جسے loop implement کرتا ہے، اور اس observation کا source کہ acting model کو “handle exceptions” کرنے دیتی ہے — بالکل وہی جسے اوپر tool-error table measure کرتی ہے۔ ↩
-
Sumers, T. R., Yao, S., Narasimhan, K. and Griffiths, T. L. Cognitive Architectures for Language Agents (CoALA). arXiv:2309.02427 (2023). اوپر والا loop informally جو کرتا ہے اس کا formal treatment: modular memory components، structured action space جو internal memory اور external environments کو span کرتا ہے، اور “a generalized decision-making process to choose actions”۔ اسے اس vocabulary کے لیے پڑھیں جو industry term میں missing ہے — خاص طور پر working، episodic، semantic اور procedural memory کی separation، جس کا practical shadow Chapter 24 کی three-store table ہے۔ ↩
-
ai(Vercel AI SDK) version 7.0.93، published 4 September 2026؛ type declarationscdn.jsdelivr.net/npm/ai@7.0.93/dist/index.d.tsسے 7 September 2026 کو read کیے گئے۔ 397 KB file میں stringharnessکی zero occurrences ہیں۔ agent classdeclare class ToolLoopAgentہے، جوToolLoopAgentاورExperimental_Agentدونوں کے طور پر exported ہے؛declare function isStepCount(stepCount: number)— exported asstepCountIs— اوپر verbatim quoted ہے؛type StopConditionاپنے second type parameter (RUNTIME_CONTEXT extends Context = Context) کے بغیر دکھایا گیا ہے، جو excerpt میں only elision ہے، جیسےstopWhen?: Arrayable<StopCondition<...>>کی shapegenerateTextاورstreamTextپر ہے۔ same filetoolApproval،ToolApprovalStatus،prepareStepاورrepairToolCalldeclare کرتی ہے، یعنی reference implementation independently approval gates، per-step preparation اور error repair تک پہنچ چکی ہے۔ ↩ ↩2 -
Jimenez, C. E., Yang, J., Wettig, A., Yao, S., Pei, K., Press, O. and Narasimhan, K. SWE-bench: Can Language Models Resolve Real-World GitHub Issues? arXiv:2310.06770 (2023). abstract artefact کو 2,294 problems کا “evaluation framework” کہتا ہے اور “harness” word کبھی use نہیں کرتا؛ project کا own README (
github.com/SWE-bench/SWE-bench، read 7 September 2026) اسے five times use کرتا ہے، always as “evaluation harness”، اور entry pointpython -m swebench.harness.run_evaluationہے۔ یہ word کا other sense ہے: ایسا scaffold جو agent کو still رکھتا ہے اور اسے score کرتا ہے، نہ کہ وہ loop جو اسے run کرتا ہے۔ ↩ -
Anthropic، Building effective agents، 19 December 2024،
anthropic.com/engineering/building-effective-agents، read 7 September 2026۔ augmented model as building block، agent as an LLM “using tools based on environmental feedback in a loop”، اور control maintain کرنے کے لیے stopping conditions “such as a maximum number of iterations” کی recommendation۔ Chapter 22 اس definition کو full quote کرتا ہے۔ ↩ -
Kwon, W., Li, Z., Zhuang, S., Sheng, Y., Zheng, L., Yu, C. H., Gonzalez, J. E., Zhang, H. and Stoica, I. Efficient Memory Management for Large Language Model Serving with PagedAttention. arXiv:2309.06180 (2023). دوسرا loop — serving scheduler جو آپ کی request کو strangers کی requests کے ساتھ batch کرتا ہے اور Chapter 13 کے KV cache کو manage کرتا ہے۔ اس کے existence کا جاننا precisely اس لیے worth ہے کہ یہ آپ کا نہیں: latency جسے آپ کا harness multiply کرتا ہے اسی کے اندر set ہوتی ہے، اور آپ کے loop پر کوئی بھی کام اسے move نہیں کرتا۔ ↩
-
npm registry download counts،
api.npmjs.org/downloads/point/2026-07-31:2026-08-29/<package>، rollinglast-monthwindow کے بجائے explicit window، اور release historiesregistry.npmjs.org/<package>سے؛ دونوں 7 September 2026 کو queried۔ Release counts اس date تک twelve months میں published versions کی تعداد ہیں، canary builds سمیت:ai945 (latest 7.0.93 on 2026-09-04، major versions 5، 6 اور 7 سب window کے اندر appear ہوئے)،langchain132 (latest 1.5.10 on 2026-08-20)،@openai/agents83 (latest 0.17.0 on 2026-08-19، first published 2025-06-03)۔ ↩ ↩2 -
Claude Agent SDK (
@anthropic-ai/claude-agent-sdk) Claude Code harness ہے جو library کے طور پر packaged ہے — agent loop، built-in file اور shell tools، context management، sessions، hooks، permissions اور subagents —code.claude.com/docs/en/agent-sdkپر documented۔ یہ اس chapter کے ہاتھ سے بنائے گئے ہر mechanism کے published account کے closest thing ہے، اور اپنی implementation کے ساتھ پڑھنے کے قابل ہے، خاص طور پر ان parts کے لیے جنہیں یہ name کرتا ہے اور یہ chapter صرف gesture کرتا ہے۔ ↩