Context Engineering: File Structure as Your Secret Weapon in claude-agent-sdk

Programming· 4 min read

The Real Problem Nobody Mentions

When I started building agents with claude-agent-sdk, I made the mistake everyone makes: I threw all possible context at the agent and hoped Claude would "figure it out."

Result: slow agents, expensive (in tokens), and making erratic decisions.

The problem wasn't Claude. It was that I was feeding the agent like it was a garbage dump of information. No structure. No purpose.

Then it clicked: the architecture of your files and how you load them is what determines whether your agent is intelligent or just noisy.

Context Engineering: More Than Prompt Engineering

Prompt engineering is what everyone does. Context engineering is what works.

The difference:

  • **Prompt engineering**: "Give the agent the right instructions"
  • **Context engineering**: "Give the agent ONLY the information it needs, at the exact moment it needs it"

This changes everything.

In my current project with claude-agent-sdk, I structured files like this:

``` project/ ├── agents/ │ ├── analyzer.ts │ ├── decision-maker.ts │ └── executor.ts ├── context/ │ ├── rules/ │ │ ├── validation.txt │ │ ├── constraints.txt │ │ └── patterns.txt │ ├── data/ │ │ ├── user-profiles.json │ │ ├── historical-decisions.log │ │ └── edge-cases.md │ └── schemas/ │ ├── input.schema.json │ └── output.schema.json └── loaders/ ├── context-loader.ts └── selective-loader.ts ```

But structure alone isn't enough. How you load that information is what matters.

Bash Commands: Your Context Filter

This is where most people fail. They load entire files when they should load specific fragments.

Using `grep` and `tail`, you can be surgical:

Example 1: Load Only Relevant Rules

```bash

Instead of loading all of validation.txt

grep -A 3 "payment_validation" context/rules/validation.txt ```

This gives you only the payment validation rules, not the 200 lines of the entire file.

Example 2: Latest Relevant Decisions

```bash

Load only the last 5 similar decisions

tail -20 context/data/historical-decisions.log | grep "similar_case_type" ```

In your agent with claude-agent-sdk, this looks like:

```typescript import { Anthropic } from "@anthropic-ai/sdk"; import { execSync } from "child_process";

const client = new Anthropic();

async function loadRelevantContext(caseType: string) { // Load only relevant rules const rules = execSync( `grep -A 5 "${caseType}" context/rules/validation.txt` ).toString();

// Load similar historical decisions const history = execSync( `grep "${caseType}" context/data/historical-decisions.log | tail -3` ).toString();

return { rules, history }; }

async function runAgent(userInput: string, caseType: string) { const context = await loadRelevantContext(caseType);

const response = await client.messages.create({ model: "claude-3-5-sonnet-20241022", max_tokens: 1024, system: `You are a decision-making agent. Use ONLY this context:

Relevant Rules: ${context.rules}

Historical Decisions: ${context.history}

Make decisions based on these patterns. Be concise.`, messages: [ { role: "user", content: userInput, }, ], });

return response; } ```

Example 3: Load Schemas Dynamically

```bash

Get only the required fields from a schema

jq '.properties | keys' context/schemas/input.schema.json ```

Combined with claude-agent-sdk:

```typescript async function validateInput(input: any, schemaType: string) { const requiredFields = execSync( `jq '.properties | keys' context/schemas/${schemaType}.schema.json` ).toString();

const response = await client.messages.create({ model: "claude-3-5-sonnet-20241022", max_tokens: 512, system: `Validate this input against required fields: ${requiredFields}`, messages: [ { role: "user", content: `Input to validate: ${JSON.stringify(input)}`, }, ], });

return response; } ```

Why This Works

1. Fewer tokens = Faster: You don't load unnecessary context 2. Better decisions: The agent focuses on what's relevant, not noise 3. Scalable: You add more files without slowing the agent down 4. Maintainable: You change rules without touching agent code

The Pattern I Use

In every project I build with claude-agent-sdk, I follow this pattern:

```typescript class SelectiveContextLoader { private basePath: string;

constructor(basePath: string = "context") { this.basePath = basePath; }

loadByPattern(filePath: string, pattern: string): string { return execSync( `grep -i "${pattern}" ${this.basePath}/${filePath} | head -10` ).toString(); }

loadRecent(filePath: string, lines: number = 5): string { return execSync( `tail -${lines} ${this.basePath}/${filePath}` ).toString(); }

loadSchema(schemaName: string): object { const content = execSync( `cat ${this.basePath}/schemas/${schemaName}.schema.json` ).toString(); return JSON.parse(content); } }

// Usage const loader = new SelectiveContextLoader(); const paymentRules = loader.loadByPattern( "rules/validation.txt", "payment" ); const recentDecisions = loader.loadRecent( "data/historical-decisions.log", 3 ); ```

What I Learned (And You Should Too)

Working with claude-agent-sdk for several months, I discovered that:

  • **Structure matters more than content**: A well-organized file is 10x better than perfect content but messy
  • **Agents are lazy**: If they can ignore context, they will. Force relevance
  • **Bash is your friend**: Grep and tail let you do things most developers don't consider
  • **Context is dynamic**: What your agent needs today is different tomorrow

Takeaway

It's not about having better context. It's about having the right context.

Next time you build an agent with claude-agent-sdk, before writing the prompt, ask yourself:

  • What's the minimum information it needs?
  • How can I load it dynamically?
  • What can I filter with bash?

Do that and you'll see the difference immediately.

---

How do you structure context in your agents? Share in the comments.