flatmachines 1.0.0__py3-none-any.whl

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 (41) hide show
  1. flatmachines/__init__.py +136 -0
  2. flatmachines/actions.py +408 -0
  3. flatmachines/adapters/__init__.py +38 -0
  4. flatmachines/adapters/flatagent.py +86 -0
  5. flatmachines/adapters/pi_agent_bridge.py +127 -0
  6. flatmachines/adapters/pi_agent_runner.mjs +99 -0
  7. flatmachines/adapters/smolagents.py +125 -0
  8. flatmachines/agents.py +144 -0
  9. flatmachines/assets/MACHINES.md +141 -0
  10. flatmachines/assets/README.md +11 -0
  11. flatmachines/assets/__init__.py +0 -0
  12. flatmachines/assets/flatagent.d.ts +219 -0
  13. flatmachines/assets/flatagent.schema.json +271 -0
  14. flatmachines/assets/flatagent.slim.d.ts +58 -0
  15. flatmachines/assets/flatagents-runtime.d.ts +523 -0
  16. flatmachines/assets/flatagents-runtime.schema.json +281 -0
  17. flatmachines/assets/flatagents-runtime.slim.d.ts +187 -0
  18. flatmachines/assets/flatmachine.d.ts +403 -0
  19. flatmachines/assets/flatmachine.schema.json +620 -0
  20. flatmachines/assets/flatmachine.slim.d.ts +106 -0
  21. flatmachines/assets/profiles.d.ts +140 -0
  22. flatmachines/assets/profiles.schema.json +93 -0
  23. flatmachines/assets/profiles.slim.d.ts +26 -0
  24. flatmachines/backends.py +222 -0
  25. flatmachines/distributed.py +835 -0
  26. flatmachines/distributed_hooks.py +351 -0
  27. flatmachines/execution.py +638 -0
  28. flatmachines/expressions/__init__.py +60 -0
  29. flatmachines/expressions/cel.py +101 -0
  30. flatmachines/expressions/simple.py +166 -0
  31. flatmachines/flatmachine.py +1263 -0
  32. flatmachines/hooks.py +381 -0
  33. flatmachines/locking.py +69 -0
  34. flatmachines/monitoring.py +505 -0
  35. flatmachines/persistence.py +213 -0
  36. flatmachines/run.py +117 -0
  37. flatmachines/utils.py +166 -0
  38. flatmachines/validation.py +79 -0
  39. flatmachines-1.0.0.dist-info/METADATA +390 -0
  40. flatmachines-1.0.0.dist-info/RECORD +41 -0
  41. flatmachines-1.0.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,523 @@
1
+ /**
2
+ * FlatAgents Runtime Interface Spec
3
+ * ==================================
4
+ *
5
+ * This file defines the runtime interfaces that SDKs MUST implement
6
+ * to be considered compliant. These are NOT configuration schemas
7
+ * (see flatagent.d.ts and flatmachine.d.ts for those).
8
+ *
9
+ * REQUIRED IMPLEMENTATIONS:
10
+ * -------------------------
11
+ * - ExecutionLock: NoOpLock (MUST), LocalFileLock (SHOULD)
12
+ * - PersistenceBackend: MemoryBackend (MUST), LocalFileBackend (SHOULD)
13
+ * - ResultBackend: InMemoryResultBackend (MUST)
14
+ * - ExecutionType: Default, Retry, Parallel, MDAPVoting (MUST)
15
+ * - MachineHooks: Base interface (MUST)
16
+ * - RegistrationBackend: SQLiteRegistrationBackend (MUST), MemoryRegistrationBackend (SHOULD)
17
+ * - WorkBackend: SQLiteWorkBackend (MUST), MemoryWorkBackend (SHOULD)
18
+ *
19
+ * OPTIONAL IMPLEMENTATIONS:
20
+ * -------------------------
21
+ * - Distributed backends (Redis, Postgres, etc.)
22
+ * - LLMBackend (SDK may use native provider SDKs)
23
+ *
24
+ * EXECUTION LOCKING:
25
+ * ------------------
26
+ * Prevents concurrent execution of the same machine instance.
27
+ *
28
+ * SDKs MUST provide:
29
+ * - NoOpLock: For when locking is handled externally or disabled
30
+ *
31
+ * SDKs SHOULD provide:
32
+ * - LocalFileLock: For single-node deployments using fcntl/flock
33
+ *
34
+ * Distributed deployments should implement Redis/Consul/etcd locks.
35
+ *
36
+ * PERSISTENCE BACKEND:
37
+ * --------------------
38
+ * Storage backend for machine checkpoints.
39
+ *
40
+ * SDKs MUST provide:
41
+ * - MemoryBackend: For testing and ephemeral runs
42
+ *
43
+ * SDKs SHOULD provide:
44
+ * - LocalFileBackend: For durable local storage with atomic writes
45
+ *
46
+ * RESULT BACKEND:
47
+ * ---------------
48
+ * Inter-machine communication via URI-addressed results.
49
+ *
50
+ * URI format: flatagents://{execution_id}/{path}
51
+ * - path is typically "result" or "checkpoint"
52
+ *
53
+ * SDKs MUST provide:
54
+ * - InMemoryResultBackend: For single-process execution
55
+ *
56
+ * EXECUTION TYPES:
57
+ * ----------------
58
+ * Execution strategy for agent calls.
59
+ *
60
+ * SDKs MUST implement all four types:
61
+ * - default: Single call, no retry
62
+ * - retry: Configurable backoffs with jitter
63
+ * - parallel: Run N samples, return all successes
64
+ * - mdap_voting: Multi-sample with consensus voting
65
+ *
66
+ * MACHINE HOOKS:
67
+ * --------------
68
+ * Extension points for machine execution.
69
+ * All methods are optional and can be sync or async.
70
+ *
71
+ * SDKs SHOULD provide:
72
+ * - WebhookHooks: Send events to HTTP endpoint
73
+ * - CompositeHooks: Combine multiple hook implementations
74
+ *
75
+ * LLM BACKEND (OPTIONAL):
76
+ * -----------------------
77
+ * Abstraction over LLM providers.
78
+ *
79
+ * This interface is OPTIONAL - SDKs may use provider SDKs directly.
80
+ * Useful for:
81
+ * - Unified retry/monitoring across providers
82
+ * - Provider-agnostic code
83
+ * - Testing with mock backends
84
+ *
85
+ * MACHINE INVOKER:
86
+ * ----------------
87
+ * Interface for invoking peer machines.
88
+ * Used internally by FlatMachine for `machine:` and `launch:` states.
89
+ *
90
+ * BACKEND CONFIGURATION:
91
+ * ----------------------
92
+ * Backend configuration for machine settings.
93
+ *
94
+ * Example in YAML:
95
+ * settings:
96
+ * backends:
97
+ * persistence: local
98
+ * locking: none
99
+ * results: memory
100
+ */
101
+
102
+ export interface ExecutionLock {
103
+ /**
104
+ * Attempt to acquire exclusive lock for the given key.
105
+ * MUST be non-blocking - returns immediately.
106
+ *
107
+ * @param key - Typically the execution_id
108
+ * @returns true if lock acquired, false if already held by another process
109
+ */
110
+ acquire(key: string): Promise<boolean>;
111
+
112
+ /**
113
+ * Release the lock for the given key.
114
+ * Safe to call even if lock not held.
115
+ */
116
+ release(key: string): Promise<void>;
117
+ }
118
+
119
+ export interface PersistenceBackend {
120
+ /**
121
+ * Save a checkpoint snapshot.
122
+ * MUST be atomic - either fully written or not at all.
123
+ *
124
+ * @param key - Checkpoint identifier (e.g., "{execution_id}/step_{step}")
125
+ * @param snapshot - The machine state to persist
126
+ */
127
+ save(key: string, snapshot: MachineSnapshot): Promise<void>;
128
+
129
+ /**
130
+ * Load a checkpoint snapshot.
131
+ * @returns The snapshot, or null if not found
132
+ */
133
+ load(key: string): Promise<MachineSnapshot | null>;
134
+
135
+ /**
136
+ * Delete a checkpoint.
137
+ * Safe to call if key doesn't exist.
138
+ */
139
+ delete(key: string): Promise<void>;
140
+
141
+ /**
142
+ * List all keys matching a prefix.
143
+ * Used to find all checkpoints for an execution.
144
+ *
145
+ * @param prefix - Key prefix to match (e.g., "{execution_id}/")
146
+ * @returns Array of matching keys, sorted lexicographically
147
+ */
148
+ list(prefix: string): Promise<string[]>;
149
+ }
150
+
151
+ export interface ResultBackend {
152
+ /**
153
+ * Write data to a URI.
154
+ * MUST notify any blocked readers.
155
+ */
156
+ write(uri: string, data: any): Promise<void>;
157
+
158
+ /**
159
+ * Read data from a URI.
160
+ *
161
+ * @param uri - The flatagents:// URI
162
+ * @param options.block - If true, wait until data is available
163
+ * @param options.timeout - Max ms to wait (undefined = forever)
164
+ * @returns The data, or undefined if not found and block=false
165
+ * @throws TimeoutError if timeout expires while blocking
166
+ */
167
+ read(uri: string, options?: {
168
+ block?: boolean;
169
+ timeout?: number;
170
+ }): Promise<any>;
171
+
172
+ /**
173
+ * Check if data exists at a URI without reading it.
174
+ */
175
+ exists(uri: string): Promise<boolean>;
176
+
177
+ /**
178
+ * Delete data at a URI.
179
+ * Safe to call if URI doesn't exist.
180
+ */
181
+ delete(uri: string): Promise<void>;
182
+ }
183
+
184
+ export interface AgentResult {
185
+ output?: Record<string, any> | null;
186
+ content?: string | null;
187
+ raw?: any;
188
+ usage?: Record<string, any> | null;
189
+ cost?: number | null;
190
+ metadata?: Record<string, any> | null;
191
+ }
192
+
193
+ export interface AgentExecutor {
194
+ execute(input: Record<string, any>, context?: Record<string, any>): Promise<AgentResult>;
195
+ metadata?: Record<string, any>;
196
+ }
197
+
198
+ export interface ExecutionType {
199
+ /**
200
+ * Execute an agent with this strategy.
201
+ */
202
+ execute(executor: AgentExecutor, input: Record<string, any>, context?: Record<string, any>): Promise<AgentResult>;
203
+ }
204
+
205
+ export interface ExecutionConfig {
206
+ type: "default" | "retry" | "parallel" | "mdap_voting";
207
+
208
+ // retry options
209
+ backoffs?: number[]; // seconds between retries
210
+ jitter?: number; // random factor (0-1)
211
+
212
+ // parallel/mdap options
213
+ n_samples?: number; // number of parallel calls
214
+ k_margin?: number; // mdap consensus threshold
215
+ max_candidates?: number;
216
+ }
217
+
218
+ export interface MachineHooks {
219
+ /** Called once at machine start. Can modify initial context. */
220
+ onMachineStart?(context: Record<string, any>): Record<string, any> | Promise<Record<string, any>>;
221
+
222
+ /** Called once at machine end. Can modify final output. */
223
+ onMachineEnd?(context: Record<string, any>, output: any): any | Promise<any>;
224
+
225
+ /** Called before each state execution. Can modify context. */
226
+ onStateEnter?(state: string, context: Record<string, any>): Record<string, any> | Promise<Record<string, any>>;
227
+
228
+ /** Called after each state execution. Can modify output. */
229
+ onStateExit?(state: string, context: Record<string, any>, output: any): any | Promise<any>;
230
+
231
+ /** Called before transition. Can redirect to different state. */
232
+ onTransition?(from: string, to: string, context: Record<string, any>): string | Promise<string>;
233
+
234
+ /** Called on error. Return state name to recover, null to propagate. */
235
+ onError?(state: string, error: Error, context: Record<string, any>): string | null | Promise<string | null>;
236
+
237
+ /** Called for custom actions defined in state config. */
238
+ onAction?(action: string, context: Record<string, any>): Record<string, any> | Promise<Record<string, any>>;
239
+ }
240
+
241
+ export interface LLMBackend {
242
+ /** Total cost accumulated across all calls. */
243
+ totalCost: number;
244
+
245
+ /** Total API calls made. */
246
+ totalApiCalls: number;
247
+
248
+ /** Call LLM and return content string. */
249
+ call(messages: Message[], options?: LLMOptions): Promise<string>;
250
+
251
+ /** Call LLM and return raw provider response. */
252
+ callRaw(messages: Message[], options?: LLMOptions): Promise<any>;
253
+ }
254
+
255
+ export interface Message {
256
+ role: "system" | "user" | "assistant" | "tool";
257
+ content: string;
258
+ tool_call_id?: string;
259
+ tool_calls?: ToolCall[];
260
+ }
261
+
262
+ export interface ToolCall {
263
+ id: string;
264
+ type: "function";
265
+ function: {
266
+ name: string;
267
+ arguments: string; // JSON string
268
+ };
269
+ }
270
+
271
+ export interface LLMOptions {
272
+ temperature?: number;
273
+ max_tokens?: number;
274
+ tools?: ToolDefinition[];
275
+ response_format?: { type: "json_object" } | { type: "text" };
276
+ }
277
+
278
+ export interface ToolDefinition {
279
+ type: "function";
280
+ function: {
281
+ name: string;
282
+ description?: string;
283
+ parameters?: Record<string, any>; // JSON Schema
284
+ };
285
+ }
286
+
287
+ export interface MachineInvoker {
288
+ /**
289
+ * Invoke a machine and wait for result.
290
+ */
291
+ invoke(
292
+ machineName: string,
293
+ input: Record<string, any>,
294
+ options?: { timeout?: number }
295
+ ): Promise<Record<string, any>>;
296
+
297
+ /**
298
+ * Launch a machine fire-and-forget style.
299
+ * @returns The execution_id of the launched machine
300
+ */
301
+ launch(
302
+ machineName: string,
303
+ input: Record<string, any>
304
+ ): Promise<string>;
305
+ }
306
+
307
+ export interface MachineSnapshot {
308
+ execution_id: string;
309
+ machine_name: string;
310
+ spec_version: string;
311
+ current_state: string;
312
+ context: Record<string, any>;
313
+ step: number;
314
+ created_at: string;
315
+ event?: string;
316
+ output?: Record<string, any>;
317
+ total_api_calls?: number;
318
+ total_cost?: number;
319
+ parent_execution_id?: string;
320
+ pending_launches?: LaunchIntent[];
321
+ }
322
+
323
+ export interface LaunchIntent {
324
+ execution_id: string;
325
+ machine: string;
326
+ input: Record<string, any>;
327
+ launched: boolean;
328
+ }
329
+
330
+ /**
331
+ * REGISTRATION BACKEND:
332
+ * ---------------------
333
+ * Worker lifecycle management for distributed execution.
334
+ *
335
+ * SDKs MUST provide:
336
+ * - SQLiteRegistrationBackend: For local deployments
337
+ *
338
+ * SDKs SHOULD provide:
339
+ * - MemoryRegistrationBackend: For testing
340
+ *
341
+ * Implementation notes:
342
+ * - Time units: Python reference SDK uses seconds for all interval values
343
+ * - Stale threshold: SDKs SHOULD default to 2× heartbeat_interval if not specified
344
+ */
345
+ export interface RegistrationBackend {
346
+ /**
347
+ * Register a new worker.
348
+ * Creates a new worker record with status "active".
349
+ */
350
+ register(worker: WorkerRegistration): Promise<WorkerRecord>;
351
+
352
+ /**
353
+ * Update worker's last_heartbeat timestamp.
354
+ * Can optionally update metadata.
355
+ */
356
+ heartbeat(worker_id: string, metadata?: Record<string, any>): Promise<void>;
357
+
358
+ /**
359
+ * Update worker status.
360
+ * Status values (string, not enum for extensibility):
361
+ * - "active": Worker is running and healthy
362
+ * - "terminating": Worker received shutdown signal
363
+ * - "terminated": Worker exited cleanly
364
+ * - "lost": Worker failed heartbeat, presumed dead
365
+ */
366
+ updateStatus(worker_id: string, status: string): Promise<void>;
367
+
368
+ /**
369
+ * Get a worker record by ID.
370
+ * @returns The worker record, or null if not found
371
+ */
372
+ get(worker_id: string): Promise<WorkerRecord | null>;
373
+
374
+ /**
375
+ * List workers matching filter criteria.
376
+ */
377
+ list(filter?: WorkerFilter): Promise<WorkerRecord[]>;
378
+ }
379
+
380
+ export interface WorkerRegistration {
381
+ worker_id: string;
382
+ host?: string;
383
+ pid?: number;
384
+ capabilities?: string[]; // e.g., ["gpu", "paper-analysis"]
385
+ pool_id?: string; // Worker pool grouping
386
+ started_at: string;
387
+ }
388
+
389
+ export interface WorkerRecord extends WorkerRegistration {
390
+ status: string; // See status values in RegistrationBackend.updateStatus
391
+ last_heartbeat: string;
392
+ current_task_id?: string;
393
+ }
394
+
395
+ export interface WorkerFilter {
396
+ status?: string | string[];
397
+ capability?: string;
398
+ pool_id?: string;
399
+ stale_threshold_seconds?: number; // Filter workers with old heartbeats
400
+ }
401
+
402
+ /**
403
+ * WORK BACKEND:
404
+ * -------------
405
+ * Work distribution via named pools with atomic claim.
406
+ *
407
+ * SDKs MUST provide:
408
+ * - SQLiteWorkBackend: For local deployments
409
+ *
410
+ * SDKs SHOULD provide:
411
+ * - MemoryWorkBackend: For testing
412
+ *
413
+ * Implementation notes:
414
+ * - Atomic claim: SDKs MUST ensure no two workers can claim the same job
415
+ * - Test requirements: Include concurrent claim race condition tests
416
+ */
417
+ export interface WorkBackend {
418
+ /**
419
+ * Get a named work pool.
420
+ * Creates the pool if it doesn't exist.
421
+ */
422
+ pool(name: string): WorkPool;
423
+ }
424
+
425
+ export interface WorkPool {
426
+ /**
427
+ * Add work item to the pool.
428
+ * @param item - The work data (will be JSON serialized)
429
+ * @param options.max_retries - Max retry attempts before poisoning (default: 3)
430
+ * @returns The item ID
431
+ */
432
+ push(item: any, options?: { max_retries?: number }): Promise<string>;
433
+
434
+ /**
435
+ * Atomically claim next available item.
436
+ * MUST be atomic - no two workers can claim the same job.
437
+ * @returns The claimed item, or null if pool is empty
438
+ */
439
+ claim(worker_id: string): Promise<WorkItem | null>;
440
+
441
+ /**
442
+ * Mark item as complete.
443
+ * Sets status to "done" and stores result.
444
+ */
445
+ complete(item_id: string, result?: any): Promise<void>;
446
+
447
+ /**
448
+ * Mark item as failed.
449
+ * Increments attempts. If attempts >= max_retries, marks as "poisoned".
450
+ * Otherwise returns to "pending" status for retry.
451
+ */
452
+ fail(item_id: string, error?: string): Promise<void>;
453
+
454
+ /**
455
+ * Get pool depth (unclaimed pending items).
456
+ */
457
+ size(): Promise<number>;
458
+
459
+ /**
460
+ * Release all jobs claimed by a worker.
461
+ * Used for stale worker cleanup.
462
+ * @returns Number of jobs released
463
+ */
464
+ releaseByWorker(worker_id: string): Promise<number>;
465
+ }
466
+
467
+ export interface WorkItem {
468
+ id: string;
469
+ data: any;
470
+ claimed_by?: string;
471
+ claimed_at?: string;
472
+ attempts: number;
473
+ max_retries: number; // default: 3
474
+ }
475
+
476
+ // Job status values (string):
477
+ // - "pending": Available for claim
478
+ // - "claimed": Currently being processed
479
+ // - "done": Successfully completed
480
+ // - "poisoned": Failed max_retries times, will not be retried
481
+
482
+ export interface BackendConfig {
483
+ /** Checkpoint storage. Default: memory */
484
+ persistence?: "memory" | "local" | "redis" | "postgres" | "s3";
485
+
486
+ /** Execution locking. Default: none */
487
+ locking?: "none" | "local" | "redis" | "consul";
488
+
489
+ /** Inter-machine results. Default: memory */
490
+ results?: "memory" | "redis";
491
+
492
+ /** Worker registration. Default: memory */
493
+ registration?: "memory" | "sqlite" | "redis";
494
+
495
+ /** Work pool. Default: memory */
496
+ work?: "memory" | "sqlite" | "redis";
497
+
498
+ /** Path for sqlite backends (registration and work share this) */
499
+ sqlite_path?: string;
500
+ }
501
+
502
+ export const SPEC_VERSION = "1.0.0";
503
+
504
+ /**
505
+ * Wrapper interface for JSON schema generation.
506
+ * Groups all runtime interfaces that SDKs must implement.
507
+ */
508
+ export interface SDKRuntimeWrapper {
509
+ spec: "flatagents-runtime";
510
+ spec_version: typeof SPEC_VERSION;
511
+ execution_lock?: ExecutionLock;
512
+ persistence_backend?: PersistenceBackend;
513
+ result_backend?: ResultBackend;
514
+ execution_config?: ExecutionConfig;
515
+ machine_hooks?: MachineHooks;
516
+ llm_backend?: LLMBackend;
517
+ machine_invoker?: MachineInvoker;
518
+ backend_config?: BackendConfig;
519
+ machine_snapshot?: MachineSnapshot;
520
+ registration_backend?: RegistrationBackend;
521
+ work_backend?: WorkBackend;
522
+ }
523
+