NotesAyomide Olajide
AI Engineering5 min read

Agentic AI: give the model tools, keep the system in control

A practical architecture for agents that plan, use tools, and recover from failure while the application owns permissions, state, and stopping rules.

Contain uncertainty inside clear edgesAuthorize and scope context before generation. Validate the output and obtain human approval before a deterministic save.01Authorize02Scope context03Model draft04Schema validation05Human approval06Save command
Authorize and scope context before generation. Validate the output and obtain human approval before a deterministic save.
Contain uncertainty inside clear edgesAuthorize and scope context before generation. Validate the output and obtain human approval before a deterministic save.01Authorize02Scope context03Model draft04Schema validation05Human approval06Save command
Authorize and scope context before generation. Validate the output and obtain human approval before a deterministic save.

What makes a workflow agentic?

In a fixed workflow, application code chooses the sequence of steps. In an agentic workflow, a model chooses some next steps using the information available to it: search, inspect a result, call another tool, or finish. That flexibility helps with tasks whose useful path cannot be fully specified in advance.

For an illustrative support assistant, the model might search documentation and inspect an authorized order record before drafting an answer. A fixed retrieval flow may be enough for common questions. Add a planning loop only when adapting the sequence earns its extra latency and operational complexity.

Build a bounded loop

Give each run a goal, a small tool set, a maximum number of steps, a time limit, and a cost budget. The model proposes a tool call; the application validates the arguments and checks permission before executing it. Return a structured result so the next step can distinguish a missing record from an unavailable service.

Persist a run ID and the state needed for recovery. A timeout should move the run into a visible interrupted state. If a tool writes data, attach an operation ID and design duplicate handling before allowing retries. A model deciding to try again is not evidence that a previous action failed.

Contain uncertainty inside clear edgesAuthorize and scope context before generation. Validate the output and obtain human approval before a deterministic save.01Authorize02Scope context03Model draft04Schema validation05Human approval06Save command
Authorize and scope context before generation. Validate the output and obtain human approval before a deterministic save.
Contain uncertainty inside clear edgesAuthorize and scope context before generation. Validate the output and obtain human approval before a deterministic save.01Authorize02Scope context03Model draft04Schema validation05Human approval06Save command
Authorize and scope context before generation. Validate the output and obtain human approval before a deterministic save.
// Illustrative orchestration; adapters are application-owned.
for (let step = 0; step < limits.maxSteps; step++) {
  assertRunBudget(run, limits)
  const action = await planner.next(run.context)
  if (action.type === "finish") return finish(run, action)
  const call = validateToolCall(action)
  await authorizeTool(user, call)
  if (requiresApproval(call)) return pauseForApproval(run, call)
  const result = await executeTool(call, { runId: run.id, step })
  await recordStep(run, call, result)
}
return pauseRun(run, "step-limit")

Treat retrieved content as data

A tool result or a document may contain instructions that conflict with the user’s goal. Keep that material separate from trusted application instructions. Narrow tool permissions and validate proposed actions even when the model sounds confident.

Read-only lookup and an irreversible action deserve different execution paths. For a support assistant, finding an order can be automatic within the user’s access rights, while issuing a refund can require an application policy check and explicit approval. Approval must authorize the exact action and parameters that will execute.

Evaluate trajectories as well as answers

A fluent answer can hide wasted calls, unauthorized searches, or a repeated write. Evaluate whether the run chose appropriate tools, stayed within scope, respected its budget, and stopped when it lacked evidence. Include injected instructions in retrieved documents, tool outages, ambiguous goals, and repeated results in your evaluation cases.

Start with one agent and a few tools. Add parallel workers only when independent subtasks have a clear merge strategy and the measured benefit outweighs coordination cost. Keep a trace of tool names, timings, policy outcomes, and state transitions so a failed run can be explained without exposing sensitive payloads.

A model is not a business rule

Language models are powerful at interpreting messy input and producing useful drafts. They are not a substitute for authorization, accounting invariants, or a database constraint. A production feature should make the uncertain part explicit and keep irreversible decisions behind deterministic checks.

Consider an assistant that proposes a performance review summary. The model may draft language, but the source records, access policy, approval state, and final save operation belong to the application. This is an illustrative design, not a claim about a shipped system.

Design the edges

At the input edge, limit context to data the current user can access and validate the request shape. At the output edge, parse structured fields, validate them against a schema, and show the user what will be saved. For actions, require a separate deterministic command after confirmation.

Log model version, prompt template version, latency, and validation failures without logging sensitive content by default. Make fallback behavior useful: a failed generation should not leave the user with a blank screen.

const draft = await generateObject({ model, schema: reviewDraftSchema, prompt: context })
const checked = reviewDraftSchema.safeParse(draft.object)
if (!checked.success) return { state: 'needs-review', reason: 'invalid-output' }
return { state: 'preview', draft: checked.data }

Evaluate the system, not only the prompt

Create a small evaluation set that represents real variation: short inputs, ambiguous requests, missing fields, and adversarial instructions. Score factuality, schema validity, refusal behavior, latency, and cost. Compare changes to the whole set rather than one impressive example.

The common mistake is adding a model call to a workflow without deciding who owns errors. AI features need product copy, observability, rate limits, and a human escape hatch.

Takeaway

Use models where ambiguity is valuable. Use deterministic code where correctness, authorization, and irreversible state matter. The strongest AI experiences combine both instead of pretending uncertainty has disappeared.

Further reading

Anthropic: Building effective AI agents