agentforge-oss 0.2.0__tar.gz → 0.4.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.4.0}/PKG-INFO +49 -18
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/README.md +35 -17
- agentforge_oss-0.4.0/examples/otel_tracing.py +34 -0
- agentforge_oss-0.4.0/examples/rag_persistent.py +31 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/__init__.py +17 -13
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/_version.py +1 -1
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/agents/agent.py +0 -1
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/agents/base.py +5 -1
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/agents/supervisor.py +0 -12
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/compliance/audit.py +7 -2
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/config.py +45 -8
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/exceptions.py +0 -15
- agentforge_oss-0.4.0/forge/memory/__init__.py +35 -0
- agentforge_oss-0.4.0/forge/memory/sqlite.py +147 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/models/providers/__init__.py +9 -1
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/models/providers/anthropic.py +0 -7
- agentforge_oss-0.4.0/forge/models/providers/bedrock.py +266 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/models/providers/echo.py +0 -4
- agentforge_oss-0.4.0/forge/models/providers/ollama.py +241 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/models/providers/openai.py +0 -8
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/models/registry.py +128 -3
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/models/router.py +0 -1
- agentforge_oss-0.4.0/forge/observability/__init__.py +34 -0
- agentforge_oss-0.4.0/forge/observability/otel.py +319 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/orchestration/orchestrator.py +88 -11
- agentforge_oss-0.4.0/forge/tools/builtin/http.py +62 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/types.py +0 -1
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/pyproject.toml +18 -3
- agentforge_oss-0.4.0/tests/test_audit_robustness.py +23 -0
- agentforge_oss-0.4.0/tests/test_bedrock_provider.py +159 -0
- agentforge_oss-0.4.0/tests/test_config_validation.py +34 -0
- agentforge_oss-0.4.0/tests/test_http_security.py +68 -0
- agentforge_oss-0.4.0/tests/test_ollama_provider.py +157 -0
- agentforge_oss-0.4.0/tests/test_otel.py +119 -0
- agentforge_oss-0.4.0/tests/test_sqlite_concurrency.py +30 -0
- agentforge_oss-0.4.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.4.0}/.gitignore +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/LICENSE +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/examples/enterprise_governance.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/examples/quickstart.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/examples/rag.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/examples/tool_use.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/agents/__init__.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/cli/__init__.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/cli/console.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/cli/main.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/compliance/__init__.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/compliance/redaction.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/memory/base.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/memory/conversation.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/memory/vector.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/models/__init__.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/models/base.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/observability/events.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/observability/logging.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/observability/usage.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/orchestration/__init__.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/orchestration/context.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/py.typed +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/security/__init__.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/security/access.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/security/sandbox.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/security/sanitization.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/tools/__init__.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/tools/base.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/tools/builtin/__init__.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/tools/builtin/calculator.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/tools/builtin/time.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/tools/registry.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/tests/conftest.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/tests/test_agents.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/tests/test_audit.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/tests/test_memory.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/tests/test_models.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/tests/test_openai_provider.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/tests/test_orchestrator.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/tests/test_parallel_workers.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/tests/test_security.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/tests/test_streaming.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/tests/test_tools.py +0 -0
- {agentforge_oss-0.2.0 → agentforge_oss-0.4.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.4.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,10 +30,17 @@ 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'
|
|
35
|
+
Requires-Dist: boto3>=1.34; extra == 'all'
|
|
34
36
|
Requires-Dist: openai>=1.0; extra == 'all'
|
|
37
|
+
Requires-Dist: opentelemetry-api>=1.20; extra == 'all'
|
|
38
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.20; extra == 'all'
|
|
39
|
+
Requires-Dist: opentelemetry-sdk>=1.20; extra == 'all'
|
|
35
40
|
Provides-Extra: anthropic
|
|
36
41
|
Requires-Dist: anthropic>=0.20; extra == 'anthropic'
|
|
42
|
+
Provides-Extra: bedrock
|
|
43
|
+
Requires-Dist: boto3>=1.34; extra == 'bedrock'
|
|
37
44
|
Provides-Extra: dev
|
|
38
45
|
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
39
46
|
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
@@ -42,6 +49,12 @@ Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
|
42
49
|
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
43
50
|
Provides-Extra: openai
|
|
44
51
|
Requires-Dist: openai>=1.0; extra == 'openai'
|
|
52
|
+
Provides-Extra: otel
|
|
53
|
+
Requires-Dist: opentelemetry-api>=1.20; extra == 'otel'
|
|
54
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.20; extra == 'otel'
|
|
55
|
+
Requires-Dist: opentelemetry-sdk>=1.20; extra == 'otel'
|
|
56
|
+
Provides-Extra: sqlite
|
|
57
|
+
Requires-Dist: aiosqlite>=0.19; extra == 'sqlite'
|
|
45
58
|
Description-Content-Type: text/markdown
|
|
46
59
|
|
|
47
60
|
<div align="center">
|
|
@@ -58,7 +71,7 @@ with cost-awareness, security, and compliance built in from line one.**
|
|
|
58
71
|
[](LICENSE)
|
|
59
72
|
[](https://www.python.org/)
|
|
60
73
|
[](pyproject.toml)
|
|
61
|
-
[](#whats-shipped-
|
|
74
|
+
[](#whats-shipped-v040)
|
|
62
75
|
|
|
63
76
|
</div>
|
|
64
77
|
|
|
@@ -101,7 +114,7 @@ asyncio.run(main())
|
|
|
101
114
|
|
|
102
115
|
---
|
|
103
116
|
|
|
104
|
-
## What's shipped (v0.
|
|
117
|
+
## What's shipped (v0.4.0)
|
|
105
118
|
|
|
106
119
|
An honest snapshot of what works today versus what is on the way. Everything marked
|
|
107
120
|
**Shipped** is implemented, typed, and covered by the test suite.
|
|
@@ -113,6 +126,8 @@ An honest snapshot of what works today versus what is on the way. Everything mar
|
|
|
113
126
|
| Anthropic provider (Claude Haiku 4.5, Sonnet 4.6, Opus 4.8, Fable 5) | Shipped |
|
|
114
127
|
| OpenAI provider (gpt-4o-mini, gpt-4o, gpt-4.1, o3) | Shipped |
|
|
115
128
|
| Offline deterministic provider (zero config, no API key) | Shipped |
|
|
129
|
+
| Ollama provider (local models, zero cost, no API key, auto-detected) | Shipped |
|
|
130
|
+
| Amazon Bedrock provider (Claude/Llama/Mistral via Converse, in-account/GovCloud) | Shipped |
|
|
116
131
|
| Pre-flight + per-step budget caps | Shipped |
|
|
117
132
|
| Tool sandboxing (allowlist/denylist, timeouts, dangerous-denied-by-default) | Shipped |
|
|
118
133
|
| RBAC (admin / operator / developer / viewer) | Shipped |
|
|
@@ -122,12 +137,13 @@ An honest snapshot of what works today versus what is on the way. Everything mar
|
|
|
122
137
|
| Event bus (21 lifecycle event types) | Shipped |
|
|
123
138
|
| Streaming token output through the event bus (`stream=True`) | Shipped |
|
|
124
139
|
| Per-run cost reporting (tokens + USD, per model, per agent) | Shipped |
|
|
140
|
+
| OpenTelemetry export (traces + metrics: console or OTLP to Jaeger/Grafana/Datadog) | Shipped |
|
|
125
141
|
| Conversation memory + in-memory RAG vector store | Shipped |
|
|
142
|
+
| Durable memory backend (SQLite, persistent RAG, no vector extension) | Shipped |
|
|
126
143
|
| CLI (`forge run`, `forge models`, `forge audit`) | Shipped |
|
|
127
|
-
|
|
|
128
|
-
| Durable memory backends (pgvector,
|
|
129
|
-
|
|
|
130
|
-
| Ollama / Bedrock / Vertex providers | Planned |
|
|
144
|
+
| 83 tests, mypy strict, ruff clean, CI on 3.11 / 3.12 / 3.13 | Shipped |
|
|
145
|
+
| Durable memory backends (pgvector, Redis) | Planned |
|
|
146
|
+
| Vertex / other cloud providers | Planned |
|
|
131
147
|
| Policy-as-code for tool governance | Planned |
|
|
132
148
|
| Hosted SaaS control plane (TypeScript / Next.js) | Future |
|
|
133
149
|
|
|
@@ -177,9 +193,10 @@ An honest snapshot of what works today versus what is on the way. Everything mar
|
|
|
177
193
|
JSON-Schema generated automatically from your type hints and docstring.
|
|
178
194
|
- **Pluggable memory.** Short-term conversation memory plus a dependency-free
|
|
179
195
|
in-memory vector store for RAG — swap in any backend behind one tiny interface.
|
|
180
|
-
- **Provider-agnostic core.** Anthropic (Claude)
|
|
181
|
-
|
|
182
|
-
|
|
196
|
+
- **Provider-agnostic core.** Anthropic (Claude), OpenAI (GPT / o-series), Amazon
|
|
197
|
+
Bedrock (Converse API), and Ollama (local models, zero cost) ship in the box
|
|
198
|
+
alongside a deterministic offline echo provider; add any provider by implementing
|
|
199
|
+
one method.
|
|
183
200
|
|
|
184
201
|
### Security from the start
|
|
185
202
|
- **Tool sandboxing** with allowlists/denylists, per-tool timeouts, and
|
|
@@ -204,6 +221,10 @@ An honest snapshot of what works today versus what is on the way. Everything mar
|
|
|
204
221
|
`TOKEN_STREAM_START` / `TOKEN_CHUNK` / `TOKEN_STREAM_END` events — each tagged with
|
|
205
222
|
the agent that produced it, so you can render live output even across parallel
|
|
206
223
|
workers. `forge run "..." --stream` gives the classic live-typing terminal feel.
|
|
224
|
+
- **OpenTelemetry export.** Every run becomes a tree of spans (`forge.run` →
|
|
225
|
+
`forge.agent` → `forge.model_call` → `forge.tool_call`) exportable to any
|
|
226
|
+
OTel-compatible backend — Jaeger, Grafana, Datadog, Honeycomb, New Relic. Console
|
|
227
|
+
exporter by default (zero infra); set an OTLP endpoint for production.
|
|
207
228
|
- **Structured logging** (human or JSON) and a per-run **usage/cost report** broken
|
|
208
229
|
down per model and per agent.
|
|
209
230
|
|
|
@@ -216,6 +237,7 @@ pip install agentforge-oss # core (works offline, zero con
|
|
|
216
237
|
pip install "agentforge-oss[anthropic]" # + Claude provider
|
|
217
238
|
pip install "agentforge-oss[openai]" # + OpenAI / GPT provider
|
|
218
239
|
pip install "agentforge-oss[anthropic,openai]" # both real providers
|
|
240
|
+
pip install "agentforge-oss[bedrock]" # + Amazon Bedrock provider (boto3)
|
|
219
241
|
pip install "agentforge-oss[all,dev]" # everything + test/lint tooling
|
|
220
242
|
```
|
|
221
243
|
|
|
@@ -229,6 +251,15 @@ pip install "agentforge-oss[all,dev]" # everything + test/lint toolin
|
|
|
229
251
|
> to route to GPT. Both keys can be set at once; Forge prefers Anthropic by default
|
|
230
252
|
> (configurable).
|
|
231
253
|
|
|
254
|
+
> **Ollama support is built in** — no extra install needed (it uses `httpx`, already a
|
|
255
|
+
> core dependency, so there is no `[ollama]` extra). Just run Ollama locally and Forge
|
|
256
|
+
> auto-detects it (or set `OLLAMA_BASE_URL` for a custom/remote server):
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
ollama serve
|
|
260
|
+
ollama pull llama3.1:8b
|
|
261
|
+
```
|
|
262
|
+
|
|
232
263
|
---
|
|
233
264
|
|
|
234
265
|
## Quickstart (CLI)
|
|
@@ -341,10 +372,10 @@ governance (RBAC + budgets + audit verification).
|
|
|
341
372
|
│ │
|
|
342
373
|
│ ┌───────────────────┴───────────────┐
|
|
343
374
|
▼ ▼ ▼
|
|
344
|
-
┌────────────────┐
|
|
345
|
-
│ Model Router │ picks model by │ Model Providers
|
|
346
|
-
│ cost / quality │ strategy + budget ───▶│ Anthropic · OpenAI · Echo │
|
|
347
|
-
└───────┬────────┘
|
|
375
|
+
┌────────────────┐ ┌────────────────────────────────────┐
|
|
376
|
+
│ Model Router │ picks model by │ Model Providers │
|
|
377
|
+
│ cost / quality │ strategy + budget ───▶│ Anthropic · OpenAI · Ollama · Bedrock · Echo │
|
|
378
|
+
└───────┬────────┘ └────────────────────────────────────┘
|
|
348
379
|
│ pricing
|
|
349
380
|
▼
|
|
350
381
|
┌────────────────┐ cross-cutting, on every step:
|
|
@@ -356,9 +387,9 @@ Every layer is swappable:
|
|
|
356
387
|
|
|
357
388
|
| Layer | Default | Swap in… |
|
|
358
389
|
|---|---|---|
|
|
359
|
-
| Provider | Echo (offline), Anthropic, OpenAI | Any `ModelProvider` (
|
|
390
|
+
| Provider | Echo (offline), Anthropic, OpenAI, Ollama, Bedrock | Any `ModelProvider` (Vertex, …) |
|
|
360
391
|
| Routing | `balanced` strategy | Your own strategy / `fixed` model |
|
|
361
|
-
| Memory |
|
|
392
|
+
| Memory | InMemoryVectorStore (default), SQLiteMemoryStore | Any `Memory` backend (pgvector, Redis, …) |
|
|
362
393
|
| Tools | `calculator`, `utc_now` | Any `@tool` function |
|
|
363
394
|
| Audit | Hash-chained JSONL | Forward events to your SIEM via the event bus |
|
|
364
395
|
|
|
@@ -449,7 +480,7 @@ ruff check . # lint
|
|
|
449
480
|
mypy forge # strict type-check
|
|
450
481
|
```
|
|
451
482
|
|
|
452
|
-
The entire
|
|
483
|
+
The entire 83-test suite runs offline against the deterministic provider — fast,
|
|
453
484
|
hermetic, and free. CI runs the same checks (ruff, ruff format, mypy strict, pytest)
|
|
454
485
|
on Python 3.11, 3.12, and 3.13.
|
|
455
486
|
|
|
@@ -462,7 +493,7 @@ the core typed (`mypy --strict`) and tested.
|
|
|
462
493
|
|
|
463
494
|
### Good first contributions
|
|
464
495
|
|
|
465
|
-
- **New model provider** (
|
|
496
|
+
- **New model provider** (Vertex, Gemini, Cohere) — implement one method.
|
|
466
497
|
- **New built-in tool** (web search, file read, database query).
|
|
467
498
|
- **Durable memory backend** (SQLite-VSS, pgvector, Redis).
|
|
468
499
|
- **Routing strategy** (a custom cost/quality tradeoff).
|
|
@@ -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-v040)
|
|
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.4.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,8 @@ 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 |
|
|
71
|
+
| Amazon Bedrock provider (Claude/Llama/Mistral via Converse, in-account/GovCloud) | Shipped |
|
|
70
72
|
| Pre-flight + per-step budget caps | Shipped |
|
|
71
73
|
| Tool sandboxing (allowlist/denylist, timeouts, dangerous-denied-by-default) | Shipped |
|
|
72
74
|
| RBAC (admin / operator / developer / viewer) | Shipped |
|
|
@@ -76,12 +78,13 @@ An honest snapshot of what works today versus what is on the way. Everything mar
|
|
|
76
78
|
| Event bus (21 lifecycle event types) | Shipped |
|
|
77
79
|
| Streaming token output through the event bus (`stream=True`) | Shipped |
|
|
78
80
|
| Per-run cost reporting (tokens + USD, per model, per agent) | Shipped |
|
|
81
|
+
| OpenTelemetry export (traces + metrics: console or OTLP to Jaeger/Grafana/Datadog) | Shipped |
|
|
79
82
|
| Conversation memory + in-memory RAG vector store | Shipped |
|
|
83
|
+
| Durable memory backend (SQLite, persistent RAG, no vector extension) | Shipped |
|
|
80
84
|
| CLI (`forge run`, `forge models`, `forge audit`) | Shipped |
|
|
81
|
-
|
|
|
82
|
-
| Durable memory backends (pgvector,
|
|
83
|
-
|
|
|
84
|
-
| Ollama / Bedrock / Vertex providers | Planned |
|
|
85
|
+
| 83 tests, mypy strict, ruff clean, CI on 3.11 / 3.12 / 3.13 | Shipped |
|
|
86
|
+
| Durable memory backends (pgvector, Redis) | Planned |
|
|
87
|
+
| Vertex / other cloud providers | Planned |
|
|
85
88
|
| Policy-as-code for tool governance | Planned |
|
|
86
89
|
| Hosted SaaS control plane (TypeScript / Next.js) | Future |
|
|
87
90
|
|
|
@@ -131,9 +134,10 @@ An honest snapshot of what works today versus what is on the way. Everything mar
|
|
|
131
134
|
JSON-Schema generated automatically from your type hints and docstring.
|
|
132
135
|
- **Pluggable memory.** Short-term conversation memory plus a dependency-free
|
|
133
136
|
in-memory vector store for RAG — swap in any backend behind one tiny interface.
|
|
134
|
-
- **Provider-agnostic core.** Anthropic (Claude)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
+
- **Provider-agnostic core.** Anthropic (Claude), OpenAI (GPT / o-series), Amazon
|
|
138
|
+
Bedrock (Converse API), and Ollama (local models, zero cost) ship in the box
|
|
139
|
+
alongside a deterministic offline echo provider; add any provider by implementing
|
|
140
|
+
one method.
|
|
137
141
|
|
|
138
142
|
### Security from the start
|
|
139
143
|
- **Tool sandboxing** with allowlists/denylists, per-tool timeouts, and
|
|
@@ -158,6 +162,10 @@ An honest snapshot of what works today versus what is on the way. Everything mar
|
|
|
158
162
|
`TOKEN_STREAM_START` / `TOKEN_CHUNK` / `TOKEN_STREAM_END` events — each tagged with
|
|
159
163
|
the agent that produced it, so you can render live output even across parallel
|
|
160
164
|
workers. `forge run "..." --stream` gives the classic live-typing terminal feel.
|
|
165
|
+
- **OpenTelemetry export.** Every run becomes a tree of spans (`forge.run` →
|
|
166
|
+
`forge.agent` → `forge.model_call` → `forge.tool_call`) exportable to any
|
|
167
|
+
OTel-compatible backend — Jaeger, Grafana, Datadog, Honeycomb, New Relic. Console
|
|
168
|
+
exporter by default (zero infra); set an OTLP endpoint for production.
|
|
161
169
|
- **Structured logging** (human or JSON) and a per-run **usage/cost report** broken
|
|
162
170
|
down per model and per agent.
|
|
163
171
|
|
|
@@ -170,6 +178,7 @@ pip install agentforge-oss # core (works offline, zero con
|
|
|
170
178
|
pip install "agentforge-oss[anthropic]" # + Claude provider
|
|
171
179
|
pip install "agentforge-oss[openai]" # + OpenAI / GPT provider
|
|
172
180
|
pip install "agentforge-oss[anthropic,openai]" # both real providers
|
|
181
|
+
pip install "agentforge-oss[bedrock]" # + Amazon Bedrock provider (boto3)
|
|
173
182
|
pip install "agentforge-oss[all,dev]" # everything + test/lint tooling
|
|
174
183
|
```
|
|
175
184
|
|
|
@@ -183,6 +192,15 @@ pip install "agentforge-oss[all,dev]" # everything + test/lint toolin
|
|
|
183
192
|
> to route to GPT. Both keys can be set at once; Forge prefers Anthropic by default
|
|
184
193
|
> (configurable).
|
|
185
194
|
|
|
195
|
+
> **Ollama support is built in** — no extra install needed (it uses `httpx`, already a
|
|
196
|
+
> core dependency, so there is no `[ollama]` extra). Just run Ollama locally and Forge
|
|
197
|
+
> auto-detects it (or set `OLLAMA_BASE_URL` for a custom/remote server):
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
ollama serve
|
|
201
|
+
ollama pull llama3.1:8b
|
|
202
|
+
```
|
|
203
|
+
|
|
186
204
|
---
|
|
187
205
|
|
|
188
206
|
## Quickstart (CLI)
|
|
@@ -295,10 +313,10 @@ governance (RBAC + budgets + audit verification).
|
|
|
295
313
|
│ │
|
|
296
314
|
│ ┌───────────────────┴───────────────┐
|
|
297
315
|
▼ ▼ ▼
|
|
298
|
-
┌────────────────┐
|
|
299
|
-
│ Model Router │ picks model by │ Model Providers
|
|
300
|
-
│ cost / quality │ strategy + budget ───▶│ Anthropic · OpenAI · Echo │
|
|
301
|
-
└───────┬────────┘
|
|
316
|
+
┌────────────────┐ ┌────────────────────────────────────┐
|
|
317
|
+
│ Model Router │ picks model by │ Model Providers │
|
|
318
|
+
│ cost / quality │ strategy + budget ───▶│ Anthropic · OpenAI · Ollama · Bedrock · Echo │
|
|
319
|
+
└───────┬────────┘ └────────────────────────────────────┘
|
|
302
320
|
│ pricing
|
|
303
321
|
▼
|
|
304
322
|
┌────────────────┐ cross-cutting, on every step:
|
|
@@ -310,9 +328,9 @@ Every layer is swappable:
|
|
|
310
328
|
|
|
311
329
|
| Layer | Default | Swap in… |
|
|
312
330
|
|---|---|---|
|
|
313
|
-
| Provider | Echo (offline), Anthropic, OpenAI | Any `ModelProvider` (
|
|
331
|
+
| Provider | Echo (offline), Anthropic, OpenAI, Ollama, Bedrock | Any `ModelProvider` (Vertex, …) |
|
|
314
332
|
| Routing | `balanced` strategy | Your own strategy / `fixed` model |
|
|
315
|
-
| Memory |
|
|
333
|
+
| Memory | InMemoryVectorStore (default), SQLiteMemoryStore | Any `Memory` backend (pgvector, Redis, …) |
|
|
316
334
|
| Tools | `calculator`, `utc_now` | Any `@tool` function |
|
|
317
335
|
| Audit | Hash-chained JSONL | Forward events to your SIEM via the event bus |
|
|
318
336
|
|
|
@@ -403,7 +421,7 @@ ruff check . # lint
|
|
|
403
421
|
mypy forge # strict type-check
|
|
404
422
|
```
|
|
405
423
|
|
|
406
|
-
The entire
|
|
424
|
+
The entire 83-test suite runs offline against the deterministic provider — fast,
|
|
407
425
|
hermetic, and free. CI runs the same checks (ruff, ruff format, mypy strict, pytest)
|
|
408
426
|
on Python 3.11, 3.12, and 3.13.
|
|
409
427
|
|
|
@@ -416,7 +434,7 @@ the core typed (`mypy --strict`) and tested.
|
|
|
416
434
|
|
|
417
435
|
### Good first contributions
|
|
418
436
|
|
|
419
|
-
- **New model provider** (
|
|
437
|
+
- **New model provider** (Vertex, Gemini, Cohere) — implement one method.
|
|
420
438
|
- **New built-in tool** (web search, file read, database query).
|
|
421
439
|
- **Durable memory backend** (SQLite-VSS, pgvector, Redis).
|
|
422
440
|
- **Routing strategy** (a custom cost/quality tradeoff).
|
|
@@ -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,13 @@ 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
|
+
BedrockProvider,
|
|
73
|
+
EchoProvider,
|
|
74
|
+
OllamaProvider,
|
|
75
|
+
OpenAIProvider,
|
|
76
|
+
)
|
|
65
77
|
from forge.observability import (
|
|
66
78
|
Event,
|
|
67
79
|
EventBus,
|
|
@@ -93,23 +105,19 @@ from forge.types import (
|
|
|
93
105
|
|
|
94
106
|
__all__ = [
|
|
95
107
|
"__version__",
|
|
96
|
-
# Orchestration
|
|
97
108
|
"Orchestrator",
|
|
98
109
|
"RunResult",
|
|
99
110
|
"RunContext",
|
|
100
|
-
# Agents
|
|
101
111
|
"Agent",
|
|
102
112
|
"Supervisor",
|
|
103
113
|
"BaseAgent",
|
|
104
114
|
"AgentResult",
|
|
105
|
-
# Config
|
|
106
115
|
"ForgeConfig",
|
|
107
116
|
"RoutingConfig",
|
|
108
117
|
"BudgetConfig",
|
|
109
118
|
"SecurityConfig",
|
|
110
119
|
"ComplianceConfig",
|
|
111
120
|
"ObservabilityConfig",
|
|
112
|
-
# Types
|
|
113
121
|
"Role",
|
|
114
122
|
"Message",
|
|
115
123
|
"ToolCall",
|
|
@@ -118,7 +126,6 @@ __all__ = [
|
|
|
118
126
|
"Usage",
|
|
119
127
|
"ModelResponse",
|
|
120
128
|
"FinishReason",
|
|
121
|
-
# Models
|
|
122
129
|
"ModelProvider",
|
|
123
130
|
"ModelRegistry",
|
|
124
131
|
"ModelInfo",
|
|
@@ -129,7 +136,8 @@ __all__ = [
|
|
|
129
136
|
"EchoProvider",
|
|
130
137
|
"AnthropicProvider",
|
|
131
138
|
"OpenAIProvider",
|
|
132
|
-
|
|
139
|
+
"OllamaProvider",
|
|
140
|
+
"BedrockProvider",
|
|
133
141
|
"Tool",
|
|
134
142
|
"tool",
|
|
135
143
|
"ToolRegistry",
|
|
@@ -137,12 +145,11 @@ __all__ = [
|
|
|
137
145
|
"calculator",
|
|
138
146
|
"http_get",
|
|
139
147
|
"utc_now",
|
|
140
|
-
# Memory
|
|
141
148
|
"Memory",
|
|
142
149
|
"MemoryItem",
|
|
143
150
|
"ConversationMemory",
|
|
144
151
|
"InMemoryVectorStore",
|
|
145
|
-
|
|
152
|
+
"SQLiteMemoryStore",
|
|
146
153
|
"Event",
|
|
147
154
|
"EventBus",
|
|
148
155
|
"EventType",
|
|
@@ -150,17 +157,14 @@ __all__ = [
|
|
|
150
157
|
"UsageReport",
|
|
151
158
|
"get_logger",
|
|
152
159
|
"configure_logging",
|
|
153
|
-
# Security
|
|
154
160
|
"InputSanitizer",
|
|
155
161
|
"ToolSandbox",
|
|
156
162
|
"AccessController",
|
|
157
163
|
"Principal",
|
|
158
164
|
"Permission",
|
|
159
|
-
# Compliance
|
|
160
165
|
"AuditLogger",
|
|
161
166
|
"AuditEntry",
|
|
162
167
|
"PIIRedactor",
|
|
163
|
-
# Exceptions
|
|
164
168
|
"ForgeError",
|
|
165
169
|
"ConfigurationError",
|
|
166
170
|
"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()
|