agentforge-oss 0.2.0__tar.gz → 0.3.0__tar.gz
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.
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/PKG-INFO +42 -17
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/README.md +31 -16
- agentforge_oss-0.3.0/examples/otel_tracing.py +34 -0
- agentforge_oss-0.3.0/examples/rag_persistent.py +31 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/__init__.py +15 -13
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/_version.py +1 -1
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/agents/agent.py +0 -1
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/agents/base.py +5 -1
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/agents/supervisor.py +0 -12
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/compliance/audit.py +7 -2
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/config.py +34 -8
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/exceptions.py +0 -15
- agentforge_oss-0.3.0/forge/memory/__init__.py +35 -0
- agentforge_oss-0.3.0/forge/memory/sqlite.py +147 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/models/providers/__init__.py +2 -1
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/models/providers/anthropic.py +0 -7
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/models/providers/echo.py +0 -4
- agentforge_oss-0.3.0/forge/models/providers/ollama.py +241 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/models/providers/openai.py +0 -8
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/models/registry.py +69 -3
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/models/router.py +0 -1
- agentforge_oss-0.3.0/forge/observability/__init__.py +34 -0
- agentforge_oss-0.3.0/forge/observability/otel.py +319 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/orchestration/orchestrator.py +58 -11
- agentforge_oss-0.3.0/forge/tools/builtin/http.py +62 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/types.py +0 -1
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/pyproject.toml +16 -3
- agentforge_oss-0.3.0/tests/test_audit_robustness.py +23 -0
- agentforge_oss-0.3.0/tests/test_config_validation.py +34 -0
- agentforge_oss-0.3.0/tests/test_http_security.py +68 -0
- agentforge_oss-0.3.0/tests/test_ollama_provider.py +157 -0
- agentforge_oss-0.3.0/tests/test_otel.py +119 -0
- agentforge_oss-0.3.0/tests/test_sqlite_concurrency.py +30 -0
- agentforge_oss-0.3.0/tests/test_sqlite_memory.py +78 -0
- agentforge_oss-0.2.0/forge/memory/__init__.py +0 -7
- agentforge_oss-0.2.0/forge/observability/__init__.py +0 -16
- agentforge_oss-0.2.0/forge/tools/builtin/http.py +0 -36
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/.gitignore +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/LICENSE +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/examples/enterprise_governance.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/examples/quickstart.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/examples/rag.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/examples/tool_use.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/agents/__init__.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/cli/__init__.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/cli/console.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/cli/main.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/compliance/__init__.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/compliance/redaction.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/memory/base.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/memory/conversation.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/memory/vector.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/models/__init__.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/models/base.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/observability/events.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/observability/logging.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/observability/usage.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/orchestration/__init__.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/orchestration/context.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/py.typed +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/security/__init__.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/security/access.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/security/sandbox.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/security/sanitization.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/tools/__init__.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/tools/base.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/tools/builtin/__init__.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/tools/builtin/calculator.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/tools/builtin/time.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/forge/tools/registry.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/tests/conftest.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/tests/test_agents.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/tests/test_audit.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/tests/test_memory.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/tests/test_models.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/tests/test_openai_provider.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/tests/test_orchestrator.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/tests/test_parallel_workers.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/tests/test_security.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/tests/test_streaming.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/tests/test_tools.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.3.0}/tests/test_types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentforge-oss
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Forge — an open-source, enterprise-ready multi-agent orchestration platform with cost awareness, governance, and security built in.
|
|
5
5
|
Project-URL: Homepage, https://github.com/sekacorn/AgentForge
|
|
6
6
|
Project-URL: Documentation, https://github.com/sekacorn/AgentForge#readme
|
|
@@ -30,8 +30,12 @@ Requires-Dist: rich>=13.7
|
|
|
30
30
|
Requires-Dist: typer>=0.12
|
|
31
31
|
Requires-Dist: typing-extensions>=4.10
|
|
32
32
|
Provides-Extra: all
|
|
33
|
+
Requires-Dist: aiosqlite>=0.19; extra == 'all'
|
|
33
34
|
Requires-Dist: anthropic>=0.20; extra == 'all'
|
|
34
35
|
Requires-Dist: openai>=1.0; extra == 'all'
|
|
36
|
+
Requires-Dist: opentelemetry-api>=1.20; extra == 'all'
|
|
37
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.20; extra == 'all'
|
|
38
|
+
Requires-Dist: opentelemetry-sdk>=1.20; extra == 'all'
|
|
35
39
|
Provides-Extra: anthropic
|
|
36
40
|
Requires-Dist: anthropic>=0.20; extra == 'anthropic'
|
|
37
41
|
Provides-Extra: dev
|
|
@@ -42,6 +46,12 @@ Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
|
42
46
|
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
43
47
|
Provides-Extra: openai
|
|
44
48
|
Requires-Dist: openai>=1.0; extra == 'openai'
|
|
49
|
+
Provides-Extra: otel
|
|
50
|
+
Requires-Dist: opentelemetry-api>=1.20; extra == 'otel'
|
|
51
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.20; extra == 'otel'
|
|
52
|
+
Requires-Dist: opentelemetry-sdk>=1.20; extra == 'otel'
|
|
53
|
+
Provides-Extra: sqlite
|
|
54
|
+
Requires-Dist: aiosqlite>=0.19; extra == 'sqlite'
|
|
45
55
|
Description-Content-Type: text/markdown
|
|
46
56
|
|
|
47
57
|
<div align="center">
|
|
@@ -58,7 +68,7 @@ with cost-awareness, security, and compliance built in from line one.**
|
|
|
58
68
|
[](LICENSE)
|
|
59
69
|
[](https://www.python.org/)
|
|
60
70
|
[](pyproject.toml)
|
|
61
|
-
[](#whats-shipped-
|
|
71
|
+
[](#whats-shipped-v030)
|
|
62
72
|
|
|
63
73
|
</div>
|
|
64
74
|
|
|
@@ -101,7 +111,7 @@ asyncio.run(main())
|
|
|
101
111
|
|
|
102
112
|
---
|
|
103
113
|
|
|
104
|
-
## What's shipped (v0.
|
|
114
|
+
## What's shipped (v0.3.0)
|
|
105
115
|
|
|
106
116
|
An honest snapshot of what works today versus what is on the way. Everything marked
|
|
107
117
|
**Shipped** is implemented, typed, and covered by the test suite.
|
|
@@ -113,6 +123,7 @@ An honest snapshot of what works today versus what is on the way. Everything mar
|
|
|
113
123
|
| Anthropic provider (Claude Haiku 4.5, Sonnet 4.6, Opus 4.8, Fable 5) | Shipped |
|
|
114
124
|
| OpenAI provider (gpt-4o-mini, gpt-4o, gpt-4.1, o3) | Shipped |
|
|
115
125
|
| Offline deterministic provider (zero config, no API key) | Shipped |
|
|
126
|
+
| Ollama provider (local models, zero cost, no API key, auto-detected) | Shipped |
|
|
116
127
|
| Pre-flight + per-step budget caps | Shipped |
|
|
117
128
|
| Tool sandboxing (allowlist/denylist, timeouts, dangerous-denied-by-default) | Shipped |
|
|
118
129
|
| RBAC (admin / operator / developer / viewer) | Shipped |
|
|
@@ -122,12 +133,13 @@ An honest snapshot of what works today versus what is on the way. Everything mar
|
|
|
122
133
|
| Event bus (21 lifecycle event types) | Shipped |
|
|
123
134
|
| Streaming token output through the event bus (`stream=True`) | Shipped |
|
|
124
135
|
| Per-run cost reporting (tokens + USD, per model, per agent) | Shipped |
|
|
136
|
+
| OpenTelemetry export (traces + metrics: console or OTLP to Jaeger/Grafana/Datadog) | Shipped |
|
|
125
137
|
| Conversation memory + in-memory RAG vector store | Shipped |
|
|
138
|
+
| Durable memory backend (SQLite, persistent RAG, no vector extension) | Shipped |
|
|
126
139
|
| CLI (`forge run`, `forge models`, `forge audit`) | Shipped |
|
|
127
|
-
|
|
|
128
|
-
| Durable memory backends (pgvector,
|
|
129
|
-
|
|
|
130
|
-
| Ollama / Bedrock / Vertex providers | Planned |
|
|
140
|
+
| 77 tests, mypy strict, ruff clean, CI on 3.11 / 3.12 / 3.13 | Shipped |
|
|
141
|
+
| Durable memory backends (pgvector, Redis) | Planned |
|
|
142
|
+
| Bedrock / Vertex providers | Planned |
|
|
131
143
|
| Policy-as-code for tool governance | Planned |
|
|
132
144
|
| Hosted SaaS control plane (TypeScript / Next.js) | Future |
|
|
133
145
|
|
|
@@ -177,9 +189,9 @@ An honest snapshot of what works today versus what is on the way. Everything mar
|
|
|
177
189
|
JSON-Schema generated automatically from your type hints and docstring.
|
|
178
190
|
- **Pluggable memory.** Short-term conversation memory plus a dependency-free
|
|
179
191
|
in-memory vector store for RAG — swap in any backend behind one tiny interface.
|
|
180
|
-
- **Provider-agnostic core.** Anthropic (Claude)
|
|
181
|
-
the box alongside a deterministic offline echo
|
|
182
|
-
implementing one method.
|
|
192
|
+
- **Provider-agnostic core.** Anthropic (Claude), OpenAI (GPT / o-series), and Ollama
|
|
193
|
+
(local models, zero cost) ship in the box alongside a deterministic offline echo
|
|
194
|
+
provider; add any provider by implementing one method.
|
|
183
195
|
|
|
184
196
|
### Security from the start
|
|
185
197
|
- **Tool sandboxing** with allowlists/denylists, per-tool timeouts, and
|
|
@@ -204,6 +216,10 @@ An honest snapshot of what works today versus what is on the way. Everything mar
|
|
|
204
216
|
`TOKEN_STREAM_START` / `TOKEN_CHUNK` / `TOKEN_STREAM_END` events — each tagged with
|
|
205
217
|
the agent that produced it, so you can render live output even across parallel
|
|
206
218
|
workers. `forge run "..." --stream` gives the classic live-typing terminal feel.
|
|
219
|
+
- **OpenTelemetry export.** Every run becomes a tree of spans (`forge.run` →
|
|
220
|
+
`forge.agent` → `forge.model_call` → `forge.tool_call`) exportable to any
|
|
221
|
+
OTel-compatible backend — Jaeger, Grafana, Datadog, Honeycomb, New Relic. Console
|
|
222
|
+
exporter by default (zero infra); set an OTLP endpoint for production.
|
|
207
223
|
- **Structured logging** (human or JSON) and a per-run **usage/cost report** broken
|
|
208
224
|
down per model and per agent.
|
|
209
225
|
|
|
@@ -229,6 +245,15 @@ pip install "agentforge-oss[all,dev]" # everything + test/lint toolin
|
|
|
229
245
|
> to route to GPT. Both keys can be set at once; Forge prefers Anthropic by default
|
|
230
246
|
> (configurable).
|
|
231
247
|
|
|
248
|
+
> **Ollama support is built in** — no extra install needed (it uses `httpx`, already a
|
|
249
|
+
> core dependency, so there is no `[ollama]` extra). Just run Ollama locally and Forge
|
|
250
|
+
> auto-detects it (or set `OLLAMA_BASE_URL` for a custom/remote server):
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
ollama serve
|
|
254
|
+
ollama pull llama3.1:8b
|
|
255
|
+
```
|
|
256
|
+
|
|
232
257
|
---
|
|
233
258
|
|
|
234
259
|
## Quickstart (CLI)
|
|
@@ -341,10 +366,10 @@ governance (RBAC + budgets + audit verification).
|
|
|
341
366
|
│ │
|
|
342
367
|
│ ┌───────────────────┴───────────────┐
|
|
343
368
|
▼ ▼ ▼
|
|
344
|
-
┌────────────────┐
|
|
345
|
-
│ Model Router │ picks model by │ Model Providers
|
|
346
|
-
│ cost / quality │ strategy + budget ───▶│ Anthropic · OpenAI · Echo │
|
|
347
|
-
└───────┬────────┘
|
|
369
|
+
┌────────────────┐ ┌────────────────────────────────────┐
|
|
370
|
+
│ Model Router │ picks model by │ Model Providers │
|
|
371
|
+
│ cost / quality │ strategy + budget ───▶│ Anthropic · OpenAI · Ollama · Echo │
|
|
372
|
+
└───────┬────────┘ └────────────────────────────────────┘
|
|
348
373
|
│ pricing
|
|
349
374
|
▼
|
|
350
375
|
┌────────────────┐ cross-cutting, on every step:
|
|
@@ -356,9 +381,9 @@ Every layer is swappable:
|
|
|
356
381
|
|
|
357
382
|
| Layer | Default | Swap in… |
|
|
358
383
|
|---|---|---|
|
|
359
|
-
| Provider | Echo (offline), Anthropic, OpenAI | Any `ModelProvider` (
|
|
384
|
+
| Provider | Echo (offline), Anthropic, OpenAI, Ollama | Any `ModelProvider` (Bedrock, Vertex, …) |
|
|
360
385
|
| Routing | `balanced` strategy | Your own strategy / `fixed` model |
|
|
361
|
-
| Memory |
|
|
386
|
+
| Memory | InMemoryVectorStore (default), SQLiteMemoryStore | Any `Memory` backend (pgvector, Redis, …) |
|
|
362
387
|
| Tools | `calculator`, `utc_now` | Any `@tool` function |
|
|
363
388
|
| Audit | Hash-chained JSONL | Forward events to your SIEM via the event bus |
|
|
364
389
|
|
|
@@ -449,7 +474,7 @@ ruff check . # lint
|
|
|
449
474
|
mypy forge # strict type-check
|
|
450
475
|
```
|
|
451
476
|
|
|
452
|
-
The entire
|
|
477
|
+
The entire 77-test suite runs offline against the deterministic provider — fast,
|
|
453
478
|
hermetic, and free. CI runs the same checks (ruff, ruff format, mypy strict, pytest)
|
|
454
479
|
on Python 3.11, 3.12, and 3.13.
|
|
455
480
|
|
|
@@ -12,7 +12,7 @@ with cost-awareness, security, and compliance built in from line one.**
|
|
|
12
12
|
[](LICENSE)
|
|
13
13
|
[](https://www.python.org/)
|
|
14
14
|
[](pyproject.toml)
|
|
15
|
-
[](#whats-shipped-
|
|
15
|
+
[](#whats-shipped-v030)
|
|
16
16
|
|
|
17
17
|
</div>
|
|
18
18
|
|
|
@@ -55,7 +55,7 @@ asyncio.run(main())
|
|
|
55
55
|
|
|
56
56
|
---
|
|
57
57
|
|
|
58
|
-
## What's shipped (v0.
|
|
58
|
+
## What's shipped (v0.3.0)
|
|
59
59
|
|
|
60
60
|
An honest snapshot of what works today versus what is on the way. Everything marked
|
|
61
61
|
**Shipped** is implemented, typed, and covered by the test suite.
|
|
@@ -67,6 +67,7 @@ An honest snapshot of what works today versus what is on the way. Everything mar
|
|
|
67
67
|
| Anthropic provider (Claude Haiku 4.5, Sonnet 4.6, Opus 4.8, Fable 5) | Shipped |
|
|
68
68
|
| OpenAI provider (gpt-4o-mini, gpt-4o, gpt-4.1, o3) | Shipped |
|
|
69
69
|
| Offline deterministic provider (zero config, no API key) | Shipped |
|
|
70
|
+
| Ollama provider (local models, zero cost, no API key, auto-detected) | Shipped |
|
|
70
71
|
| Pre-flight + per-step budget caps | Shipped |
|
|
71
72
|
| Tool sandboxing (allowlist/denylist, timeouts, dangerous-denied-by-default) | Shipped |
|
|
72
73
|
| RBAC (admin / operator / developer / viewer) | Shipped |
|
|
@@ -76,12 +77,13 @@ An honest snapshot of what works today versus what is on the way. Everything mar
|
|
|
76
77
|
| Event bus (21 lifecycle event types) | Shipped |
|
|
77
78
|
| Streaming token output through the event bus (`stream=True`) | Shipped |
|
|
78
79
|
| Per-run cost reporting (tokens + USD, per model, per agent) | Shipped |
|
|
80
|
+
| OpenTelemetry export (traces + metrics: console or OTLP to Jaeger/Grafana/Datadog) | Shipped |
|
|
79
81
|
| Conversation memory + in-memory RAG vector store | Shipped |
|
|
82
|
+
| Durable memory backend (SQLite, persistent RAG, no vector extension) | Shipped |
|
|
80
83
|
| CLI (`forge run`, `forge models`, `forge audit`) | Shipped |
|
|
81
|
-
|
|
|
82
|
-
| Durable memory backends (pgvector,
|
|
83
|
-
|
|
|
84
|
-
| Ollama / Bedrock / Vertex providers | Planned |
|
|
84
|
+
| 77 tests, mypy strict, ruff clean, CI on 3.11 / 3.12 / 3.13 | Shipped |
|
|
85
|
+
| Durable memory backends (pgvector, Redis) | Planned |
|
|
86
|
+
| Bedrock / Vertex providers | Planned |
|
|
85
87
|
| Policy-as-code for tool governance | Planned |
|
|
86
88
|
| Hosted SaaS control plane (TypeScript / Next.js) | Future |
|
|
87
89
|
|
|
@@ -131,9 +133,9 @@ An honest snapshot of what works today versus what is on the way. Everything mar
|
|
|
131
133
|
JSON-Schema generated automatically from your type hints and docstring.
|
|
132
134
|
- **Pluggable memory.** Short-term conversation memory plus a dependency-free
|
|
133
135
|
in-memory vector store for RAG — swap in any backend behind one tiny interface.
|
|
134
|
-
- **Provider-agnostic core.** Anthropic (Claude)
|
|
135
|
-
the box alongside a deterministic offline echo
|
|
136
|
-
implementing one method.
|
|
136
|
+
- **Provider-agnostic core.** Anthropic (Claude), OpenAI (GPT / o-series), and Ollama
|
|
137
|
+
(local models, zero cost) ship in the box alongside a deterministic offline echo
|
|
138
|
+
provider; add any provider by implementing one method.
|
|
137
139
|
|
|
138
140
|
### Security from the start
|
|
139
141
|
- **Tool sandboxing** with allowlists/denylists, per-tool timeouts, and
|
|
@@ -158,6 +160,10 @@ An honest snapshot of what works today versus what is on the way. Everything mar
|
|
|
158
160
|
`TOKEN_STREAM_START` / `TOKEN_CHUNK` / `TOKEN_STREAM_END` events — each tagged with
|
|
159
161
|
the agent that produced it, so you can render live output even across parallel
|
|
160
162
|
workers. `forge run "..." --stream` gives the classic live-typing terminal feel.
|
|
163
|
+
- **OpenTelemetry export.** Every run becomes a tree of spans (`forge.run` →
|
|
164
|
+
`forge.agent` → `forge.model_call` → `forge.tool_call`) exportable to any
|
|
165
|
+
OTel-compatible backend — Jaeger, Grafana, Datadog, Honeycomb, New Relic. Console
|
|
166
|
+
exporter by default (zero infra); set an OTLP endpoint for production.
|
|
161
167
|
- **Structured logging** (human or JSON) and a per-run **usage/cost report** broken
|
|
162
168
|
down per model and per agent.
|
|
163
169
|
|
|
@@ -183,6 +189,15 @@ pip install "agentforge-oss[all,dev]" # everything + test/lint toolin
|
|
|
183
189
|
> to route to GPT. Both keys can be set at once; Forge prefers Anthropic by default
|
|
184
190
|
> (configurable).
|
|
185
191
|
|
|
192
|
+
> **Ollama support is built in** — no extra install needed (it uses `httpx`, already a
|
|
193
|
+
> core dependency, so there is no `[ollama]` extra). Just run Ollama locally and Forge
|
|
194
|
+
> auto-detects it (or set `OLLAMA_BASE_URL` for a custom/remote server):
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
ollama serve
|
|
198
|
+
ollama pull llama3.1:8b
|
|
199
|
+
```
|
|
200
|
+
|
|
186
201
|
---
|
|
187
202
|
|
|
188
203
|
## Quickstart (CLI)
|
|
@@ -295,10 +310,10 @@ governance (RBAC + budgets + audit verification).
|
|
|
295
310
|
│ │
|
|
296
311
|
│ ┌───────────────────┴───────────────┐
|
|
297
312
|
▼ ▼ ▼
|
|
298
|
-
┌────────────────┐
|
|
299
|
-
│ Model Router │ picks model by │ Model Providers
|
|
300
|
-
│ cost / quality │ strategy + budget ───▶│ Anthropic · OpenAI · Echo │
|
|
301
|
-
└───────┬────────┘
|
|
313
|
+
┌────────────────┐ ┌────────────────────────────────────┐
|
|
314
|
+
│ Model Router │ picks model by │ Model Providers │
|
|
315
|
+
│ cost / quality │ strategy + budget ───▶│ Anthropic · OpenAI · Ollama · Echo │
|
|
316
|
+
└───────┬────────┘ └────────────────────────────────────┘
|
|
302
317
|
│ pricing
|
|
303
318
|
▼
|
|
304
319
|
┌────────────────┐ cross-cutting, on every step:
|
|
@@ -310,9 +325,9 @@ Every layer is swappable:
|
|
|
310
325
|
|
|
311
326
|
| Layer | Default | Swap in… |
|
|
312
327
|
|---|---|---|
|
|
313
|
-
| Provider | Echo (offline), Anthropic, OpenAI | Any `ModelProvider` (
|
|
328
|
+
| Provider | Echo (offline), Anthropic, OpenAI, Ollama | Any `ModelProvider` (Bedrock, Vertex, …) |
|
|
314
329
|
| Routing | `balanced` strategy | Your own strategy / `fixed` model |
|
|
315
|
-
| Memory |
|
|
330
|
+
| Memory | InMemoryVectorStore (default), SQLiteMemoryStore | Any `Memory` backend (pgvector, Redis, …) |
|
|
316
331
|
| Tools | `calculator`, `utc_now` | Any `@tool` function |
|
|
317
332
|
| Audit | Hash-chained JSONL | Forward events to your SIEM via the event bus |
|
|
318
333
|
|
|
@@ -403,7 +418,7 @@ ruff check . # lint
|
|
|
403
418
|
mypy forge # strict type-check
|
|
404
419
|
```
|
|
405
420
|
|
|
406
|
-
The entire
|
|
421
|
+
The entire 77-test suite runs offline against the deterministic provider — fast,
|
|
407
422
|
hermetic, and free. CI runs the same checks (ruff, ruff format, mypy strict, pytest)
|
|
408
423
|
on Python 3.11, 3.12, and 3.13.
|
|
409
424
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OpenTelemetry tracing example — exports spans to the console.
|
|
3
|
+
|
|
4
|
+
pip install "agentforge-oss[otel]"
|
|
5
|
+
python examples/otel_tracing.py
|
|
6
|
+
|
|
7
|
+
To send traces to Jaeger, Grafana Tempo, or any OTel collector instead:
|
|
8
|
+
|
|
9
|
+
FORGE_OTEL_ENDPOINT=http://localhost:4317 python examples/otel_tracing.py
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
import os
|
|
14
|
+
|
|
15
|
+
from forge import ForgeConfig, Orchestrator
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
async def main() -> None:
|
|
19
|
+
config = ForgeConfig(
|
|
20
|
+
otel_enabled=True,
|
|
21
|
+
otel_service_name="forge-demo",
|
|
22
|
+
otel_endpoint=os.environ.get("FORGE_OTEL_ENDPOINT"),
|
|
23
|
+
)
|
|
24
|
+
async with Orchestrator(config) as forge:
|
|
25
|
+
result = await forge.run(
|
|
26
|
+
"Calculate 15% of 4200 and summarize the result",
|
|
27
|
+
mode="single",
|
|
28
|
+
)
|
|
29
|
+
print(result.output)
|
|
30
|
+
print(result.usage.format_table())
|
|
31
|
+
print("\nSpans exported above (ConsoleSpanExporter).")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Persistent RAG example — memories survive process restarts.
|
|
3
|
+
|
|
4
|
+
Run this script twice. The second run finds the facts stored by the first.
|
|
5
|
+
|
|
6
|
+
pip install "agentforge-oss[sqlite]"
|
|
7
|
+
python examples/rag_persistent.py
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
|
|
12
|
+
from forge.memory.sqlite import SQLiteMemoryStore
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def main() -> None:
|
|
16
|
+
async with SQLiteMemoryStore("demo_memory.db") as store:
|
|
17
|
+
count_before = len(await store.search("forge", k=100))
|
|
18
|
+
if count_before == 0:
|
|
19
|
+
print("First run — storing facts...")
|
|
20
|
+
await store.add("Forge routes cheap tasks to small models to save cost.")
|
|
21
|
+
await store.add("The audit log is hash-chained and tamper-evident.")
|
|
22
|
+
await store.add("Ollama runs local LLMs with zero API cost.")
|
|
23
|
+
print("Stored 3 facts. Run again to retrieve them.")
|
|
24
|
+
else:
|
|
25
|
+
print(f"Second run — found {count_before} stored facts.")
|
|
26
|
+
hits = await store.search("how does Forge reduce cost?", k=2)
|
|
27
|
+
for hit in hits:
|
|
28
|
+
print(f" [{hit.score:.3f}] {hit.text}")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
asyncio.run(main())
|
|
@@ -51,7 +51,13 @@ from forge.exceptions import (
|
|
|
51
51
|
ToolTimeoutError,
|
|
52
52
|
ToolValidationError,
|
|
53
53
|
)
|
|
54
|
-
from forge.memory import
|
|
54
|
+
from forge.memory import (
|
|
55
|
+
ConversationMemory,
|
|
56
|
+
InMemoryVectorStore,
|
|
57
|
+
Memory,
|
|
58
|
+
MemoryItem,
|
|
59
|
+
SQLiteMemoryStore,
|
|
60
|
+
)
|
|
55
61
|
from forge.models import (
|
|
56
62
|
Complexity,
|
|
57
63
|
ModelInfo,
|
|
@@ -61,7 +67,12 @@ from forge.models import (
|
|
|
61
67
|
ModelTier,
|
|
62
68
|
RoutingDecision,
|
|
63
69
|
)
|
|
64
|
-
from forge.models.providers import
|
|
70
|
+
from forge.models.providers import (
|
|
71
|
+
AnthropicProvider,
|
|
72
|
+
EchoProvider,
|
|
73
|
+
OllamaProvider,
|
|
74
|
+
OpenAIProvider,
|
|
75
|
+
)
|
|
65
76
|
from forge.observability import (
|
|
66
77
|
Event,
|
|
67
78
|
EventBus,
|
|
@@ -93,23 +104,19 @@ from forge.types import (
|
|
|
93
104
|
|
|
94
105
|
__all__ = [
|
|
95
106
|
"__version__",
|
|
96
|
-
# Orchestration
|
|
97
107
|
"Orchestrator",
|
|
98
108
|
"RunResult",
|
|
99
109
|
"RunContext",
|
|
100
|
-
# Agents
|
|
101
110
|
"Agent",
|
|
102
111
|
"Supervisor",
|
|
103
112
|
"BaseAgent",
|
|
104
113
|
"AgentResult",
|
|
105
|
-
# Config
|
|
106
114
|
"ForgeConfig",
|
|
107
115
|
"RoutingConfig",
|
|
108
116
|
"BudgetConfig",
|
|
109
117
|
"SecurityConfig",
|
|
110
118
|
"ComplianceConfig",
|
|
111
119
|
"ObservabilityConfig",
|
|
112
|
-
# Types
|
|
113
120
|
"Role",
|
|
114
121
|
"Message",
|
|
115
122
|
"ToolCall",
|
|
@@ -118,7 +125,6 @@ __all__ = [
|
|
|
118
125
|
"Usage",
|
|
119
126
|
"ModelResponse",
|
|
120
127
|
"FinishReason",
|
|
121
|
-
# Models
|
|
122
128
|
"ModelProvider",
|
|
123
129
|
"ModelRegistry",
|
|
124
130
|
"ModelInfo",
|
|
@@ -129,7 +135,7 @@ __all__ = [
|
|
|
129
135
|
"EchoProvider",
|
|
130
136
|
"AnthropicProvider",
|
|
131
137
|
"OpenAIProvider",
|
|
132
|
-
|
|
138
|
+
"OllamaProvider",
|
|
133
139
|
"Tool",
|
|
134
140
|
"tool",
|
|
135
141
|
"ToolRegistry",
|
|
@@ -137,12 +143,11 @@ __all__ = [
|
|
|
137
143
|
"calculator",
|
|
138
144
|
"http_get",
|
|
139
145
|
"utc_now",
|
|
140
|
-
# Memory
|
|
141
146
|
"Memory",
|
|
142
147
|
"MemoryItem",
|
|
143
148
|
"ConversationMemory",
|
|
144
149
|
"InMemoryVectorStore",
|
|
145
|
-
|
|
150
|
+
"SQLiteMemoryStore",
|
|
146
151
|
"Event",
|
|
147
152
|
"EventBus",
|
|
148
153
|
"EventType",
|
|
@@ -150,17 +155,14 @@ __all__ = [
|
|
|
150
155
|
"UsageReport",
|
|
151
156
|
"get_logger",
|
|
152
157
|
"configure_logging",
|
|
153
|
-
# Security
|
|
154
158
|
"InputSanitizer",
|
|
155
159
|
"ToolSandbox",
|
|
156
160
|
"AccessController",
|
|
157
161
|
"Principal",
|
|
158
162
|
"Permission",
|
|
159
|
-
# Compliance
|
|
160
163
|
"AuditLogger",
|
|
161
164
|
"AuditEntry",
|
|
162
165
|
"PIIRedactor",
|
|
163
|
-
# Exceptions
|
|
164
166
|
"ForgeError",
|
|
165
167
|
"ConfigurationError",
|
|
166
168
|
"ProviderError",
|
|
@@ -52,7 +52,6 @@ class Agent(BaseAgent):
|
|
|
52
52
|
agent=self.name, output=response.content, usage=self._own_usage(), steps=steps
|
|
53
53
|
)
|
|
54
54
|
|
|
55
|
-
# Loop exhausted without a final answer.
|
|
56
55
|
ctx.events.emit(
|
|
57
56
|
EventType.AGENT_FAILED, run_id=ctx.run_id, agent=self.name, reason="max_steps_exceeded"
|
|
58
57
|
)
|
|
@@ -105,7 +105,11 @@ class BaseAgent(abc.ABC):
|
|
|
105
105
|
max_tokens = min(self.max_tokens, info.max_output_tokens)
|
|
106
106
|
|
|
107
107
|
ctx.events.emit(
|
|
108
|
-
EventType.MODEL_CALL_STARTED,
|
|
108
|
+
EventType.MODEL_CALL_STARTED,
|
|
109
|
+
run_id=ctx.run_id,
|
|
110
|
+
agent=self.name,
|
|
111
|
+
model=decision.model,
|
|
112
|
+
provider=decision.provider,
|
|
109
113
|
)
|
|
110
114
|
try:
|
|
111
115
|
if ctx.stream:
|
|
@@ -67,7 +67,6 @@ class Supervisor(BaseAgent):
|
|
|
67
67
|
)
|
|
68
68
|
ctx.audit.record("agent.start", actor=self.name, run_id=ctx.run_id, resource=goal[:200])
|
|
69
69
|
|
|
70
|
-
# 1) Plan.
|
|
71
70
|
plan = await self._plan(goal)
|
|
72
71
|
ctx.events.emit(EventType.PLAN_CREATED, run_id=ctx.run_id, agent=self.name, subtasks=plan)
|
|
73
72
|
ctx.audit.record(
|
|
@@ -78,10 +77,8 @@ class Supervisor(BaseAgent):
|
|
|
78
77
|
subtasks=plan,
|
|
79
78
|
)
|
|
80
79
|
|
|
81
|
-
# 2) Delegate subtasks to workers, running each batch concurrently.
|
|
82
80
|
children = await self._run_workers(plan)
|
|
83
81
|
|
|
84
|
-
# 3) Synthesise a final answer and assemble the report.
|
|
85
82
|
summary = await self._synthesize(goal, children)
|
|
86
83
|
report = self._format_report(goal, plan, children, summary)
|
|
87
84
|
|
|
@@ -102,9 +99,6 @@ class Supervisor(BaseAgent):
|
|
|
102
99
|
children=children,
|
|
103
100
|
)
|
|
104
101
|
|
|
105
|
-
# ------------------------------------------------------------------ #
|
|
106
|
-
# Parallel worker execution
|
|
107
|
-
# ------------------------------------------------------------------ #
|
|
108
102
|
async def _run_workers(self, plan: list[str]) -> list[AgentResult]:
|
|
109
103
|
"""Run the planned subtasks as workers, concurrently and in bounded batches.
|
|
110
104
|
|
|
@@ -194,9 +188,6 @@ class Supervisor(BaseAgent):
|
|
|
194
188
|
success=False,
|
|
195
189
|
)
|
|
196
190
|
|
|
197
|
-
# ------------------------------------------------------------------ #
|
|
198
|
-
# Planning & synthesis
|
|
199
|
-
# ------------------------------------------------------------------ #
|
|
200
191
|
async def _plan(self, goal: str) -> list[str]:
|
|
201
192
|
prompt = (
|
|
202
193
|
f"{PLAN_MARKER} You are a planning supervisor. Decompose the goal into a minimal, "
|
|
@@ -225,9 +216,6 @@ class Supervisor(BaseAgent):
|
|
|
225
216
|
response = await self._invoke_model(self._with_system(prompt), complexity=Complexity.MEDIUM)
|
|
226
217
|
return response.content
|
|
227
218
|
|
|
228
|
-
# ------------------------------------------------------------------ #
|
|
229
|
-
# Helpers
|
|
230
|
-
# ------------------------------------------------------------------ #
|
|
231
219
|
def _with_system(self, user_content: str) -> list[Message]:
|
|
232
220
|
messages: list[Message] = []
|
|
233
221
|
if self.system_prompt:
|
|
@@ -118,7 +118,13 @@ class AuditLogger:
|
|
|
118
118
|
line = line.strip()
|
|
119
119
|
if not line:
|
|
120
120
|
continue
|
|
121
|
-
|
|
121
|
+
try:
|
|
122
|
+
entry = AuditEntry.model_validate_json(line)
|
|
123
|
+
except ValueError:
|
|
124
|
+
# A line that no longer parses is itself tampering: fail closed
|
|
125
|
+
# (return False) rather than raising out of a bool-returning check.
|
|
126
|
+
_log.error("audit chain broken at line %d (unparseable record)", line_no)
|
|
127
|
+
return False
|
|
122
128
|
if entry.previous_hash != prev:
|
|
123
129
|
_log.error("audit chain broken at line %d (prev_hash mismatch)", line_no)
|
|
124
130
|
return False
|
|
@@ -128,7 +134,6 @@ class AuditLogger:
|
|
|
128
134
|
prev = entry.hash
|
|
129
135
|
return True
|
|
130
136
|
|
|
131
|
-
# ------------------------------------------------------------------ #
|
|
132
137
|
@staticmethod
|
|
133
138
|
def _compute_hash(entry: AuditEntry) -> str:
|
|
134
139
|
digest = hashlib.sha256()
|
|
@@ -23,6 +23,7 @@ from pydantic import BaseModel, Field
|
|
|
23
23
|
from forge.exceptions import ConfigurationError
|
|
24
24
|
|
|
25
25
|
RoutingStrategy = Literal["balanced", "cost_optimized", "quality_first", "fixed"]
|
|
26
|
+
MemoryBackend = Literal["inmemory", "sqlite"]
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
def _env_bool(name: str, default: bool) -> bool:
|
|
@@ -61,13 +62,13 @@ class RoutingConfig(BaseModel):
|
|
|
61
62
|
class BudgetConfig(BaseModel):
|
|
62
63
|
"""Hard limits that keep an autonomous run from running away with spend."""
|
|
63
64
|
|
|
64
|
-
max_usd_per_run: float | None = None
|
|
65
|
-
max_tokens_per_run: int | None = None
|
|
65
|
+
max_usd_per_run: float | None = Field(default=None, ge=0)
|
|
66
|
+
max_tokens_per_run: int | None = Field(default=None, ge=1)
|
|
66
67
|
#: Maximum reasoning/acting iterations a single agent may take.
|
|
67
|
-
max_steps_per_agent: int = 12
|
|
68
|
+
max_steps_per_agent: int = Field(default=12, ge=1)
|
|
68
69
|
#: Maximum number of dynamic workers a supervisor may spawn for one goal.
|
|
69
70
|
#: Also bounds parallelism: subtasks run in concurrent batches of this size.
|
|
70
|
-
max_workers: int = 5
|
|
71
|
+
max_workers: int = Field(default=5, ge=1)
|
|
71
72
|
|
|
72
73
|
|
|
73
74
|
class SecurityConfig(BaseModel):
|
|
@@ -115,10 +116,22 @@ class ForgeConfig(BaseModel):
|
|
|
115
116
|
observability: ObservabilityConfig = Field(default_factory=ObservabilityConfig)
|
|
116
117
|
#: Provider name -> API key. Populated from the environment by default.
|
|
117
118
|
api_keys: dict[str, str] = Field(default_factory=dict)
|
|
119
|
+
#: Base URL of a local Ollama server (overridable via ``OLLAMA_BASE_URL``). The
|
|
120
|
+
#: orchestrator offers the Ollama provider when this is set explicitly or when a
|
|
121
|
+
#: server is reachable here; see ``Orchestrator._build_default_providers``.
|
|
122
|
+
ollama_base_url: str = "http://localhost:11434"
|
|
123
|
+
#: Retrieval-memory backend (see ``forge.memory.build_memory``). ``"sqlite"``
|
|
124
|
+
#: persists RAG state across restarts and needs the optional ``aiosqlite`` extra.
|
|
125
|
+
memory_backend: MemoryBackend = "inmemory"
|
|
126
|
+
#: Database file path for the SQLite memory backend.
|
|
127
|
+
memory_path: str = "forge_memory.db"
|
|
128
|
+
#: Export traces + metrics via OpenTelemetry (needs the optional ``otel`` extra).
|
|
129
|
+
otel_enabled: bool = False
|
|
130
|
+
#: OTLP endpoint (e.g. ``http://localhost:4317``); ``None`` exports to the console.
|
|
131
|
+
otel_endpoint: str | None = None
|
|
132
|
+
#: ``service.name`` resource attribute reported to the OTel backend.
|
|
133
|
+
otel_service_name: str = "forge"
|
|
118
134
|
|
|
119
|
-
# ------------------------------------------------------------------ #
|
|
120
|
-
# Loaders
|
|
121
|
-
# ------------------------------------------------------------------ #
|
|
122
135
|
@classmethod
|
|
123
136
|
def load(cls, path: str | Path | None = None) -> ForgeConfig:
|
|
124
137
|
"""Build a config from (optional) TOML file overlaid with environment.
|
|
@@ -145,7 +158,6 @@ class ForgeConfig(BaseModel):
|
|
|
145
158
|
|
|
146
159
|
def _apply_environment(self) -> None:
|
|
147
160
|
"""Overlay environment variables onto this config in place."""
|
|
148
|
-
# Provider API keys (conventional names take priority).
|
|
149
161
|
for provider, env_name in (
|
|
150
162
|
("anthropic", "ANTHROPIC_API_KEY"),
|
|
151
163
|
("openai", "OPENAI_API_KEY"),
|
|
@@ -154,6 +166,20 @@ class ForgeConfig(BaseModel):
|
|
|
154
166
|
if value:
|
|
155
167
|
self.api_keys[provider] = value
|
|
156
168
|
|
|
169
|
+
if (ollama_url := os.environ.get("OLLAMA_BASE_URL")) is not None:
|
|
170
|
+
self.ollama_base_url = ollama_url
|
|
171
|
+
|
|
172
|
+
if (backend := os.environ.get("FORGE_MEMORY_BACKEND")) is not None:
|
|
173
|
+
self.memory_backend = backend # type: ignore[assignment]
|
|
174
|
+
if (mem_path := os.environ.get("FORGE_MEMORY_PATH")) is not None:
|
|
175
|
+
self.memory_path = mem_path
|
|
176
|
+
|
|
177
|
+
self.otel_enabled = _env_bool("FORGE_OTEL_ENABLED", self.otel_enabled)
|
|
178
|
+
if (otel_ep := os.environ.get("FORGE_OTEL_ENDPOINT")) is not None:
|
|
179
|
+
self.otel_endpoint = otel_ep
|
|
180
|
+
if (otel_sn := os.environ.get("FORGE_OTEL_SERVICE_NAME")) is not None:
|
|
181
|
+
self.otel_service_name = otel_sn
|
|
182
|
+
|
|
157
183
|
if (level := os.environ.get("FORGE_LOG_LEVEL")) is not None:
|
|
158
184
|
self.observability.log_level = level
|
|
159
185
|
self.observability.json_logs = _env_bool("FORGE_JSON_LOGS", self.observability.json_logs)
|
|
@@ -34,16 +34,10 @@ class ForgeError(Exception):
|
|
|
34
34
|
return self.message
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
# --------------------------------------------------------------------------- #
|
|
38
|
-
# Configuration
|
|
39
|
-
# --------------------------------------------------------------------------- #
|
|
40
37
|
class ConfigurationError(ForgeError):
|
|
41
38
|
"""Invalid, missing, or inconsistent configuration."""
|
|
42
39
|
|
|
43
40
|
|
|
44
|
-
# --------------------------------------------------------------------------- #
|
|
45
|
-
# Model providers & routing
|
|
46
|
-
# --------------------------------------------------------------------------- #
|
|
47
41
|
class ProviderError(ForgeError):
|
|
48
42
|
"""Base class for errors originating from a model provider."""
|
|
49
43
|
|
|
@@ -64,9 +58,6 @@ class ModelRoutingError(ForgeError):
|
|
|
64
58
|
"""No model could be selected that satisfies the routing constraints."""
|
|
65
59
|
|
|
66
60
|
|
|
67
|
-
# --------------------------------------------------------------------------- #
|
|
68
|
-
# Tools
|
|
69
|
-
# --------------------------------------------------------------------------- #
|
|
70
61
|
class ToolError(ForgeError):
|
|
71
62
|
"""Base class for tool-related errors."""
|
|
72
63
|
|
|
@@ -87,9 +78,6 @@ class ToolTimeoutError(ToolError):
|
|
|
87
78
|
"""A tool exceeded its execution time budget."""
|
|
88
79
|
|
|
89
80
|
|
|
90
|
-
# --------------------------------------------------------------------------- #
|
|
91
|
-
# Agents & orchestration
|
|
92
|
-
# --------------------------------------------------------------------------- #
|
|
93
81
|
class AgentError(ForgeError):
|
|
94
82
|
"""Base class for agent execution errors."""
|
|
95
83
|
|
|
@@ -102,9 +90,6 @@ class OrchestrationError(ForgeError):
|
|
|
102
90
|
"""An error occurred while coordinating multiple agents."""
|
|
103
91
|
|
|
104
92
|
|
|
105
|
-
# --------------------------------------------------------------------------- #
|
|
106
|
-
# Governance: budgets, security, compliance
|
|
107
|
-
# --------------------------------------------------------------------------- #
|
|
108
93
|
class BudgetExceededError(ForgeError):
|
|
109
94
|
"""A run exceeded its configured cost or token budget."""
|
|
110
95
|
|