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.