agentic-fabric 1.2.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.
- agentic_fabric/__init__.py +90 -0
- agentic_fabric/__main__.py +9 -0
- agentic_fabric/agentic_data.py +284 -0
- agentic_fabric/base/__init__.py +150 -0
- agentic_fabric/base/archetypes.yaml +75 -0
- agentic_fabric/capabilities.py +130 -0
- agentic_fabric/config/__init__.py +10 -0
- agentic_fabric/config/agents.yaml +101 -0
- agentic_fabric/config/llm.py +285 -0
- agentic_fabric/config/tasks.yaml +177 -0
- agentic_fabric/core/__init__.py +17 -0
- agentic_fabric/core/decomposer.py +278 -0
- agentic_fabric/core/discovery.py +359 -0
- agentic_fabric/core/loader.py +207 -0
- agentic_fabric/core/manager.py +296 -0
- agentic_fabric/core/runner.py +70 -0
- agentic_fabric/fabric_agents/__init__.py +1 -0
- agentic_fabric/fabric_agents/connector_builder/__init__.py +1 -0
- agentic_fabric/fabric_agents/connector_builder/config/agents.yaml +16 -0
- agentic_fabric/fabric_agents/connector_builder/config/tasks.yaml +14 -0
- agentic_fabric/fabric_agents/connector_builder/connector_builder_fabric.py +103 -0
- agentic_fabric/main.py +591 -0
- agentic_fabric/runners/__init__.py +32 -0
- agentic_fabric/runners/base.py +369 -0
- agentic_fabric/runners/crewai_runner.py +206 -0
- agentic_fabric/runners/langgraph_runner.py +199 -0
- agentic_fabric/runners/local_cli_profiles.yaml +169 -0
- agentic_fabric/runners/local_cli_runner.py +491 -0
- agentic_fabric/runners/registry.py +165 -0
- agentic_fabric/runners/single_agent_runner.py +77 -0
- agentic_fabric/runners/strands_runner.py +214 -0
- agentic_fabric/tools/__init__.py +76 -0
- agentic_fabric/tools/adapters.py +128 -0
- agentic_fabric/tools/file_tools.py +391 -0
- agentic_fabric/tools/meshy_mcp.py +214 -0
- agentic_fabric/tools/registry.py +169 -0
- agentic_fabric/tools/scraping_tools.py +148 -0
- agentic_fabric/tools/vendor.py +110 -0
- agentic_fabric/tools/vendor_mcp.py +338 -0
- agentic_fabric/utils/__init__.py +6 -0
- agentic_fabric/utils/files.py +25 -0
- agentic_fabric-1.2.0.dist-info/METADATA +292 -0
- agentic_fabric-1.2.0.dist-info/RECORD +45 -0
- agentic_fabric-1.2.0.dist-info/WHEEL +4 -0
- agentic_fabric-1.2.0.dist-info/entry_points.txt +4 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""agentic-fabric: Framework-agnostic AI fabric orchestration.
|
|
2
|
+
|
|
3
|
+
Declare fabric agents once, run on CrewAI, LangGraph, or Strands.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
from agentic_fabric.core.decomposer import run_fabric_agent_auto, get_runner, detect_framework
|
|
7
|
+
from agentic_fabric.core.discovery import discover_packages, get_fabric_agent_config
|
|
8
|
+
from agentic_fabric.core.manager import ManagerAgent
|
|
9
|
+
|
|
10
|
+
# Auto-detect framework and run a fabric agent
|
|
11
|
+
packages = discover_packages()
|
|
12
|
+
config = get_fabric_agent_config(packages["my-package"], "reviewer")
|
|
13
|
+
result = run_fabric_agent_auto(config, inputs={"task": "..."})
|
|
14
|
+
|
|
15
|
+
# Or get a specific runner
|
|
16
|
+
runner = get_runner("crewai") # or "langgraph", "strands"
|
|
17
|
+
fabric_agent = runner.build_fabric_agent(config)
|
|
18
|
+
result = runner.run(fabric_agent, inputs)
|
|
19
|
+
|
|
20
|
+
# Or use a hierarchical manager agent
|
|
21
|
+
class MyManager(ManagerAgent):
|
|
22
|
+
def __init__(self):
|
|
23
|
+
super().__init__(fabric_agents={
|
|
24
|
+
"design": "design_review",
|
|
25
|
+
"implementation": "implementation_review"
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
async def execute_workflow(self, task):
|
|
29
|
+
design = await self.delegate_async("design", task)
|
|
30
|
+
return await self.delegate_async("implementation", design)
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
__version__ = version("agentic-fabric")
|
|
40
|
+
except PackageNotFoundError: # pragma: no cover - only hit when not installed
|
|
41
|
+
__version__ = "1.2.0"
|
|
42
|
+
|
|
43
|
+
# Core exports - framework-agnostic functionality
|
|
44
|
+
from agentic_fabric.agentic_data import AgenticData
|
|
45
|
+
from agentic_fabric.capabilities import (
|
|
46
|
+
AgentCapabilityProviderMixin,
|
|
47
|
+
AgentCapabilitySpec,
|
|
48
|
+
agent_capability,
|
|
49
|
+
runtime_capability,
|
|
50
|
+
tool_capability,
|
|
51
|
+
)
|
|
52
|
+
from agentic_fabric.core.decomposer import (
|
|
53
|
+
compose_fabric_agent,
|
|
54
|
+
detect_framework,
|
|
55
|
+
get_available_frameworks,
|
|
56
|
+
get_framework_info,
|
|
57
|
+
get_runner,
|
|
58
|
+
is_framework_available,
|
|
59
|
+
run_fabric_agent_auto,
|
|
60
|
+
)
|
|
61
|
+
from agentic_fabric.core.discovery import (
|
|
62
|
+
discover_all_framework_configs,
|
|
63
|
+
discover_packages,
|
|
64
|
+
get_fabric_agent_config,
|
|
65
|
+
list_fabric_agents,
|
|
66
|
+
)
|
|
67
|
+
from agentic_fabric.core.manager import ManagerAgent
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
__all__ = [
|
|
71
|
+
"AgentCapabilityProviderMixin",
|
|
72
|
+
"AgentCapabilitySpec",
|
|
73
|
+
"AgenticData",
|
|
74
|
+
"ManagerAgent",
|
|
75
|
+
"__version__",
|
|
76
|
+
"agent_capability",
|
|
77
|
+
"compose_fabric_agent",
|
|
78
|
+
"detect_framework",
|
|
79
|
+
"discover_all_framework_configs",
|
|
80
|
+
"discover_packages",
|
|
81
|
+
"get_available_frameworks",
|
|
82
|
+
"get_fabric_agent_config",
|
|
83
|
+
"get_framework_info",
|
|
84
|
+
"get_runner",
|
|
85
|
+
"is_framework_available",
|
|
86
|
+
"list_fabric_agents",
|
|
87
|
+
"run_fabric_agent_auto",
|
|
88
|
+
"runtime_capability",
|
|
89
|
+
"tool_capability",
|
|
90
|
+
]
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"""Agent runtime facade over the vendor and data layers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from types import MappingProxyType
|
|
7
|
+
from typing import Any, ClassVar
|
|
8
|
+
|
|
9
|
+
from agentic_fabric.runners.registry import RuntimeUnavailableError, install_command, runtime_info, runtime_names
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
try: # pragma: no cover - exercised when vendor-fabric is installed by consumers
|
|
13
|
+
from vendor_fabric.vendor_data import VendorData as _VendorDataBase
|
|
14
|
+
except ImportError: # pragma: no cover - default in this workspace until vendor-fabric is published
|
|
15
|
+
_VENDOR_FABRIC_AVAILABLE = False
|
|
16
|
+
|
|
17
|
+
class _VendorDataBase: # type: ignore[no-redef]
|
|
18
|
+
"""Small fallback that keeps AgenticData importable without vendor-fabric.
|
|
19
|
+
|
|
20
|
+
This is an importability shim, not a second architecture. It implements
|
|
21
|
+
the minimal ``VendorData`` surface needed for ``AgenticData`` to function.
|
|
22
|
+
Methods that require vendor-fabric raise clear ``ImportError`` guidance.
|
|
23
|
+
|
|
24
|
+
Omitted vs. real ``VendorData``: ``capabilities()`` returns an empty list
|
|
25
|
+
(so ``vendor_tools()`` degrades gracefully instead of crashing).
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, value: Any = None, *, fabric: Any = None, logger: Any = None, **_: Any) -> None:
|
|
29
|
+
self._agentic_value = value
|
|
30
|
+
self._active_provider: str | None = None
|
|
31
|
+
self._logger = logger
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def value(self) -> Any:
|
|
35
|
+
"""Return the wrapped value."""
|
|
36
|
+
return self._agentic_value
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def active_provider(self) -> str | None:
|
|
40
|
+
"""Return the active vendor provider, when available."""
|
|
41
|
+
return self._active_provider
|
|
42
|
+
|
|
43
|
+
def as_builtin(self) -> Any:
|
|
44
|
+
"""Return the wrapped value unchanged."""
|
|
45
|
+
return self._agentic_value
|
|
46
|
+
|
|
47
|
+
def cast(self, value: Any) -> _VendorDataBase:
|
|
48
|
+
"""Replace the wrapped value."""
|
|
49
|
+
self._agentic_value = value
|
|
50
|
+
return self
|
|
51
|
+
|
|
52
|
+
def open(self, provider_id: str, *, strict: bool = True, **_: Any) -> _VendorDataBase:
|
|
53
|
+
"""Record a provider or raise install guidance when strict."""
|
|
54
|
+
if strict:
|
|
55
|
+
msg = (
|
|
56
|
+
f"Provider '{provider_id}' requires vendor-fabric. "
|
|
57
|
+
"Install vendor-fabric after it is published, then reinstall agentic-fabric."
|
|
58
|
+
)
|
|
59
|
+
raise ImportError(msg)
|
|
60
|
+
self._active_provider = provider_id
|
|
61
|
+
return self
|
|
62
|
+
|
|
63
|
+
def call(self, operation: str, *_: Any, **__: Any) -> Any:
|
|
64
|
+
"""Raise clear guidance for vendor-backed operations."""
|
|
65
|
+
msg = f"Vendor operation '{operation}' requires vendor-fabric."
|
|
66
|
+
raise ImportError(msg)
|
|
67
|
+
|
|
68
|
+
def capabilities(self, provider: str | None = None, *, include_unavailable: bool = True) -> list[Any]:
|
|
69
|
+
"""Return an empty capability list without vendor-fabric.
|
|
70
|
+
|
|
71
|
+
Without vendor-fabric, no provider capabilities are available.
|
|
72
|
+
This lets ``vendor_tools()`` degrade gracefully (returns ``[]``).
|
|
73
|
+
"""
|
|
74
|
+
return []
|
|
75
|
+
|
|
76
|
+
else:
|
|
77
|
+
_VENDOR_FABRIC_AVAILABLE = True
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class AgenticData(_VendorDataBase):
|
|
81
|
+
"""VendorData extension with active runtime and fabric agent registry context."""
|
|
82
|
+
|
|
83
|
+
runtime_priority: ClassVar[tuple[str, ...]] = tuple(runtime_names())
|
|
84
|
+
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
value: Any = None,
|
|
88
|
+
*,
|
|
89
|
+
fabric: Any | None = None,
|
|
90
|
+
fabric_agents: Mapping[str, Mapping[str, Any]] | None = None,
|
|
91
|
+
logger: Any | None = None,
|
|
92
|
+
active_runtime: str | None = None,
|
|
93
|
+
**fabric_kwargs: Any,
|
|
94
|
+
) -> None:
|
|
95
|
+
"""Initialize data, registered fabric agents, and optional active runtime."""
|
|
96
|
+
super().__init__(value, fabric=fabric, logger=logger, **fabric_kwargs)
|
|
97
|
+
self._fabric_agents: dict[str, dict[str, Any]] = {
|
|
98
|
+
name: dict(config) for name, config in (fabric_agents or {}).items()
|
|
99
|
+
}
|
|
100
|
+
self._active_runtime: str | None = None
|
|
101
|
+
if active_runtime is not None:
|
|
102
|
+
self.use_runtime(active_runtime, strict=False)
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def fabric_agents(self) -> Mapping[str, Mapping[str, Any]]:
|
|
106
|
+
"""Return registered fabric agent definitions."""
|
|
107
|
+
return MappingProxyType(self._fabric_agents)
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def active_runtime(self) -> str | None:
|
|
111
|
+
"""Return the active runtime name, when one has been selected."""
|
|
112
|
+
return self._active_runtime
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def vendor_fabric_available(self) -> bool:
|
|
116
|
+
"""Return whether this import is backed by a real VendorData class."""
|
|
117
|
+
return _VENDOR_FABRIC_AVAILABLE
|
|
118
|
+
|
|
119
|
+
def cast(self, value: Any) -> AgenticData:
|
|
120
|
+
"""Mutate the wrapped data while preserving runtime context."""
|
|
121
|
+
super().cast(value)
|
|
122
|
+
return self
|
|
123
|
+
|
|
124
|
+
def register_fabric_agent(self, name: str, fabric_agent_config: Mapping[str, Any]) -> AgenticData:
|
|
125
|
+
"""Register a named fabric agent config for ``run_fabric_agent`` lookup."""
|
|
126
|
+
self._fabric_agents[name] = dict(fabric_agent_config)
|
|
127
|
+
return self
|
|
128
|
+
|
|
129
|
+
def unregister_fabric_agent(self, name: str) -> AgenticData:
|
|
130
|
+
"""Remove a named fabric agent config if it exists."""
|
|
131
|
+
self._fabric_agents.pop(name, None)
|
|
132
|
+
return self
|
|
133
|
+
|
|
134
|
+
def use_runtime(self, runtime: str, *, strict: bool = True) -> AgenticData:
|
|
135
|
+
"""Select the active runtime for future agent calls."""
|
|
136
|
+
normalized = runtime.strip().lower()
|
|
137
|
+
if normalized == "auto":
|
|
138
|
+
normalized = self.select_runtime()
|
|
139
|
+
if strict and not self.is_runtime_available(normalized):
|
|
140
|
+
raise RuntimeUnavailableError(normalized, install_command(normalized))
|
|
141
|
+
self._active_runtime = normalized
|
|
142
|
+
return self
|
|
143
|
+
|
|
144
|
+
def clear_runtime(self) -> AgenticData:
|
|
145
|
+
"""Clear the active runtime selection."""
|
|
146
|
+
self._active_runtime = None
|
|
147
|
+
return self
|
|
148
|
+
|
|
149
|
+
def runtimes(self) -> list[dict[str, Any]]:
|
|
150
|
+
"""Return runtime registry metadata with availability."""
|
|
151
|
+
info = runtime_info()
|
|
152
|
+
return list(info) if isinstance(info, list) else [info]
|
|
153
|
+
|
|
154
|
+
def runtime_info(self, runtime: str | None = None) -> list[dict[str, Any]] | dict[str, Any]:
|
|
155
|
+
"""Return runtime metadata with current availability."""
|
|
156
|
+
return runtime_info(runtime)
|
|
157
|
+
|
|
158
|
+
def is_runtime_available(self, runtime: str) -> bool:
|
|
159
|
+
"""Return whether a runtime is importable in the current environment."""
|
|
160
|
+
from agentic_fabric.core.decomposer import is_framework_available
|
|
161
|
+
|
|
162
|
+
return is_framework_available(runtime)
|
|
163
|
+
|
|
164
|
+
def select_runtime(
|
|
165
|
+
self,
|
|
166
|
+
runtime: str | None = None,
|
|
167
|
+
*,
|
|
168
|
+
fabric_agent_config: Mapping[str, Any] | None = None,
|
|
169
|
+
) -> str:
|
|
170
|
+
"""Select a runtime using explicit, active, manifest, then auto priority."""
|
|
171
|
+
required_runtime = _manifest_runtime(fabric_agent_config)
|
|
172
|
+
requested = _normalize_runtime(runtime)
|
|
173
|
+
active = _normalize_runtime(self._active_runtime)
|
|
174
|
+
|
|
175
|
+
if requested is not None and required_runtime is not None and requested != required_runtime:
|
|
176
|
+
msg = f"Fabric agent requires {required_runtime} but {requested} was requested"
|
|
177
|
+
raise ValueError(msg)
|
|
178
|
+
|
|
179
|
+
if requested is not None:
|
|
180
|
+
return _require_available(requested)
|
|
181
|
+
|
|
182
|
+
if active is not None:
|
|
183
|
+
if required_runtime is not None and active != required_runtime:
|
|
184
|
+
msg = f"Active runtime {active} conflicts with fabric agent requirement {required_runtime}"
|
|
185
|
+
raise ValueError(msg)
|
|
186
|
+
return _require_available(active)
|
|
187
|
+
|
|
188
|
+
if required_runtime is not None:
|
|
189
|
+
return _require_available(required_runtime)
|
|
190
|
+
|
|
191
|
+
from agentic_fabric.core.decomposer import detect_framework
|
|
192
|
+
|
|
193
|
+
return detect_framework()
|
|
194
|
+
|
|
195
|
+
def run_fabric_agent(
|
|
196
|
+
self,
|
|
197
|
+
fabric_agent: str | Mapping[str, Any],
|
|
198
|
+
inputs: Mapping[str, Any] | None = None,
|
|
199
|
+
*,
|
|
200
|
+
runtime: str | None = None,
|
|
201
|
+
**input_kwargs: Any,
|
|
202
|
+
) -> str:
|
|
203
|
+
"""Run a registered fabric agent by name or a direct fabric agent config."""
|
|
204
|
+
from agentic_fabric.core.decomposer import run_fabric_agent_auto
|
|
205
|
+
|
|
206
|
+
fabric_agent_config = self._lookup_fabric_agent(fabric_agent)
|
|
207
|
+
merged_inputs = dict(inputs or {})
|
|
208
|
+
merged_inputs.update(input_kwargs)
|
|
209
|
+
selected_runtime = self.select_runtime(runtime, fabric_agent_config=fabric_agent_config)
|
|
210
|
+
return run_fabric_agent_auto(fabric_agent_config, inputs=merged_inputs, framework=selected_runtime)
|
|
211
|
+
|
|
212
|
+
def call_runtime(self, capability: str, *args: Any, runtime: str | None = None, **kwargs: Any) -> Any:
|
|
213
|
+
"""Call a declared capability on a selected runtime runner."""
|
|
214
|
+
from agentic_fabric.core.decomposer import get_runner
|
|
215
|
+
|
|
216
|
+
selected_runtime = self.select_runtime(runtime)
|
|
217
|
+
runner = get_runner(selected_runtime)
|
|
218
|
+
return runner.call_capability(capability, *args, **kwargs)
|
|
219
|
+
|
|
220
|
+
def vendor_tools(
|
|
221
|
+
self,
|
|
222
|
+
provider: str | None = None,
|
|
223
|
+
*,
|
|
224
|
+
include_unavailable: bool = True,
|
|
225
|
+
) -> list[Any]:
|
|
226
|
+
"""Return agent-facing tools built from inherited vendor capabilities."""
|
|
227
|
+
from agentic_fabric.tools.vendor import vendor_capability_tools
|
|
228
|
+
|
|
229
|
+
return vendor_capability_tools(self, provider=provider, include_unavailable=include_unavailable)
|
|
230
|
+
|
|
231
|
+
def __getattr__(self, name: str) -> Any:
|
|
232
|
+
"""Expose ``run_<fabric_agent>`` helpers while preserving VendorData dispatch."""
|
|
233
|
+
if name.startswith("run_"):
|
|
234
|
+
fabric_agent_name = name.removeprefix("run_")
|
|
235
|
+
if fabric_agent_name in self._fabric_agents:
|
|
236
|
+
return lambda *args, **kwargs: self.run_fabric_agent(fabric_agent_name, *args, **kwargs)
|
|
237
|
+
|
|
238
|
+
# When real VendorData is installed, it may define __getattr__ for
|
|
239
|
+
# provider dispatch. Try the superclass, but fall through to a clean
|
|
240
|
+
# AttributeError if neither this class nor the superclass handles it.
|
|
241
|
+
if hasattr(super(), "__getattr__"):
|
|
242
|
+
return super().__getattr__(name) # type: ignore[misc]
|
|
243
|
+
raise AttributeError(f"{type(self).__name__!s} has no attribute {name!r}")
|
|
244
|
+
|
|
245
|
+
def __dir__(self) -> list[str]:
|
|
246
|
+
"""Include dynamic registered-fabric-agent helpers in introspection."""
|
|
247
|
+
dynamic = [f"run_{name}" for name in self._fabric_agents]
|
|
248
|
+
return sorted({*super().__dir__(), *dynamic})
|
|
249
|
+
|
|
250
|
+
def _lookup_fabric_agent(self, fabric_agent: str | Mapping[str, Any]) -> dict[str, Any]:
|
|
251
|
+
"""Return a fabric agent config from a name or direct mapping."""
|
|
252
|
+
if isinstance(fabric_agent, Mapping):
|
|
253
|
+
return dict(fabric_agent)
|
|
254
|
+
try:
|
|
255
|
+
return dict(self._fabric_agents[fabric_agent])
|
|
256
|
+
except KeyError as exc:
|
|
257
|
+
options = ", ".join(sorted(self._fabric_agents)) or "none registered"
|
|
258
|
+
msg = f"Unknown fabric agent '{fabric_agent}'. Registered fabric agents: {options}"
|
|
259
|
+
raise KeyError(msg) from exc
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _normalize_runtime(runtime: str | None) -> str | None:
|
|
263
|
+
"""Normalize runtime names and treat auto as no explicit choice."""
|
|
264
|
+
if runtime is None:
|
|
265
|
+
return None
|
|
266
|
+
normalized = runtime.strip().lower()
|
|
267
|
+
return None if normalized == "auto" else normalized
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _manifest_runtime(fabric_agent_config: Mapping[str, Any] | None) -> str | None:
|
|
271
|
+
"""Read runtime requirements from a fabric agent manifest/config mapping."""
|
|
272
|
+
if fabric_agent_config is None:
|
|
273
|
+
return None
|
|
274
|
+
runtime = fabric_agent_config.get("required_framework") or fabric_agent_config.get("runtime") or fabric_agent_config.get("framework")
|
|
275
|
+
return _normalize_runtime(str(runtime)) if runtime else None
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _require_available(runtime: str) -> str:
|
|
279
|
+
"""Return a runtime or raise install guidance."""
|
|
280
|
+
from agentic_fabric.core.decomposer import is_framework_available
|
|
281
|
+
|
|
282
|
+
if not is_framework_available(runtime):
|
|
283
|
+
raise RuntimeUnavailableError(runtime, install_command(runtime))
|
|
284
|
+
return runtime
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Base module - reusable agent archetypes and shared tool re-exports.
|
|
2
|
+
|
|
3
|
+
Archetypes are base templates that packages can extend in their own
|
|
4
|
+
``agents.yaml``. Use the ``extends`` field to inherit from an archetype:
|
|
5
|
+
|
|
6
|
+
.. code:: yaml
|
|
7
|
+
|
|
8
|
+
my_engineer:
|
|
9
|
+
extends: senior_engineer
|
|
10
|
+
variables:
|
|
11
|
+
language: Python
|
|
12
|
+
backstory: |
|
|
13
|
+
{base}
|
|
14
|
+
Additional context specific to my package...
|
|
15
|
+
|
|
16
|
+
The ``{base}`` placeholder in agent config is replaced with the
|
|
17
|
+
archetype's value. ``{language}`` and other ``variables`` are interpolated
|
|
18
|
+
into the resolved role/goal/backstory strings.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import logging
|
|
24
|
+
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Any
|
|
27
|
+
|
|
28
|
+
import yaml
|
|
29
|
+
|
|
30
|
+
from agentic_fabric.tools.file_tools import (
|
|
31
|
+
DirectoryListTool,
|
|
32
|
+
GameCodeReaderTool,
|
|
33
|
+
GameCodeWriterTool,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
logger = logging.getLogger(__name__)
|
|
38
|
+
|
|
39
|
+
_ARCHETYPES_PATH = Path(__file__).parent / "archetypes.yaml"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _load_archetypes() -> dict[str, dict[str, Any]]:
|
|
43
|
+
"""Load built-in archetypes from the bundled YAML file."""
|
|
44
|
+
try:
|
|
45
|
+
data = yaml.safe_load(_ARCHETYPES_PATH.read_text(encoding="utf-8"))
|
|
46
|
+
except OSError as exc:
|
|
47
|
+
logger.warning("Could not load archetypes.yaml: %s", exc)
|
|
48
|
+
return {}
|
|
49
|
+
if not isinstance(data, dict):
|
|
50
|
+
return {}
|
|
51
|
+
return data.get("archetypes", {})
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _interpolate(text: str, variables: dict[str, Any]) -> str:
|
|
55
|
+
"""Interpolate ``{key}`` placeholders in *text* using *variables*."""
|
|
56
|
+
if not text:
|
|
57
|
+
return text
|
|
58
|
+
try:
|
|
59
|
+
return text.format(**variables)
|
|
60
|
+
except (KeyError, IndexError, ValueError):
|
|
61
|
+
return text
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def resolve_archetype(
|
|
65
|
+
agent_config: dict[str, Any],
|
|
66
|
+
*,
|
|
67
|
+
archetypes: dict[str, dict[str, Any]] | None = None,
|
|
68
|
+
) -> dict[str, Any]:
|
|
69
|
+
"""Resolve ``extends`` and ``variables`` in an agent config.
|
|
70
|
+
|
|
71
|
+
If the config has an ``extends`` key, the named archetype is loaded
|
|
72
|
+
and its role/goal/backstory are merged. ``{base}`` in the agent's
|
|
73
|
+
own field is replaced with the archetype's value. Other ``{variables}``
|
|
74
|
+
are interpolated into the final resolved strings.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
agent_config: Raw agent config dict that may contain ``extends``
|
|
78
|
+
and ``variables``.
|
|
79
|
+
archetypes: Optional pre-loaded archetypes dict. If ``None``,
|
|
80
|
+
built-in archetypes are loaded from ``archetypes.yaml``.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
New config dict with ``extends`` and ``variables`` consumed
|
|
84
|
+
and all string fields interpolated.
|
|
85
|
+
"""
|
|
86
|
+
extends = agent_config.get("extends")
|
|
87
|
+
if not extends:
|
|
88
|
+
return dict(agent_config)
|
|
89
|
+
|
|
90
|
+
if archetypes is None:
|
|
91
|
+
archetypes = _load_archetypes()
|
|
92
|
+
|
|
93
|
+
archetype = archetypes.get(extends)
|
|
94
|
+
if archetype is None:
|
|
95
|
+
logger.warning("Agent config extends unknown archetype '%s'", extends)
|
|
96
|
+
result = dict(agent_config)
|
|
97
|
+
result.pop("extends", None)
|
|
98
|
+
result.pop("variables", None)
|
|
99
|
+
return result
|
|
100
|
+
|
|
101
|
+
variables = agent_config.get("variables", {})
|
|
102
|
+
if not isinstance(variables, dict):
|
|
103
|
+
variables = {}
|
|
104
|
+
|
|
105
|
+
resolved: dict[str, Any] = dict(agent_config)
|
|
106
|
+
resolved.pop("extends", None)
|
|
107
|
+
resolved.pop("variables", None)
|
|
108
|
+
|
|
109
|
+
for field in ("role", "goal", "backstory"):
|
|
110
|
+
agent_value = agent_config.get(field, "")
|
|
111
|
+
archetype_value = archetype.get(field, "")
|
|
112
|
+
# {base} in the agent's value is replaced with the archetype value
|
|
113
|
+
merged = agent_value.replace("{base}", archetype_value) if agent_value else archetype_value
|
|
114
|
+
resolved[field] = _interpolate(merged, variables)
|
|
115
|
+
|
|
116
|
+
# Copy any fields from the archetype not overridden by the agent
|
|
117
|
+
for key, value in archetype.items():
|
|
118
|
+
if key not in resolved:
|
|
119
|
+
resolved[key] = value
|
|
120
|
+
|
|
121
|
+
return resolved
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def resolve_agent_archetypes(
|
|
125
|
+
agents_config: dict[str, dict[str, Any]],
|
|
126
|
+
*,
|
|
127
|
+
archetypes: dict[str, dict[str, Any]] | None = None,
|
|
128
|
+
) -> dict[str, dict[str, Any]]:
|
|
129
|
+
"""Resolve archetypes for every agent in a config dict.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
agents_config: Dict mapping agent names to agent configs.
|
|
133
|
+
archetypes: Optional pre-loaded archetypes dict.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
New dict with archetypes resolved for each agent.
|
|
137
|
+
"""
|
|
138
|
+
return {
|
|
139
|
+
name: resolve_archetype(config, archetypes=archetypes)
|
|
140
|
+
for name, config in agents_config.items()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
__all__ = [
|
|
145
|
+
"DirectoryListTool",
|
|
146
|
+
"GameCodeReaderTool",
|
|
147
|
+
"GameCodeWriterTool",
|
|
148
|
+
"resolve_agent_archetypes",
|
|
149
|
+
"resolve_archetype",
|
|
150
|
+
]
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Reusable Agent Archetypes
|
|
2
|
+
#
|
|
3
|
+
# These are base templates that packages can extend in their own agents.yaml.
|
|
4
|
+
# Use the 'extends' field to inherit from an archetype.
|
|
5
|
+
#
|
|
6
|
+
# Example usage in packages/mypackage/.fabric/fabric_agents/builder/agents.yaml:
|
|
7
|
+
# my_engineer:
|
|
8
|
+
# extends: senior_engineer
|
|
9
|
+
# variables:
|
|
10
|
+
# language: Python
|
|
11
|
+
# backstory: |
|
|
12
|
+
# {base}
|
|
13
|
+
# Additional context specific to my package...
|
|
14
|
+
|
|
15
|
+
archetypes:
|
|
16
|
+
senior_engineer:
|
|
17
|
+
role: "Senior {language} Engineer"
|
|
18
|
+
goal: >
|
|
19
|
+
Write production-quality {language} code following best practices
|
|
20
|
+
and existing patterns in the codebase.
|
|
21
|
+
backstory: >
|
|
22
|
+
You are a senior developer with 10+ years of experience.
|
|
23
|
+
You always read existing code before writing new code.
|
|
24
|
+
You follow project conventions exactly.
|
|
25
|
+
You write clean, well-documented code with proper types.
|
|
26
|
+
|
|
27
|
+
qa_engineer:
|
|
28
|
+
role: "Quality Assurance Engineer"
|
|
29
|
+
goal: >
|
|
30
|
+
Review code for errors, security issues, type safety problems,
|
|
31
|
+
and deviations from project conventions.
|
|
32
|
+
backstory: >
|
|
33
|
+
You specialize in code review with an eye for:
|
|
34
|
+
- Type safety and proper interface usage
|
|
35
|
+
- Missing imports that would cause errors
|
|
36
|
+
- Inconsistent naming conventions
|
|
37
|
+
- Performance anti-patterns
|
|
38
|
+
- Security vulnerabilities
|
|
39
|
+
|
|
40
|
+
technical_lead:
|
|
41
|
+
role: "Technical Lead"
|
|
42
|
+
goal: >
|
|
43
|
+
Ensure all code is complete, correct, and properly integrated
|
|
44
|
+
with the existing architecture.
|
|
45
|
+
backstory: >
|
|
46
|
+
You are the technical lead with final approval authority.
|
|
47
|
+
You verify:
|
|
48
|
+
- Code fulfills the original requirement
|
|
49
|
+
- Architecture patterns are followed
|
|
50
|
+
- Code integrates with existing systems
|
|
51
|
+
- No regressions or breaking changes
|
|
52
|
+
|
|
53
|
+
architect:
|
|
54
|
+
role: "Software Architect"
|
|
55
|
+
goal: >
|
|
56
|
+
Design scalable, maintainable system architectures and
|
|
57
|
+
ensure code follows established patterns.
|
|
58
|
+
backstory: >
|
|
59
|
+
You are a software architect focused on:
|
|
60
|
+
- Clean architecture principles
|
|
61
|
+
- SOLID design patterns
|
|
62
|
+
- Separation of concerns
|
|
63
|
+
- Extensibility and maintainability
|
|
64
|
+
|
|
65
|
+
designer:
|
|
66
|
+
role: "System Designer"
|
|
67
|
+
goal: >
|
|
68
|
+
Create detailed designs and specifications for new features
|
|
69
|
+
that align with project goals.
|
|
70
|
+
backstory: >
|
|
71
|
+
You are a system designer who:
|
|
72
|
+
- Translates requirements into technical designs
|
|
73
|
+
- Considers edge cases and error handling
|
|
74
|
+
- Documents design decisions
|
|
75
|
+
- Balances complexity with maintainability
|