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.
Files changed (84) hide show
  1. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/PKG-INFO +49 -18
  2. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/README.md +35 -17
  3. agentforge_oss-0.4.0/examples/otel_tracing.py +34 -0
  4. agentforge_oss-0.4.0/examples/rag_persistent.py +31 -0
  5. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/__init__.py +17 -13
  6. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/_version.py +1 -1
  7. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/agents/agent.py +0 -1
  8. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/agents/base.py +5 -1
  9. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/agents/supervisor.py +0 -12
  10. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/compliance/audit.py +7 -2
  11. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/config.py +45 -8
  12. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/exceptions.py +0 -15
  13. agentforge_oss-0.4.0/forge/memory/__init__.py +35 -0
  14. agentforge_oss-0.4.0/forge/memory/sqlite.py +147 -0
  15. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/models/providers/__init__.py +9 -1
  16. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/models/providers/anthropic.py +0 -7
  17. agentforge_oss-0.4.0/forge/models/providers/bedrock.py +266 -0
  18. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/models/providers/echo.py +0 -4
  19. agentforge_oss-0.4.0/forge/models/providers/ollama.py +241 -0
  20. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/models/providers/openai.py +0 -8
  21. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/models/registry.py +128 -3
  22. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/models/router.py +0 -1
  23. agentforge_oss-0.4.0/forge/observability/__init__.py +34 -0
  24. agentforge_oss-0.4.0/forge/observability/otel.py +319 -0
  25. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/orchestration/orchestrator.py +88 -11
  26. agentforge_oss-0.4.0/forge/tools/builtin/http.py +62 -0
  27. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/types.py +0 -1
  28. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/pyproject.toml +18 -3
  29. agentforge_oss-0.4.0/tests/test_audit_robustness.py +23 -0
  30. agentforge_oss-0.4.0/tests/test_bedrock_provider.py +159 -0
  31. agentforge_oss-0.4.0/tests/test_config_validation.py +34 -0
  32. agentforge_oss-0.4.0/tests/test_http_security.py +68 -0
  33. agentforge_oss-0.4.0/tests/test_ollama_provider.py +157 -0
  34. agentforge_oss-0.4.0/tests/test_otel.py +119 -0
  35. agentforge_oss-0.4.0/tests/test_sqlite_concurrency.py +30 -0
  36. agentforge_oss-0.4.0/tests/test_sqlite_memory.py +78 -0
  37. agentforge_oss-0.2.0/forge/memory/__init__.py +0 -7
  38. agentforge_oss-0.2.0/forge/observability/__init__.py +0 -16
  39. agentforge_oss-0.2.0/forge/tools/builtin/http.py +0 -36
  40. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/.gitignore +0 -0
  41. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/LICENSE +0 -0
  42. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/examples/enterprise_governance.py +0 -0
  43. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/examples/quickstart.py +0 -0
  44. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/examples/rag.py +0 -0
  45. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/examples/tool_use.py +0 -0
  46. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/agents/__init__.py +0 -0
  47. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/cli/__init__.py +0 -0
  48. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/cli/console.py +0 -0
  49. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/cli/main.py +0 -0
  50. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/compliance/__init__.py +0 -0
  51. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/compliance/redaction.py +0 -0
  52. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/memory/base.py +0 -0
  53. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/memory/conversation.py +0 -0
  54. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/memory/vector.py +0 -0
  55. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/models/__init__.py +0 -0
  56. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/models/base.py +0 -0
  57. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/observability/events.py +0 -0
  58. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/observability/logging.py +0 -0
  59. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/observability/usage.py +0 -0
  60. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/orchestration/__init__.py +0 -0
  61. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/orchestration/context.py +0 -0
  62. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/py.typed +0 -0
  63. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/security/__init__.py +0 -0
  64. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/security/access.py +0 -0
  65. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/security/sandbox.py +0 -0
  66. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/security/sanitization.py +0 -0
  67. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/tools/__init__.py +0 -0
  68. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/tools/base.py +0 -0
  69. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/tools/builtin/__init__.py +0 -0
  70. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/tools/builtin/calculator.py +0 -0
  71. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/tools/builtin/time.py +0 -0
  72. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/forge/tools/registry.py +0 -0
  73. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/tests/conftest.py +0 -0
  74. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/tests/test_agents.py +0 -0
  75. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/tests/test_audit.py +0 -0
  76. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/tests/test_memory.py +0 -0
  77. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/tests/test_models.py +0 -0
  78. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/tests/test_openai_provider.py +0 -0
  79. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/tests/test_orchestrator.py +0 -0
  80. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/tests/test_parallel_workers.py +0 -0
  81. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/tests/test_security.py +0 -0
  82. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/tests/test_streaming.py +0 -0
  83. {agentforge_oss-0.2.0 → agentforge_oss-0.4.0}/tests/test_tools.py +0 -0
  84. {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.2.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: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
59
72
  [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/)
60
73
  [![Typed](https://img.shields.io/badge/typed-mypy%20strict-blue.svg)](pyproject.toml)
61
- [![Status: Beta](https://img.shields.io/badge/status-beta-orange.svg)](#whats-shipped-v020)
74
+ [![Status: Beta](https://img.shields.io/badge/status-beta-orange.svg)](#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.2.0)
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
- | 47 tests, mypy strict, ruff clean, CI on 3.11 / 3.12 / 3.13 | Shipped |
128
- | Durable memory backends (pgvector, SQLite-VSS) | Planned |
129
- | OpenTelemetry export for traces and metrics | Planned |
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) and OpenAI (GPT / o-series) ship in
181
- the box alongside a deterministic offline echo provider; add any provider by
182
- implementing one method.
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` (local, Bedrock, Vertex, …) |
390
+ | Provider | Echo (offline), Anthropic, OpenAI, Ollama, Bedrock | Any `ModelProvider` (Vertex, …) |
360
391
  | Routing | `balanced` strategy | Your own strategy / `fixed` model |
361
- | Memory | In-memory vector store | Any `Memory` backend (pgvector, Pinecone, …) |
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 47-test suite runs offline against the deterministic provider — fast,
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** (Ollama, Bedrock, Vertex) — implement one method.
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: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
13
13
  [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/)
14
14
  [![Typed](https://img.shields.io/badge/typed-mypy%20strict-blue.svg)](pyproject.toml)
15
- [![Status: Beta](https://img.shields.io/badge/status-beta-orange.svg)](#whats-shipped-v020)
15
+ [![Status: Beta](https://img.shields.io/badge/status-beta-orange.svg)](#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.2.0)
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
- | 47 tests, mypy strict, ruff clean, CI on 3.11 / 3.12 / 3.13 | Shipped |
82
- | Durable memory backends (pgvector, SQLite-VSS) | Planned |
83
- | OpenTelemetry export for traces and metrics | Planned |
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) and OpenAI (GPT / o-series) ship in
135
- the box alongside a deterministic offline echo provider; add any provider by
136
- implementing one method.
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` (local, Bedrock, Vertex, …) |
331
+ | Provider | Echo (offline), Anthropic, OpenAI, Ollama, Bedrock | Any `ModelProvider` (Vertex, …) |
314
332
  | Routing | `balanced` strategy | Your own strategy / `fixed` model |
315
- | Memory | In-memory vector store | Any `Memory` backend (pgvector, Pinecone, …) |
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 47-test suite runs offline against the deterministic provider — fast,
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** (Ollama, Bedrock, Vertex) — implement one method.
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 ConversationMemory, InMemoryVectorStore, Memory, MemoryItem
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 AnthropicProvider, EchoProvider, OpenAIProvider
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
- # Tools
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
- # Observability
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",
@@ -5,4 +5,4 @@ Hatchling reads ``__version__`` from this file at build time (see
5
5
  it for runtime access via ``forge.__version__``.
6
6
  """
7
7
 
8
- __version__ = "0.2.0"
8
+ __version__ = "0.4.0"
@@ -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, run_id=ctx.run_id, agent=self.name, model=decision.model
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
- entry = AuditEntry.model_validate_json(line)
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()