-
Notifications
You must be signed in to change notification settings - Fork 0
feat: bootstrap Ami reference agent #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | ||
|
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(); | ||
|
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 | ||
| }; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
| }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.