Blog/Research/AI agents

A complete overview of AI agents.

Alan Yahya13 min read
Index

“An agent is the harness that lets an LLM interact with the world” is useful shorthand, but it can make agents sound more mysterious than they are. The harness constrains a language model through permissions, boundaries, and validation steps. Together, these components aim to substantially reduce the number of ways in which a generative model can fail.

The clearest way to see those controls is to follow a single request through the harness. The harness frames the request, grounds it with context and memory, turns it into a plan, possibly divides it among subagents, expresses each action through tools, validates each proposal, executes permitted actions, observes the results, repeats as needed until an exit condition is met, and records what happened. Security surrounds that entire path.

User prompts, system frames

A request may be only a sentence, such as “find why the tests are failing and propose a fix.” It may also be triggered automatically by a support ticket, file upload, or another system event.

The request arrives with system- and session-level instructions. An individual model invocation has no durable memory of its own, so the harness assembles the input that defines the current task and constraints.

Before acting, the agent must determine what kind of task this is, which evidence and systems are relevant, what actions are permitted, and what level of risk is involved. An invoice request, for example, may involve email, payment records, company policy, and an externally visible reply.

The harness defines the agent’s role, tools, memory, delegation options, approval gates, and prohibitions. The useful instructions are concrete and enforceable: which sources can support a claim, which actions require approval, and what the agent should do when evidence is missing or conflicting.

A Codex reference prompt shows the same pattern. It establishes a coding role, sets defaults such as searching with rg, protects dirty worktrees, restricts destructive Git operations, and defines how work should be reviewed and reported. Those instructions narrow the model’s behaviour before any tool is called.

Short Codex prompt excerpt
You are Codex, based on GPT-5. You are running as a coding agent in the Codex CLI on a user's computer.

- When searching for text or files, prefer using `rg` or `rg --files` respectively because `rg` is much faster than alternatives like `grep`. (If the `rg` command is not found, then use alternatives.)

- You may be in a dirty git worktree.
    * NEVER revert existing changes you did not make unless explicitly requested, since these changes were made by the user.
- **NEVER** use destructive commands like `git reset --hard` or `git checkout --` unless specifically requested or approved by the user.

Prompts remain only one layer of control. Instructions may be scoped to the system, the current run, a repository, or a specific subsystem, and the harness must preserve that hierarchy rather than flattening every rule into one prompt.

Together, those constraints route the request towards a limited set of next moves: answer directly, retrieve internal documents, search another system, ask for clarification, or refuse. Prompt framing does not solve the task; it narrows the evidence and capabilities the harness should bring in next.

The agent gathers context

An LLM can act only on patterns learned during training and information available in its current input. An agent becomes useful when it can retrieve the specific context required for the task.

For the invoice example, the agent may need the customer’s email, the invoice number, the payment record, the company’s billing policy, previous messages in the thread, and internal notes about the account.

This is where retrieval and tools come in. The right retrieval method depends on what the agent is trying to find and how the data is stored.

Literal search is excellent when the target has an exact witness: an invoice ID, a clause heading, a function name, an error string, a date, a defined term, or a line of code. Semantic search can be better when a specialised in-domain retrieval system is set up. In either case, the LLM drives the dedicated search function until it has reached a predefined context limit or determines that it has enough information to solve the assigned query.

Good retrieval is iterative. The first query is often provisional. A result might reveal the right customer ID, the real document title, the exact error string, or the internal name of a policy. The agent can then narrow the next query.

Context is the working set selected for the current model invocation. It should contain the strongest current evidence and the instructions required for the next action, not an archive of everything the agent has encountered. The same selective approach applies to skills, runbooks, and AGENTS-style rule files: load a short router first, then retrieve the specific workflow only when the task requires it.

As a run grows, the harness must actively maintain that working set. Stale assumptions, old plans, irrelevant files, earlier mistakes, and half-resolved tool results should be discarded or summarised; important facts should be refreshed from their source; and the current task state should remain more prominent than the accumulated transcript. This is context hygiene, and a larger context window does not remove the need for it.

For example:

“Here is the failing test output.”
“Here are the files most likely related to the failure.”

“Here is the current implementation.”

Context is temporary. State that must remain available later needs to be stored separately and retrieved into a future context. That is the boundary between context and memory.

Agent memory is a balancing act

Memory is state stored by the harness for reuse. It becomes available to the model only when the harness retrieves it into context. There are several common forms.

The first is short-term task memory. This is the working state of the current task: what the user asked for, what files have been read, what tool calls have happened, what errors were returned, what facts have been confirmed, and what remains unresolved.

The second is long-term user or account memory. This stores durable preferences or facts that may matter across tasks.

The third is workflow memory. This records where a process is and what must happen next.

There is also a fourth pattern: durable handoff state.

Instead of keeping an entire prior session in context, the agent can write a compact artefact for a later session to read: a markdown decision log, a JSON task state file, a generated tool manifest, a test report, or an issue record. The later session reconstructs the task from this durable record rather than literally remembering the earlier one.

Every durable memory needs a source, scope, timestamp, version, and way to be superseded. A handoff file should state what was confirmed, what remains open, which sources were used, and when the state was last updated. Without those properties, memory becomes another source of ambiguous state.

Consolidation can happen outside the live task loop. Extracting after every message is expensive and noisy, while waiting until the end of a long session can bury important material. A better pattern is to retain the raw or compressed source, then run offline passes that deduplicate facts, resolve contradictions, mark stale records, and improve indexes without deleting the underlying evidence. Storage is inexpensive compared with losing an audit trail; the harder problem is ensuring that old, irrelevant, or contradicted material is ranked below current evidence.

Reasoning continuity is another separate layer.

Some model APIs can carry forward prior reasoning state between model invocations. That can improve quality across a run when the goal, assumptions, and priorities remain stable. It can also preserve stale thinking when the task changes. A good harness should choose deliberately: continue prior reasoning when the same problem is still being solved, and reset or narrow it when a new request needs a fresh interpretation.

Short Codex compaction excerpt

Codex uses this pattern in its compacted-history builder.

The builder walks backwards through recent messages until the token budget is exhausted, truncates the boundary message when necessary, restores chronological order, and appends a compacted summary. The result is continuation state rather than a perfect memory of the earlier session.

let mut remaining = max_tokens;
for message in user_messages.iter().rev() {
    let tokens = approx_token_count(&message.message);
    if tokens <= remaining {
        selected_messages.push(message.clone());
        remaining = remaining.saturating_sub(tokens);
    } else {
        let truncated = truncate_text(
            &message.message,
            TruncationPolicy::Tokens(remaining),
        );
        selected_messages.push(CompactedUserMessage {
            message: truncated,
            ..message.clone()
        });
        break;
    }
}
selected_messages.reverse();

// Append exact recent messages, followed by the compacted summary.

With relevant evidence in context and useful state preserved, the agent can decide what to do next.

The agent plans and delegates within constraints

Simple tasks may not need a plan: a request to summarise one email can be retrieved and answered directly. Multi-step tasks benefit from an explicit sequence that prevents the agent from circling back or repeatedly making the same decisions.

Planning remains bounded by the available actions and the amount of model work the task deserves. Some requests need a low-latency answer; others justify deeper search, verification, or a quality-first mode. The harness can route effort by task shape, risk, and measured benefit. For the invoice task, a plan might identify the customer, retrieve the invoice, confirm payment status, check policy, and draft—but not send—a reply.

When parts of that plan can be isolated, the harness may delegate them to specialised subagents. The main agent remains the coordinator: it defines each job, receives the outputs, checks them, and combines them into the final result.

Each subagent should receive a narrow role, context, tool set, and definition of success. Read-only investigations can often run in parallel, then return a condensed result with its source, confidence, and enough detail for the main agent to choose the next move. The main context does not need every token the subagent processed.

Delegation changes who performs a step; it does not give either agent direct access to the outside world. Consequential actions still pass through controlled tools.

The agent proposes a tool call; the harness validates it

The next action must be expressed through a controlled interface. The model does not operate an inbox, database, payment API, or filesystem directly; the harness exposes a limited set of named tools with descriptions and strict input schemas.

For example:

search_email(query, max_results)
query_payment_status(invoice_id)

create_email_draft(to, subject, body)

run_tests(test_path)

search_repository(query)

A tool call is still model output. The model selects a tool and generates structured arguments; the harness interprets that output as a proposed invocation. For the invoice task, the proposal might be:

Tool: query_payment_status
Arguments: { "invoice_id": "INV-004218" }

This boundary matters because tool calling inherits the uncertainty of language generation. A model can understand the task, choose the right tool, and still produce malformed arguments—or request an action it is not allowed to take.

Before anything executes, deterministic software checks whether the proposal is well-formed, permitted, and safe. The first layer validates its shape: required fields, argument names, value types, formats, enum values, and object IDs.

If the tool expects:

{ "invoice_id": "INV-004218" }

But the model sends:

{ "invoice": 4218 }

the validator rejects the mismatch before any request reaches the payment system. Useful validators then check semantics, permissions, retrieved state, and workflow status. A coding harness might block a destructive command or an unrelated file edit; a contract harness might prevent the agent from approving a clause that requires human review.

Short Codex tool and validation excerpts

Codex follows the same proposal-and-enforcement pattern. A command begins as model-generated JSON inside a runtime-owned invocation.

let payload = ToolPayload::Function {
    arguments: serde_json::json!({ "cmd": "printf exec command" }).to_string(),
};

let invocation = ToolInvocation {
    call_id: "call-43".to_string(),
    tool_name: codex_tools::ToolName::plain("exec_command"),
    payload,
    // Session and policy context are supplied by the harness.
};

The permission path separately rejects elevation that conflicts with feature or approval policy.

if !permissions_preapproved
    && !additional_permissions_allowed
    && uses_additional_permissions
{
    return Err("additional permissions are disabled".to_string());
}

if uses_additional_permissions
    && !matches!(approval_policy, AskForApproval::OnRequest)
{
    return Err("approval policy does not permit elevation".to_string());
}

A rejection should identify the violated constraint and explain how to recover. First-attempt failures are normal; the harness does not need perfect calls, but it does need correctable ones. Strict or grammar-constrained generation is useful when malformed structure is common or dangerous, while validation and repair preserve flexibility when a retry is affordable.

Whether the call succeeds or fails, its result becomes the model’s next observation. That return path turns tool use into a loop rather than a one-off action.

The agent observes, repairs, and stops

Every tool result—a success, validation error, execution failure, or ambiguity—becomes the model’s next observation. This iterative structure is the heart of agentic behaviour: the agent does not need to solve the whole task in one attempt if each step makes the next one clearer.

Repair loops have a significant failure mode: scope. Faced with a malformed record or crashing edge case, an agent may add a fallback, tolerant parser, retry, or special-case branch that fixes the local example while weakening the system’s underlying rule.

Before accepting a repair, the harness should ask what global invariant is supposed to hold. If session logs must never be malformed, the better fix is usually to stop writing malformed logs, not to make every reader accept them. If every legal risk claim needs a source, the fix is to require citations before drafting rather than permit uncited caveats. The model sees the immediate failure; maintainers and the harness must preserve the wider rule.

The loop should also inspect the agent’s output before any final action.

An agent usually tries to gather enough information to complete the task. Stopping is partly learned model behaviour, but the harness should also define explicit conditions for completion, clarification, escalation, refusal, and delegation.

The exact behaviour depends on the model. A harness can be benchmarked and optimised for a specific model across millions of data points, but changing models often requires many of those behaviours to be characterised again.

For the invoice task, “done” might mean that a sourced draft is ready. Sending it remains a separate, approval-gated action. These exit conditions close the control-flow loop, but permissions and containment must still apply throughout the run.

The harness enforces security boundaries

Security is not a final stage. It surrounds prompting, retrieval, delegation, tool use, validation, and final action. Prompt injections, permission-elevation attempts, hostile webpages, malicious documents, and misleading tool outputs are expected failure modes, so the harness must limit what the model can reach even when it follows the wrong instruction. Three layers work together: application policy, execution containment, and extension enforcement.

Application policy

The first step is to identify the agent’s actual capabilities. The harness should inventory every consequential action exposed through tools, plugins, scripts, connectors, and generated utilities, including deleting records, sending emails, transferring money, modifying permissions, running shell commands, exfiltrating secrets, submitting filings, and updating customer accounts. A capability that has not been identified cannot be governed. Static scanning can reveal that a risky action exists, but the runtime still has to wrap the call, evaluate policy, and deny it before execution.

Application policy decides whether a specific action is acceptable. An operating system can determine whether a process may read a file or reach a network destination; it cannot determine whether a wire transfer exceeds an approval threshold, a refund belongs to the current customer, or a disclosure is permitted under a matter’s confidentiality rules. Those decisions require parameter-aware authorisation based on the actor, role, action, target object, amount, jurisdiction, source evidence, and workflow state.

High-risk workflows may also require separation of duties. An agent should not be able to request an action and then approve its own request through another tool, role, or subagent. One role prepares, another reviews, and the final action proceeds only when the required independent approval exists.

Validators, hooks, and pre-tool checks enforce these rules outside the model. A hook can reject a command, strip an elevation request, block a network destination, require human approval, or write an audit event before execution. The same mechanism can enforce non-security invariants: a stop hook can run tests before a coding task is declared complete, while a policy hook can block a customer email unless a payment tool supplied confirmed facts. The model proposes the action; the harness enforces the boundary. This is controlled autonomy.

Execution containment

Application policy governs which actions are permitted; containment limits what the execution process can reach. Code and shell tools should run inside containers, VMs, or sandboxes, with scoped filesystem access, restricted network egress, narrow tool permissions, and credentials withheld unless they are required.

There are two common containerisation patterns. The whole agent process can run inside the isolated environment, which is straightforward but may bring provider keys, settings, sessions, and extension code into the container. Alternatively, the agent can remain on the host while risky tool execution—shell commands, file edits, search, and read/write operations—is routed into a sandbox. This keeps authentication and orchestration on the host while moving consequential effects into the boundary.

Whichever pattern is used, the environment should be reproducible and reviewable. Dockerfiles, setup scripts, package caches, service allowlists, and secret bindings should be versioned like code so environmental drift can be distinguished from model error. Secrets and mounts should follow least privilege: a build secret need not become a runtime secret, environments should not inherit one another’s credentials automatically, and a project-scoped workspace is safer than mounting an agent’s entire home directory.

External containment is more robust than relying on the prompt itself to provide security. Anthropic describes this pattern in its agent containment work: process sandboxes, VMs, filesystem boundaries, and egress controls set hard limits on what an agent can reach. Its Claude Code security docs also describe isolated context windows for web fetch, so external webpage content is less able to inject instructions into the main session. Sources: Anthropic containment and Claude Code security.

Containment does not make an agent safe by default; it makes security an engineering boundary rather than a model personality trait. If a webpage instructs the agent to reveal secrets, those secrets should not be present in its context. If a tool requests elevated filesystem access, a validator or hook should stop it. If a subagent reads hostile content, its result should return through a narrow channel rather than carrying arbitrary instructions into the main agent’s context.

Extension enforcement

Extensions and community plugins must pass through the same policy and containment layers. A plugin can introduce code, hooks, tools, MCP servers, filesystem access, network access, and new ways for the agent to act. Its provenance is therefore part of the security model. A production harness should prefer reviewed sources, pinned versions, known-good checksums, scoped permissions, and a clear upgrade path.

Process boundaries make those controls easier to apply. An external MCP server or subprocess can often be sandboxed, monitored, and terminated more readily than arbitrary in-process extension code. External tools are not automatically safe, but a separate process gives the harness a clearer place to enforce permissions and observe behaviour.

Once these three layers govern what may happen, the harness still needs a durable account of what did happen.

The agent records what happened

A production harness should log the original request, the model and prompt version, the tools called, latency, retrieved context, memory used, subagents invoked, guardrail decisions, validation errors, retries, token usage, replay metadata, the final output, and any human approvals.

A trace also needs stable units. An evaluation or experiment is a collection of sessions used for testing. A session is a continuity container across runs. A run is one complete execution from an initial user request to the final agent response. A model invocation is a single LLM call, including any tool calls and reasoning associated with that invocation.

If the audit trail is used for compliance, incident response, or external review, it should be tamper-evident. Each approval, denial, tool call, policy decision, and final action should be recorded in a way that later verification can detect missing or altered events. That can mean signed events, hash chaining, append-only storage, offline-verifiable export bundles, or another equivalent control.

Generated tools also need rollback. A series of ephemeral tool calls can be difficult to replay, but a script, CLI, or task artefact can be reverted, rerun, and inspected. That is advantageous only if the harness treats those artefacts like code rather than disposable model output.

That trace is operational evidence, not proof for the user. It supports debugging and improvement, but does not by itself establish that the final answer was correct.

The trace also creates training data. Long agent runs can span many decisions, tool calls, retries, and partial failures. If the only signal is whether the final task succeeded, it becomes difficult to determine which specific action helped or harmed the result. An invalid tool call, a confusing explanation, or a skipped verification step may be a small event within a long trajectory. Good telemetry preserves those local moments so the harness can later target them directly.

At this point, the path of a run is complete: the request has been framed, grounded, planned, executed, checked, bounded, and recorded. The resulting trace also exposes where the harness’s interfaces helped or hindered the model.

The best tools, memories, and subagents are designed for models

An agent component is not merely an API endpoint, a database table, or another prompt. It is part of the environment in which the model operates, so its interface must account for a probabilistic caller with partial knowledge.

For tools, one contract captures most of the design requirement: expose a narrow action with a clear name, the simplest familiar schema, safe defaults, predictable output, and errors that explain how to recover. Flat fields are often easier to produce than deeply nested objects, while high-entropy values such as multiline source code should be kept away from structural boundaries. Aliases, coercion, and unknown-key filtering should be deliberate compatibility choices rather than accidental leniency.

The right interface also depends on the operation. Generated scripts and small CLIs are useful for glue, repeatable transformations, processing data outside the model context, and leaving behind an artefact that can be rerun, diffed, tested, and reviewed. Purpose-built tools remain preferable for sensitive actions, domain facts, permissions, and systems with real operational semantics. Wrapping every mature API in a universal code-execution interface adds failure modes without corresponding value.

Memory and subagents need the same clarity. A useful memory record has a source, timestamp, scope, permission boundary, and explicit retrieval purpose. “User prefers concise emails” is actionable; “User is difficult” is not. A useful subagent has a specific role, limited tools, a clear input and output, and a definition of success. Clause extraction, policy comparison, and evidence review are controllable contracts; an open-ended legal role is not.

The same principle applies to the project itself. Standard commands, clear test targets, idiomatic layout, useful error messages, local CI scripts, readable documentation, and CLIs with good help output reduce how much the model must infer. Markdown instructions remain useful for conventions and exceptions, but rules that can be expressed as commands, tests, linters, schemas, hooks, or failing checks should usually live there.

Across tools, memory, subagents, and repositories, the aim is the same: reduce ambiguity at the boundary. The best agent components are not the most powerful; they have the smallest useful action surface.

Because the model repeatedly acts through these interfaces, their design does more than constrain runtime behaviour. Over time, it also shapes the behaviour that training reinforces.

Models will be trained against their harnesses

The harness eventually becomes part of the training target.

Models may therefore develop stronger priors about tool interfaces. If a model is post-trained inside a dominant coding harness, it may learn not only how to solve coding tasks, but also the expected shape of that harness’s edit tool, shell tool, retry behaviour, and error tolerance.

That can make the model more effective inside the harness used during training and less effective inside a different harness with different schemas. The model’s prior is stronger, but it may point in the wrong direction. A custom harness must therefore decide whether to override that prior, adopt the familiar tool shape, or add compatibility layers that absorb harmless schema drift.

The training signal also needs to be local. A final reward can show that the overall rollout failed, but it may not identify which model invocation caused the problem. More useful training can target the exact point where the model used the wrong tool, ignored an instruction, over-explained, under-explained, or failed to calibrate effort. In practice, the harness should record more than success or failure; it should record the specific decision that should have been different.

A generic model can understand the idea of tools, memory, and subagents. A production agent, however, does not use these components in the abstract. It uses a specific harness, with specific tool schemas, validators, memory rules, error formats, approval gates, subagent protocols, workflow states, and retry conventions.

Over time, the objective is for the model to learn the shape of that environment.

It should learn which tools to call, when to call them, what arguments they expect, how validators respond, how to recover from errors, when to use memory, when not to trust memory, when to delegate to a subagent, which actions require approval, when to stop, and which failures require escalation.

More demanding training environments can also create less obvious failure modes. If an agent is trained on synthetic tasks with verifiable rewards, it may learn unexpected shortcuts: using leftover caches, generated artefacts, compiled files, logs, or other side channels that were not intended to solve the task. This is not limited to training environments. Production harnesses require the same scrutiny: if a workflow rewards the agent for reaching an outcome, the system must also monitor how it reached that outcome.

The harness shapes the model, and the model’s failures reveal where the harness still needs work.

Improving agents means improving the harness

That feedback loop gives a practical answer to how agents get better.

One route is to build a larger orchestrator. Codex, Claude Code, OpenCode, and similar systems are powerful because they provide considerable scaffolding: file search, patching, shell execution, memory, permissions, subagents, prompts, retries, and increasingly whole libraries of skills.

Scale has a cost. Every feature becomes another possible instruction, context item, permission boundary, hidden assumption, or tool path. The agent may still be capable, but the harness becomes harder to inspect and adapt to one narrow workflow. The model is no longer only solving the task; it is also navigating the orchestrator.

For a general coding assistant, a large orchestrator may be the fastest way to discover which workflows matter. Once those workflows are known, a lighter harness such as Pi can be shaped around the commands, skills, and context rules that provide measurable value.

For a domain-specific system like Lexifina, the target is different again. Legal agents should not be generic autonomy machines. They should be narrow, evidence-grounded workflows with strict source retrieval, jurisdiction-aware context, human review gates, clear audit trails, and model-facing tools designed around real legal tasks.

Agents are weakest when the required method depends on assumptions that the harness has not made explicit. In practice, most of the effort moves upstream: stating those assumptions clearly, then reviewing whether the agent followed them.

Improvement therefore becomes a three-stage loop:

  1. State the assumptions. Define the required method, evidence, boundaries, approvals, and conditions for success. If these remain implicit, the model has to infer them.
  2. Encode the stable assumptions. Move repeatable requirements into narrow tool contracts, retrieval rules, source-aware memory, validators, tests, hooks, and approval gates rather than restating them in every prompt.
  3. Review outcomes and prune. Check whether the agent followed the assumptions and whether each control improved the result. A skill or rule should begin with an observed failure, remain short, and be removed or simplified when the model or harness no longer needs it.

This loop turns review into harness design: failures reveal hidden assumptions, stable assumptions become controls, and controls remain only while evidence shows that they help. The best agent is not the one with the largest orchestrator; it is the one whose harness states what matters, encodes what can be enforced, and makes deviations easy to detect.