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,140 @@
1
+ /**
2
+ * FlatAgents Model Profiles Schema
3
+ * =================================
4
+ *
5
+ * Model profiles provide reusable model configurations that agents can reference
6
+ * by name. This enables centralized model management and easy switching between
7
+ * configurations (e.g., development vs production, fast vs quality).
8
+ *
9
+ * STRUCTURE:
10
+ * ----------
11
+ * spec - Fixed string "flatprofiles"
12
+ * spec_version - Semver string
13
+ * data - Profile definitions and settings
14
+ * metadata - Extensibility layer
15
+ *
16
+ * DERIVED SCHEMAS:
17
+ * ----------------
18
+ * This file (/profiles.d.ts) is the SOURCE OF TRUTH for all profile schemas.
19
+ * Other schemas (JSON Schema, etc.) are DERIVED from this file using scripts.
20
+ * See: /scripts/generate-spec-assets.ts
21
+ *
22
+ * PROFILE RESOLUTION ORDER (low to high priority):
23
+ * ------------------------------------------------
24
+ * 1. default profile from profiles.yml (fallback when agent has no model config)
25
+ * 2. Named profile from agent's model: "profile-name"
26
+ * 3. Inline overrides from agent's model: { profile: "name", temperature: 0.5 }
27
+ * 4. override profile from profiles.yml (trumps all agent configs)
28
+ *
29
+ * AGENT USAGE:
30
+ * ------------
31
+ * String shorthand - profile lookup:
32
+ *
33
+ * data:
34
+ * model: "fast-cheap"
35
+ *
36
+ * Profile with overrides:
37
+ *
38
+ * data:
39
+ * model:
40
+ * profile: "fast-cheap"
41
+ * temperature: 0.9
42
+ *
43
+ * Inline config (no profile):
44
+ *
45
+ * data:
46
+ * model:
47
+ * provider: cerebras
48
+ * name: zai-glm-4.6
49
+ * temperature: 0.6
50
+ *
51
+ * EXAMPLE CONFIGURATION:
52
+ * ----------------------
53
+ *
54
+ * spec: flatprofiles
55
+ * spec_version: "0.7.0"
56
+ *
57
+ * data:
58
+ * model_profiles:
59
+ * fast-cheap:
60
+ * provider: cerebras
61
+ * name: zai-glm-4.6
62
+ * temperature: 0.6
63
+ * max_tokens: 2048
64
+ *
65
+ * smart-expensive:
66
+ * provider: anthropic
67
+ * name: claude-3-opus-20240229
68
+ * temperature: 0.3
69
+ * max_tokens: 4096
70
+ *
71
+ * local-dev:
72
+ * provider: ollama
73
+ * name: llama3
74
+ * base_url: http://localhost:11434
75
+ * temperature: 0.7
76
+ *
77
+ * deterministic:
78
+ * provider: openai
79
+ * name: gpt-4-turbo
80
+ * temperature: 0
81
+ * seed: 42
82
+ *
83
+ * default: fast-cheap
84
+ * # override: smart-expensive
85
+ *
86
+ * metadata:
87
+ * description: "Model profiles for the project"
88
+ *
89
+ * PROFILESDATA FIELDS:
90
+ * --------------------
91
+ * model_profiles - Named model profiles, keyed by profile name
92
+ * default - Default profile name, used when agent has no model config
93
+ * override - Override profile name, trumps all agent model configs
94
+ *
95
+ * MODELPROFILECONFIG FIELDS:
96
+ * --------------------------
97
+ * Defines all parameters for an LLM model.
98
+ * name - Model name (e.g., "gpt-4", "zai-glm-4.6", "claude-3-opus-20240229")
99
+ * provider - Provider name (e.g., "openai", "anthropic", "cerebras", "ollama")
100
+ * temperature - Sampling temperature (0.0 to 2.0)
101
+ * max_tokens - Maximum tokens to generate
102
+ * top_p - Nucleus sampling parameter (0.0 to 1.0)
103
+ * top_k - Top-k sampling parameter
104
+ * frequency_penalty - Frequency penalty (-2.0 to 2.0)
105
+ * presence_penalty - Presence penalty (-2.0 to 2.0)
106
+ * seed - Random seed for reproducibility
107
+ * base_url - Custom base URL for the API (e.g., for local models or proxies)
108
+ * stream - Enable streaming responses (default: false)
109
+ */
110
+
111
+ export const SPEC_VERSION = "1.0.0";
112
+
113
+ export interface ProfilesWrapper {
114
+ spec: "flatprofiles";
115
+ spec_version: string;
116
+ data: ProfilesData;
117
+ metadata?: Record<string, any>;
118
+ }
119
+
120
+ export interface ProfilesData {
121
+ model_profiles: Record<string, ModelProfileConfig>;
122
+ default?: string;
123
+ override?: string;
124
+ }
125
+
126
+ export interface ModelProfileConfig {
127
+ name: string;
128
+ provider?: string;
129
+ temperature?: number;
130
+ max_tokens?: number;
131
+ top_p?: number;
132
+ top_k?: number;
133
+ frequency_penalty?: number;
134
+ presence_penalty?: number;
135
+ seed?: number;
136
+ base_url?: string;
137
+ stream?: boolean;
138
+ }
139
+
140
+ export type FlatprofilesConfig = ProfilesWrapper;
@@ -0,0 +1,93 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$ref": "#/definitions/ProfilesWrapper",
4
+ "definitions": {
5
+ "ProfilesWrapper": {
6
+ "type": "object",
7
+ "properties": {
8
+ "spec": {
9
+ "type": "string",
10
+ "const": "flatprofiles"
11
+ },
12
+ "spec_version": {
13
+ "type": "string"
14
+ },
15
+ "data": {
16
+ "$ref": "#/definitions/ProfilesData"
17
+ },
18
+ "metadata": {
19
+ "type": "object"
20
+ }
21
+ },
22
+ "required": [
23
+ "spec",
24
+ "spec_version",
25
+ "data"
26
+ ],
27
+ "additionalProperties": false
28
+ },
29
+ "ProfilesData": {
30
+ "type": "object",
31
+ "properties": {
32
+ "model_profiles": {
33
+ "type": "object",
34
+ "additionalProperties": {
35
+ "$ref": "#/definitions/ModelProfileConfig"
36
+ }
37
+ },
38
+ "default": {
39
+ "type": "string"
40
+ },
41
+ "override": {
42
+ "type": "string"
43
+ }
44
+ },
45
+ "required": [
46
+ "model_profiles"
47
+ ],
48
+ "additionalProperties": false
49
+ },
50
+ "ModelProfileConfig": {
51
+ "type": "object",
52
+ "properties": {
53
+ "name": {
54
+ "type": "string"
55
+ },
56
+ "provider": {
57
+ "type": "string"
58
+ },
59
+ "temperature": {
60
+ "type": "number"
61
+ },
62
+ "max_tokens": {
63
+ "type": "number"
64
+ },
65
+ "top_p": {
66
+ "type": "number"
67
+ },
68
+ "top_k": {
69
+ "type": "number"
70
+ },
71
+ "frequency_penalty": {
72
+ "type": "number"
73
+ },
74
+ "presence_penalty": {
75
+ "type": "number"
76
+ },
77
+ "seed": {
78
+ "type": "number"
79
+ },
80
+ "base_url": {
81
+ "type": "string"
82
+ },
83
+ "stream": {
84
+ "type": "boolean"
85
+ }
86
+ },
87
+ "required": [
88
+ "name"
89
+ ],
90
+ "additionalProperties": false
91
+ }
92
+ }
93
+ }
@@ -0,0 +1,26 @@
1
+ export const SPEC_VERSION = "1.0.0";
2
+ export interface ProfilesWrapper {
3
+ spec: "flatprofiles";
4
+ spec_version: string;
5
+ data: ProfilesData;
6
+ metadata?: Record<string, any>;
7
+ }
8
+ export interface ProfilesData {
9
+ model_profiles: Record<string, ModelProfileConfig>;
10
+ default?: string;
11
+ override?: string;
12
+ }
13
+ export interface ModelProfileConfig {
14
+ name: string;
15
+ provider?: string;
16
+ temperature?: number;
17
+ max_tokens?: number;
18
+ top_p?: number;
19
+ top_k?: number;
20
+ frequency_penalty?: number;
21
+ presence_penalty?: number;
22
+ seed?: number;
23
+ base_url?: string;
24
+ stream?: boolean;
25
+ }
26
+ export type FlatprofilesConfig = ProfilesWrapper;
@@ -0,0 +1,222 @@
1
+ """
2
+ Result backends for FlatMachine inter-machine communication.
3
+
4
+ Result backends handle the storage and retrieval of machine execution results,
5
+ enabling machines to read outputs from peer machines they launched.
6
+
7
+ URI Scheme: flatagents://{execution_id}/[checkpoint|result]
8
+ """
9
+
10
+ import asyncio
11
+ import logging
12
+ from abc import ABC, abstractmethod
13
+ from dataclasses import dataclass, asdict
14
+ from typing import Any, Dict, Optional, Protocol, runtime_checkable
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ def make_uri(execution_id: str, path: str = "result") -> str:
20
+ """Generate a FlatAgents URI for a given execution and path.
21
+
22
+ Args:
23
+ execution_id: Unique execution identifier
24
+ path: URI path component (default: "result")
25
+
26
+ Returns:
27
+ URI string in format flatagents://{execution_id}/{path}
28
+ """
29
+ return f"flatagents://{execution_id}/{path}"
30
+
31
+
32
+ def parse_uri(uri: str) -> tuple[str, str]:
33
+ """Parse a FlatAgents URI into execution_id and path.
34
+
35
+ Args:
36
+ uri: URI in format flatagents://{execution_id}/{path}
37
+
38
+ Returns:
39
+ Tuple of (execution_id, path)
40
+
41
+ Raises:
42
+ ValueError: If URI format is invalid
43
+ """
44
+ if not uri.startswith("flatagents://"):
45
+ raise ValueError(f"Invalid FlatAgents URI: {uri}")
46
+
47
+ rest = uri[len("flatagents://"):]
48
+ parts = rest.split("/", 1)
49
+
50
+ if len(parts) == 1:
51
+ return parts[0], "result"
52
+ return parts[0], parts[1]
53
+
54
+
55
+ @dataclass
56
+ class LaunchIntent:
57
+ """
58
+ Launch intent for outbox pattern.
59
+ Recorded in checkpoint before launching to ensure exactly-once semantics.
60
+ """
61
+ execution_id: str
62
+ machine: str
63
+ input: Dict[str, Any]
64
+ launched: bool = False
65
+
66
+ def to_dict(self) -> Dict[str, Any]:
67
+ return asdict(self)
68
+
69
+ @classmethod
70
+ def from_dict(cls, data: Dict[str, Any]) -> "LaunchIntent":
71
+ return cls(**data)
72
+
73
+
74
+ @runtime_checkable
75
+ class ResultBackend(Protocol):
76
+ """
77
+ Protocol for result backends.
78
+
79
+ Result backends store and retrieve machine execution results,
80
+ enabling inter-machine communication.
81
+ """
82
+
83
+ async def write(self, uri: str, data: Any) -> None:
84
+ """Write data to a URI.
85
+
86
+ Args:
87
+ uri: FlatAgents URI (flatagents://{execution_id}/{path})
88
+ data: Data to store (will be serialized)
89
+ """
90
+ ...
91
+
92
+ async def read(self, uri: str, block: bool = True, timeout: Optional[float] = None) -> Any:
93
+ """Read data from a URI.
94
+
95
+ Args:
96
+ uri: FlatAgents URI
97
+ block: If True, wait for data to be available
98
+ timeout: Maximum seconds to wait (None = forever, only used if block=True)
99
+
100
+ Returns:
101
+ The stored data, or None if not found and block=False
102
+
103
+ Raises:
104
+ TimeoutError: If timeout expires while waiting
105
+ """
106
+ ...
107
+
108
+ async def exists(self, uri: str) -> bool:
109
+ """Check if data exists at a URI.
110
+
111
+ Args:
112
+ uri: FlatAgents URI
113
+
114
+ Returns:
115
+ True if data exists, False otherwise
116
+ """
117
+ ...
118
+
119
+ async def delete(self, uri: str) -> None:
120
+ """Delete data at a URI.
121
+
122
+ Args:
123
+ uri: FlatAgents URI
124
+ """
125
+ ...
126
+
127
+
128
+ class InMemoryResultBackend:
129
+ """
130
+ In-memory result backend for local execution.
131
+
132
+ Stores results in memory with asyncio Event-based blocking reads.
133
+ Suitable for single-process execution where machines run in the same process.
134
+ """
135
+
136
+ def __init__(self):
137
+ self._store: Dict[str, Any] = {}
138
+ self._events: Dict[str, asyncio.Event] = {}
139
+ self._lock = asyncio.Lock()
140
+
141
+ def _get_key(self, uri: str) -> str:
142
+ """Convert URI to storage key."""
143
+ execution_id, path = parse_uri(uri)
144
+ return f"{execution_id}/{path}"
145
+
146
+ def _get_event(self, key: str) -> asyncio.Event:
147
+ """Get or create an event for a key."""
148
+ if key not in self._events:
149
+ self._events[key] = asyncio.Event()
150
+ return self._events[key]
151
+
152
+ async def write(self, uri: str, data: Any) -> None:
153
+ """Write data to a URI."""
154
+ key = self._get_key(uri)
155
+ async with self._lock:
156
+ self._store[key] = data
157
+ event = self._get_event(key)
158
+ event.set()
159
+ logger.debug(f"ResultBackend: wrote to {uri}")
160
+
161
+ async def read(self, uri: str, block: bool = True, timeout: Optional[float] = None) -> Any:
162
+ """Read data from a URI."""
163
+ key = self._get_key(uri)
164
+
165
+ if not block:
166
+ return self._store.get(key)
167
+
168
+ event = self._get_event(key)
169
+
170
+ # Check if already available
171
+ if key in self._store:
172
+ return self._store[key]
173
+
174
+ # Wait for data
175
+ try:
176
+ await asyncio.wait_for(event.wait(), timeout=timeout)
177
+ except asyncio.TimeoutError:
178
+ raise TimeoutError(f"Timeout waiting for result at {uri}")
179
+
180
+ return self._store.get(key)
181
+
182
+ async def exists(self, uri: str) -> bool:
183
+ """Check if data exists at a URI."""
184
+ key = self._get_key(uri)
185
+ return key in self._store
186
+
187
+ async def delete(self, uri: str) -> None:
188
+ """Delete data at a URI."""
189
+ key = self._get_key(uri)
190
+ async with self._lock:
191
+ self._store.pop(key, None)
192
+ if key in self._events:
193
+ del self._events[key]
194
+
195
+
196
+ # Singleton for shared in-memory backend
197
+ _default_backend: Optional[InMemoryResultBackend] = None
198
+
199
+
200
+ def get_default_result_backend() -> InMemoryResultBackend:
201
+ """Get the default shared in-memory result backend."""
202
+ global _default_backend
203
+ if _default_backend is None:
204
+ _default_backend = InMemoryResultBackend()
205
+ return _default_backend
206
+
207
+
208
+ def reset_default_result_backend() -> None:
209
+ """Reset the default result backend (for testing)."""
210
+ global _default_backend
211
+ _default_backend = None
212
+
213
+
214
+ __all__ = [
215
+ "ResultBackend",
216
+ "InMemoryResultBackend",
217
+ "LaunchIntent",
218
+ "make_uri",
219
+ "parse_uri",
220
+ "get_default_result_backend",
221
+ "reset_default_result_backend",
222
+ ]