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
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
## High Priority (Cognitive Modules)

- [x] Implement `KnowledgeDistiller` (The "Agent Dream" loop). (Done in `reference-implementation`)
- [ ] Bootstrap the reference agent: **Ami**.
- [x] Bootstrap the reference agent: **Ami**. (Done: `Ami` class + demo + capability-based architecture)

## Medium Priority

Expand Down
10 changes: 7 additions & 3 deletions packages/reference-implementation/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"test": "node --test __tests__/*.test.js"
"test": "node --test __tests__/*.test.js",
"demo": "tsx src/demo.ts",
"dev": "tsx --watch src/demo.ts"
},
"keywords": ["ami", "cognitive", "implementation"],
Comment thread
ManniTheRaccoon marked this conversation as resolved.
"author": "",
"license": "ISC",
"type": "commonjs",
"type": "module",
"dependencies": {
"@ami/skeleton": "workspace:*"
},
"devDependencies": {
"typescript": "^5.0.0"
"typescript": "^5.0.0",
"tsx": "^4.0.0",
"@types/node": "^20.0.0"
}
}
242 changes: 242 additions & 0 deletions packages/reference-implementation/src/ami.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
/**
* Ami β€” The reference agent implementation.
*
* Demonstrates the capability-based architecture (DEC-003) in action:
* - Uses CognitiveRegistry to discover available modules
* - Communicates only through CognitiveBus events
* - Gracefully degrades when capabilities are missing
*
* This is the "hello world" of AMI β€” a proof that "if it runs, I AM!"
*/

import type { CognitiveBus, CognitiveRegistry, CognitiveLoop, CognitiveModule, Message } from '@ami/skeleton';

export interface AmiConfig {
/** Maximum episode context for distillation */
maxEpisodeContext: number;
/** How often to trigger distillation (ms) */
distillationInterval: number;
/** Debug logging enabled */
debug: boolean;
}

/**
* Ami β€” The reference agent that demonstrates cognitive capabilities.
*
* Architecture:
* - Registry: Discovers available cognitive modules
* - Bus: Event-driven communication between modules
* - Loop: Simple OODA cycle (Observe β†’ Orient β†’ Decide β†’ Act)
*
* Capability graceful degradation:
* - No episodic memory? Processes inputs immediately
* - No distiller? Skips fact extraction
* - No semantic memory? Facts are discarded
* - No actuators? Runs in "silent mode"
*/
export class Ami implements CognitiveLoop {
private readonly registry: CognitiveRegistry;
private readonly bus: CognitiveBus;
private readonly config: AmiConfig;

private distillationTimer?: NodeJS.Timeout;
private isRunning = false;

constructor(
registry: CognitiveRegistry,
bus: CognitiveBus,
config: Partial<AmiConfig> = {}
) {
this.registry = registry;
this.bus = bus;
this.config = {
maxEpisodeContext: 50,
distillationInterval: 30000, // 30 seconds
debug: false,
...config
};

this.setupEventHandlers();
}

/**
* Start Ami's cognitive loop.
* Initializes all registered modules and begins periodic distillation.
*/
async start(moduleConfig: Record<string, unknown> = {}): Promise<void> {
if (this.isRunning) {
this.log('Already running');
return;
}

this.log('🧠 Ami awakening...');

// Initialize all registered modules
await this.registry.initAll(moduleConfig);

// Announce ourselves
this.bus.emit('module.ready', {
id: 'ami-core',
capabilities: ['processor'],
status: 'ready'
});

// Start periodic distillation if we have the capability
if (this.registry.hasCapability('processor')) {
this.startDistillationLoop();
}

this.isRunning = true;
this.log('βœ… Ami is alive and thinking');
}

/**
* Stop Ami gracefully.
*/
async stop(): Promise<void> {
if (!this.isRunning) return;

this.log('πŸ›‘ Ami shutting down...');

if (this.distillationTimer) {
clearInterval(this.distillationTimer);
}

await this.registry.destroyAll();
this.isRunning = false;

this.log('πŸ’€ Ami has stopped');
}

/**
* Single cognitive step β€” the core OODA loop.
* This can be called manually or triggered by events.
*/
async step(): Promise<void> {
if (!this.isRunning) return;

this.log('πŸ”„ Cognitive step...');

// OBSERVE: Get recent episodes if episodic memory is available
const episodes = await this.getRecentEpisodes();

// ORIENT: Process context (future: attention/relevance filtering)
const contextSize = episodes.length;

// DECIDE: Should we distill? (simple heuristic for now)
const shouldDistill = contextSize >= 5; // Arbitrary threshold

// ACT: Trigger distillation if needed
if (shouldDistill && this.registry.hasCapability('processor')) {
this.log(`πŸ“š Triggering distillation with ${contextSize} episodes`);
this.bus.emit('episodes.batch', episodes);
}
}

/**
* Process text input β€” the primary interface for interaction.
*/
async processInput(text: string, role: 'user' | 'assistant' | 'system' = 'user'): Promise<void> {
const message: Message = {
role,
content: text,
timestamp: Date.now(),
metadata: { source: 'ami-input' }
};

this.log(`πŸ“₯ Input: ${text.slice(0, 50)}...`);

// Emit as a perception event
this.bus.emit('perception.text', message);

// Store in episodic memory if available
const episodicModules = this.registry.getProviders('memory');
if (episodicModules.length > 0) {
// For simplicity, assume first memory module has episodic capability
// Real implementation would check module-specific capabilities
this.bus.emit('episode.store', message);
}
}

private setupEventHandlers(): void {
// Listen for facts created by distillation
this.bus.on('fact.created', (event) => {
const payload = event.payload as any;
Comment thread
ManniTheRaccoon marked this conversation as resolved.
this.log(`πŸ’‘ New fact discovered: ${payload.text}`);
});

// Listen for module status changes
this.bus.on('module.ready', (event) => {
const payload = event.payload as any;
this.log(`πŸ”§ Module ready: ${payload.id}`);
});

this.bus.on('module.degraded', (event) => {
const payload = event.payload as any;
this.log(`⚠️ Module degraded: ${payload.id}`);
});

// Handle text input events
this.bus.on('perception.text', async (event) => {
// This could trigger immediate processing or queue for later
await this.step();
Comment thread
ManniTheRaccoon marked this conversation as resolved.
});
}

private async getRecentEpisodes(): Promise<Message[]> {
// For this reference implementation, we'll simulate episodes
// Real implementation would query episodic memory modules

if (!this.registry.hasCapability('memory')) {
this.log('πŸ“­ No episodic memory available, returning empty episodes');
return [];
}

// Placeholder: In real implementation, would call episodic memory
// through the bus: bus.emit('episodic.query', { limit: this.config.maxEpisodeContext })
// For now, return empty array
return [];
}

private startDistillationLoop(): void {
this.distillationTimer = setInterval(async () => {
await this.step();
}, this.config.distillationInterval);

this.log(`⏰ Distillation loop started (${this.config.distillationInterval}ms interval)`);
}

private log(message: string): void {
if (this.config.debug) {
console.log(`[Ami] ${new Date().toISOString()} ${message}`);
}
}

/**
* Get current status and capabilities.
*/
getStatus(): {
running: boolean;
capabilities: string[];
modules: Array<{ id: string; status: string; capabilities: string[] }>;
} {
const modules = this.registry.getProviders('sensor')
.concat(this.registry.getProviders('processor'))
.concat(this.registry.getProviders('actuator'))
.concat(this.registry.getProviders('memory'))
.map(module => ({
id: module.id,
status: module.status,
capabilities: module.capabilities
}));

const capabilities = ['sensor', 'processor', 'actuator', 'memory']
.filter(cap => this.registry.hasCapability(cap as any));

return {
running: this.isRunning,
capabilities,
modules
};
}
}
101 changes: 101 additions & 0 deletions packages/reference-implementation/src/demo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#!/usr/bin/env node
/**
* Demo: Bootstrap and run the Ami reference agent.
*
* This demonstrates the capability-based architecture:
* 1. Registry discovers modules
* 2. Bus handles inter-module communication
* 3. Ami orchestrates the cognitive loop
* 4. Graceful degradation when modules are missing
*
* Usage:
* npx tsx demo.ts
* npm run demo
*/

import { ReferenceCognitiveRegistry } from './cognitive-registry.js';
import { ReferenceCognitiveBus } from './cognitive-bus.js';
import { DistillerModule } from './distiller-module.js';
import { PatternExtractionStrategy } from './strategies/index.js';
import { Ami } from './ami.js';

async function runDemo(): Promise<void> {
console.log('πŸš€ AMI Reference Agent Demo');
console.log('============================\n');

// 1. Create the cognitive infrastructure
const bus = new ReferenceCognitiveBus();
const registry = new ReferenceCognitiveRegistry(bus);

// 2. Register available modules
console.log('πŸ”§ Registering cognitive modules...');

// Register the knowledge distiller as a processor
const distillerModule = new DistillerModule({
strategy: new PatternExtractionStrategy()
});
registry.register(distillerModule);

console.log(` βœ… Registered: ${distillerModule.name} (${distillerModule.capabilities.join(', ')})`);

// 3. Create and start Ami
console.log('\n🧠 Starting Ami...');
const ami = new Ami(registry, bus, {
debug: true,
maxEpisodeContext: 20,
distillationInterval: 10000 // 10 seconds for demo
});

await ami.start();

// 4. Show current status
console.log('\nπŸ“Š Current status:');
const status = ami.getStatus();
console.log(JSON.stringify(status, null, 2));

// 5. Simulate some interactions
console.log('\nπŸ’¬ Simulating interactions...');
await ami.processInput('Hello Ami, can you remember this message?', 'user');
await ami.processInput('I am testing the AMI framework', 'user');
await ami.processInput('The weather is nice today', 'user');
await ami.processInput('I like cognitive architectures', 'user');
await ami.processInput('This is the fifth message, should trigger distillation', 'user');

// 6. Wait a bit to see distillation in action
console.log('\n⏳ Waiting for cognitive processing...');
await new Promise(resolve => setTimeout(resolve, 5000));

// 7. Trigger a manual cognitive step
console.log('\nπŸ”„ Manual cognitive step...');
await ami.step();

// 8. Show final status
console.log('\nπŸ“Š Final status:');
const finalStatus = ami.getStatus();
console.log(JSON.stringify(finalStatus, null, 2));

// 9. Graceful shutdown
console.log('\nπŸ›‘ Shutting down...');
await ami.stop();

console.log('\nβœ… Demo completed! This proves that "If it runs, I AM!" πŸŽ‰');
}

// Handle graceful shutdown
process.on('SIGINT', () => {
console.log('\nπŸ›‘ Received SIGINT, shutting down gracefully...');
process.exit(0);
});

process.on('SIGTERM', () => {
console.log('\nπŸ›‘ Received SIGTERM, shutting down gracefully...');
process.exit(0);
});

// Run the demo
if (import.meta.url === new URL(process.argv[1], 'file://').href) {
runDemo().catch(error => {
console.error('❌ Demo failed:', error);
process.exit(1);
});
}
2 changes: 2 additions & 0 deletions packages/reference-implementation/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ export { ReferenceCognitiveBus } from './cognitive-bus.js';
export { ReferenceCognitiveRegistry } from './cognitive-registry.js';
export { DistillerModule } from './distiller-module.js';
export type { DistillerModuleConfig } from './distiller-module.js';
export { Ami } from './ami.js';
export type { AmiConfig } from './ami.js';
Loading
Loading