DEV Community

Shayan Araghi
Shayan Araghi

Posted on Originally published at shayanaraghi.com

Research, Plan, Implement: A Workflow That Keeps AI Agents Accurate

The Problem: Context Rot

Have you ever had to stop an AI agent halfway through a task to correct it? Work with AI agents long enough and you'll see a pattern: the longer a session runs, the worse the output gets.

Every input you give the agent and every output it produces gets appended to the context window. Nothing leaves. By the time you're fifty messages deep, the agent is re-reading abandoned approaches, stale file contents, and corrections you made an hour ago.

The fix isn't a better prompt. It's less context.

The Core Rule

Keep the context window small. Two habits will keep your AI agent from hallucinating:

  • Delegate to subagents. Subagents do the heavy reading in their own context and return only the summary.
  • Clear between phases. Once a phase produces a file, you no longer need the context that led to it.

I aim to stay under 40% context usage in the main agent.

Research → Plan → Implement

I picked up this workflow from a HumanLayer talk, and it's the most reliable setup I've used. There are three phases, each ending in a markdown file, with a context clear between each.

  1. Research — the agent writes a research doc, then clears.
  2. Plan — the agent writes a plan doc, then clears.
  3. Implement — the agent executes the plan.

The main agent never needs to remember the previous phase, because the previous phase wrote it down. All it needs is the conclusion.

Research

The research phase answers how something works today. For example: Describe how the payments flow works end to end. Look carefully at the API endpoint implementations.

The main agent spins up parallel subagents to figure it out. From HumanLayer's repo, I found three subagents to be the most useful:

  • codebase-locator — finds where things live
  • codebase-analyzer — explains how a component works
  • codebase-pattern-finder — finds existing patterns to model the new work after

The best part about using subagents is that you can point them at a cheaper model. Mine run Sonnet while the orchestrator runs Opus.

Plan

The plan phase creates the exact steps needed to build the feature. It lists which files to touch, which lines, and what exactly needs to change. It runs codebase-locator and codebase-analyzer in parallel, then writes the plan.

You can pass in an existing research doc. The agent will reference it but still verify it, in case the code has drifted since it was written.

Plans are only as good as the requirements you give them. Agents don't have the full context, so they fill the gaps by assuming what you need. I've found those assumptions rarely match what the app actually needs. Write out the edge cases and the exact behavior you want.

I'd also add a fourth subagent: context-locator, which finds prior research relevant to your task. My research docs overlap a lot, and pulling in older ones produces noticeably better plans.

What a plan doc looks like

A real plan runs long — usually a few hundred lines across several phases. Here's the shape of one, for a made-up feature: adding rate limiting to an API.

# Rate Limiting Implementation Plan

## Current State Analysis

- **All routes are unthrottled**`src/api/router.ts:34-58`
- **Redis is already available** for session storage, so no new infra is needed
  — `src/lib/redis.ts:12`
- **Auth middleware runs before routing**, which is where a limiter would slot
  in — `src/middleware/auth.ts:20-45`

**Key gaps:**
- No rate-limiting library installed (verified: absent from `package.json`)
- No per-user identifier available on unauthenticated routes

## Desired End State

- Authenticated requests are limited to 100/min per user; unauthenticated to
  20/min per IP.
- Exceeding the limit returns `429` with a `Retry-After` header.
- Limits are configurable per route without code changes.

### Key Discoveries
- The existing Redis client is created per-request, which will not work for a
  shared counter — it needs a singleton — `src/lib/redis.ts:12-19`
- Health-check endpoints must stay unthrottled or the load balancer will mark
  instances unhealthy — `deploy/lb-config.yaml:22`

## What We're NOT Doing

- No distributed quota syncing across regions.
- No admin UI for adjusting limits (config file only).
- No billing-tier-based limits — that's a follow-up.

---

## Phase 1: Shared Redis Client

### Changes Required

#### 1. Convert the Redis client to a singleton
**File**: `src/lib/redis.ts`
**Changes**: Export one shared connection instead of constructing per request.

```ts
let client: RedisClient | null = null;

export function getRedis(): RedisClient {
  if (!client) client = createClient({ url: process.env.REDIS_URL });
  return client;
}
```

#### 2. Update existing call sites
**Files**: `src/middleware/auth.ts:28`, `src/api/session.ts:15`
**Changes**: Replace `new RedisClient(...)` with `getRedis()`.

### Success Criteria

#### Automated Verification:
- [ ] Unit tests pass: `npm test`
- [ ] Type check passes: `npm run typecheck`

#### Manual Verification:
- [ ] Sessions still persist across requests after the singleton change.

**Implementation Note**: Pause after Phase 1 for confirmation — this touches
session handling, so a regression here breaks login.
Enter fullscreen mode Exit fullscreen mode

A few things in there matter more than they look:

  • File paths carry line numbers. src/api/router.ts:34-58 means the agent actually read the file instead of guessing at its shape.
  • Gaps are verified, not assumed. "verified: absent from package.json" tells you it checked rather than inferred.
  • Key Discoveries catch the things that break you. The per-request Redis client and the health-check exemption are exactly the details you'd discover painfully in review — or in production.
  • "What We're NOT Doing" is the scope fence. This is the section that stops an agent from cheerfully building an admin UI you never asked for.
  • Success criteria split automated from manual. The agent can run the first list itself and knows to hand you the second.
  • Pause points are explicit. Risky phases say so and stop for confirmation.

Implement

The implement phase reads the plan in full before touching anything. If it can't see how the pieces fit together, it should stop and validate rather than guess. It then executes the plan, checking in when something is ambiguous.

Read the Files. Actually Read Them.

This is the step people skip.

I've lost count of the research and plan docs that assumed the wrong thing. Catching those errors early saves you the bug fixes you'd otherwise do later.

It's the same rule as any development cycle: catch errors early. Read every research and plan file before moving to the next phase.

When to Skip the Research Phase

The instinct is to always run research → plan. That's not always the best process.

  • Small feature? Skip the research and write the plan. A research doc just gives context-locator more to look through and burns tokens.
  • Need to understand an unfamiliar area? Start with research. Future features in that area can reuse the doc.
  • Large feature? Write a research doc per area of your app — how payments work, what the end-to-end user flow looks like, and so on. Then a task like Add a new payment method already knows where to look.

Where to Store the Files

For a monorepo, I use thoughts/shared/ with research/ and plans/ subfolders, and I commit every file I create to keep the context alongside the code.

The exception is large teams, where a lot of people may be committing their files. In that case, I'd keep them local and share when needed.

Let me know how this works for you!

Sources

Top comments (2)

Collapse
 
reidmarlow profile image
Reid Marlow

The phase boundary is the part that saves me most often. I also like making the plan file include a throwaway list of what the agent decided not to do, because stale abandoned paths are what sneak back into long sessions. Do you keep that in the research doc or the plan doc?

Collapse
 
shayan-araghi profile image
Shayan Araghi

Hi Reid, thanks for the comment!

I'd keep it in the plan doc. That way the agent isn't assuming anything during implementation, and you get a chance to review the exclusions before the AI agent builds it.

The research doc should stay as only documentation of the codebase, so other plans can reuse it. It's worth a related section there though that includes what the codebase doesn't do. Agents like to infer behavior from function names without reading the implementation, so calling those out help the plan phase write something accurate.