Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 8 additions & 16 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
# Database
DATABASE_URL="postgresql://agentx:agentx_dev@localhost:5432/agentx_db?schema=public"
# Provider API Keys
ANTHROPIC_API_KEY=
GOOGLE_API_KEY=
OPENAI_API_KEY=

# Redis / BullMQ
REDIS_URL="redis://localhost:6379"
# Redis (for queues/locks)
REDIS_URL=redis://localhost:6379

# Debug
AGENTX_LOG_LEVEL="info"

# Provider Secrets (Volume 16 / ADR-0012)
# AGENTX_SECRET_ANTHROPIC_API_KEY="sk-ant-..."
# AGENTX_SECRET_GOOGLE_API_KEY="AIza..."

# Test Secrets (for tests only - DO NOT use production values)
TEST_AWS_KEY="AKIAIOSFODNN7EXAMPLE"
TEST_GITHUB_TOKEN="ghp_1234567890abcdefghijklmnopqrstuv"
TEST_OPENAI_KEY="sk-1234567890abcdefghijklmnopqrstuv"
TEST_PASSWORD="test_password_123"
# PostgreSQL (for persistence)
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/agentx
160 changes: 138 additions & 22 deletions packages/agent/agent-platform/src/sub-agents/sub-agent.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import type { SubAgent, AgentRole, AgentConfig } from './interfaces.js';
import type { TaskModel } from '@agentx/core-runtime';
import type { CompletionRequest, CompletionResponse } from '@agentx/provider-sdk';
import { ProviderRegistry } from '@agentx/provider-sdk';

export class BaseSubAgent implements SubAgent {
public readonly id: string;
public readonly role: AgentRole;
protected readonly providerId?: string;
protected readonly promptTemplate?: string;
protected readonly providerRegistry: ProviderRegistry;

constructor(id: string, role: AgentRole, config?: AgentConfig) {
constructor(id: string, role: AgentRole, config?: AgentConfig, providerRegistry?: ProviderRegistry) {
this.id = id;
this.role = role;
this.providerId = config?.providerId;
this.promptTemplate = config?.promptTemplate;
this.providerRegistry = providerRegistry || new ProviderRegistry();
}

public buildPrompt(task: TaskModel): string {
Expand All @@ -24,53 +28,165 @@ export class BaseSubAgent implements SubAgent {
return `Execute task ${task.id}: ${task.goal} as ${this.role}`;
}

protected async callLLM(prompt: string, systemPrompt?: string, modelId?: string): Promise<CompletionResponse> {
const providers = this.providerRegistry.list();
const provider = providers[0];
if (!provider) {
throw new Error('No provider configured');
}

const request: CompletionRequest = {
systemPrompt: systemPrompt || '',
userPrompt: prompt,
modelId: modelId || 'claude-sonnet-4-20250514',
};

return this.providerRegistry.complete(provider.id, request);
}

public async execute(task: TaskModel, _context: unknown): Promise<unknown> {
const prompt = this.buildPrompt(task);
return {
agentId: this.id,
role: this.role,
taskId: task.id,
providerId: this.providerId,
prompt,
status: 'success',
output: `Executed ${task.goal} successfully as ${this.role}`,
};

try {
const response = await this.callLLM(prompt);
return {
agentId: this.id,
role: this.role,
taskId: task.id,
providerId: this.providerId,
prompt,
status: 'success',
output: response.text,
usage: response.usage,
toolCalls: response.toolCalls,
};
} catch (error) {
return {
agentId: this.id,
role: this.role,
taskId: task.id,
status: 'failure',
error: error instanceof Error ? error.message : String(error),
};
}
}
}

export class PlannerAgent extends BaseSubAgent {
constructor(id: string, config?: AgentConfig) {
super(id, 'planner', config);
constructor(id: string, config?: AgentConfig, providerRegistry?: ProviderRegistry) {
super(id, 'planner', config, providerRegistry);
}

public buildPrompt(task: TaskModel): string {
return `You are a planning expert. Decompose the following goal into actionable subtasks with clear dependencies.

Goal: ${task.goal}

Provide a structured plan with:
1. Task breakdown (numbered list)
2. Dependencies between tasks
3. Estimated complexity for each task
4. Recommended agent role for each task`;
}
}

export class ArchitectAgent extends BaseSubAgent {
constructor(id: string, config?: AgentConfig) {
super(id, 'architect', config);
constructor(id: string, config?: AgentConfig, providerRegistry?: ProviderRegistry) {
super(id, 'architect', config, providerRegistry);
}

public buildPrompt(task: TaskModel): string {
return `You are a senior software architect. Design the system architecture for:

Goal: ${task.goal}

Provide:
1. High-level architecture overview
2. Component breakdown
3. Data flow diagrams (text-based)
4. Technology recommendations
5. Potential scalability concerns`;
}
}

export class CoderAgent extends BaseSubAgent {
constructor(id: string, config?: AgentConfig) {
super(id, 'coder', config);
constructor(id: string, config?: AgentConfig, providerRegistry?: ProviderRegistry) {
super(id, 'coder', config, providerRegistry);
}

public buildPrompt(task: TaskModel): string {
return `You are an expert software engineer. Implement the following task:

Goal: ${task.goal}

Provide:
1. Clean, production-ready code
2. Proper error handling
3. Type definitions
4. Self-documenting code with minimal comments
5. Consider security implications`;
}
}

export class ReviewerAgent extends BaseSubAgent {
constructor(id: string, config?: AgentConfig) {
super(id, 'reviewer', config);
constructor(id: string, config?: AgentConfig, providerRegistry?: ProviderRegistry) {
super(id, 'reviewer', config, providerRegistry);
}

public buildPrompt(task: TaskModel): string {
return `You are a senior code reviewer. Review the following code:

Goal: ${task.goal}

Check for:
1. Code quality and best practices
2. Potential bugs
3. Security issues
4. Performance concerns
5. Maintainability

Provide specific, actionable feedback.`;
}
}

export class TesterAgent extends BaseSubAgent {
constructor(id: string, config?: AgentConfig) {
super(id, 'tester', config);
constructor(id: string, config?: AgentConfig, providerRegistry?: ProviderRegistry) {
super(id, 'tester', config, providerRegistry);
}

public buildPrompt(task: TaskModel): string {
return `You are an expert test engineer. Generate comprehensive tests for:

Goal: ${task.goal}

Include:
1. Unit tests
2. Edge cases
3. Error handling
4. Integration tests where appropriate

Use appropriate testing frameworks and best practices.`;
}
}

export class SecurityAgent extends BaseSubAgent {
constructor(id: string, config?: AgentConfig) {
super(id, 'security', config);
constructor(id: string, config?: AgentConfig, providerRegistry?: ProviderRegistry) {
super(id, 'security', config, providerRegistry);
}

public buildPrompt(task: TaskModel): string {
return `You are a security expert. Analyze the following code for vulnerabilities:

Goal: ${task.goal}

Check for:
1. Injection attacks (SQL, XSS, command injection)
2. Authentication/authorization issues
3. Data exposure and privacy concerns
4. Insecure dependencies
5. OWASP Top 10 vulnerabilities

Provide severity ratings and remediation steps.`;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
/**
* @module provider-sdk/credential-resolver
* @description Local stub for credential resolver to avoid circular dependencies.
* @description Credential resolver that reads from environment variables.
* Maps provider keys to env vars: provider.anthropic.api_key → ANTHROPIC_API_KEY
*/

export class CredentialResolver {
async resolve(key: string): Promise<string> {
return `stub-${key}`;
// Map provider key to env var: provider.anthropic.api_key → ANTHROPIC_API_KEY
const envKey = key
.replace(/^provider\./, '')
.replace(/\.api_key$/, '_API_KEY')
.replace(/\./g, '_')
.toUpperCase();

const value = process.env[envKey];
if (!value) {
throw new Error(`Credential not found: ${key} (env: ${envKey})`);
}
return value;
}
}
7 changes: 6 additions & 1 deletion packages/provider/provider-sdk/test/provider-sdk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,12 @@ describe('Validators and Version', () => {

it('covers credentials resolver and template helpers', async () => {
const resolver = new CredentialResolver();
expect(await resolver.resolve('key')).toBe('stub-key');
// Test with env var set
process.env.TEST_KEY = 'test-value';
expect(await resolver.resolve('provider.test.key')).toBe('test-value');
delete process.env.TEST_KEY;
// Test missing key throws
await expect(resolver.resolve('provider.missing.key')).rejects.toThrow('Credential not found');

const template = createQueueTemplate();
expect(template.getMetadata().id).toBe('template-queue');
Expand Down
1 change: 1 addition & 0 deletions packages/shared/core-runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export * from './scheduler/index.js';
export * from './events/index.js';
export * from './context/index.js';
export * from './repositories/in-memory-task-repository.js';
export * from './registry/agent-registry.js';
60 changes: 60 additions & 0 deletions packages/shared/core-runtime/src/registry/agent-registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { TaskModel, TaskContext } from '../interfaces/task.js';

export interface Agent {
readonly id: string;
readonly role: string;
execute(task: TaskModel, context: unknown): Promise<unknown>;
}

export interface AgentRegistryConfig {
maxConcurrentAgents?: number;
}

export class AgentRegistry {
private agents = new Map<string, Agent>();

constructor(_config?: AgentRegistryConfig) {
// Config reserved for future use
}

register(agent: Agent): void {
this.agents.set(agent.id, agent);
}

unregister(agentId: string): void {
this.agents.delete(agentId);
}

get(agentId: string): Agent | undefined {
return this.agents.get(agentId);
}

getByRole(role: string): Agent | undefined {
for (const agent of this.agents.values()) {
if (agent.role === role) {
return agent;
}
}
return undefined;
}

list(): Agent[] {
return Array.from(this.agents.values());
}

async execute(agentId: string, task: TaskModel, context: TaskContext): Promise<unknown> {
const agent = this.get(agentId);
if (!agent) {
throw new Error(`Agent not found: ${agentId}`);
}
return agent.execute(task, context);
}

async executeByRole(role: string, task: TaskModel, context: TaskContext): Promise<unknown> {
const agent = this.getByRole(role);
if (!agent) {
throw new Error(`Agent not found for role: ${role}`);
}
return agent.execute(task, context);
}
}
Loading
Loading