@koi-language/koi 1.0.6 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/README.md +4 -125
  2. package/examples/.build/agent-dialogue.ts +138 -0
  3. package/examples/.build/agent-dialogue.ts.map +1 -0
  4. package/examples/.build/chess.ts +77 -0
  5. package/examples/.build/chess.ts.map +1 -0
  6. package/examples/.build/delegation-test.ts +140 -0
  7. package/examples/.build/delegation-test.ts.map +1 -0
  8. package/examples/.build/dialog-demo.ts +77 -0
  9. package/examples/.build/dialog-demo.ts.map +1 -0
  10. package/examples/.build/hello-world.ts +77 -0
  11. package/examples/.build/hello-world.ts.map +1 -0
  12. package/examples/.build/lover-dialog-demo.ts +77 -0
  13. package/examples/.build/lover-dialog-demo.ts.map +1 -0
  14. package/examples/.build/package.json +3 -0
  15. package/examples/.build/registry-interactive-demo.ts +202 -0
  16. package/examples/.build/registry-interactive-demo.ts.map +1 -0
  17. package/examples/.build/registry-playbook-demo.ts +201 -0
  18. package/examples/.build/registry-playbook-demo.ts.map +1 -0
  19. package/examples/.build/tic-tac-toe.ts +77 -0
  20. package/examples/.build/tic-tac-toe.ts.map +1 -0
  21. package/examples/actions-demo.koi +8 -9
  22. package/examples/activists-dialogue.koi +75 -0
  23. package/examples/agent-dialogue.koi +66 -0
  24. package/examples/chess.koi +19 -0
  25. package/examples/counter.koi +20 -69
  26. package/examples/delegation-test.koi +16 -18
  27. package/examples/dialog-demo.koi +20 -0
  28. package/examples/hello-world.koi +7 -43
  29. package/examples/mcp-stdio-demo.koi +29 -0
  30. package/examples/memory-test.koi +49 -0
  31. package/examples/mobile-mcp-demo.koi +32 -0
  32. package/examples/multi-event-handler-test.koi +16 -18
  33. package/examples/pipeline.koi +15 -17
  34. package/examples/prompt-demo.koi +20 -0
  35. package/examples/{registry-playbook-email-compositor.koi → registry-interactive-demo.koi} +27 -27
  36. package/examples/registry-playbook-demo.koi +28 -28
  37. package/examples/skill-import-test.koi +7 -9
  38. package/examples/skills/.build/math-operations.ts +1656 -0
  39. package/examples/skills/.build/math-operations.ts.map +1 -0
  40. package/examples/skills/.build/package.json +3 -0
  41. package/examples/skills/.build/string-operations.ts +1643 -0
  42. package/examples/skills/.build/string-operations.ts.map +1 -0
  43. package/examples/skills/advanced/.build/index.ts +3223 -0
  44. package/examples/skills/advanced/.build/index.ts.map +1 -0
  45. package/examples/skills/advanced/.build/package.json +3 -0
  46. package/examples/skills/advanced/index.koi +3 -5
  47. package/examples/skills/math-operations.koi +1 -3
  48. package/examples/skills/string-operations.koi +1 -3
  49. package/examples/tic-tac-toe.koi +19 -0
  50. package/examples/utils/echo-mcp-server.js +141 -0
  51. package/examples/web-delegation-demo.koi +15 -17
  52. package/package.json +2 -1
  53. package/src/cli/koi.js +30 -41
  54. package/src/compiler/build-optimizer.js +204 -289
  55. package/src/compiler/cache-manager.js +1 -1
  56. package/src/compiler/import-resolver.js +5 -9
  57. package/src/compiler/parser.js +6072 -3476
  58. package/src/compiler/transpiler.js +346 -38
  59. package/src/grammar/koi.pegjs +302 -62
  60. package/src/runtime/actions/{format.js → call-llm.js} +37 -44
  61. package/src/runtime/actions/call-mcp.js +97 -0
  62. package/src/runtime/actions/if.js +179 -0
  63. package/src/runtime/actions/print.js +3 -1
  64. package/src/runtime/actions/prompt-user.js +75 -0
  65. package/src/runtime/actions/repeat.js +147 -0
  66. package/src/runtime/actions/shell.js +185 -0
  67. package/src/runtime/actions/while.js +205 -0
  68. package/src/runtime/agent.js +592 -178
  69. package/src/runtime/cli-display.js +26 -0
  70. package/src/runtime/cli-input.js +421 -0
  71. package/src/runtime/cli-logger.js +2 -5
  72. package/src/runtime/cli-markdown.js +61 -0
  73. package/src/runtime/cli-select.js +106 -0
  74. package/src/runtime/incremental-json-parser.js +27 -17
  75. package/src/runtime/index.js +1 -0
  76. package/src/runtime/llm-provider.js +1083 -572
  77. package/src/runtime/mcp-registry.js +141 -0
  78. package/src/runtime/mcp-stdio-client.js +334 -0
  79. package/src/runtime/planner.js +1 -1
  80. package/src/runtime/playbook-session.js +259 -0
  81. package/src/runtime/registry-backends/keyv-sqlite.js +1 -1
  82. package/src/runtime/registry-backends/local.js +1 -1
  83. package/src/runtime/router.js +22 -26
  84. package/src/runtime/runtime.js +7 -1
  85. package/examples/cache-test.koi +0 -29
  86. package/examples/calculator.koi +0 -61
  87. package/examples/clear-registry.js +0 -33
  88. package/examples/clear-registry.koi +0 -30
  89. package/examples/code-introspection-test.koi +0 -149
  90. package/examples/directory-import-test.koi +0 -84
  91. package/examples/hello-world-claude.koi +0 -52
  92. package/examples/hello.koi +0 -24
  93. package/examples/mcp-example.koi +0 -70
  94. package/examples/new-import-test.koi +0 -89
  95. package/examples/registry-demo.koi +0 -184
  96. package/examples/registry-playbook-email-compositor-2.koi +0 -140
  97. package/examples/sentiment.koi +0 -90
  98. package/examples/simple.koi +0 -48
  99. package/examples/task-chaining-demo.koi +0 -244
  100. package/examples/test-await.koi +0 -22
  101. package/examples/test-crypto-sha256.koi +0 -196
  102. package/examples/test-delegation.koi +0 -41
  103. package/examples/test-multi-team-routing.koi +0 -258
  104. package/examples/test-no-handler.koi +0 -35
  105. package/examples/test-npm-import.koi +0 -67
  106. package/examples/test-parse.koi +0 -10
  107. package/examples/test-peers-with-team.koi +0 -59
  108. package/examples/test-permissions-fail.koi +0 -20
  109. package/examples/test-permissions.koi +0 -36
  110. package/examples/test-simple-registry.koi +0 -31
  111. package/examples/test-typescript-import.koi +0 -64
  112. package/examples/test-uses-team-syntax.koi +0 -25
  113. package/examples/test-uses-team.koi +0 -31
@@ -0,0 +1,259 @@
1
+ /**
2
+ * PlaybookSession - Tracks state for the reactive agentic loop.
3
+ *
4
+ * In reactive mode, the LLM decides ONE action per iteration,
5
+ * receives feedback from the result, and adapts its strategy.
6
+ * This class maintains the state across iterations.
7
+ *
8
+ * The loop runs indefinitely until the agent decides to terminate
9
+ * via a "return" action, or consecutive errors exceed the threshold.
10
+ */
11
+ export class PlaybookSession {
12
+ constructor({ playbook, agentName } = {}) {
13
+ this.playbook = playbook;
14
+ this.agentName = agentName;
15
+
16
+ // Iteration tracking
17
+ this.iteration = 0;
18
+ this.isTerminated = false;
19
+ this.finalResult = null;
20
+
21
+ // Action history: { action, result, error, timestamp, iteration }
22
+ this.actionHistory = [];
23
+
24
+ // Action context: accumulates results for template variables (${a1.output.field})
25
+ // Same structure as actionContext in agent.js
26
+ this.actionContext = {
27
+ state: {},
28
+ results: [],
29
+ args: {}
30
+ };
31
+
32
+ // Error tracking
33
+ this.lastError = null;
34
+ this.consecutiveErrors = 0;
35
+ this.maxConsecutiveErrors = 10;
36
+ }
37
+
38
+ /**
39
+ * Check if the loop can continue.
40
+ * The loop only stops when the agent terminates or consecutive errors exceed threshold.
41
+ */
42
+ canContinue() {
43
+ return (
44
+ !this.isTerminated &&
45
+ this.consecutiveErrors < this.maxConsecutiveErrors
46
+ );
47
+ }
48
+
49
+ /**
50
+ * Record an action and its result/error.
51
+ * Updates actionContext with the same chaining logic as agent.js.
52
+ */
53
+ recordAction(action, result, error = null) {
54
+ this.iteration++;
55
+
56
+ const entry = {
57
+ action,
58
+ result: result || null,
59
+ error: error ? { message: error.message || String(error) } : null,
60
+ timestamp: Date.now(),
61
+ iteration: this.iteration
62
+ };
63
+
64
+ this.actionHistory.push(entry);
65
+
66
+ if (error) {
67
+ this.lastError = error;
68
+ this.consecutiveErrors++;
69
+ } else {
70
+ this.lastError = null;
71
+ this.consecutiveErrors = 0;
72
+
73
+ // Update actionContext (same logic as agent.js onStreamAction)
74
+ if (result && typeof result === 'object') {
75
+ // Unwrap double-encoded results
76
+ let unwrappedResult = result;
77
+ if (result.result && typeof result.result === 'string' && Object.keys(result).length === 1) {
78
+ try {
79
+ const parsed = JSON.parse(result.result);
80
+ if (typeof parsed === 'object') {
81
+ unwrappedResult = parsed;
82
+ }
83
+ } catch (e) {
84
+ // Not JSON, keep as-is
85
+ }
86
+ }
87
+
88
+ const resultForContext = JSON.parse(JSON.stringify(unwrappedResult));
89
+ this.actionContext.results.push(resultForContext);
90
+
91
+ // Store result with action ID for explicit referencing
92
+ if (action.id) {
93
+ this.actionContext[action.id] = { output: resultForContext };
94
+ }
95
+
96
+ // Only update previousResult for actions that produce meaningful data
97
+ const intent = action.intent || action.type;
98
+ const nonDataActions = ['print', 'log', 'format'];
99
+
100
+ if (!nonDataActions.includes(intent)) {
101
+ this.actionContext.previousResult = resultForContext;
102
+ this.actionContext.lastResult = resultForContext;
103
+ }
104
+
105
+ // Make result fields directly accessible
106
+ Object.keys(resultForContext).forEach(key => {
107
+ if (!this.actionContext[key]) {
108
+ this.actionContext[key] = resultForContext[key];
109
+ }
110
+ });
111
+ }
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Terminate the loop with a final result
117
+ */
118
+ terminate(result) {
119
+ this.isTerminated = true;
120
+ this.finalResult = result;
121
+ }
122
+
123
+ /**
124
+ * Build feedback context for the next LLM iteration.
125
+ * Kept MINIMAL to avoid context bloat — the LLM already has
126
+ * the full conversation history with all previous actions/results.
127
+ */
128
+ buildFeedbackContext() {
129
+ const parts = [];
130
+
131
+ // Error feedback
132
+ if (this.lastError) {
133
+ const errorMsg = this.lastError.message || String(this.lastError);
134
+ parts.push(`\u274c LAST ACTION FAILED: ${errorMsg}`);
135
+
136
+ // Count recent MCP errors to suggest diagnostics
137
+ const recentMcpErrors = this.actionHistory.slice(-5).filter(
138
+ e => e.error && (e.action.intent === 'call_mcp' || e.action.type === 'call_mcp')
139
+ ).length;
140
+
141
+ if (recentMcpErrors >= 2) {
142
+ parts.push('Multiple MCP failures detected. DIAGNOSE: check available tools on that MCP server for status/diagnostic tools and use them to understand the current state before retrying. The MCP server may have been restarted automatically.');
143
+ }
144
+
145
+ parts.push('NEVER give up. Try a DIFFERENT approach — do NOT repeat the same action that failed. Think about what went wrong and find an alternative path to achieve the goal.');
146
+ }
147
+
148
+ // Loop detection warnings
149
+ const warnings = this._detectLoops();
150
+ for (const warning of warnings) {
151
+ parts.push(`\u26a0\ufe0f ${warning}`);
152
+ }
153
+
154
+ // Last action result
155
+ const lastEntry = this.actionHistory[this.actionHistory.length - 1];
156
+ if (lastEntry && !lastEntry.error) {
157
+ const intent = lastEntry.action.intent || lastEntry.action.type || 'unknown';
158
+ const id = lastEntry.action.id ? ` [${lastEntry.action.id}]` : '';
159
+ const resultStr = lastEntry.result
160
+ ? JSON.stringify(lastEntry.result)
161
+ : 'ok';
162
+
163
+ // Detect when result payload indicates failure (e.g. MCP returns {success: false, error: "..."})
164
+ if (lastEntry.result && lastEntry.result.success === false && lastEntry.result.error) {
165
+ const errorMsg = lastEntry.result.error;
166
+ parts.push(`\u274c${id} ${intent} returned an error: ${errorMsg}`);
167
+
168
+ // Include MCP server output (stderr) if available — this often contains
169
+ // the actual installation commands or detailed error info
170
+ if (lastEntry.result.serverOutput) {
171
+ parts.push(`Server output:\n${lastEntry.result.serverOutput}`);
172
+ }
173
+
174
+ // Check if server output contains actual installation commands
175
+ const serverOutput = lastEntry.result.serverOutput || '';
176
+ const hasInstallCommands = /\b(brew\s+install|pip\s+install|npm\s+install|apt\s+install|apt-get\s+install)\b/i.test(serverOutput);
177
+
178
+ // Check if this SAME error appeared before in history (fix attempt didn't work)
179
+ const sameErrorBefore = this.actionHistory.slice(0, -1).some(
180
+ e => e.result && e.result.success === false && e.result.error === errorMsg
181
+ );
182
+
183
+ if (sameErrorBefore) {
184
+ parts.push('WARNING: This is the SAME error as before — your previous fix did NOT solve it. If you have already tried everything mentioned, inform the user and return with an error.');
185
+ } else if (hasInstallCommands) {
186
+ parts.push('APPLY RULE 8: Read the server output above. Use "shell" to run the EXACT commands listed there — ALL of them in a SINGLE shell command chained with && (e.g. "brew tap X && brew install Y && pip install Z"). IMPORTANT: The shell "description" field must express NEED, not action (e.g. "Need to install X, Y, and Z because the MCP server requires IDB for iOS Simulator control" — NOT "Installing X..."). Then RETRY the failed action.');
187
+ parts.push('SHELL RULES: NEVER use placeholder values like <your_api_key> or <TOKEN> in commands — they cause syntax errors. NEVER try to set/export API keys — they are already in the environment. ONLY install what the server output explicitly lists.');
188
+ } else {
189
+ parts.push('This is a configuration or environment error (e.g. missing API key, wrong path). Do NOT try to install anything. Inform the user what is wrong and return with an error explaining how to fix it.');
190
+ }
191
+ } else {
192
+ parts.push(`\u2705${id} ${intent} -> ${resultStr}`);
193
+ }
194
+ }
195
+
196
+ return parts.join('\n');
197
+ }
198
+
199
+ /**
200
+ * Detect loop patterns in action history
201
+ */
202
+ _detectLoops() {
203
+ const warnings = [];
204
+ const history = this.actionHistory;
205
+
206
+ if (history.length < 2) return warnings;
207
+
208
+ // Same action repeated 2x consecutively — compare full action shape
209
+ // (not just intent+data, since call_mcp actions differ by tool/input, not data)
210
+ const last = history[history.length - 1];
211
+ const prev = history[history.length - 2];
212
+ const lastKey = JSON.stringify({ intent: last.action.intent, tool: last.action.tool, data: last.action.data, input: last.action.input });
213
+ const prevKey = JSON.stringify({ intent: prev.action.intent, tool: prev.action.tool, data: prev.action.data, input: prev.action.input });
214
+
215
+ if (lastKey === prevKey) {
216
+ warnings.push('You repeated the same action with the same data. Try a different approach!');
217
+ }
218
+
219
+ // Consecutive errors
220
+ if (this.consecutiveErrors >= 2) {
221
+ warnings.push(`${this.consecutiveErrors} consecutive errors. Try a completely different approach!`);
222
+ }
223
+
224
+ // Oscillating pattern A-B-A-B (compare full action shape, not just intent)
225
+ if (history.length >= 4) {
226
+ const h = history.slice(-4);
227
+ const keys = h.map(e => JSON.stringify({
228
+ intent: e.action.intent, tool: e.action.tool,
229
+ data: e.action.data, input: e.action.input, command: e.action.command
230
+ }));
231
+ if (keys[0] === keys[2] && keys[1] === keys[3] && keys[0] !== keys[1]) {
232
+ const intent0 = h[0].action.tool || h[0].action.intent || h[0].action.type;
233
+ const intent1 = h[1].action.tool || h[1].action.intent || h[1].action.type;
234
+ warnings.push(`Oscillating pattern detected (${intent0} <-> ${intent1}). Break the cycle!`);
235
+ }
236
+ }
237
+
238
+ return warnings;
239
+ }
240
+
241
+ /**
242
+ * Get available data references (action IDs with output)
243
+ */
244
+ _getAvailableDataRefs() {
245
+ const refs = [];
246
+
247
+ for (const key of Object.keys(this.actionContext)) {
248
+ if (key.match(/^a\d+$/) || (this.actionContext[key]?.output !== undefined && !['state', 'results', 'args', 'previousResult', 'lastResult'].includes(key))) {
249
+ const output = this.actionContext[key]?.output;
250
+ if (output !== undefined) {
251
+ const preview = JSON.stringify(output).substring(0, 80);
252
+ refs.push(`\${${key}.output} = ${preview}`);
253
+ }
254
+ }
255
+ }
256
+
257
+ return refs;
258
+ }
259
+ }
@@ -12,7 +12,7 @@ import path from 'path';
12
12
 
13
13
  export default class KeyvSqliteBackend {
14
14
  constructor(options = {}) {
15
- this.dbPath = options.path || '.koi-registry/registry.sqlite';
15
+ this.dbPath = options.path || '.koi/registry/registry.sqlite';
16
16
  this.keyv = null;
17
17
  this.namespace = options.namespace || 'koi';
18
18
  this._keysCache = new Set();
@@ -10,7 +10,7 @@ import path from 'path';
10
10
 
11
11
  export default class LocalBackend {
12
12
  constructor(options = {}) {
13
- this.dataDir = options.path || '.koi-registry';
13
+ this.dataDir = options.path || '.koi/registry';
14
14
  this.dataFile = path.join(this.dataDir, 'data.json');
15
15
  this.cache = new Map();
16
16
  this.dirty = false;
@@ -39,49 +39,33 @@ export class AgentRouter {
39
39
 
40
40
  this.agents.set(agent.name, agent);
41
41
 
42
- // Use cached affordances if available
42
+ // Store affordances without generating embeddings (lazy generation)
43
43
  if (cachedAffordances) {
44
44
  for (const [eventName, aff] of Object.entries(cachedAffordances)) {
45
- if (!aff.embedding) {
46
- // Fallback: generate at runtime if cache is incomplete
47
- if (aff.description && aff.description.trim() !== '') {
48
- aff.embedding = await this.getEmbedding(aff.description);
49
- } else {
50
- console.warn(`⚠️ [Router] Skipping ${agent.name}.${eventName} - empty description`);
51
- continue;
52
- }
53
- }
45
+ if (!aff.description || aff.description.trim() === '') continue;
54
46
 
55
47
  this.affordanceEmbeddings.push({
56
48
  agent: agent,
57
49
  event: eventName,
58
50
  description: aff.description,
59
- embedding: aff.embedding,
51
+ embedding: aff.embedding || null, // May be null — generated lazily
60
52
  confidence: aff.confidence,
61
53
  metadata: { hasPlaybook: aff.hasPlaybook }
62
54
  });
63
55
  }
64
-
65
56
  return;
66
57
  }
67
58
 
68
- // No cache: extract and generate affordances at runtime
59
+ // No cache: extract affordances (still no embedding generation)
69
60
  const affordances = this.extractAffordances(agent);
70
-
71
- // Generate embeddings for each affordance
72
61
  for (const aff of affordances) {
73
- if (!aff.description || aff.description.trim() === '') {
74
- console.warn(`⚠️ [Router] Skipping ${agent.name}.${aff.event} - empty description`);
75
- continue;
76
- }
77
-
78
- const embedding = await this.getEmbedding(aff.description);
62
+ if (!aff.description || aff.description.trim() === '') continue;
79
63
 
80
64
  this.affordanceEmbeddings.push({
81
65
  agent: agent,
82
66
  event: aff.event,
83
67
  description: aff.description,
84
- embedding: embedding,
68
+ embedding: null, // Generated lazily on first findMatches call
85
69
  confidence: aff.confidence,
86
70
  metadata: aff.metadata
87
71
  });
@@ -206,13 +190,25 @@ export class AgentRouter {
206
190
  return [];
207
191
  }
208
192
 
193
+ // Lazy embedding generation: only compute when actually needed for routing
194
+ const needsEmbeddings = this.affordanceEmbeddings.some(aff => !aff.embedding);
195
+ if (needsEmbeddings) {
196
+ for (const aff of this.affordanceEmbeddings) {
197
+ if (!aff.embedding && aff.description) {
198
+ aff.embedding = await this.getEmbedding(aff.description);
199
+ }
200
+ }
201
+ }
202
+
209
203
  // Phase 1: Embedding-based similarity search
210
204
  const intentEmbedding = await this.getEmbedding(intent);
211
205
 
212
- const similarities = this.affordanceEmbeddings.map(aff => ({
213
- ...aff,
214
- similarity: this.cosineSimilarity(intentEmbedding, aff.embedding)
215
- }));
206
+ const similarities = this.affordanceEmbeddings
207
+ .filter(aff => aff.embedding)
208
+ .map(aff => ({
209
+ ...aff,
210
+ similarity: this.cosineSimilarity(intentEmbedding, aff.embedding)
211
+ }));
216
212
 
217
213
  // Sort by similarity descending
218
214
  similarities.sort((a, b) => b.similarity - a.similarity);
@@ -3,7 +3,7 @@ import { cliLogger } from './cli-logger.js';
3
3
 
4
4
  export class Runtime {
5
5
  static async send(config) {
6
- const { base, filters = [], args = {}, timeout = 30000 } = config;
6
+ const { base, filters = [], args = {}, timeout = 30000, caller = null } = config;
7
7
 
8
8
  // Validate that base (team/peers) is not null
9
9
  if (!base || base === null || base === undefined) {
@@ -11,6 +11,12 @@ export class Runtime {
11
11
  throw new Error(`NO_AGENT_HANDLER:${eventName}::no-team`);
12
12
  }
13
13
 
14
+ // Check that calling agent has delegate permission
15
+ if (caller && typeof caller.hasPermission === 'function' && !caller.hasPermission('delegate')) {
16
+ const eventName = filters.find(f => f.type === 'event')?.name || 'unknown event';
17
+ throw new Error(`Agent "${caller.name}" cannot delegate: role "${caller.role?.name || 'unknown'}" lacks "can delegate" permission. Add "can delegate" to the role definition.`);
18
+ }
19
+
14
20
  // Apply timeout
15
21
  const timeoutPromise = new Promise((_, reject) => {
16
22
  setTimeout(() => reject(new Error(`Timeout after ${timeout}ms`)), timeout);
@@ -1,29 +0,0 @@
1
- // Cache Test Example
2
- package "test.cache"
3
-
4
- role Worker { can execute }
5
-
6
- Agent TextTranslator : Worker {
7
- llm default = { provider: "openai", model: "gpt-4o-mini" }
8
-
9
- on translate(args: Json) {
10
- playbook """
11
- Translate this text to the target language: ${args.text}
12
- Target language: ${args.language}
13
- Return JSON with translated text.
14
- """
15
- }
16
- }
17
-
18
- Agent WordAnalyzer : Worker {
19
- llm default = { provider: "openai", model: "gpt-4o-mini" }
20
-
21
- on analyze(args: Json) {
22
- playbook """
23
- Analyze this text and count words: ${args.text}
24
- Return JSON with word count and analysis.
25
- """
26
- }
27
- }
28
-
29
- run TextTranslator.translate({ text: "hello", language: "spanish" })
@@ -1,61 +0,0 @@
1
- // ============================================================
2
- // Koi — Calculator Example
3
- // Simple calculator with multiple operations (procedural code only)
4
- // ============================================================
5
-
6
- package "demo.koi.calculator"
7
-
8
- role Worker { can execute }
9
-
10
- // All handlers use procedural code (no playbooks)
11
- Agent Calculator : Worker {
12
- on add(args: Json) {
13
- const a = args.a
14
- const b = args.b
15
- return { result: a + b, operation: "add" }
16
- }
17
-
18
- on multiply(args: Json) {
19
- const a = args.a
20
- const b = args.b
21
- return { result: a * b, operation: "multiply" }
22
- }
23
-
24
- on divide(args: Json) {
25
- const a = args.a
26
- const b = args.b
27
-
28
- if b == 0 {
29
- return { error: "Division by zero", operation: "divide" }
30
- }
31
-
32
- return { result: a / b, operation: "divide" }
33
- }
34
- }
35
-
36
- Team CalcTeam {
37
- calc = Calculator
38
- }
39
-
40
- Agent Orchestrator : Worker {
41
- uses Team CalcTeam
42
-
43
- on start(args: Json) {
44
- const sum =
45
- await send peers.event("add").role(Worker).any()({ a: 10, b: 5 }) timeout 2s
46
-
47
- const product =
48
- await send peers.event("multiply").role(Worker).any()({ a: 10, b: 5 }) timeout 2s
49
-
50
- const quotient =
51
- await send peers.event("divide").role(Worker).any()({ a: 10, b: 5 }) timeout 2s
52
-
53
- return {
54
- sum: sum,
55
- product: product,
56
- quotient: quotient
57
- }
58
- }
59
- }
60
-
61
- run Orchestrator.start({})
@@ -1,33 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // Clear Registry - Delete all data from the registry
4
- import { registry } from '../src/runtime/index.js';
5
-
6
- async function clearRegistry() {
7
- console.log("⚠️ WARNING: Clearing all registry data...");
8
- console.log("");
9
-
10
- // Show stats before clearing
11
- const statsBefore = await registry.stats();
12
- console.log(" Current entries:", statsBefore.count);
13
- console.log(" Storage:", statsBefore.file);
14
- console.log("");
15
-
16
- // Clear the registry
17
- await registry.clear();
18
-
19
- console.log("✅ Registry cleared successfully!");
20
- console.log("");
21
-
22
- // Show stats after clearing
23
- const statsAfter = await registry.stats();
24
- console.log(" Entries remaining:", statsAfter.count);
25
- console.log("");
26
-
27
- process.exit(0);
28
- }
29
-
30
- clearRegistry().catch((error) => {
31
- console.error("❌ Error clearing registry:", error.message);
32
- process.exit(1);
33
- });
@@ -1,30 +0,0 @@
1
- // Clear Registry - Delete all data from the registry
2
- package "clear.registry"
3
-
4
- role Admin { can execute }
5
-
6
- Agent Cleaner : Admin {
7
- on clearAll(args: Json) {
8
- console.log("⚠️ WARNING: Clearing all registry data...")
9
-
10
- // Show current stats before clearing
11
- const statsBefore = await registry.stats()
12
- console.log(" Current entries:", statsBefore.count)
13
- console.log(" Storage:", statsBefore.file)
14
- console.log("")
15
-
16
- // Clear the registry
17
- await registry.clear()
18
-
19
- console.log("✅ Registry cleared successfully!")
20
-
21
- // Show stats after clearing
22
- const statsAfter = await registry.stats()
23
- console.log(" Entries remaining:", statsAfter.count)
24
- console.log("")
25
-
26
- return { success: true, clearedCount: statsBefore.count }
27
- }
28
- }
29
-
30
- run Cleaner.clearAll({})
@@ -1,149 +0,0 @@
1
- package "demo.code.introspection"
2
-
3
- role Calculator { can calculate }
4
- role Coordinator { can coordinate }
5
-
6
- // Agent con event handler que tiene código (no playbook)
7
- // El sistema debe hacer introspección para generar affordance
8
- Agent MathProcessor : Calculator {
9
- llm default = {
10
- provider: "openai",
11
- model: "gpt-4o-mini",
12
- temperature: 0.1
13
- }
14
-
15
- // Event handler con código Koi (sin playbook)
16
- // El sistema hará introspección del código para generar affordance
17
- // Este handler suma todos los números en el array
18
- on calculateSum(args: Json) {
19
- const numbers = args.numbers
20
-
21
- // Procesar números y sumarlos
22
- const result = await send peers.event("sumArray").role(Calculator).any()({
23
- data: numbers
24
- }) timeout 5s
25
-
26
- return {
27
- operation: "sum",
28
- result: result
29
- }
30
- }
31
-
32
- // Event handler que multiplica todos los números
33
- on calculateProduct(args: Json) {
34
- const numbers = args.numbers
35
-
36
- // Multiplicar los números usando delegación
37
- const result = await send peers.event("multiplyArray").role(Calculator).any()({
38
- data: numbers
39
- }) timeout 5s
40
-
41
- return {
42
- operation: "product",
43
- result: result
44
- }
45
- }
46
-
47
- // Event handler con playbook (para comparar)
48
- on calculateAverage(args: Json) {
49
- playbook """
50
- Calculate the average of ${JSON.stringify(args.numbers)}
51
-
52
- Sum all numbers and divide by count.
53
- Return: { "operation": "average", "result": <value> }
54
- """
55
- }
56
- }
57
-
58
- Team MathTeam {
59
- processor = MathProcessor
60
- }
61
-
62
- // Coordinator que delega basándose en affordances
63
- Agent Coordinator : Coordinator {
64
- llm default = {
65
- provider: "openai",
66
- model: "gpt-4o-mini",
67
- temperature: 0.1
68
- }
69
-
70
- uses Team MathTeam
71
-
72
- on process(args: Json) {
73
- playbook """
74
- Process this math request: ${JSON.stringify(args)}
75
-
76
- Available operations (delegate to Calculator role):
77
- - Calculate sum/total of numbers
78
- - Calculate product/multiplication of numbers
79
- - Calculate average/mean of numbers
80
-
81
- Determine the intent and delegate with an action:
82
- {
83
- "actions": [{
84
- "title": "Calculating...",
85
- "intent": "<your interpretation of what math operation is needed>",
86
- "data": { "numbers": ${JSON.stringify(args.numbers)} }
87
- }]
88
- }
89
- """
90
- }
91
- }
92
-
93
- Team CoordinatorTeam {
94
- coordinator = Coordinator
95
- }
96
-
97
- Agent Main : Coordinator {
98
- uses Team CoordinatorTeam
99
-
100
- on start(args: Json) {
101
- console.log("╔════════════════════════════════════════════╗")
102
- console.log("║ Code Introspection Test ║")
103
- console.log("║ Event handlers with source code ║")
104
- console.log("╚════════════════════════════════════════════╝\n")
105
-
106
- // Test 1: Request sum (should route to calculateSum)
107
- console.log("📋 Test 1: Calculate the total")
108
- console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
109
-
110
- const sumResult =
111
- await send peers.event("process").role(Coordinator).any()({
112
- operation: "calculate the total of these numbers",
113
- numbers: [5, 10, 15, 20]
114
- }) timeout 30s
115
-
116
- console.log("Result:", JSON.stringify(sumResult, null, 2))
117
-
118
- // Test 2: Request product (should route to calculateProduct)
119
- console.log("\n📋 Test 2: Multiply all numbers")
120
- console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
121
-
122
- const productResult =
123
- await send peers.event("process").role(Coordinator).any()({
124
- operation: "multiply all the numbers together",
125
- numbers: [2, 3, 4, 5]
126
- }) timeout 30s
127
-
128
- console.log("Result:", JSON.stringify(productResult, null, 2))
129
-
130
- // Test 3: Request average (should route to calculateAverage with playbook)
131
- console.log("\n📋 Test 3: Find the average")
132
- console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
133
-
134
- const avgResult =
135
- await send peers.event("process").role(Coordinator).any()({
136
- operation: "find the average value",
137
- numbers: [10, 20, 30, 40, 50]
138
- }) timeout 30s
139
-
140
- console.log("Result:", JSON.stringify(avgResult, null, 2))
141
-
142
- return {
143
- success: true,
144
- tests_completed: 3
145
- }
146
- }
147
- }
148
-
149
- run Main.start({})