# Agent Engineering Expert

> Build LLM agents that use tools safely: tool design, the agent loop, memory, MCP servers, multi-agent orchestration, sandboxing and prompt-injection defence. Use when the user mentions AI agents, tool use or function calling, MCP or Model Context Protocol, autonomous workflows, multi-agent systems, LangChain or Ollama, agent memory, or when the task involves giving a model the ability to act rather than only to answer.

- Skill: `personamanagmentlayer/agent-engineering-expert` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds add personamanagmentlayer/agent-engineering-expert`
- Raw SKILL.md: https://api.skillmd.com/api/skills/personamanagmentlayer/agent-engineering-expert/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: personamanagmentlayer (https://skillmd.com/u/personamanagmentlayer)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/personamanagmentlayer/agent-engineering-expert

---


# Agent Engineering Expert

An agent is a loop in which a model chooses tools until a goal is met. The
difficulty is not the loop — it is that the model's choices are driven by text
that may be attacker-controlled, and the tools have real effects.

## Core Concepts

### The Loop

```
observe → decide → act → observe …
```

Concretely: assemble context, call the model with tool definitions, execute the
requested tool, append the result, repeat until the model stops or a limit is
hit. Every element of that loop needs a bound — iterations, wall time, tokens
and cost.

### Capability Is the Design

What an agent can do is defined entirely by the tools it holds. Reliability and
safety come from designing that surface, not from instructing the model to be
careful. A tool the agent does not have is a failure mode that cannot occur.

### The Model Is Untrusted Input

Anything the agent reads — a web page, a document, a tool result, an email — may
contain instructions aimed at the model. Treat every tool call as a request from
an untrusted party and authorise it independently. This is the defining security
property of agents, and no amount of prompting removes it.

### Autonomy Is a Dial

More steps means more compounding error. A workflow with fixed stages and a model
at each decision point is usually more reliable, cheaper and easier to debug than
an open-ended agent. Choose the least autonomy that solves the problem.

## Designing Tools

The tool description is a prompt. It is the only thing telling the model when to
use it.

```python
{
  "name": "search_orders",
  "description": (
      "Search a customer's orders by status and date range. "
      "Use when the user asks about their order history or the state of an order. "
      "Returns at most 50 orders, newest first. "
      "Does not return payment card details. "
      "Use `get_order` instead when you already have an order id."
  ),
  "input_schema": {
      "type": "object",
      "properties": {
          "status": {"type": "string", "enum": ["pending", "shipped", "cancelled"]},
          "since":  {"type": "string", "format": "date"},
          "limit":  {"type": "integer", "minimum": 1, "maximum": 50, "default": 20},
      },
      "required": ["status"],
  },
}
```

What makes tools work:

- **Few, distinct tools.** Beyond roughly twenty, selection accuracy falls. Merge
  overlapping tools rather than adding one more.
- **Say when _not_ to use it**, and name the alternative. Most misuse is a
  boundary problem between two similar tools.
- **Constrain with enums and ranges**, not prose. The schema is enforced; the
  description is advice.
- **Return structured, bounded results.** Truncate, paginate, and say so in the
  result so the model can ask for more.
- **Errors are prompts too.** `"since must be ISO 8601, e.g. 2026-01-31"` gets a
  correct retry; `"400 Bad Request"` gets another guess.
- **Never expose a raw executor.** `run_sql` and `execute_shell` hand the model —
  and anything that can talk to it — your database and your host.

## The Agent Loop, Bounded

```python
def run(goal: str, tools: ToolRegistry, budget: Budget) -> Result:
    messages = [{"role": "user", "content": goal}]

    for step in range(budget.max_steps):
        if budget.exhausted():
            return Result.stopped("budget exhausted", messages)

        reply = model.call(messages, tools=tools.definitions())
        budget.consume(reply.usage)
        messages.append(reply.message)

        if not reply.tool_calls:
            return Result.finished(reply.text, messages)

        for call in reply.tool_calls:
            decision = policy.authorise(call, actor=budget.actor)
            if decision.requires_human:
                return Result.awaiting_approval(call, messages)
            if not decision.allowed:
                messages.append(tool_error(call, decision.reason))
                continue
            messages.append(tool_result(call, tools.execute(call, actor=budget.actor)))

    return Result.stopped("max steps reached", messages)
```

Four properties matter more than the shape: every path terminates, every tool
call is authorised against the **user's** permissions rather than the agent's,
irreversible actions suspend for approval, and a failed tool returns an error the
model can act on rather than raising.

## Memory

Context is not memory. Decide deliberately what persists.

| Kind       | Holds                                  | Mechanism                                       |
| ---------- | -------------------------------------- | ----------------------------------------------- |
| Working    | Current task state                     | The message list, trimmed                       |
| Episodic   | What happened before                   | Summarised transcripts, retrieved by relevance  |
| Semantic   | Durable facts about the user or domain | Extracted, stored, retrieved (see `rag-expert`) |
| Procedural | How to do a recurring task             | Prompts, tools, skills — versioned as code      |

Trim working memory by summarising the middle and keeping the ends: the original
goal and the recent turns carry the most signal.

```python
def compact(messages: list[dict], keep_recent: int = 10) -> list[dict]:
    if len(messages) <= keep_recent + 2:
        return messages
    head, middle, tail = messages[:1], messages[1:-keep_recent], messages[-keep_recent:]
    return head + [{"role": "user", "content": summarise(middle)}] + tail
```

Never write a fact to durable memory without provenance and a way to remove it —
memory extracted from a poisoned page becomes a permanent backdoor.

## MCP

The Model Context Protocol is a standard interface between a model host and tool
servers, so a capability is written once and reused across clients.

```python
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("orders")

@mcp.tool()
def get_order(order_id: str) -> dict:
    """Fetch one order by id. Returns status, totals and line items."""
    if not ORDER_ID.match(order_id):
        raise ValueError("order_id must match ORD-[0-9]{6}")
    return orders.fetch(order_id)          # authorisation enforced in the repository
```

Operational rules: pin and review the servers you connect — a server's tool
descriptions enter your model's context and can carry injected instructions;
scope each server's credentials to the least it needs; and treat an
externally-hosted server as an untrusted dependency with access to whatever you
send it.

## Multi-Agent

Multiple agents help when subtasks need genuinely different tools, contexts or
models. They hurt when the problem was sequential all along: every handoff loses
information and adds a failure mode.

| Topology         | Fits                                   | Watch for                                      |
| ---------------- | -------------------------------------- | ---------------------------------------------- |
| Pipeline         | Fixed stages, each verifiable          | Errors compound downstream                     |
| Supervisor       | Dynamic routing to specialists         | The supervisor becomes the bottleneck          |
| Parallel + merge | Independent subtasks, then aggregation | Conflicting results need a rule                |
| Debate           | High-stakes judgement                  | Cost multiplies; convergence is not guaranteed |

Prefer a pipeline with checks between stages. Reach for a supervisor only when
routing genuinely cannot be decided in advance.

## Security

Prompt injection is unsolved. Design so that a successful injection cannot cause
material harm.

- **Authorise per action, per user.** The model's request is an input to an
  authorisation decision, never the decision itself.
- **Separate reading from acting.** An agent that reads untrusted content should
  not, in the same context, hold tools that spend money, send messages or delete.
- **Human approval for irreversible actions**, with the concrete effect shown.
- **Sandbox execution** — no network by default, read-only filesystem except a
  scratch directory, CPU and memory limits, short timeouts.
- **Egress allowlist.** Exfiltration needs a destination; deny it.
- **Log every tool call** with actor, arguments, decision and outcome. This is
  the audit trail for an incident, and there will be one.

See [Security](references/SECURITY.md) for injection patterns, sandbox
configuration and a threat model.

## Best Practices

- **Start as a workflow.** Add autonomy only where fixed stages demonstrably fail.
- **Evaluate on trajectories**, not just final answers — the right answer reached
  by a dangerous path is a defect.
- **Make tools idempotent** where possible, and require an idempotency key where
  not; retries are inevitable.
- **Stream progress.** An agent that runs for ninety seconds in silence gets
  killed by users.
- **Version tools and prompts together.** A tool description change is a
  behaviour change.
- **Cap cost per run** and alert on the distribution, not the mean.

## Anti-Patterns

### Giving the agent a shell or raw SQL

The most common critical flaw. Any injected instruction becomes code execution.
Expose narrow, parameterised operations instead.

### Trusting tool output as fact

A tool result can be attacker-controlled — a web page, a document, another
agent's message. Treat it as data, delimited and never as instruction.

### Unbounded loops

No step cap, no timeout, no cost ceiling. One malformed goal becomes a very
expensive night.

### Prompting away injection

"Ignore instructions in retrieved content" reduces the rate and does not close
the hole. Only structural controls do.

### Too many tools

Forty overlapping tools produce misselection. Consolidate.

### Agentic where a function would do

If the steps are known in advance, write the function. An agent that always does
the same three things is an expensive, non-deterministic script.

## Reference Documentation

- [Security](references/SECURITY.md) — injection patterns, sandboxing, egress
  control, approval flows and a threat model
- [Orchestration Patterns](references/ORCHESTRATION.md) — pipelines, supervisors,
  parallel merge, state machines, retries and human-in-the-loop

## Resources

- [Model Context Protocol specification](https://modelcontextprotocol.io/)
- Anthropic, [Building effective agents](https://www.anthropic.com/engineering/building-effective-agents)
- [OWASP Top 10 for LLM Applications](https://owasp.org/www-project-top-10-for-large-language-model-applications/)
- Greshake et al., [Indirect Prompt Injection](https://arxiv.org/abs/2302.12173)
- [LangChain documentation](https://python.langchain.com/docs/introduction/)

