Skip to main content

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​

  1. Separation of Concerns: Each agent has focused expertise
  2. Composability: Agents can be combined for complex tasks
  3. Scalability: Add new agents without changing existing ones
  4. Flexibility: Multiple workflow patterns (sequential, parallel, consensus)
  5. Standards-Based: Uses industry protocols (MCP, A2A)
  6. 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>
);
}

References​