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,136 @@
1
+ __version__ = "1.0.0"
2
+
3
+ from .flatmachine import FlatMachine
4
+ from .hooks import MachineHooks, LoggingHooks, MetricsHooks, CompositeHooks, WebhookHooks
5
+ from .actions import SubprocessInvoker, launch_machine
6
+ from .expressions import get_expression_engine, ExpressionEngine
7
+ from .execution import (
8
+ ExecutionType,
9
+ DefaultExecution,
10
+ ParallelExecution,
11
+ RetryExecution,
12
+ MDAPVotingExecution,
13
+ get_execution_type,
14
+ )
15
+ from .validation import (
16
+ validate_flatmachine_config,
17
+ get_flatmachine_schema,
18
+ get_asset,
19
+ ValidationWarning,
20
+ )
21
+ from .monitoring import (
22
+ setup_logging,
23
+ get_logger,
24
+ get_meter,
25
+ AgentMonitor,
26
+ track_operation,
27
+ )
28
+ from .backends import (
29
+ ResultBackend,
30
+ InMemoryResultBackend,
31
+ LaunchIntent,
32
+ make_uri,
33
+ parse_uri,
34
+ get_default_result_backend,
35
+ reset_default_result_backend,
36
+ )
37
+ from .persistence import (
38
+ PersistenceBackend,
39
+ LocalFileBackend,
40
+ MemoryBackend,
41
+ CheckpointManager,
42
+ MachineSnapshot,
43
+ )
44
+ from .locking import ExecutionLock, LocalFileLock, NoOpLock
45
+ from .agents import (
46
+ AgentExecutor,
47
+ AgentResult,
48
+ AgentRef,
49
+ AgentAdapter,
50
+ AgentAdapterContext,
51
+ AgentAdapterRegistry,
52
+ normalize_agent_ref,
53
+ coerce_agent_result,
54
+ )
55
+ from .distributed import (
56
+ WorkerRegistration,
57
+ WorkerRecord,
58
+ WorkerFilter,
59
+ WorkItem,
60
+ RegistrationBackend,
61
+ WorkBackend,
62
+ WorkPool,
63
+ MemoryRegistrationBackend,
64
+ MemoryWorkBackend,
65
+ SQLiteRegistrationBackend,
66
+ SQLiteWorkBackend,
67
+ create_registration_backend,
68
+ create_work_backend,
69
+ )
70
+ from .distributed_hooks import DistributedWorkerHooks
71
+
72
+ __all__ = [
73
+ "__version__",
74
+ "FlatMachine",
75
+ "MachineHooks",
76
+ "LoggingHooks",
77
+ "MetricsHooks",
78
+ "CompositeHooks",
79
+ "WebhookHooks",
80
+ "ExpressionEngine",
81
+ "get_expression_engine",
82
+ "ExecutionType",
83
+ "DefaultExecution",
84
+ "ParallelExecution",
85
+ "RetryExecution",
86
+ "MDAPVotingExecution",
87
+ "get_execution_type",
88
+ "validate_flatmachine_config",
89
+ "get_flatmachine_schema",
90
+ "get_asset",
91
+ "ValidationWarning",
92
+ "setup_logging",
93
+ "get_logger",
94
+ "get_meter",
95
+ "AgentMonitor",
96
+ "track_operation",
97
+ "ResultBackend",
98
+ "InMemoryResultBackend",
99
+ "LaunchIntent",
100
+ "make_uri",
101
+ "parse_uri",
102
+ "get_default_result_backend",
103
+ "reset_default_result_backend",
104
+ "PersistenceBackend",
105
+ "LocalFileBackend",
106
+ "MemoryBackend",
107
+ "CheckpointManager",
108
+ "MachineSnapshot",
109
+ "ExecutionLock",
110
+ "LocalFileLock",
111
+ "NoOpLock",
112
+ "AgentExecutor",
113
+ "AgentResult",
114
+ "AgentRef",
115
+ "AgentAdapter",
116
+ "AgentAdapterContext",
117
+ "AgentAdapterRegistry",
118
+ "normalize_agent_ref",
119
+ "coerce_agent_result",
120
+ "WorkerRegistration",
121
+ "WorkerRecord",
122
+ "WorkerFilter",
123
+ "WorkItem",
124
+ "RegistrationBackend",
125
+ "WorkBackend",
126
+ "WorkPool",
127
+ "MemoryRegistrationBackend",
128
+ "MemoryWorkBackend",
129
+ "SQLiteRegistrationBackend",
130
+ "SQLiteWorkBackend",
131
+ "create_registration_backend",
132
+ "create_work_backend",
133
+ "SubprocessInvoker",
134
+ "launch_machine",
135
+ "DistributedWorkerHooks",
136
+ ]
@@ -0,0 +1,408 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any, Dict, Optional, TYPE_CHECKING
3
+ import logging
4
+ import os
5
+
6
+ if TYPE_CHECKING:
7
+ from .flatmachine import FlatMachine
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ class Action(ABC):
12
+ """
13
+ Base class for state actions (when state has 'action:' key).
14
+ """
15
+
16
+ @abstractmethod
17
+ async def execute(
18
+ self,
19
+ action_name: str,
20
+ context: Dict[str, Any],
21
+ config: Dict[str, Any]
22
+ ) -> Dict[str, Any]:
23
+ """
24
+ Execute the action.
25
+ Returns modified context.
26
+ """
27
+ pass
28
+
29
+ class HookAction(Action):
30
+ """
31
+ Default action: delegates to machine hooks (on_action).
32
+ """
33
+
34
+ def __init__(self, hooks):
35
+ self.hooks = hooks
36
+
37
+ async def execute(
38
+ self,
39
+ action_name: str,
40
+ context: Dict[str, Any],
41
+ config: Dict[str, Any]
42
+ ) -> Dict[str, Any]:
43
+ import asyncio
44
+ result = self.hooks.on_action(action_name, context)
45
+ if asyncio.iscoroutine(result):
46
+ return await result
47
+ return result
48
+
49
+ # -------------------------------------------------------------------------
50
+ # Machine Invokers (Graph Execution)
51
+ # -------------------------------------------------------------------------
52
+
53
+ class MachineInvoker(ABC):
54
+ """
55
+ Interface for invoking other machines (graph execution).
56
+
57
+ See flatagents-runtime.d.ts for canonical interface definition.
58
+ """
59
+
60
+ @abstractmethod
61
+ async def invoke(
62
+ self,
63
+ caller_machine: 'FlatMachine',
64
+ target_config: Dict[str, Any],
65
+ input_data: Dict[str, Any],
66
+ execution_id: Optional[str] = None
67
+ ) -> Dict[str, Any]:
68
+ """
69
+ Invoke another machine and wait for result.
70
+
71
+ Args:
72
+ caller_machine: The machine initiating the call
73
+ target_config: Config dict for the target machine
74
+ input_data: Input to pass to the target machine
75
+ execution_id: Optional predetermined ID (for resume support)
76
+
77
+ Returns:
78
+ The target machine's output
79
+ """
80
+ pass
81
+
82
+ @abstractmethod
83
+ async def launch(
84
+ self,
85
+ caller_machine: 'FlatMachine',
86
+ target_config: Dict[str, Any],
87
+ input_data: Dict[str, Any],
88
+ execution_id: str
89
+ ) -> None:
90
+ """
91
+ Launch a machine fire-and-forget style.
92
+
93
+ The launched machine runs independently. Results are written
94
+ to the result backend using the execution_id.
95
+
96
+ Args:
97
+ caller_machine: The machine initiating the launch
98
+ target_config: Config dict for the target machine
99
+ input_data: Input to pass to the target machine
100
+ execution_id: The predetermined execution ID for the launched machine
101
+ """
102
+ pass
103
+
104
+ class InlineInvoker(MachineInvoker):
105
+ """
106
+ Default Invoker for local execution.
107
+
108
+ - invoke(): Runs target machine in same event loop, awaits result
109
+ - launch(): Creates background task, returns immediately
110
+
111
+ Both share the same persistence/lock backends as the caller.
112
+ """
113
+
114
+ def __init__(self):
115
+ # Track background tasks for cleanup
116
+ self._background_tasks: set = set()
117
+
118
+ async def invoke(
119
+ self,
120
+ caller_machine: 'FlatMachine',
121
+ target_config: Dict[str, Any],
122
+ input_data: Dict[str, Any],
123
+ execution_id: Optional[str] = None
124
+ ) -> Dict[str, Any]:
125
+ from .flatmachine import FlatMachine # lazy import to avoid cycle
126
+ import hashlib
127
+
128
+ target_name = target_config.get('data', {}).get('name', 'unknown')
129
+
130
+ # Generate execution_id if not provided
131
+ if not execution_id:
132
+ context_hash = hashlib.md5(str(sorted(input_data.items())).encode()).hexdigest()[:8]
133
+ execution_id = f"{caller_machine.execution_id}:peer:{target_name}:{context_hash}"
134
+
135
+ logger.info(f"Invoking peer machine: {target_name} (ID: {execution_id})")
136
+
137
+ target = FlatMachine(
138
+ config_dict=target_config,
139
+ persistence=caller_machine.persistence,
140
+ lock=caller_machine.lock,
141
+ result_backend=caller_machine.result_backend,
142
+ agent_registry=caller_machine.agent_registry,
143
+ _config_dir=caller_machine._config_dir,
144
+ _execution_id=execution_id,
145
+ _parent_execution_id=caller_machine.execution_id,
146
+ _profiles_dict=getattr(caller_machine, "_profiles_dict", None),
147
+ _profiles_file=getattr(caller_machine, "_profiles_file", None),
148
+ )
149
+
150
+ result = await target.execute(input=input_data, resume_from=execution_id)
151
+
152
+ # Aggregate stats back to caller
153
+ caller_machine.total_api_calls += target.total_api_calls
154
+ caller_machine.total_cost += target.total_cost
155
+
156
+ return result
157
+
158
+ async def launch(
159
+ self,
160
+ caller_machine: 'FlatMachine',
161
+ target_config: Dict[str, Any],
162
+ input_data: Dict[str, Any],
163
+ execution_id: str
164
+ ) -> None:
165
+ import asyncio
166
+ from .flatmachine import FlatMachine
167
+ from .backends import make_uri
168
+
169
+ target_name = target_config.get('data', {}).get('name', 'unknown')
170
+ logger.info(f"Launching peer machine (fire-and-forget): {target_name} (ID: {execution_id})")
171
+
172
+ async def _execute_and_write():
173
+ target = FlatMachine(
174
+ config_dict=target_config,
175
+ persistence=caller_machine.persistence,
176
+ lock=caller_machine.lock,
177
+ result_backend=caller_machine.result_backend,
178
+ agent_registry=caller_machine.agent_registry,
179
+ _config_dir=caller_machine._config_dir,
180
+ _execution_id=execution_id,
181
+ _parent_execution_id=caller_machine.execution_id,
182
+ _profiles_dict=getattr(caller_machine, "_profiles_dict", None),
183
+ _profiles_file=getattr(caller_machine, "_profiles_file", None),
184
+ )
185
+
186
+ try:
187
+ result = await target.execute(input=input_data)
188
+ # Write result to backend so parent can read if needed
189
+ uri = make_uri(execution_id, "result")
190
+ await caller_machine.result_backend.write(uri, result)
191
+ except Exception as e:
192
+ uri = make_uri(execution_id, "result")
193
+ await caller_machine.result_backend.write(uri, {
194
+ "_error": str(e),
195
+ "_error_type": type(e).__name__
196
+ })
197
+ raise
198
+
199
+ # Create background task
200
+ task = asyncio.create_task(_execute_and_write())
201
+ caller_machine._background_tasks.add(task)
202
+ task.add_done_callback(caller_machine._background_tasks.discard)
203
+
204
+
205
+ class QueueInvoker(MachineInvoker):
206
+ """
207
+ Invoker that enqueues launches to an external queue.
208
+
209
+ For production deployments using SQS, Cloud Tasks, etc.
210
+ Subclass and implement _enqueue() for your queue provider.
211
+ """
212
+
213
+ async def invoke(
214
+ self,
215
+ caller_machine: 'FlatMachine',
216
+ target_config: Dict[str, Any],
217
+ input_data: Dict[str, Any],
218
+ execution_id: Optional[str] = None
219
+ ) -> Dict[str, Any]:
220
+ # For queue-based invocation, we launch and then poll for result
221
+ import uuid
222
+ from .backends import make_uri
223
+
224
+ if not execution_id:
225
+ execution_id = str(uuid.uuid4())
226
+
227
+ await self.launch(caller_machine, target_config, input_data, execution_id)
228
+
229
+ # Block until result is available
230
+ uri = make_uri(execution_id, "result")
231
+ return await caller_machine.result_backend.read(uri, block=True)
232
+
233
+ async def launch(
234
+ self,
235
+ caller_machine: 'FlatMachine',
236
+ target_config: Dict[str, Any],
237
+ input_data: Dict[str, Any],
238
+ execution_id: str
239
+ ) -> None:
240
+ await self._enqueue(execution_id, target_config, input_data)
241
+
242
+ async def _enqueue(
243
+ self,
244
+ execution_id: str,
245
+ config: Dict[str, Any],
246
+ input_data: Dict[str, Any]
247
+ ) -> None:
248
+ """Override in subclass to enqueue to your queue provider."""
249
+ raise NotImplementedError("Subclass must implement _enqueue()")
250
+
251
+
252
+ class SubprocessInvoker(MachineInvoker):
253
+ """
254
+ Invoker that launches machines as independent subprocesses.
255
+
256
+ For local distributed execution where each worker is a separate process.
257
+ Used by the parallelization checker to spawn worker machines.
258
+
259
+ The subprocess runs `python -m flatmachines.run` with the machine config,
260
+ enabling true process isolation and independent lifecycle.
261
+ """
262
+
263
+ def __init__(self,
264
+ machine_path: Optional[str] = None,
265
+ working_dir: Optional[str] = None):
266
+ """
267
+ Args:
268
+ machine_path: Base path for resolving machine configs
269
+ working_dir: Working directory for spawned processes
270
+ """
271
+ self.machine_path = machine_path
272
+ self.working_dir = working_dir
273
+
274
+ async def invoke(
275
+ self,
276
+ caller_machine: 'FlatMachine',
277
+ target_config: Dict[str, Any],
278
+ input_data: Dict[str, Any],
279
+ execution_id: Optional[str] = None
280
+ ) -> Dict[str, Any]:
281
+ """Launch subprocess and poll for result."""
282
+ import uuid
283
+ from .backends import make_uri
284
+
285
+ if not execution_id:
286
+ execution_id = str(uuid.uuid4())
287
+
288
+ await self.launch(caller_machine, target_config, input_data, execution_id)
289
+
290
+ # Block until result is available
291
+ uri = make_uri(execution_id, "result")
292
+ return await caller_machine.result_backend.read(uri, block=True)
293
+
294
+ async def launch(
295
+ self,
296
+ caller_machine: 'FlatMachine',
297
+ target_config: Dict[str, Any],
298
+ input_data: Dict[str, Any],
299
+ execution_id: str
300
+ ) -> None:
301
+ """Launch machine as independent subprocess (fire-and-forget)."""
302
+ import subprocess
303
+ import sys
304
+ import json
305
+ import tempfile
306
+ import os
307
+
308
+ target_name = target_config.get('data', {}).get('name', 'unknown')
309
+ logger.info(f"Launching subprocess: {target_name} (ID: {execution_id})")
310
+
311
+ # Write config to temp file for subprocess to read
312
+ with tempfile.NamedTemporaryFile(
313
+ mode='w',
314
+ suffix='.json',
315
+ delete=False,
316
+ dir=self.working_dir
317
+ ) as f:
318
+ json.dump(target_config, f)
319
+ config_path = f.name
320
+
321
+ # Build command
322
+ cmd = [
323
+ sys.executable, "-m", "flatmachines.run",
324
+ "--config", config_path,
325
+ "--input", json.dumps(input_data),
326
+ "--execution-id", execution_id,
327
+ ]
328
+
329
+ # Add parent execution ID for lineage tracking
330
+ if caller_machine.execution_id:
331
+ cmd.extend(["--parent-id", caller_machine.execution_id])
332
+
333
+ # Spawn detached process
334
+ cwd = self.working_dir or caller_machine._config_dir
335
+
336
+ # Use Popen for fire-and-forget
337
+ subprocess.Popen(
338
+ cmd,
339
+ cwd=cwd,
340
+ stdout=subprocess.DEVNULL,
341
+ stderr=subprocess.DEVNULL,
342
+ start_new_session=True # Detach from parent process group
343
+ )
344
+
345
+ logger.debug(f"Subprocess launched: {' '.join(cmd)}")
346
+
347
+
348
+ def launch_machine(
349
+ machine_config: str,
350
+ input_data: Dict[str, Any],
351
+ execution_id: Optional[str] = None,
352
+ working_dir: Optional[str] = None,
353
+ parent_id: Optional[str] = None
354
+ ) -> str:
355
+ """
356
+ Fire-and-forget machine execution via subprocess.
357
+
358
+ Standalone utility function for launching machines without an existing
359
+ FlatMachine context. Useful for trigger scripts and manual invocation.
360
+
361
+ Args:
362
+ machine_config: Path to machine YAML file
363
+ input_data: Input dictionary for the machine
364
+ execution_id: Optional predetermined execution ID
365
+ working_dir: Working directory for the subprocess
366
+ parent_id: Optional parent execution ID for lineage
367
+
368
+ Returns:
369
+ The execution ID of the launched machine
370
+
371
+ Example:
372
+ # From a trigger script
373
+ exec_id = launch_machine(
374
+ "job_worker.yml",
375
+ {"pool_id": "paper_analysis"},
376
+ working_dir="/path/to/project"
377
+ )
378
+ """
379
+ import subprocess
380
+ import sys
381
+ import json
382
+ import uuid
383
+
384
+ if not execution_id:
385
+ execution_id = str(uuid.uuid4())
386
+
387
+ cmd = [
388
+ sys.executable, "-m", "flatmachines.run",
389
+ "--config", machine_config,
390
+ "--input", json.dumps(input_data),
391
+ "--execution-id", execution_id,
392
+ ]
393
+
394
+ if parent_id:
395
+ cmd.extend(["--parent-id", parent_id])
396
+
397
+ subprocess.Popen(
398
+ cmd,
399
+ cwd=working_dir,
400
+ env=os.environ.copy(), # Pass parent environment (includes PYTHONPATH, venv)
401
+ stdout=subprocess.DEVNULL,
402
+ stderr=subprocess.DEVNULL,
403
+ start_new_session=True
404
+ )
405
+
406
+ logger.info(f"Launched machine subprocess: {machine_config} (ID: {execution_id})")
407
+ return execution_id
408
+
@@ -0,0 +1,38 @@
1
+ """Adapter registry helpers for FlatMachines."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Optional
6
+
7
+ from ..agents import AgentAdapterRegistry
8
+
9
+
10
+ def register_builtin_adapters(registry: AgentAdapterRegistry) -> None:
11
+ """Register built-in adapters if their dependencies are installed."""
12
+ try:
13
+ from .flatagent import FlatAgentAdapter
14
+
15
+ registry.register(FlatAgentAdapter())
16
+ except ImportError:
17
+ pass
18
+
19
+ try:
20
+ from .smolagents import SmolagentsAdapter
21
+
22
+ registry.register(SmolagentsAdapter())
23
+ except ImportError:
24
+ pass
25
+
26
+ try:
27
+ from .pi_agent_bridge import PiAgentBridgeAdapter
28
+
29
+ registry.register(PiAgentBridgeAdapter())
30
+ except ImportError:
31
+ pass
32
+
33
+
34
+ def create_registry(with_builtins: bool = True) -> AgentAdapterRegistry:
35
+ registry = AgentAdapterRegistry()
36
+ if with_builtins:
37
+ register_builtin_adapters(registry)
38
+ return registry
@@ -0,0 +1,86 @@
1
+ """FlatAgent adapter for FlatMachines."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, Optional
6
+
7
+ from ..agents import AgentAdapter, AgentAdapterContext, AgentExecutor, AgentRef, AgentResult
8
+
9
+ try:
10
+ from flatagents.flatagent import FlatAgent
11
+ from flatagents.profiles import (
12
+ discover_profiles_file,
13
+ load_profiles_from_file,
14
+ resolve_profiles_with_fallback,
15
+ )
16
+ except ImportError as exc: # pragma: no cover - optional dependency
17
+ raise ImportError("flatagents is required for FlatAgentAdapter") from exc
18
+
19
+
20
+ class FlatAgentExecutor(AgentExecutor):
21
+ def __init__(self, agent: FlatAgent):
22
+ self._agent = agent
23
+
24
+ @property
25
+ def metadata(self) -> Dict[str, Any]:
26
+ return getattr(self._agent, "metadata", {})
27
+
28
+ async def execute(
29
+ self,
30
+ input_data: Dict[str, Any],
31
+ context: Optional[Dict[str, Any]] = None,
32
+ ) -> AgentResult:
33
+ pre_calls = self._agent.total_api_calls
34
+ pre_cost = self._agent.total_cost
35
+
36
+ result = await self._agent.call(**input_data)
37
+
38
+ delta_calls = self._agent.total_api_calls - pre_calls
39
+ delta_cost = self._agent.total_cost - pre_cost
40
+
41
+ return AgentResult(
42
+ output=result.output,
43
+ content=result.content,
44
+ raw=result,
45
+ usage={"api_calls": delta_calls},
46
+ cost=delta_cost,
47
+ metadata=getattr(self._agent, "metadata", None),
48
+ )
49
+
50
+
51
+ class FlatAgentAdapter(AgentAdapter):
52
+ type_name = "flatagent"
53
+
54
+ def create_executor(
55
+ self,
56
+ *,
57
+ agent_name: str,
58
+ agent_ref: AgentRef,
59
+ context: AgentAdapterContext,
60
+ ) -> AgentExecutor:
61
+ profiles_file = discover_profiles_file(context.config_dir, context.profiles_file)
62
+ own_profiles = load_profiles_from_file(profiles_file) if profiles_file else None
63
+ profiles_dict = resolve_profiles_with_fallback(own_profiles, context.profiles_dict)
64
+
65
+ if agent_ref.ref:
66
+ return FlatAgentExecutor(
67
+ FlatAgent(
68
+ config_file=self._resolve_ref(agent_ref.ref, context),
69
+ profiles_dict=profiles_dict,
70
+ )
71
+ )
72
+ if agent_ref.config:
73
+ return FlatAgentExecutor(
74
+ FlatAgent(
75
+ config_dict=agent_ref.config,
76
+ profiles_dict=profiles_dict,
77
+ )
78
+ )
79
+ raise ValueError(f"FlatAgent reference missing ref/config for agent '{agent_name}'")
80
+
81
+ def _resolve_ref(self, ref: str, context: AgentAdapterContext) -> str:
82
+ import os
83
+
84
+ if os.path.isabs(ref):
85
+ return ref
86
+ return os.path.join(context.config_dir, ref)