ADR-013: Implement Agentic Architecture
Date: 2025-10-06 Status: Accepted Deciders: Development Team Tags: architecture, agents, ai
Context​
The IDE must support a sophisticated agentic system with:
- Multiple specialized agents
- Sub-agent hierarchies
- Agent-to-agent collaboration
- Tool access via MCP
- Multi-agent workflows
- Human-in-the-loop capabilities
- Session-aware agent state
User Requirements:
- "it will also be agentic with agents and sub-agents"
- "follow the Anthropic methodologies for MCP tools and workflows"
- "Add the Google A2A standard"
Decision​
Implement a hierarchical agentic architecture using:
- MCP (Model Context Protocol) for tool/resource access
- A2A (Agent2Agent Protocol) for agent collaboration
- Multi-tier agent system with coordinators and specialists
- Session-aware agents integrated with multi-session architecture
Rationale​
Agent Hierarchy Design​
Coordinator Agent (Top Level)
├── Planning Agent
│ └── Task Decomposition Sub-Agent
├── Code Generation Agent
│ ├── TypeScript Sub-Agent
│ ├── Python Sub-Agent
│ └── Rust Sub-Agent
├── Code Review Agent
│ ├── Security Review Sub-Agent
│ ├── Performance Review Sub-Agent
│ └── Style Review Sub-Agent
├── Testing Agent
│ ├── Unit Test Sub-Agent
│ ├── Integration Test Sub-Agent
│ └── E2E Test Sub-Agent
└── Documentation Agent
├── Code Comment Sub-Agent
└── README Generator Sub-Agent
Why This Architecture​
- Separation of Concerns: Each agent has focused expertise
- Composability: Agents can be combined for complex tasks
- Scalability: Add new agents without changing existing ones
- Flexibility: Multiple workflow patterns (sequential, parallel, consensus)
- Standards-Based: Uses industry protocols (MCP, A2A)
- Session-Aware: Each session has independent agent context
Architecture​
Agent Implementations​
Base Agent Interface​
// src/agents/base-agent.ts
import { mcpClient } from '@/services/mcp-client';
import { ideAgentClient } from '@/services/a2a-client';
export interface AgentCapabilities {
name: string;
description: string;
skills: string[];
mcpTools: string[];
subAgents?: Agent[];
}
export abstract class Agent {
constructor(
protected capabilities: AgentCapabilities,
protected sessionId: string
) {}
abstract execute(task: Task): Promise<Result>;
// Use MCP for tools
protected async useTool(
server: string,
tool: string,
args: any
): Promise<any> {
return await mcpClient.callTool(server, tool, args);
}
// Use A2A for collaboration
protected async collaborate(
agentName: string,
request: AgentRequest
): Promise<AgentResponse> {
return await ideAgentClient.requestFromAgent(agentName, request);
}
// Delegate to sub-agent
protected async delegate(
subAgentName: string,
task: Task
): Promise<Result> {
const subAgent = this.capabilities.subAgents?.find(
a => a.capabilities.name === subAgentName
);
if (!subAgent) throw new Error(`Sub-agent ${subAgentName} not found`);
return await subAgent.execute(task);
}
}
Coordinator Agent​
// src/agents/coordinator-agent.ts
export class CoordinatorAgent extends Agent {
constructor(sessionId: string) {
super(
{
name: 'coordinator',
description: 'Top-level agent that coordinates workflows',
skills: ['planning', 'orchestration', 'decision-making'],
mcpTools: [],
subAgents: [
new PlanningAgent(sessionId),
new CodeGenerationAgent(sessionId),
new CodeReviewAgent(sessionId),
new TestingAgent(sessionId),
new DocumentationAgent(sessionId)
]
},
sessionId
);
}
async execute(task: Task): Promise<Result> {
// Step 1: Plan the task
const plan = await this.collaborate('planning-agent', {
action: 'plan',
payload: { task }
});
// Step 2: Execute plan steps
const results = [];
for (const step of plan.data.steps) {
const result = await this.executeStep(step);
results.push(result);
}
// Step 3: Synthesize results
return this.synthesizeResults(results);
}
private async executeStep(step: PlanStep): Promise<Result> {
const agentName = this.selectAgentForStep(step);
return await this.collaborate(agentName, {
action: step.action,
payload: step.payload
});
}
private selectAgentForStep(step: PlanStep): string {
const agentMap: Record<string, string> = {
'generate': 'code-generation-agent',
'review': 'code-review-agent',
'test': 'testing-agent',
'document': 'documentation-agent'
};
return agentMap[step.type] || 'code-generation-agent';
}
}
Code Generation Agent​
// src/agents/code-generation-agent.ts
export class CodeGenerationAgent extends Agent {
constructor(sessionId: string) {
super(
{
name: 'code-generation-agent',
description: 'Generates code in multiple languages',
skills: ['typescript', 'python', 'rust', 'code-generation'],
mcpTools: ['lmstudio_chat', 'lmstudio_list_models'],
subAgents: [
new TypeScriptSubAgent(sessionId),
new PythonSubAgent(sessionId),
new RustSubAgent(sessionId)
]
},
sessionId
);
}
async execute(task: Task): Promise<Result> {
const { language, prompt, context } = task;
// Select appropriate sub-agent
const subAgent = this.selectSubAgent(language);
// Get relevant context via MCP
const fileContext = await this.useTool('filesystem', 'read_file', {
path: context.filePath
});
// Delegate to sub-agent
const code = await this.delegate(subAgent, {
...task,
context: { ...context, fileContent: fileContext }
});
// Auto-review with code review agent
const review = await this.collaborate('code-review-agent', {
action: 'review',
payload: { code: code.data }
});
return {
code: code.data,
review: review.data,
language
};
}
private selectSubAgent(language: string): string {
const agentMap: Record<string, string> = {
typescript: 'typescript-sub-agent',
python: 'python-sub-agent',
rust: 'rust-sub-agent'
};
return agentMap[language] || 'typescript-sub-agent';
}
}
TypeScript Sub-Agent​
// src/agents/sub-agents/typescript-sub-agent.ts
export class TypeScriptSubAgent extends Agent {
constructor(sessionId: string) {
super(
{
name: 'typescript-sub-agent',
description: 'Specialized TypeScript code generation',
skills: ['typescript', 'react', 'node'],
mcpTools: ['lmstudio_chat']
},
sessionId
);
}
async execute(task: Task): Promise<Result> {
// Use LM Studio via MCP
const models = await this.useTool('lmstudio', 'lmstudio_list_models', {});
const tsModel = models.find((m: any) => m.id.includes('deepseek-coder')) || models[0];
const systemPrompt = `You are a TypeScript expert. Generate clean, type-safe TypeScript code following best practices.`;
const response = await this.useTool('lmstudio', 'lmstudio_chat', {
model: tsModel.id,
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: task.prompt }
],
temperature: 0.3,
max_tokens: 2000
});
return {
data: response.content[0].text,
metadata: {
model: tsModel.id,
language: 'typescript'
}
};
}
}
Code Review Agent​
// src/agents/code-review-agent.ts
export class CodeReviewAgent extends Agent {
constructor(sessionId: string) {
super(
{
name: 'code-review-agent',
description: 'Comprehensive code review',
skills: ['security', 'performance', 'best-practices'],
mcpTools: ['lmstudio_chat'],
subAgents: [
new SecurityReviewSubAgent(sessionId),
new PerformanceReviewSubAgent(sessionId),
new StyleReviewSubAgent(sessionId)
]
},
sessionId
);
}
async execute(task: Task): Promise<Result> {
const { code } = task;
// Parallel review by sub-agents
const [securityReview, perfReview, styleReview] = await Promise.all([
this.delegate('security-review-sub-agent', { ...task, focus: 'security' }),
this.delegate('performance-review-sub-agent', { ...task, focus: 'performance' }),
this.delegate('style-review-sub-agent', { ...task, focus: 'style' })
]);
// Synthesize reviews
return {
data: {
security: securityReview.data,
performance: perfReview.data,
style: styleReview.data,
overall: this.calculateOverallScore([
securityReview.data.score,
perfReview.data.score,
styleReview.data.score
])
}
};
}
private calculateOverallScore(scores: number[]): number {
return scores.reduce((a, b) => a + b, 0) / scores.length;
}
}
Workflow Patterns​
Sequential Workflow​
// src/workflows/sequential-workflow.ts
export class SequentialWorkflow {
async execute(sessionId: string, prompt: string): Promise<Result> {
const coordinator = new CoordinatorAgent(sessionId);
// Step 1: Generate code
const generated = await coordinator.collaborate('code-generation-agent', {
action: 'generate',
payload: { prompt }
});
// Step 2: Review code (uses output from step 1)
const reviewed = await coordinator.collaborate('code-review-agent', {
action: 'review',
payload: { code: generated.data.code }
});
// Step 3: Fix based on review
if (reviewed.data.overall < 0.8) {
const fixed = await coordinator.collaborate('code-generation-agent', {
action: 'fix',
payload: {
code: generated.data.code,
feedback: reviewed.data
}
});
return fixed;
}
return generated;
}
}
Parallel Workflow​
// src/workflows/parallel-workflow.ts
export class ParallelWorkflow {
async execute(sessionId: string, tasks: Task[]): Promise<Result[]> {
const coordinator = new CoordinatorAgent(sessionId);
// Execute all tasks in parallel
const results = await Promise.all(
tasks.map(task =>
coordinator.collaborate('code-generation-agent', {
action: 'generate',
payload: task
})
)
);
return results;
}
}
Consensus Workflow​
// src/workflows/consensus-workflow.ts
export class ConsensusWorkflow {
async execute(sessionId: string, prompt: string): Promise<Result> {
const coordinator = new CoordinatorAgent(sessionId);
// Get multiple implementations
const implementations = await Promise.all([
coordinator.collaborate('code-generation-agent', {
action: 'generate',
payload: { prompt, model: 'lmstudio' }
}),
coordinator.collaborate('code-generation-agent', {
action: 'generate',
payload: { prompt, model: 'claude' }
})
]);
// Synthesize best solution
const synthesized = await coordinator.collaborate('synthesis-agent', {
action: 'synthesize',
payload: { implementations }
});
return synthesized;
}
}
Session Integration​
Session-Aware Agents​
// src/agents/session-aware-coordinator.ts
export class SessionAwareCoordinator {
private agents: Map<string, CoordinatorAgent> = new Map();
getAgentForSession(sessionId: string): CoordinatorAgent {
if (!this.agents.has(sessionId)) {
this.agents.set(sessionId, new CoordinatorAgent(sessionId));
}
return this.agents.get(sessionId)!;
}
async executeInSession(
sessionId: string,
task: Task
): Promise<Result> {
const agent = this.getAgentForSession(sessionId);
return await agent.execute(task);
}
disposeSession(sessionId: string): void {
this.agents.delete(sessionId);
}
}
export const sessionCoordinator = new SessionAwareCoordinator();
Agent State Persistence​
// Save agent state to session
interface AgentState {
sessionId: string;
agentName: string;
context: any;
history: Message[];
timestamp: Date;
}
async function saveAgentState(state: AgentState): Promise<void> {
await fdbService.saveAgentState(state.sessionId, state);
}
async function loadAgentState(sessionId: string): Promise<AgentState | null> {
return await fdbService.loadAgentState(sessionId);
}
UI Integration​
// src/components/AgentPanel.tsx
export function AgentPanel() {
const session = useSession();
const [agentStatus, setAgentStatus] = useState<AgentStatus[]>([]);
const executeWorkflow = async (prompt: string) => {
const result = await sessionCoordinator.executeInSession(session.id, {
type: 'generate',
prompt,
context: { sessionId: session.id }
});
// Update UI with result
updateeditorWithCode(result.code);
};
return (
<VStack>
<Heading size="sm">Active Agents</Heading>
{agentStatus.map(agent => (
<AgentStatusCard key={agent.name} agent={agent} />
))}
<Input
placeholder="Describe what you want to build..."
onKeyPress={(e) => {
if (e.key === 'Enter') {
executeWorkflow(e.currentTarget.value);
}
}}
/>
</VStack>
);
}