Level 5 — MasteryAI Agents & Tool Use

AI Agents & Tool Use

90 minutes60 marksprintable — key stays hidden on paper

Level: 5 (Mastery — cross-domain: math + systems + coding) Time limit: 90 minutes Total marks: 60

Instructions: Answer all three questions. Show derivations, justify design choices, and provide runnable pseudocode/code where asked. Partial credit is awarded for correct reasoning.


Question 1 — ReAct Loops, Cost Bounds & Convergence (20 marks)

A ReAct agent solves tasks by alternating Thought → Action → Observation steps until it emits a final answer. Each step tt consumes cin(t)c_{\text{in}}(t) input tokens (the full running transcript) and coutc_{\text{out}} output tokens (fixed). The transcript grows because every step is appended: after step tt the transcript has length Lt=L0+tsL_t = L_0 + t\cdot s tokens, where ss is the average tokens added per step and L0L_0 is the system+task prompt.

(a) Assuming the model re-reads the entire transcript each step, derive a closed-form expression for the total input token cost Cin(T)C_{\text{in}}(T) over a run of TT steps. State its asymptotic complexity in TT. (5)

(b) A summarizing memory system compresses the transcript to a fixed cap of MM tokens whenever Lt>ML_t > M (cost of summarization ignored). Derive Cincap(T)C_{\text{in}}^{\text{cap}}(T) and compare its asymptotic complexity to part (a). At what step t\*t^\* does capping first activate? (5)

(c) Model the ReAct loop as a Markov process: at each step the agent reaches the goal with probability pp (independent), otherwise it continues. Derive the expected number of steps E[T]\mathbb{E}[T] to termination and the probability the run exceeds a budget of BB steps. Using this, give the expected total input cost E[Cin]\mathbb{E}[C_{\text{in}}] under the uncapped model of part (a). (6)

(d) Explain, with reference to your results, why unbounded ReAct loops are dangerous in production and name two guardrail mechanisms (one on token budget, one on action safety) that address distinct failure modes. (4)


Question 2 — Function Calling as Constrained Generation: Build & Prove (22 marks)

You must design a guardrail that forces an LLM to emit only valid tool calls conforming to a JSON schema, by constraining decoding to a formal grammar.

(a) Given the tool signature search(query: string, limit: int in [1,50]), write a context-free grammar (in BNF) that generates exactly the set of valid JSON call objects {"name":"search","arguments":{"query":<str>,"limit":<int 1..50>}}. Enforce the integer range at the grammar level as far as CFGs allow, and state precisely which constraint cannot be expressed by a pure CFG and must be enforced by a validator. (6)

(b) Constrained decoding masks the next-token logits to the set of tokens permitted by the grammar state. Let the vocabulary be VV and the allowed set at a step be AVA \subseteq V. Prove that renormalizing the softmax over AA is equivalent to conditioning the model distribution on the event "next token A\in A", i.e. show p(v)=p(v)1[vA]uAp(u)=p(vtokenA).p'(v) = \frac{p(v)\,\mathbb{1}[v\in A]}{\sum_{u\in A} p(u)} = p(v \mid \text{token} \in A). Then show this masking can only decrease or preserve the probability mass assigned to any valid full sequence relative to the unconstrained model, and state the condition for equality. (8)

(c) Implement a Python function mask_logits(logits, allowed_ids) that applies the mask numerically stably (using -\infty for disallowed logits) and returns a valid probability distribution. Then argue why setting disallowed logits to -\infty before softmax is numerically preferable to zeroing probabilities after softmax. (5)

(d) Give one concrete failure case where grammar-constrained decoding produces a syntactically valid but semantically wrong tool call, and name the complementary guardrail layer that catches it. (3)


Question 3 — Multi-Agent Planning, Evaluation & Decomposition (18 marks)

A "planner–executor" system decomposes a task into a DAG of nn subtasks. Edges encode dependencies; independent subtasks may run in parallel across kk worker agents.

(a) Given a subtask DAG, define the critical path and prove that the minimum makespan (wall-clock time) with unlimited workers equals the critical-path length. For the DAG below with unit-cost nodes and edges ABA\to B, ACA\to C, BDB\to D, CDC\to D, CEC\to E, DFD\to F, EFE\to F, compute the critical-path length and the minimum number of workers needed to achieve it. (7)

(b) Define an evaluation metric for an autonomous agent that separates task success rate from cost efficiency. Propose a single scalar score combining success and cost, and explain why a naive "success ÷ tokens" ratio can be gamed. Give the corrective term. (6)

(c) In a multi-agent debate/critic setup, one agent proposes and another critiques. Argue formally why, under the assumption that the critic's error rate on catching a wrong proposal is q<1q<1, chaining a critic reduces the final error rate — and derive the residual error rate for a proposal with base error ee. State one assumption that, if violated, makes the critic useless. (5)


Answer keyMark scheme & solutions

Question 1

(a) Input cost at step tt (steps 1..T1..T, define L1=L0+sL_1 = L_0 + s, or index from 0; use Lt=L0+tsL_t = L_0 + t s): Cin(T)=t=1T(L0+ts)=TL0+sT(T+1)2.C_{\text{in}}(T) = \sum_{t=1}^{T}(L_0 + t s) = T L_0 + s\frac{T(T+1)}{2}.

  • Sum of arithmetic series (2); correct linear + quadratic terms (2).
  • Asymptotically Cin(T)=Θ(T2)C_{\text{in}}(T) = \Theta(T^2) (dominated by sT2/2sT^2/2). (1)

(b) Capping activates when Lt=L0+ts>Mt\*=(ML0)/s+L_t = L_0 + ts > M \Rightarrow t^\* = \lceil (M-L_0)/s\rceil + (first integer exceeding). Before t\*t^\*, cost grows as in (a); after, each step costs MM: Cincap(T)=t=1t\*(L0+ts)+(Tt\*)M.C_{\text{in}}^{\text{cap}}(T) = \sum_{t=1}^{t^\*}(L_0+ts) + (T-t^\*)M.

  • For Tt\*T\gg t^\* the second term dominates: Cincap(T)=Θ(T)C_{\text{in}}^{\text{cap}}(T) = \Theta(T)linear vs quadratic. (2)
  • Correct piecewise expression (2); correct t\*t^\* (1).

(c) Geometric distribution with success prob pp: E[T]=1p,Pr(T>B)=(1p)B.\mathbb{E}[T] = \frac{1}{p}, \qquad \Pr(T > B) = (1-p)^B.

  • E[T]=1/p\mathbb{E}[T]=1/p (2), tail (1p)B(1-p)^B (2).
  • Expected cost: since Cin(T)=TL0+sT(T+1)/2C_{\text{in}}(T)=TL_0 + s\,T(T+1)/2, and for geometric E[T]=1/p\mathbb{E}[T]=1/p, E[T2]=(2p)/p2\mathbb{E}[T^2]=(2-p)/p^2: E[Cin]=L0E[T]+s2(E[T2]+E[T])=L0p+s2(2pp2+1p)=L0p+s(3p)2p2.\mathbb{E}[C_{\text{in}}] = L_0\mathbb{E}[T] + \frac{s}{2}(\mathbb{E}[T^2]+\mathbb{E}[T]) = \frac{L_0}{p} + \frac{s}{2}\left(\frac{2-p}{p^2}+\frac1p\right)=\frac{L_0}{p}+\frac{s(3-p)}{2p^2}.
  • Correct use of E[T2]\mathbb{E}[T^2] (2).

(d) Unbounded loops → quadratic cost blow-up (from a & c) and non-termination when pp small (tail decays only geometrically). Guardrails:

  • Token/step budget cap (hard max iterations or token ceiling) — bounds cost & guarantees termination. (2)
  • Action-level safety guardrail (allowlist of tools / sandbox / human-in-loop confirmation for destructive actions) — bounds impact of a wrong action. (2) These address distinct failure modes: runaway resource use vs. harmful side effects.

Question 2

(a) BNF (integers 1..50 enumerable, so range is CFG-expressible by enumeration):

<call>   ::= '{"name":"search","arguments":{"query":' <str> ',"limit":' <int> '}}'
<str>    ::= '"' <chars> '"'
<chars>  ::= '' | <char> <chars>
<char>   ::= any escaped JSON char
<int>    ::= <1-9> | <1-9><0-9> | ...   ; enumerate 1..50 explicitly
  • Structure fixed by literal tokens (2); integer range via enumeration of 1..50 (a finite regular set, hence CFG) (2).
  • The constraint a pure CFG cannot express: string content restrictions that require matching/counting or semantic validity — e.g. that query be non-empty and balanced-escaped is fine, but cross-field constraints or "limit must not exceed number of available results" require a validator. Accept: "arbitrary length numeric ranges with unbounded digits" or "cross-field/context-sensitive constraints." (2)

(b) Conditioning: By definition of conditional probability with event E={tokenA}E=\{\text{token}\in A\}, p(vE)=p(vE)p(E)=p(v)1[vA]uAp(u),p(v\mid E) = \frac{p(v \cap E)}{p(E)} = \frac{p(v)\mathbb{1}[v\in A]}{\sum_{u\in A}p(u)}, since p(vE)=p(v)p(v\cap E)=p(v) if vAv\in A else 00, and p(E)=uAp(u)p(E)=\sum_{u\in A}p(u). This is exactly p(v)p'(v). (3)

Sequence mass: For a full valid sequence y=(y1,,yn)y=(y_1,\dots,y_n) where every yiy_i is allowed given prefix, unconstrained prob P(y)=ip(yiy<i)P(y)=\prod_i p(y_i\mid y_{<i}); constrained P(y)=ip(yiy<i)ZiP'(y)=\prod_i \frac{p(y_i\mid y_{<i})}{Z_i} where Zi=uAip(uy<i)1Z_i=\sum_{u\in A_i}p(u\mid y_{<i})\le 1. Hence P(y)P(y)=i1Zi1.\frac{P'(y)}{P(y)} = \prod_i \frac{1}{Z_i} \ge 1. Wait — this increases the valid sequence's probability (mass redistributed toward allowed tokens). Correct statement: masking never decreases the probability of a valid sequence; the mass removed from invalid tokens is redistributed to valid ones. Award marks for correct derivation of the ratio 1/Zi1\prod 1/Z_i \ge 1 and the conclusion. (4)

  • Equality (Zi=1 iZ_i=1\ \forall i) holds iff the unconstrained model already placed zero probability on disallowed tokens at every step. (1)

(Grading note: full marks for the derivation showing valid sequences are up-weighted with the correct equality condition; the direction of the inequality is the key insight.)

(c)

import numpy as np
def mask_logits(logits, allowed_ids):
    masked = np.full_like(logits, -np.inf)
    masked[allowed_ids] = logits[allowed_ids]
    m = np.max(masked[np.isfinite(masked)])      # stable shift
    exp = np.exp(masked - m)                       # disallowed -> exp(-inf)=0
    return exp / exp.sum()
  • Correct masking + returns normalized distribution (3).
  • Why -\infty before softmax: (i) guarantees masked tokens get exactly zero probability after normalization with no floating residue; (ii) zeroing after softmax requires re-normalizing (extra pass) and can leave tiny numerical mass; (iii) the pre-softmax shift by max keeps exponents bounded — zeroing post-hoc still computed unstable exponents for huge disallowed logits first. (2)

(d) Example: schema-valid call {"name":"delete_file","arguments":{"path":"/"}} — syntactically perfect but semantically catastrophic; or search(query="", limit=50) valid grammar but empty query. Complementary layer: runtime validator / policy guardrail (semantic argument validation + action allowlist / dry-run). (3)


Question 3

(a) Critical path = longest-weight path from any source to any sink in the DAG; its length lower-bounds makespan because those tasks are strictly sequential (each depends on the previous), so no scheduling can overlap them → makespan \ge CP. With unlimited workers, list-scheduling by "start each task as soon as predecessors finish" achieves start time = longest path into the node, so finish = CP length → makespan == CP. (3)

For the DAG (unit costs), longest paths: A ⁣ ⁣B ⁣ ⁣D ⁣ ⁣F=4A\!\to\!B\!\to\!D\!\to\!F = 4; A ⁣ ⁣C ⁣ ⁣D ⁣ ⁣F=4A\!\to\!C\!\to\!D\!\to\!F = 4; A ⁣ ⁣C ⁣ ⁣E ⁣ ⁣F=4A\!\to\!C\!\to\!E\!\to\!F=4. Critical-path length = 4. (2)

Level schedule (ASAP): L0={A}, L1={B,C}, L2={D,E}, L3={F}. Max width = 2 → 2 workers suffice to hit makespan 4. (2)

(b) Let success indicator S{0,1}S\in\{0,1\} over tasks, mean success rate Sˉ\bar S; token cost cc. Report them separately, then a combined score e.g. Score=Sˉeλcˉ,orSˉλcˉ.\text{Score} = \bar S \cdot e^{-\lambda \bar c},\quad \text{or}\quad \bar S - \lambda\,\bar c.

  • Separated metrics (2); sensible combined scalar (2).
  • Naive Sˉ/cˉ\bar S/\bar c is gamed by an agent that fails cheaply: return trivial near-zero-cost answers → tiny denominator inflates ratio even at low success. Corrective term: gate on a minimum success threshold, or use Sˉλcˉ\bar S - \lambda \bar c (additive penalty, so failing cheaply gives low score) rather than a ratio. (2)

(c) Proposal wrong with prob ee. Critic independently catches a wrong proposal with prob (1q)(1-q) (misses with prob qq). A wrong proposal survives only if critic misses: efinal=eq<e(q<1).e_{\text{final}} = e\cdot q < e \quad (q<1).

  • Derivation efinal=eqe_{final}=eq (3).
  • If the critic also rejects correct proposals (false positives), or catches wrong ones (add: net improvement requires false-positive rate low). Key assumption for usefulness: critic errors must be (approximately) independent of proposer errors — if both share the same blind spot (q1q\to 1 exactly on the cases the proposer gets wrong), the critic catches nothing and efinal=ee_{final}=e. (2)

[
  {"claim":"E[C_in] for geometric T equals L0/p + s(3-p)/(2 p^2)",
   "code":"p,L0,s=symbols('p L0 s',positive=True); T=symbols('T'); ET=1/p; ET2=(2-p)/p**2; expr=L0*ET + Rational(1,2)*s*(ET2+ET); result=simplify(expr-(L0/p + s*(3-p)/(2*p**2)))==0"},
  {"claim":"Uncapped input cost sum_{t=1}^{T}(L0+ t s)=T*L0 + s*T*(T+1)/2",
   "code":"t,T,L0,s=symbols('t T L0 s'); S=summation(L0+t*s,(t,1,T)); result=simplify(S-(T*L0+s*T*(T+1)/2))==0"},
  {"claim":"Critical path length of the DAG is 4",
   "code":"import networkx as nx; G=nx.DiGraph(); G.add_edges_from([('A','B'),('A','C'),('B','D'),('C','D'),('C','E'),('D','F'),('E','F')]); L=nx.dag_longest_path_length(G)+1; result=(L==4)"},
  {"claim":"Critic reduces error: e*q < e for q<1, e>0",
   "code":"e,q=symbols('e q',positive=True); result=simplify((e - e*q))==simplify(e*(1-q)) and bool((e*(1-q)>0).subs({e:Rational(1,2),q:Rational(1,3)}))"}
]