---
title: Your First Agent
description: A step-by-step guide to building a complete agent.
sidebar:
  order: 5
---

import { CardGrid, LinkCard, Aside } from '@astrojs/starlight/components';

This guide walks through building a research assistant agent with memory, reasoning, and guardrails.

## The Builder Pattern

Every agent starts with `ReactiveAgents.create()`:

```typescript
import { ReactiveAgents } from "reactive-agents";

const agent = await ReactiveAgents.create()
  .withName("research-assistant")
  .withProvider("anthropic")
  .withModel("claude-sonnet-4-20250514")
  .build();
```

This creates a minimal agent with:
- LLM provider (Anthropic, Claude Sonnet 4)
- Direct LLM loop (no reasoning strategy, no memory, no tools)

## Adding Memory

Memory persists context across conversations:

```typescript
const agent = await ReactiveAgents.create()
  .withName("research-assistant")
  .withProvider("anthropic")
  .withModel("claude-sonnet-4-20250514")
  .withMemory()                          // tier: "standard" (default)
  .build();
```

The 4-layer memory system has two tiers:

| Tier         | Layers active                                      | When to use                                |
|--------------|----------------------------------------------------|--------------------------------------------|
| `"standard"` | Working + Episodic + FTS5 keyword search           | Conversational agents, default for most apps |
| `"enhanced"` | All 4 layers + vector embeddings (semantic recall) | Research agents, long-running tasks needing semantic similarity |

```typescript
.withMemory({ tier: "enhanced", dbPath: "./data/memory.db" })   // Full 4-layer
```

`"enhanced"` requires an embedding provider — set `EMBEDDING_PROVIDER=openai` or `EMBEDDING_PROVIDER=ollama` in `.env`.

## Adding Reasoning

The reasoning layer gives your agent structured thinking:

```typescript
const agent = await ReactiveAgents.create()
  .withName("research-assistant")
  .withProvider("anthropic")
  .withModel("claude-sonnet-4-20250514")
  .withMemory()
  .withReasoning()  // ReAct loop: Think -> Act -> Observe
  .build();
```

With reasoning enabled, the agent uses a ReAct loop instead of a simple LLM call. It can:
- Break tasks into steps
- Request tool calls
- Observe results and adjust

## Adding Safety

Guardrails protect against prompt injection, PII leakage, and toxic content:

```typescript
const agent = await ReactiveAgents.create()
  .withName("research-assistant")
  .withProvider("anthropic")
  .withModel("claude-sonnet-4-20250514")
  .withMemory()
  .withReasoning()
  .withGuardrails()       // Input/output safety
  .withCostTracking()     // Budget controls
  .build();
```

## Running the Agent

```typescript
const result = await agent.run("Explain the difference between TCP and UDP");

console.log(result.output);       // The agent's response
console.log(result.success);      // true
console.log(result.metadata);     // { duration, cost, tokensUsed, stepsCount }
```

## Using the Effect API

For advanced use cases, use the Effect-based API:

```typescript
import { Effect } from "effect";

const program = Effect.gen(function* () {
  const agent = yield* ReactiveAgents.create()
    .withName("research-assistant")
    .withProvider("anthropic")
    .withReasoning()
    .buildEffect();

  const result = yield* agent.runEffect("Explain quantum entanglement");
  return result;
});

const result = await Effect.runPromise(program);
```

## Lifecycle Hooks

Observe and modify agent behavior at any phase:

```typescript
const agent = await ReactiveAgents.create()
  .withName("research-assistant")
  .withProvider("anthropic")
  .withHook({
    phase: "think",
    timing: "after",
    handler: (ctx) => {
      console.log(`[think] Response: ${ctx.metadata.lastResponse}`);
      return Effect.succeed(ctx);
    },
  })
  .build();
```

Available phases: `bootstrap`, `guardrail`, `cost-route`, `strategy-select`, `think`, `act`, `observe`, `verify`, `memory-flush`, `cost-track`, `audit`, `complete`.

Each phase supports `before`, `after`, and `on-error` timing.

## Testing

Use `withTestScenario()` for deterministic tests:

```typescript title="src/agent.test.ts"
const agent = await ReactiveAgents.create()
  .withName("test-agent")
  .withTestScenario([
    { match: "capital of France", text: "Paris is the capital of France." },
    { match: "quantum", text: "Quantum mechanics describes nature at the atomic scale." },
  ])
  .build();

const result = await agent.run("What is the capital of France?");
expect(result.output).toContain("Paris");
```

## Where to next

<CardGrid>
  <LinkCard
    title="Common Builder Stacks"
    description="Copy-paste recipes for streaming, multi-agent, gateway, and Agent-as-data."
    href="/cookbook/builder-stacks/"
  />
  <LinkCard
    title="Choosing a Reasoning Strategy"
    description="ReAct vs Reflexion vs Plan-Execute vs ToT vs Adaptive — decision tree + perf characteristics."
    href="../choosing-strategies/"
  />
  <LinkCard
    title="Memory Guide"
    description="The 4-layer memory architecture: working, episodic, semantic, procedural."
    href="../memory/"
  />
  <LinkCard
    title="Local Models"
    description="Run on Ollama 4B+ with the same code — Healing Pipeline lifts accuracy +80pp."
    href="../local-models/"
  />
  <LinkCard
    title="Production Checklist"
    description="Everything to enable before deploying: budgets, kill switch, structured logs."
    href="../production-checklist/"
  />
  <LinkCard
    title="Architecture"
    description="The full layer system, 12-phase lifecycle, and the kernel structure."
    href="../../concepts/architecture/"
  />
</CardGrid>
