@memgrafter/flatagents 0.9.0 → 2.0.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.
@@ -15,10 +15,12 @@
15
15
  * - MachineHooks: Base interface (MUST)
16
16
  * - RegistrationBackend: SQLiteRegistrationBackend (MUST), MemoryRegistrationBackend (SHOULD)
17
17
  * - WorkBackend: SQLiteWorkBackend (MUST), MemoryWorkBackend (SHOULD)
18
+ * - SignalBackend: MemorySignalBackend (MUST), SQLiteSignalBackend (SHOULD)
19
+ * - TriggerBackend: NoOpTrigger (MUST), FileTrigger (SHOULD)
18
20
  *
19
21
  * OPTIONAL IMPLEMENTATIONS:
20
22
  * -------------------------
21
- * - Distributed backends (Redis, Postgres, etc.)
23
+ * - Distributed backends (Redis, Postgres, DynamoDB, etc.)
22
24
  * - LLMBackend (SDK may use native provider SDKs)
23
25
  *
24
26
  * EXECUTION LOCKING:
@@ -68,10 +70,30 @@
68
70
  * Extension points for machine execution.
69
71
  * All methods are optional and can be sync or async.
70
72
  *
73
+ * SDKs MUST provide:
74
+ * - HooksRegistry: Name-based registry for resolving hooks from config
75
+ *
71
76
  * SDKs SHOULD provide:
72
77
  * - WebhookHooks: Send events to HTTP endpoint
73
78
  * - CompositeHooks: Combine multiple hook implementations
74
79
  *
80
+ * HOOKS REGISTRY:
81
+ * ---------------
82
+ * Maps hook names (strings) to implementations. Machine configs reference
83
+ * hooks by name; the SDK's HooksRegistry resolves them at runtime.
84
+ *
85
+ * This decouples machine configs from any specific language or file path,
86
+ * enabling the same YAML to work across Python, JavaScript, Rust, etc.
87
+ *
88
+ * SDKs MUST provide:
89
+ * - HooksRegistry with register(), resolve(), has()
90
+ *
91
+ * Built-in registrations (when available in the SDK):
92
+ * - "logging": LoggingHooks
93
+ * - "webhook": WebhookHooks (args: { url, timeout?, api_key? })
94
+ * - "metrics": MetricsHooks
95
+ * - "distributed-worker": DistributedWorkerHooks
96
+ *
75
97
  * LLM BACKEND (OPTIONAL):
76
98
  * -----------------------
77
99
  * Abstraction over LLM providers.
@@ -87,6 +109,83 @@
87
109
  * Interface for invoking peer machines.
88
110
  * Used internally by FlatMachine for `machine:` and `launch:` states.
89
111
  *
112
+ * AGENT RESULT:
113
+ * --------------
114
+ * Universal result contract for agent execution across all backends.
115
+ * All fields are optional and use structured data (no classes) for
116
+ * cross-language and cross-process/network compatibility.
117
+ *
118
+ * REGISTRATION BACKEND:
119
+ * ---------------------
120
+ * Worker lifecycle management for distributed execution.
121
+ *
122
+ * SDKs MUST provide:
123
+ * - SQLiteRegistrationBackend: For local deployments
124
+ *
125
+ * SDKs SHOULD provide:
126
+ * - MemoryRegistrationBackend: For testing
127
+ *
128
+ * Implementation notes:
129
+ * - Time units: Python reference SDK uses seconds for all interval values
130
+ * - Stale threshold: SDKs SHOULD default to 2× heartbeat_interval if not specified
131
+ *
132
+ * WORK BACKEND:
133
+ * -------------
134
+ * Work distribution via named pools with atomic claim.
135
+ *
136
+ * SDKs MUST provide:
137
+ * - SQLiteWorkBackend: For local deployments
138
+ *
139
+ * SDKs SHOULD provide:
140
+ * - MemoryWorkBackend: For testing
141
+ *
142
+ * Implementation notes:
143
+ * - Atomic claim: SDKs MUST ensure no two workers can claim the same job
144
+ * - Test requirements: Include concurrent claim race condition tests
145
+ *
146
+ * SIGNAL BACKEND:
147
+ * ---------------
148
+ * Durable signal storage for cross-process machine activation.
149
+ *
150
+ * Signals are named-channel messages that wake checkpointed machines.
151
+ * A machine paused at a `wait_for` state checkpoints with a `waiting_channel`
152
+ * tag and exits (no process running). When a signal arrives on that channel,
153
+ * a dispatcher finds matching checkpoints and resumes them.
154
+ *
155
+ * Signal data becomes the `wait_for` state's output — accessible via
156
+ * output.* in output_to_context templates. No new template syntax needed.
157
+ *
158
+ * Channel semantics:
159
+ * - Addressed: "approval/task-001" → one waiting machine
160
+ * - Broadcast: "quota/openai" → N waiting machines (dispatcher controls limit)
161
+ *
162
+ * SDKs MUST provide:
163
+ * - MemorySignalBackend: For testing and single-process use
164
+ *
165
+ * SDKs SHOULD provide:
166
+ * - SQLiteSignalBackend: For durable local use
167
+ *
168
+ * TRIGGER BACKEND:
169
+ * ----------------
170
+ * Process activation when signals or work arrive.
171
+ * The OS-native equivalent of Lambda triggers / DynamoDB Streams.
172
+ *
173
+ * Called after SignalBackend.send() or WorkPool.push() to wake
174
+ * a dispatcher process. The dispatcher then queries for matching
175
+ * checkpoints and resumes machines.
176
+ *
177
+ * Deployment mapping:
178
+ * - NoOpTrigger: Consumer already running (polling/in-process)
179
+ * - FileTrigger: Touch file → launchd WatchPaths (macOS) / systemd PathChanged (Linux)
180
+ * - SocketTrigger: UDS notify → in-host low-latency wake (optional)
181
+ * - DynamoDB: Implicit via Streams → Lambda (no application code)
182
+ *
183
+ * SDKs MUST provide:
184
+ * - NoOpTrigger: For in-process and polling consumers
185
+ *
186
+ * SDKs SHOULD provide:
187
+ * - FileTrigger: For OS-level activation with zero running processes
188
+ *
90
189
  * BACKEND CONFIGURATION:
91
190
  * ----------------------
92
191
  * Backend configuration for machine settings.
@@ -146,6 +245,24 @@ export interface PersistenceBackend {
146
245
  * @returns Array of matching keys, sorted lexicographically
147
246
  */
148
247
  list(prefix: string): Promise<string[]>;
248
+
249
+ /**
250
+ * List execution IDs, optionally filtered by latest checkpoint state.
251
+ *
252
+ * @param options.event - Filter by latest checkpoint event (e.g., "machine_end")
253
+ * @param options.waiting_channel - Filter by waiting_channel metadata
254
+ * @returns Array of matching execution IDs
255
+ */
256
+ listExecutionIds?(options?: {
257
+ event?: string;
258
+ waiting_channel?: string;
259
+ }): Promise<string[]>;
260
+
261
+ /**
262
+ * Delete all checkpoint data for an execution.
263
+ * Safe to call if execution doesn't exist.
264
+ */
265
+ deleteExecution?(execution_id: string): Promise<void>;
149
266
  }
150
267
 
151
268
  export interface ResultBackend {
@@ -181,14 +298,126 @@ export interface ResultBackend {
181
298
  delete(uri: string): Promise<void>;
182
299
  }
183
300
 
301
+ export interface AgentResult {
302
+ // Content
303
+ output?: Record<string, any> | null;
304
+ content?: string | null;
305
+ raw?: any; // In-process only, not serialized across boundaries
306
+
307
+ // Metrics
308
+ usage?: UsageInfo | null;
309
+ cost?: CostInfo | number | null; // number for backwards compatibility
310
+ metadata?: Record<string, any> | null;
311
+
312
+ // Completion status
313
+ /** Known values: "stop", "length", "tool_use", "error", "content_filter", "aborted" */
314
+ finish_reason?: string | null;
315
+
316
+ // Error info (null/undefined = success)
317
+ error?: AgentError | null;
318
+
319
+ // Rate limit state (normalized for orchestration)
320
+ rate_limit?: RateLimitState | null;
321
+
322
+ // Provider-specific data (includes raw_headers when available)
323
+ provider_data?: ProviderData | null;
324
+ }
325
+
326
+ export interface UsageInfo {
327
+ input_tokens?: number;
328
+ output_tokens?: number;
329
+ total_tokens?: number;
330
+ cache_read_tokens?: number;
331
+ cache_write_tokens?: number;
332
+ }
333
+
334
+ export interface CostInfo {
335
+ input?: number;
336
+ output?: number;
337
+ cache_read?: number;
338
+ cache_write?: number;
339
+ total?: number;
340
+ }
341
+
342
+ export interface AgentError {
343
+ /**
344
+ * Known values: "rate_limit", "timeout", "server_error", "invalid_request",
345
+ * "auth_error", "content_filter", "context_length", "model_unavailable"
346
+ * Custom codes allowed for extension.
347
+ */
348
+ code?: string;
349
+
350
+ /** Original error type name (e.g., "RateLimitError", "TimeoutError") */
351
+ type?: string;
352
+
353
+ /** Human-readable error message */
354
+ message: string;
355
+
356
+ /** HTTP status code if applicable */
357
+ status_code?: number;
358
+
359
+ /** Whether retry might succeed */
360
+ retryable?: boolean;
361
+ }
362
+
363
+ export interface RateLimitState {
364
+ /** Is any limit exhausted? */
365
+ limited: boolean;
366
+
367
+ /** Recommended wait time in seconds */
368
+ retry_after?: number;
369
+
370
+ /** Per-window breakdown for smart orchestration */
371
+ windows?: RateLimitWindow[];
372
+ }
373
+
374
+ export interface RateLimitWindow {
375
+ /** Identifier: "requests_per_minute", "tokens_per_day", etc. */
376
+ name: string;
377
+
378
+ /** Known values: "requests", "tokens", "input_tokens", "output_tokens" */
379
+ resource: string;
380
+
381
+ /** Current remaining in this window */
382
+ remaining?: number;
383
+
384
+ /** Maximum for this window */
385
+ limit?: number;
386
+
387
+ /** Seconds until this window resets */
388
+ resets_in?: number;
389
+
390
+ /** Unix timestamp when this window resets */
391
+ reset_at?: number;
392
+ }
393
+
394
+ export interface ProviderData {
395
+ /** Provider name (e.g., "openai", "anthropic", "cerebras") */
396
+ provider?: string;
397
+
398
+ /** Model identifier */
399
+ model?: string;
400
+
401
+ /** Provider's request ID for debugging/support */
402
+ request_id?: string;
403
+
404
+ /** Raw HTTP headers from response (when backend exposes them) */
405
+ raw_headers?: Record<string, string>;
406
+
407
+ /** Any other provider-specific data */
408
+ [key: string]: any;
409
+ }
410
+
411
+ export interface AgentExecutor {
412
+ execute(input: Record<string, any>, context?: Record<string, any>): Promise<AgentResult>;
413
+ metadata?: Record<string, any>;
414
+ }
415
+
184
416
  export interface ExecutionType {
185
417
  /**
186
- * Execute a function with this strategy.
187
- *
188
- * @param fn - The async function to execute (typically agent.call)
189
- * @returns The result(s) according to strategy
418
+ * Execute an agent with this strategy.
190
419
  */
191
- execute<T>(fn: () => Promise<T>): Promise<T>;
420
+ execute(executor: AgentExecutor, input: Record<string, any>, context?: Record<string, any>): Promise<AgentResult>;
192
421
  }
193
422
 
194
423
  export interface ExecutionConfig {
@@ -227,6 +456,37 @@ export interface MachineHooks {
227
456
  onAction?(action: string, context: Record<string, any>): Record<string, any> | Promise<Record<string, any>>;
228
457
  }
229
458
 
459
+ export interface HooksRegistry {
460
+ /**
461
+ * Register a hooks implementation by name.
462
+ * @param name - Hook name referenced in machine config (e.g., "my-hooks")
463
+ * @param factory - Class constructor or factory function
464
+ */
465
+ register(name: string, factory: HooksFactory): void;
466
+
467
+ /**
468
+ * Resolve a HooksRef from machine config into a MachineHooks instance.
469
+ * - String: lookup by name, no args
470
+ * - HooksRefConfig: lookup by name, pass args to factory
471
+ * - Array: resolve each entry, combine with CompositeHooks
472
+ * @throws Error if name is not registered
473
+ */
474
+ resolve(ref: HooksRef): MachineHooks;
475
+
476
+ /** Check if a name is registered. */
477
+ has(name: string): boolean;
478
+ }
479
+
480
+ /**
481
+ * Factory for creating MachineHooks instances.
482
+ * Can be a class constructor or a function.
483
+ */
484
+ export type HooksFactory =
485
+ | { new (args?: Record<string, any>): MachineHooks }
486
+ | ((args?: Record<string, any>) => MachineHooks);
487
+
488
+ import { HooksRef, HooksRefConfig } from "./flatmachine";
489
+
230
490
  export interface LLMBackend {
231
491
  /** Total cost accumulated across all calls. */
232
492
  totalCost: number;
@@ -307,6 +567,7 @@ export interface MachineSnapshot {
307
567
  total_cost?: number;
308
568
  parent_execution_id?: string;
309
569
  pending_launches?: LaunchIntent[];
570
+ waiting_channel?: string;
310
571
  }
311
572
 
312
573
  export interface LaunchIntent {
@@ -316,21 +577,6 @@ export interface LaunchIntent {
316
577
  launched: boolean;
317
578
  }
318
579
 
319
- /**
320
- * REGISTRATION BACKEND:
321
- * ---------------------
322
- * Worker lifecycle management for distributed execution.
323
- *
324
- * SDKs MUST provide:
325
- * - SQLiteRegistrationBackend: For local deployments
326
- *
327
- * SDKs SHOULD provide:
328
- * - MemoryRegistrationBackend: For testing
329
- *
330
- * Implementation notes:
331
- * - Time units: Python reference SDK uses seconds for all interval values
332
- * - Stale threshold: SDKs SHOULD default to 2× heartbeat_interval if not specified
333
- */
334
580
  export interface RegistrationBackend {
335
581
  /**
336
582
  * Register a new worker.
@@ -388,21 +634,6 @@ export interface WorkerFilter {
388
634
  stale_threshold_seconds?: number; // Filter workers with old heartbeats
389
635
  }
390
636
 
391
- /**
392
- * WORK BACKEND:
393
- * -------------
394
- * Work distribution via named pools with atomic claim.
395
- *
396
- * SDKs MUST provide:
397
- * - SQLiteWorkBackend: For local deployments
398
- *
399
- * SDKs SHOULD provide:
400
- * - MemoryWorkBackend: For testing
401
- *
402
- * Implementation notes:
403
- * - Atomic claim: SDKs MUST ensure no two workers can claim the same job
404
- * - Test requirements: Include concurrent claim race condition tests
405
- */
406
637
  export interface WorkBackend {
407
638
  /**
408
639
  * Get a named work pool.
@@ -468,32 +699,89 @@ export interface WorkItem {
468
699
  // - "done": Successfully completed
469
700
  // - "poisoned": Failed max_retries times, will not be retried
470
701
 
702
+ export interface SignalBackend {
703
+ /**
704
+ * Send a signal to a named channel.
705
+ * @param channel - Channel name (e.g., "approval/task-001", "quota/openai")
706
+ * @param data - Signal payload (JSON-serializable)
707
+ * @returns Signal ID
708
+ */
709
+ send(channel: string, data: any): Promise<string>;
710
+
711
+ /**
712
+ * Atomically consume the next signal on a channel.
713
+ * Removes the signal from storage.
714
+ * @returns Signal or null if none pending
715
+ */
716
+ consume(channel: string): Promise<Signal | null>;
717
+
718
+ /**
719
+ * Peek at pending signals without consuming.
720
+ * @returns Array of pending signals on this channel
721
+ */
722
+ peek(channel: string): Promise<Signal[]>;
723
+
724
+ /**
725
+ * List channels that have pending signals.
726
+ * Used by dispatcher to find actionable channels.
727
+ */
728
+ channels(): Promise<string[]>;
729
+ }
730
+
731
+ export interface Signal {
732
+ id: string;
733
+ channel: string;
734
+ data: any;
735
+ created_at: string;
736
+ }
737
+
738
+ export interface TriggerBackend {
739
+ /**
740
+ * Signal that activity occurred on a channel.
741
+ * Implementation touches a file, writes to a socket, or no-ops.
742
+ *
743
+ * @param channel - Channel name
744
+ */
745
+ notify(channel: string): Promise<void>;
746
+ }
747
+
471
748
  export interface BackendConfig {
472
749
  /** Checkpoint storage. Default: memory */
473
- persistence?: "memory" | "local" | "redis" | "postgres" | "s3";
750
+ persistence?: "memory" | "local" | "sqlite" | "redis" | "postgres" | "s3" | "dynamodb";
474
751
 
475
752
  /** Execution locking. Default: none */
476
- locking?: "none" | "local" | "redis" | "consul";
753
+ locking?: "none" | "local" | "sqlite" | "redis" | "consul" | "dynamodb";
477
754
 
478
755
  /** Inter-machine results. Default: memory */
479
- results?: "memory" | "redis";
756
+ results?: "memory" | "redis" | "dynamodb";
480
757
 
481
758
  /** Worker registration. Default: memory */
482
- registration?: "memory" | "sqlite" | "redis";
759
+ registration?: "memory" | "sqlite" | "redis" | "dynamodb";
483
760
 
484
761
  /** Work pool. Default: memory */
485
- work?: "memory" | "sqlite" | "redis";
762
+ work?: "memory" | "sqlite" | "redis" | "dynamodb";
763
+
764
+ /** Signal storage. Default: memory */
765
+ signal?: "memory" | "sqlite" | "redis" | "dynamodb";
486
766
 
487
- /** Path for sqlite backends (registration and work share this) */
767
+ /** Trigger activation. Default: none */
768
+ trigger?: "none" | "file" | "socket";
769
+
770
+ /** Path for sqlite backends (registration, work, and signals share this) */
488
771
  sqlite_path?: string;
772
+
773
+ /** Base path for file triggers (default: /tmp/flatmachines) */
774
+ trigger_path?: string;
775
+
776
+ /** DynamoDB table name (single-table design) */
777
+ dynamodb_table?: string;
778
+
779
+ /** AWS region for DynamoDB */
780
+ aws_region?: string;
489
781
  }
490
782
 
491
- export const SPEC_VERSION = "0.9.0";
783
+ export const SPEC_VERSION = "2.0.0";
492
784
 
493
- /**
494
- * Wrapper interface for JSON schema generation.
495
- * Groups all runtime interfaces that SDKs must implement.
496
- */
497
785
  export interface SDKRuntimeWrapper {
498
786
  spec: "flatagents-runtime";
499
787
  spec_version: typeof SPEC_VERSION;
@@ -502,11 +790,14 @@ export interface SDKRuntimeWrapper {
502
790
  result_backend?: ResultBackend;
503
791
  execution_config?: ExecutionConfig;
504
792
  machine_hooks?: MachineHooks;
793
+ hooks_registry?: HooksRegistry;
505
794
  llm_backend?: LLMBackend;
506
795
  machine_invoker?: MachineInvoker;
507
796
  backend_config?: BackendConfig;
508
797
  machine_snapshot?: MachineSnapshot;
509
798
  registration_backend?: RegistrationBackend;
510
799
  work_backend?: WorkBackend;
800
+ signal_backend?: SignalBackend;
801
+ trigger_backend?: TriggerBackend;
511
802
  }
512
803
 
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "spec_version": {
13
13
  "type": "string",
14
- "const": "0.9.0"
14
+ "const": "2.0.0"
15
15
  },
16
16
  "execution_lock": {
17
17
  "$ref": "#/definitions/ExecutionLock"
@@ -28,6 +28,9 @@
28
28
  "machine_hooks": {
29
29
  "$ref": "#/definitions/MachineHooks"
30
30
  },
31
+ "hooks_registry": {
32
+ "$ref": "#/definitions/HooksRegistry"
33
+ },
31
34
  "llm_backend": {
32
35
  "$ref": "#/definitions/LLMBackend"
33
36
  },
@@ -45,19 +48,24 @@
45
48
  },
46
49
  "work_backend": {
47
50
  "$ref": "#/definitions/WorkBackend"
51
+ },
52
+ "signal_backend": {
53
+ "$ref": "#/definitions/SignalBackend"
54
+ },
55
+ "trigger_backend": {
56
+ "$ref": "#/definitions/TriggerBackend"
48
57
  }
49
58
  },
50
59
  "required": [
51
60
  "spec",
52
61
  "spec_version"
53
62
  ],
54
- "additionalProperties": false,
55
- "description": "Wrapper interface for JSON schema generation. Groups all runtime interfaces that SDKs must implement."
63
+ "additionalProperties": false
56
64
  },
57
65
  "ExecutionLock": {
58
66
  "type": "object",
59
67
  "additionalProperties": false,
60
- "description": "FlatAgents Runtime Interface Spec ==================================\n\nThis file defines the runtime interfaces that SDKs MUST implement to be considered compliant. These are NOT configuration schemas (see flatagent.d.ts and flatmachine.d.ts for those).\n\nREQUIRED IMPLEMENTATIONS:\n------------------------- - ExecutionLock: NoOpLock (MUST), LocalFileLock (SHOULD) - PersistenceBackend: MemoryBackend (MUST), LocalFileBackend (SHOULD) - ResultBackend: InMemoryResultBackend (MUST) - ExecutionType: Default, Retry, Parallel, MDAPVoting (MUST) - MachineHooks: Base interface (MUST) - RegistrationBackend: SQLiteRegistrationBackend (MUST), MemoryRegistrationBackend (SHOULD) - WorkBackend: SQLiteWorkBackend (MUST), MemoryWorkBackend (SHOULD)\n\nOPTIONAL IMPLEMENTATIONS:\n------------------------- - Distributed backends (Redis, Postgres, etc.) - LLMBackend (SDK may use native provider SDKs)\n\nEXECUTION LOCKING:\n------------------ Prevents concurrent execution of the same machine instance.\n\nSDKs MUST provide: - NoOpLock: For when locking is handled externally or disabled\n\nSDKs SHOULD provide: - LocalFileLock: For single-node deployments using fcntl/flock\n\nDistributed deployments should implement Redis/Consul/etcd locks.\n\nPERSISTENCE BACKEND:\n-------------------- Storage backend for machine checkpoints.\n\nSDKs MUST provide: - MemoryBackend: For testing and ephemeral runs\n\nSDKs SHOULD provide: - LocalFileBackend: For durable local storage with atomic writes\n\nRESULT BACKEND:\n--------------- Inter-machine communication via URI-addressed results.\n\nURI format: flatagents://{execution_id}/{path} - path is typically \"result\" or \"checkpoint\"\n\nSDKs MUST provide: - InMemoryResultBackend: For single-process execution\n\nEXECUTION TYPES:\n---------------- Execution strategy for agent calls.\n\nSDKs MUST implement all four types: - default: Single call, no retry - retry: Configurable backoffs with jitter - parallel: Run N samples, return all successes - mdap_voting: Multi-sample with consensus voting\n\nMACHINE HOOKS:\n-------------- Extension points for machine execution. All methods are optional and can be sync or async.\n\nSDKs SHOULD provide: - WebhookHooks: Send events to HTTP endpoint - CompositeHooks: Combine multiple hook implementations\n\nLLM BACKEND (OPTIONAL):\n----------------------- Abstraction over LLM providers.\n\nThis interface is OPTIONAL - SDKs may use provider SDKs directly. Useful for: - Unified retry/monitoring across providers - Provider-agnostic code - Testing with mock backends\n\nMACHINE INVOKER:\n---------------- Interface for invoking peer machines. Used internally by FlatMachine for `machine:` and `launch:` states.\n\nBACKEND CONFIGURATION:\n---------------------- Backend configuration for machine settings.\n\nExample in YAML: settings: backends: persistence: local locking: none results: memory"
68
+ "description": "FlatAgents Runtime Interface Spec ==================================\n\nThis file defines the runtime interfaces that SDKs MUST implement to be considered compliant. These are NOT configuration schemas (see flatagent.d.ts and flatmachine.d.ts for those).\n\nREQUIRED IMPLEMENTATIONS:\n------------------------- - ExecutionLock: NoOpLock (MUST), LocalFileLock (SHOULD) - PersistenceBackend: MemoryBackend (MUST), LocalFileBackend (SHOULD) - ResultBackend: InMemoryResultBackend (MUST) - ExecutionType: Default, Retry, Parallel, MDAPVoting (MUST) - MachineHooks: Base interface (MUST) - RegistrationBackend: SQLiteRegistrationBackend (MUST), MemoryRegistrationBackend (SHOULD) - WorkBackend: SQLiteWorkBackend (MUST), MemoryWorkBackend (SHOULD) - SignalBackend: MemorySignalBackend (MUST), SQLiteSignalBackend (SHOULD) - TriggerBackend: NoOpTrigger (MUST), FileTrigger (SHOULD)\n\nOPTIONAL IMPLEMENTATIONS:\n------------------------- - Distributed backends (Redis, Postgres, DynamoDB, etc.) - LLMBackend (SDK may use native provider SDKs)\n\nEXECUTION LOCKING:\n------------------ Prevents concurrent execution of the same machine instance.\n\nSDKs MUST provide: - NoOpLock: For when locking is handled externally or disabled\n\nSDKs SHOULD provide: - LocalFileLock: For single-node deployments using fcntl/flock\n\nDistributed deployments should implement Redis/Consul/etcd locks.\n\nPERSISTENCE BACKEND:\n-------------------- Storage backend for machine checkpoints.\n\nSDKs MUST provide: - MemoryBackend: For testing and ephemeral runs\n\nSDKs SHOULD provide: - LocalFileBackend: For durable local storage with atomic writes\n\nRESULT BACKEND:\n--------------- Inter-machine communication via URI-addressed results.\n\nURI format: flatagents://{execution_id}/{path} - path is typically \"result\" or \"checkpoint\"\n\nSDKs MUST provide: - InMemoryResultBackend: For single-process execution\n\nEXECUTION TYPES:\n---------------- Execution strategy for agent calls.\n\nSDKs MUST implement all four types: - default: Single call, no retry - retry: Configurable backoffs with jitter - parallel: Run N samples, return all successes - mdap_voting: Multi-sample with consensus voting\n\nMACHINE HOOKS:\n-------------- Extension points for machine execution. All methods are optional and can be sync or async.\n\nSDKs MUST provide: - HooksRegistry: Name-based registry for resolving hooks from config\n\nSDKs SHOULD provide: - WebhookHooks: Send events to HTTP endpoint - CompositeHooks: Combine multiple hook implementations\n\nHOOKS REGISTRY:\n--------------- Maps hook names (strings) to implementations. Machine configs reference hooks by name; the SDK's HooksRegistry resolves them at runtime.\n\nThis decouples machine configs from any specific language or file path, enabling the same YAML to work across Python, JavaScript, Rust, etc.\n\nSDKs MUST provide: - HooksRegistry with register(), resolve(), has()\n\nBuilt-in registrations (when available in the SDK): - \"logging\": LoggingHooks - \"webhook\": WebhookHooks (args: { url, timeout?, api_key? }) - \"metrics\": MetricsHooks - \"distributed-worker\": DistributedWorkerHooks\n\nLLM BACKEND (OPTIONAL):\n----------------------- Abstraction over LLM providers.\n\nThis interface is OPTIONAL - SDKs may use provider SDKs directly. Useful for: - Unified retry/monitoring across providers - Provider-agnostic code - Testing with mock backends\n\nMACHINE INVOKER:\n---------------- Interface for invoking peer machines. Used internally by FlatMachine for `machine:` and `launch:` states.\n\nAGENT RESULT:\n-------------- Universal result contract for agent execution across all backends. All fields are optional and use structured data (no classes) for cross-language and cross-process/network compatibility.\n\nREGISTRATION BACKEND:\n--------------------- Worker lifecycle management for distributed execution.\n\nSDKs MUST provide: - SQLiteRegistrationBackend: For local deployments\n\nSDKs SHOULD provide: - MemoryRegistrationBackend: For testing\n\nImplementation notes: - Time units: Python reference SDK uses seconds for all interval values - Stale threshold: SDKs SHOULD default to 2× heartbeat_interval if not specified\n\nWORK BACKEND:\n------------- Work distribution via named pools with atomic claim.\n\nSDKs MUST provide: - SQLiteWorkBackend: For local deployments\n\nSDKs SHOULD provide: - MemoryWorkBackend: For testing\n\nImplementation notes: - Atomic claim: SDKs MUST ensure no two workers can claim the same job - Test requirements: Include concurrent claim race condition tests\n\nSIGNAL BACKEND:\n--------------- Durable signal storage for cross-process machine activation.\n\nSignals are named-channel messages that wake checkpointed machines. A machine paused at a `wait_for` state checkpoints with a `waiting_channel` tag and exits (no process running). When a signal arrives on that channel, a dispatcher finds matching checkpoints and resumes them.\n\nSignal data becomes the `wait_for` state's output — accessible via output.* in output_to_context templates. No new template syntax needed.\n\nChannel semantics: - Addressed: \"approval/task-001\" → one waiting machine - Broadcast: \"quota/openai\" → N waiting machines (dispatcher controls limit)\n\nSDKs MUST provide: - MemorySignalBackend: For testing and single-process use\n\nSDKs SHOULD provide: - SQLiteSignalBackend: For durable local use\n\nTRIGGER BACKEND:\n---------------- Process activation when signals or work arrive. The OS-native equivalent of Lambda triggers / DynamoDB Streams.\n\nCalled after SignalBackend.send() or WorkPool.push() to wake a dispatcher process. The dispatcher then queries for matching checkpoints and resumes machines.\n\nDeployment mapping: - NoOpTrigger: Consumer already running (polling/in-process) - FileTrigger: Touch file → launchd WatchPaths (macOS) / systemd PathChanged (Linux) - SocketTrigger: UDS notify → in-host low-latency wake (optional) - DynamoDB: Implicit via Streams → Lambda (no application code)\n\nSDKs MUST provide: - NoOpTrigger: For in-process and polling consumers\n\nSDKs SHOULD provide: - FileTrigger: For OS-level activation with zero running processes\n\nBACKEND CONFIGURATION:\n---------------------- Backend configuration for machine settings.\n\nExample in YAML: settings: backends: persistence: local locking: none results: memory"
61
69
  },
62
70
  "PersistenceBackend": {
63
71
  "type": "object",
@@ -107,6 +115,10 @@
107
115
  "type": "object",
108
116
  "additionalProperties": false
109
117
  },
118
+ "HooksRegistry": {
119
+ "type": "object",
120
+ "additionalProperties": false
121
+ },
110
122
  "LLMBackend": {
111
123
  "type": "object",
112
124
  "properties": {
@@ -137,9 +149,11 @@
137
149
  "enum": [
138
150
  "memory",
139
151
  "local",
152
+ "sqlite",
140
153
  "redis",
141
154
  "postgres",
142
- "s3"
155
+ "s3",
156
+ "dynamodb"
143
157
  ],
144
158
  "description": "Checkpoint storage. Default: memory"
145
159
  },
@@ -148,8 +162,10 @@
148
162
  "enum": [
149
163
  "none",
150
164
  "local",
165
+ "sqlite",
151
166
  "redis",
152
- "consul"
167
+ "consul",
168
+ "dynamodb"
153
169
  ],
154
170
  "description": "Execution locking. Default: none"
155
171
  },
@@ -157,7 +173,8 @@
157
173
  "type": "string",
158
174
  "enum": [
159
175
  "memory",
160
- "redis"
176
+ "redis",
177
+ "dynamodb"
161
178
  ],
162
179
  "description": "Inter-machine results. Default: memory"
163
180
  },
@@ -166,7 +183,8 @@
166
183
  "enum": [
167
184
  "memory",
168
185
  "sqlite",
169
- "redis"
186
+ "redis",
187
+ "dynamodb"
170
188
  ],
171
189
  "description": "Worker registration. Default: memory"
172
190
  },
@@ -175,13 +193,45 @@
175
193
  "enum": [
176
194
  "memory",
177
195
  "sqlite",
178
- "redis"
196
+ "redis",
197
+ "dynamodb"
179
198
  ],
180
199
  "description": "Work pool. Default: memory"
181
200
  },
201
+ "signal": {
202
+ "type": "string",
203
+ "enum": [
204
+ "memory",
205
+ "sqlite",
206
+ "redis",
207
+ "dynamodb"
208
+ ],
209
+ "description": "Signal storage. Default: memory"
210
+ },
211
+ "trigger": {
212
+ "type": "string",
213
+ "enum": [
214
+ "none",
215
+ "file",
216
+ "socket"
217
+ ],
218
+ "description": "Trigger activation. Default: none"
219
+ },
182
220
  "sqlite_path": {
183
221
  "type": "string",
184
- "description": "Path for sqlite backends (registration and work share this)"
222
+ "description": "Path for sqlite backends (registration, work, and signals share this)"
223
+ },
224
+ "trigger_path": {
225
+ "type": "string",
226
+ "description": "Base path for file triggers (default: /tmp/flatmachines)"
227
+ },
228
+ "dynamodb_table": {
229
+ "type": "string",
230
+ "description": "DynamoDB table name (single-table design)"
231
+ },
232
+ "aws_region": {
233
+ "type": "string",
234
+ "description": "AWS region for DynamoDB"
185
235
  }
186
236
  },
187
237
  "additionalProperties": false
@@ -230,6 +280,9 @@
230
280
  "items": {
231
281
  "$ref": "#/definitions/LaunchIntent"
232
282
  }
283
+ },
284
+ "waiting_channel": {
285
+ "type": "string"
233
286
  }
234
287
  },
235
288
  "required": [
@@ -269,13 +322,19 @@
269
322
  },
270
323
  "RegistrationBackend": {
271
324
  "type": "object",
272
- "additionalProperties": false,
273
- "description": "REGISTRATION BACKEND:\n--------------------- Worker lifecycle management for distributed execution.\n\nSDKs MUST provide: - SQLiteRegistrationBackend: For local deployments\n\nSDKs SHOULD provide: - MemoryRegistrationBackend: For testing\n\nImplementation notes: - Time units: Python reference SDK uses seconds for all interval values - Stale threshold: SDKs SHOULD default to 2× heartbeat_interval if not specified"
325
+ "additionalProperties": false
274
326
  },
275
327
  "WorkBackend": {
276
328
  "type": "object",
277
- "additionalProperties": false,
278
- "description": "WORK BACKEND:\n------------- Work distribution via named pools with atomic claim.\n\nSDKs MUST provide: - SQLiteWorkBackend: For local deployments\n\nSDKs SHOULD provide: - MemoryWorkBackend: For testing\n\nImplementation notes: - Atomic claim: SDKs MUST ensure no two workers can claim the same job - Test requirements: Include concurrent claim race condition tests"
329
+ "additionalProperties": false
330
+ },
331
+ "SignalBackend": {
332
+ "type": "object",
333
+ "additionalProperties": false
334
+ },
335
+ "TriggerBackend": {
336
+ "type": "object",
337
+ "additionalProperties": false
279
338
  }
280
339
  }
281
340
  }