← System Design
AI Systems

Design an AI Agent Platform (Tool Use)

In simple words

An intern with a to-do list and a set of office tools. They think about the next step, use a tool (search, email, spreadsheet), read the result, and repeat until done, while a manager must sign off on anything risky like refunds, and a diary records every step.

Problem Statement

Design a platform for AI agents: an LLM that doesn't just answer but takes actions by calling tools (search the web, query a database, send an email, run code) in a loop until a task is done. Examples: coding agents, customer-support agents that can issue refunds, research assistants. The system must run long, multi-step tasks reliably and safely.

Functional Requirements

Non-Functional Requirements

Core Entities

API Design

POST /v1/runs { "goal": "Refund order 812 if it arrived damaged", "agent": "support" } -> { run_id }
GET  /v1/runs/{id}/events        (SSE: step started, tool called, approval needed, done)
POST /v1/runs/{id}/approvals/{step} { "decision": "approve" | "reject" }
POST /v1/runs/{id}/cancel

High-Level Design (HLD)

The agent loop as a durable workflowUserOrchestrator(durable steps)LLM gatewayTool executor(schema + auth)Human approvalTrace / step logTools: APIs,sandboxed codegoalnext step?tool callsensitive?every step
The orchestrator asks the model for the next step, runs tools safely, pauses for approval when needed, and logs everything.

Low-Level Design (LLD)

Data modelrunsrun_id PKuser_id, goalstatus, budgetsstepsrun_id PKindex PKtype, input, outputtokens, latencytoolsname PKjson_schemapermissionssensitive?1 → manycalls
The step log is what makes runs resumable, auditable and replayable for evaluation.

The agent loop

while run.steps < budget.max_steps and not run.done:
    reply = llm(messages + tool_schemas)                  # persisted as a step
    if reply.is_final_answer:
        finish(run, reply.text); break
    for call in reply.tool_calls:
        validate(call.args, tool.schema); authorize(run.user, tool)
        if tool.sensitive: wait_for_approval(run, call)   # run status = waiting
        result = execute(tool, call, idempotency_key=f"{run.id}:{step}")
        messages.append(tool_result(call.id, truncate(result)))   # persisted
if budget exceeded: fail(run, "step/token budget exhausted")

Reliability

Safety

Follow-up Questions

The agent gets stuck repeating the same tool call. How do you stop it?
Budgets (max steps, tokens, wall time), loop detection (same call with same arguments N times), and returning a clear error so the model changes approach; if still stuck, end the run and hand off to a human.
How do you evaluate agent quality?
Replay a suite of recorded tasks against new prompts/models and score outcomes (task success, cost, steps, policy violations); traces from production feed new test cases.
Why a durable workflow engine instead of a simple loop in a web request?
Runs outlive requests (minutes, waiting on humans), must survive deploys and crashes, and must not repeat side effects. A durable log of steps provides all three.

Related