forge-agent-runtime 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- forge_agent_runtime-0.1.0.dist-info/METADATA +364 -0
- forge_agent_runtime-0.1.0.dist-info/RECORD +25 -0
- forge_agent_runtime-0.1.0.dist-info/WHEEL +4 -0
- forge_agent_runtime-0.1.0.dist-info/entry_points.txt +2 -0
- forge_agent_runtime-0.1.0.dist-info/licenses/LICENSE +17 -0
- forge_runtime/__init__.py +32 -0
- forge_runtime/api.py +139 -0
- forge_runtime/cli.py +400 -0
- forge_runtime/config.py +41 -0
- forge_runtime/evaluation.py +22 -0
- forge_runtime/graph.py +85 -0
- forge_runtime/models.py +137 -0
- forge_runtime/orchestrator.py +289 -0
- forge_runtime/providers/__init__.py +1 -0
- forge_runtime/providers/anthropic_provider.py +256 -0
- forge_runtime/providers/base.py +10 -0
- forge_runtime/providers/factory.py +46 -0
- forge_runtime/providers/mock.py +24 -0
- forge_runtime/providers/openai_provider.py +264 -0
- forge_runtime/registry.py +245 -0
- forge_runtime/sandbox.py +872 -0
- forge_runtime/sandbox_assets/Dockerfile +51 -0
- forge_runtime/sandbox_assets/__init__.py +1 -0
- forge_runtime/storage.py +82 -0
- forge_runtime/workspace.py +280 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: forge-agent-runtime
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Docker-isolated multi-agent orchestration runtime for the AgentForge ecosystem
|
|
5
|
+
Project-URL: Homepage, https://github.com/mhingan/forge-runtime
|
|
6
|
+
Project-URL: Repository, https://github.com/mhingan/forge-runtime
|
|
7
|
+
Project-URL: Issues, https://github.com/mhingan/forge-runtime/issues
|
|
8
|
+
Author: Mihaita Hingan
|
|
9
|
+
License: MIT License
|
|
10
|
+
|
|
11
|
+
Copyright (c) 2026 Mihaita Hingan
|
|
12
|
+
|
|
13
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
14
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
15
|
+
in the Software without restriction, including without limitation the rights
|
|
16
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
17
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
18
|
+
furnished to do so, subject to the following conditions:
|
|
19
|
+
|
|
20
|
+
The above copyright notice and this permission notice shall be included in all
|
|
21
|
+
copies or substantial portions of the Software.
|
|
22
|
+
|
|
23
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
24
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
25
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Keywords: agentforge,agents,coding-agent,llm,multi-agent,orchestration
|
|
28
|
+
Classifier: Development Status :: 3 - Alpha
|
|
29
|
+
Classifier: Environment :: Console
|
|
30
|
+
Classifier: Intended Audience :: Developers
|
|
31
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
32
|
+
Classifier: Operating System :: OS Independent
|
|
33
|
+
Classifier: Programming Language :: Python :: 3
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
37
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
38
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
39
|
+
Requires-Python: >=3.11
|
|
40
|
+
Requires-Dist: agentforge-toolkit==1.1.0
|
|
41
|
+
Requires-Dist: httpx<1,>=0.27
|
|
42
|
+
Requires-Dist: pydantic-settings<3,>=2.2
|
|
43
|
+
Requires-Dist: pydantic<3,>=2.7
|
|
44
|
+
Requires-Dist: rich<15,>=13.7
|
|
45
|
+
Requires-Dist: typer<1,>=0.12
|
|
46
|
+
Provides-Extra: anthropic
|
|
47
|
+
Requires-Dist: anthropic<1,>=0.28; extra == 'anthropic'
|
|
48
|
+
Provides-Extra: api
|
|
49
|
+
Requires-Dist: fastapi<1,>=0.115; extra == 'api'
|
|
50
|
+
Requires-Dist: uvicorn[standard]<1,>=0.30; extra == 'api'
|
|
51
|
+
Provides-Extra: dev
|
|
52
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
53
|
+
Requires-Dist: pytest-asyncio<2,>=0.23; extra == 'dev'
|
|
54
|
+
Requires-Dist: pytest-cov<8,>=5; extra == 'dev'
|
|
55
|
+
Requires-Dist: pytest<9,>=8; extra == 'dev'
|
|
56
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
57
|
+
Provides-Extra: openai
|
|
58
|
+
Requires-Dist: openai<3,>=1.30; extra == 'openai'
|
|
59
|
+
Description-Content-Type: text/markdown
|
|
60
|
+
|
|
61
|
+
# Forge Runtime
|
|
62
|
+
|
|
63
|
+
**A provider-neutral execution and orchestration engine for the AgentForge ecosystem.**
|
|
64
|
+
|
|
65
|
+
Forge Runtime turns one user goal into a validated multi-agent execution graph. Forge Master selects specialist roles from AgentForge, asks an LLM provider to create a plan, validates the task dependencies, dispatches work concurrently, passes successful dependency results downstream, evaluates task outputs, and persists an execution history.
|
|
66
|
+
|
|
67
|
+
## AgentForge integration
|
|
68
|
+
|
|
69
|
+
Forge Runtime depends on the published distribution **agentforge-toolkit 1.1.0**. The toolkit exposes its bundled library directory through `agentforge.core.data_root()`; the runtime reads the catalogs and Markdown definitions from that packaged data.
|
|
70
|
+
|
|
71
|
+
| Project | Responsibility |
|
|
72
|
+
|---|---|
|
|
73
|
+
| [AgentForge](https://github.com/mhingan/agent-forge) / [PyPI](https://pypi.org/project/agentforge-toolkit/1.1.0/) | Agent roles, skills, rules, workflows, catalogs, provider-specific coding-agent installation |
|
|
74
|
+
| **Forge Runtime** | Forge Master, planning, DAG scheduling, dispatch, model-provider calls, state, evaluation, API and execution reports |
|
|
75
|
+
|
|
76
|
+
The runtime does not call an undocumented AgentForge registry class or assume the toolkit is a hosted service. The adapter uses the package's real `data_root()` entry point. A local repository checkout and GitHub raw source are also supported.
|
|
77
|
+
|
|
78
|
+
## Runtime capabilities
|
|
79
|
+
|
|
80
|
+
- **Forge Master:** central coordinator for each execution run.
|
|
81
|
+
- **AgentForge registry:** reads packaged roles, skills, rules and workflow playbooks.
|
|
82
|
+
- **Planning:** OpenAI and Anthropic providers produce a structured execution plan; mock provider supports deterministic smoke tests.
|
|
83
|
+
- **Task graph:** validates unique IDs, known roles/skills, dependencies, cycles and task limits before dispatch.
|
|
84
|
+
- **Parallel scheduler:** independent tasks run concurrently under a configurable semaphore.
|
|
85
|
+
- **Dependency context:** successful outputs are passed to tasks that depend on them.
|
|
86
|
+
- **Workspace tools:** OpenAI and Anthropic agents can list, read, search and (for `read-write` roles) write files in a configured project workspace.
|
|
87
|
+
- **Sandbox executor:** LLM agents can run tests, install dependencies and use the internet from disposable Docker containers with resource limits and controlled change-back.
|
|
88
|
+
- **Retries and deadlines:** bounded attempts and per-task timeouts.
|
|
89
|
+
- **Failure propagation:** tasks that depend on failed work are marked blocked.
|
|
90
|
+
- **Evaluation:** baseline output validation behind a replaceable evaluator contract.
|
|
91
|
+
- **Persistence:** SQLite records runs, task states, outputs and execution events.
|
|
92
|
+
- **CLI and HTTP API:** inspect the registry, submit work and inspect run history.
|
|
93
|
+
|
|
94
|
+
## Architecture
|
|
95
|
+
|
|
96
|
+
```text
|
|
97
|
+
User goal / CLI / HTTP API
|
|
98
|
+
|
|
|
99
|
+
v
|
|
100
|
+
FORGE MASTER
|
|
101
|
+
|
|
|
102
|
+
Load AgentForge toolkit data
|
|
103
|
+
|
|
|
104
|
+
LLM planner / Mock planner
|
|
105
|
+
|
|
|
106
|
+
Validate execution DAG
|
|
107
|
+
|
|
|
108
|
+
Task scheduler
|
|
109
|
+
/ | \
|
|
110
|
+
v v v
|
|
111
|
+
Architect Backend Product ...
|
|
112
|
+
| | |
|
|
113
|
+
+----------+-----------+
|
|
114
|
+
|
|
|
115
|
+
Dependency result hand-off
|
|
116
|
+
|
|
|
117
|
+
Evaluator / Retry
|
|
118
|
+
|
|
|
119
|
+
SQLite store
|
|
120
|
+
|
|
|
121
|
+
Run report + event history
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
AgentForge profiles and skills are untrusted model context, not executable permissions. OpenAI and Anthropic providers can modify ordinary files only inside `FORGE_WORKSPACE_ROOT`, and only when the selected role is `read-write`. Commands run inside Docker, never directly on the host. Git commit/push, browser, deployment and arbitrary MCP tools are not provided.
|
|
125
|
+
|
|
126
|
+
## Requirements
|
|
127
|
+
|
|
128
|
+
- Python 3.11+
|
|
129
|
+
- AgentForge Toolkit 1.1.0 (installed automatically as a project dependency)
|
|
130
|
+
- An API key only when using OpenAI or Anthropic
|
|
131
|
+
|
|
132
|
+
## Install from source
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
git clone https://github.com/mhingan/forge-runtime.git
|
|
136
|
+
cd forge-runtime
|
|
137
|
+
python -m venv .venv
|
|
138
|
+
source .venv/bin/activate
|
|
139
|
+
python -m pip install -e ".[openai]"
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The dependency installation also installs `agentforge-toolkit==1.1.0`. No separate AgentForge clone is required for the standard configuration.
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
## Install from PyPI
|
|
146
|
+
|
|
147
|
+
Once the first release is published:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
python -m pip install "forge-agent-runtime[openai]"
|
|
151
|
+
forge setup
|
|
152
|
+
forge run "Inspect this project, implement the requested feature and run the tests"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
For Anthropic, install `forge-agent-runtime[anthropic]`. The PyPI distribution is named `forge-agent-runtime`; the Python import package remains `forge_runtime`, and the command-line executable remains `forge`.
|
|
156
|
+
|
|
157
|
+
## Quick start — two commands after installation
|
|
158
|
+
|
|
159
|
+
From the project directory, run:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
# Prompts for your OpenAI key, saves it safely in .env, and builds the Docker image
|
|
163
|
+
forge setup
|
|
164
|
+
|
|
165
|
+
# Give Forge a task; agents can edit files, install dependencies and access the internet
|
|
166
|
+
forge run "Inspect this project, implement the missing features and run the tests"
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Use `forge setup --provider anthropic` for Anthropic, or `forge setup --provider mock` for a no-key demo. Setup builds the sandbox image only once; later runs reuse it.
|
|
170
|
+
|
|
171
|
+
The sandbox uses Docker's `bridge` network by default, which enables outbound internet access for commands such as `curl`, `wget`, `pip install`, `npm install` and Git operations. Set `FORGE_SANDBOX_NETWORK=none` in `.env` to run offline.
|
|
172
|
+
|
|
173
|
+
## First run — mock provider
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
forge registry validate
|
|
177
|
+
forge agent list
|
|
178
|
+
forge skill list
|
|
179
|
+
forge rule list
|
|
180
|
+
forge workflow list
|
|
181
|
+
|
|
182
|
+
forge run "Design a secure REST API for an e-commerce platform" --provider mock
|
|
183
|
+
forge runs list
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Mock mode verifies registry loading, planning, graph validation, scheduling, persistence and event creation without making model API calls. Its outputs are clearly marked as demonstrations; it does not produce real code changes.
|
|
187
|
+
|
|
188
|
+
## Run with an LLM
|
|
189
|
+
|
|
190
|
+
For OpenAI:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
export OPENAI_API_KEY="..."
|
|
194
|
+
export FORGE_DEFAULT_PROVIDER=openai
|
|
195
|
+
forge run "Build a secure customer management API with tests"
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Or choose a provider per run:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
forge run "Review the backend architecture" --provider openai
|
|
202
|
+
forge run "Plan a Flutter application" --provider anthropic
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Optional provider installs:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
python -m pip install -e ".[openai]"
|
|
209
|
+
python -m pip install -e ".[anthropic]"
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The LLM providers have bounded workspace and Docker command tools, but no direct host shell or deployment access. Their command execution runs in disposable containers.
|
|
213
|
+
|
|
214
|
+
## Workspace access for coding tasks
|
|
215
|
+
|
|
216
|
+
OpenAI and Anthropic providers expose bounded workspace tools during specialist execution:
|
|
217
|
+
|
|
218
|
+
| Tool | Capability | AgentForge permission |
|
|
219
|
+
|---|---|---|
|
|
220
|
+
| `workspace_list_files` | List project files | read-only and read-write |
|
|
221
|
+
| `workspace_read_file` | Read UTF-8 source files | read-only and read-write |
|
|
222
|
+
| `workspace_search_files` | Search source text | read-only and read-write |
|
|
223
|
+
| `workspace_write_file` | Create or replace UTF-8 files | read-write only |
|
|
224
|
+
| `workspace_run_command` | Run allowlisted developer commands in isolated Docker | read-only and read-write |
|
|
225
|
+
|
|
226
|
+
The default workspace is the directory from which `forge` is launched. Set `FORGE_WORKSPACE_ROOT` to select a specific project. The runtime confines paths to that root and rejects traversal paths, drive-qualified paths, symlinks that escape the root, secret environment files and private-key files. It excludes common dependency/build directories and caps file sizes, search results, tool rounds and total tool calls.
|
|
227
|
+
|
|
228
|
+
Example:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
export FORGE_WORKSPACE_ROOT="/path/to/my-project"
|
|
232
|
+
export OPENAI_API_KEY="..."
|
|
233
|
+
forge run "Inspect the project, implement a responsive settings page, and add tests" --provider openai
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
The model can read and write files through workspace tools. Command execution is a separate Docker capability described below. Mock mode never modifies files. Review the resulting diff before using changes in production.
|
|
237
|
+
|
|
238
|
+
## Sandboxed command execution
|
|
239
|
+
|
|
240
|
+
Install and start Docker Desktop or Docker Engine. The first setup builds a local toolchain image and verifies that the sandbox can reach the internet.
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
# Check the Docker daemon and configured image
|
|
244
|
+
forge sandbox check
|
|
245
|
+
|
|
246
|
+
# Build forge-runtime-sandbox:internet-v1 once
|
|
247
|
+
forge sandbox build
|
|
248
|
+
|
|
249
|
+
# Smoke-test the image
|
|
250
|
+
forge sandbox run -- python --version
|
|
251
|
+
forge sandbox run -- pytest --version
|
|
252
|
+
|
|
253
|
+
# Run the project's tests in a disposable container
|
|
254
|
+
forge sandbox run -- python -m pytest
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
The LLM providers expose `workspace_run_command`. The tool accepts an argv array, not a raw shell string. Typical commands include `python -m pytest`, `ruff check .`, `mypy`, `npm test`, `node --test` and `make test`. Shells (`bash`/`sh`) are available inside the container for project scripts, but never on the host.
|
|
258
|
+
|
|
259
|
+
The sandbox uses Docker bridge networking for internet access, a read-only container root filesystem, dropped Linux capabilities, `no-new-privileges`, a non-root UID, PID/CPU/memory limits, an execution timeout and capped output. It receives a sanitized temporary copy of the project, excluding `.git`, environment secrets, credentials, private keys, dependency folders and common build caches. With internet enabled, `node_modules` is a temporary writable volume for package installation; in offline mode, an existing local `node_modules` may be mounted read-only. No host API keys or environment variables are forwarded.
|
|
260
|
+
|
|
261
|
+
For AgentForge `read-write` roles, newly created or modified regular files are copied back after the command. Deletions and symlinks are not copied back. For `read-only` roles, all container changes are discarded. A failed test command still returns stdout, stderr and its exit code so the agent can diagnose, fix and rerun.
|
|
262
|
+
|
|
263
|
+
The default image includes Python, pytest, Ruff, mypy, Node.js/npm, Git and common build tools. Projects requiring additional Python, Node or system libraries should customize `src/forge_runtime/sandbox_assets/Dockerfile`, rebuild the image and set `FORGE_SANDBOX_IMAGE` if using a different tag. Runtime containers never pull missing images automatically; run `forge sandbox build` explicitly.
|
|
264
|
+
|
|
265
|
+
Set `FORGE_SANDBOX_ENABLED=false` to remove command execution from model tool schemas. This is a Docker container boundary, not a VM: run Docker Engine on a trusted host, keep it patched and review agent changes before merging.
|
|
266
|
+
|
|
267
|
+
## Registry source selection
|
|
268
|
+
|
|
269
|
+
The default setting `FORGE_AGENTFORGE_SOURCE=auto` loads the packaged AgentForge data via `agentforge.core.data_root()`.
|
|
270
|
+
|
|
271
|
+
| Setting | Behavior |
|
|
272
|
+
|---|---|
|
|
273
|
+
| `auto` | Use installed toolkit; fall back to GitHub when it is unavailable |
|
|
274
|
+
| `installed` | Require installed `agentforge-toolkit` package data |
|
|
275
|
+
| `local` | Use `FORGE_AGENTFORGE_LOCAL_PATH` |
|
|
276
|
+
| `github` | Fetch catalogs and referenced Markdown from the configured GitHub ref |
|
|
277
|
+
|
|
278
|
+
For a local AgentForge checkout:
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
export FORGE_AGENTFORGE_SOURCE=local
|
|
282
|
+
export FORGE_AGENTFORGE_LOCAL_PATH="../agent-forge"
|
|
283
|
+
forge registry validate
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## Local HTTP API
|
|
287
|
+
|
|
288
|
+
Install the API extra and start the service:
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
python -m pip install -e ".[api]"
|
|
292
|
+
forge serve
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Default address: `http://127.0.0.1:8787`. Interactive API documentation is available at `/docs`.
|
|
296
|
+
|
|
297
|
+
| Method | Endpoint | Description |
|
|
298
|
+
|---|---|---|
|
|
299
|
+
| GET | `/health` | Service health |
|
|
300
|
+
| GET | `/v1/agents` | AgentForge agent catalog |
|
|
301
|
+
| GET | `/v1/skills` | AgentForge skill catalog |
|
|
302
|
+
| POST | `/v1/runs` | Submit a goal and execute the run |
|
|
303
|
+
| GET | `/v1/runs` | List recent runs |
|
|
304
|
+
| GET | `/v1/runs/{run_id}` | Read run, task and event records |
|
|
305
|
+
|
|
306
|
+
Example:
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
curl http://127.0.0.1:8787/v1/agents
|
|
310
|
+
|
|
311
|
+
curl -X POST http://127.0.0.1:8787/v1/runs \
|
|
312
|
+
-H "Content-Type: application/json" \
|
|
313
|
+
-d '{"goal":"Plan a secure inventory API","provider":"mock"}'
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
The API executes a submitted run synchronously and returns the final run record. This is a local control API, not yet a durable background job service. Keep it bound to localhost for personal development. For a network bind, set a strong `FORGE_API_TOKEN`; send it as `X-Forge-API-Key`.
|
|
317
|
+
|
|
318
|
+
## Configuration
|
|
319
|
+
|
|
320
|
+
See `.env.example` and [the PyPI publishing guide](docs/publishing.md).
|
|
321
|
+
|
|
322
|
+
| Variable | Purpose | Default |
|
|
323
|
+
|---|---|---|
|
|
324
|
+
| `FORGE_DEFAULT_PROVIDER` | Default model provider | `mock` |
|
|
325
|
+
| `FORGE_MAX_PARALLEL_TASKS` | Max concurrent tasks | `4` |
|
|
326
|
+
| `FORGE_TASK_TIMEOUT_SECONDS` | Per-task timeout ceiling | `900` |
|
|
327
|
+
| `FORGE_DATABASE_URL` | SQLite database URL | `sqlite:///./.forge/forge.db` |
|
|
328
|
+
| `FORGE_WORKSPACE_ROOT` | Project directory visible to the runtime | `.` |
|
|
329
|
+
| `FORGE_SANDBOX_ENABLED` | Expose Docker command tool to LLM agents | `true` |
|
|
330
|
+
| `FORGE_SANDBOX_IMAGE` | Prebuilt local Docker image name | `forge-runtime-sandbox:internet-v1` |
|
|
331
|
+
| `FORGE_SANDBOX_NETWORK` | Sandbox network (`bridge` or `none`) | `bridge` |
|
|
332
|
+
| `FORGE_SANDBOX_CPU_LIMIT` / `MEMORY_MB` | Per-container CPU and memory limits | `2` / `2048` |
|
|
333
|
+
| `FORGE_SANDBOX_DEFAULT_TIMEOUT_SECONDS` | Default command timeout | `180` |
|
|
334
|
+
| `FORGE_AGENTFORGE_SOURCE` | Registry source selection | `auto` |
|
|
335
|
+
| `OPENAI_API_KEY` | OpenAI key | unset |
|
|
336
|
+
| `ANTHROPIC_API_KEY` | Anthropic key | unset |
|
|
337
|
+
| `FORGE_API_TOKEN` | HTTP API key for network binds | unset |
|
|
338
|
+
|
|
339
|
+
## Security and limitations
|
|
340
|
+
|
|
341
|
+
- All user goals, registry files and model outputs are treated as untrusted content.
|
|
342
|
+
- Plans are validated against installed AgentForge role and skill names before any task runs.
|
|
343
|
+
- The scheduler enforces bounded concurrency, retries and timeouts.
|
|
344
|
+
- OpenAI and Anthropic agents can change ordinary files only inside `FORGE_WORKSPACE_ROOT` and only with a `read-write` role; secrets are excluded from workspace tools and sandbox snapshots.
|
|
345
|
+
- Shell/test/build commands run only inside ephemeral Docker containers. Bridge mode provides outbound internet and may reach private/LAN destinations accessible from the Docker host. Use `none` for offline tasks. This is container isolation, not a VM or protection from host-kernel/Docker-daemon vulnerabilities.
|
|
346
|
+
- The sandbox does not provide Git commit/push, deployment, browser access or arbitrary MCP tools. Deletions are not copied back.
|
|
347
|
+
- Tasks flagged as requiring human approval are rejected because the approval subsystem is not enabled yet.
|
|
348
|
+
- SQLite is intended for local and single-process use; it is not a distributed job queue.
|
|
349
|
+
- The baseline evaluator checks output presence and size; it is not a semantic correctness guarantee.
|
|
350
|
+
- The HTTP API should not be exposed publicly without a hardened deployment, authentication, TLS and tenant isolation.
|
|
351
|
+
|
|
352
|
+
## Development
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
python -m pip install -e ".[dev,api]"
|
|
356
|
+
pytest
|
|
357
|
+
ruff check . --fix && ruff check .
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
See [architecture](docs/architecture.md), [provider contracts](docs/providers.md), [sandbox details](docs/sandbox.md), [security](docs/security.md), [roadmap](ROADMAP.md) and [contributing](CONTRIBUTING.md).
|
|
361
|
+
|
|
362
|
+
## License
|
|
363
|
+
|
|
364
|
+
MIT.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
forge_runtime/__init__.py,sha256=uzGnG4FbdHGEWjkVquImoARA8AF4-usCgUzWIM6sYZA,656
|
|
2
|
+
forge_runtime/api.py,sha256=OESanpWFJeIhctiBTp4XXFaUf9j3nmyxwkn94ferM7c,4675
|
|
3
|
+
forge_runtime/cli.py,sha256=HtL8uRb6qCp12GPqGBIILNUmxR_a_oXGCPjraEeCeCM,14734
|
|
4
|
+
forge_runtime/config.py,sha256=qZabx3nz8OPaRMe2kG-XqNuCZefa4vm3N8zNO-_i4Sw,2195
|
|
5
|
+
forge_runtime/evaluation.py,sha256=3rvJE6EPoHTeI82qLVVFnJJrJpRIDyY2Xrls6MG2ttA,726
|
|
6
|
+
forge_runtime/graph.py,sha256=6mIwOdAruTRGe23qslu6Ee9Ctt7LU1dadryjC81nGxc,2922
|
|
7
|
+
forge_runtime/models.py,sha256=aXUsWOuBVgus_Ft1Oxoti-uoETlC79K1EWEA1d640XY,3529
|
|
8
|
+
forge_runtime/orchestrator.py,sha256=_q3gGd-m6OkSmZk-GjHp2X7bH3pmEoFZN2OODCmEwfs,11890
|
|
9
|
+
forge_runtime/registry.py,sha256=Sg8d7C19nOFDSwE8EPrNyUYuWyoyySEZhX4RH_Y9-Xk,10466
|
|
10
|
+
forge_runtime/sandbox.py,sha256=JT9wM7YB4lZgSTxeT4Hkj09RmaQrqvrT0YDSqRsg7EQ,37644
|
|
11
|
+
forge_runtime/storage.py,sha256=ctViXZ4fKxq5d2Yqhbc7XuzwBRepRpzt9ugPBPSJbRY,4228
|
|
12
|
+
forge_runtime/workspace.py,sha256=gwmQjy_LissW6H3cUmnNQ3qBxsXPImqRnfE19HrE62Q,12543
|
|
13
|
+
forge_runtime/providers/__init__.py,sha256=slHNb94H6kLyUibHZkTYkhrtVv0S4MXW29892XJ2jwU,35
|
|
14
|
+
forge_runtime/providers/anthropic_provider.py,sha256=LANfUE0r8ITo1TC1xIs-4I52anyYtTGRkRbVkERy1bk,10492
|
|
15
|
+
forge_runtime/providers/base.py,sha256=xTssjgbIkghth8vBl0HSfPsGnjFM_ryTQfdFOYg01lo,394
|
|
16
|
+
forge_runtime/providers/factory.py,sha256=51JtrrnCsB-PDtZULar0HjKfxa33rq0Ll0vuJOK_2zM,1981
|
|
17
|
+
forge_runtime/providers/mock.py,sha256=11hgq1tfGARv8iPNthXjE-qJEh0IROcrDnXqNs5bQ9Q,1864
|
|
18
|
+
forge_runtime/providers/openai_provider.py,sha256=L4s7Dcv8kaBjbzSL1pcLtdbJYd-JwvxqDl2PNeAz_FM,11552
|
|
19
|
+
forge_runtime/sandbox_assets/__init__.py,sha256=1DtyJxsY2Ax3GNWLVs2N-z5rFJm3RODrxZ8Y18W-UV8,66
|
|
20
|
+
forge_runtime/sandbox_assets/Dockerfile,sha256=kWKehULCDMtHPt2xxK4ZPG5G3FDI8gDPfZcRuagKijk,1296
|
|
21
|
+
forge_agent_runtime-0.1.0.dist-info/METADATA,sha256=dKUOUezw9NGoGQP9ENc2lsaIKoeMEp3L5ZK5fKtIXVY,17957
|
|
22
|
+
forge_agent_runtime-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
23
|
+
forge_agent_runtime-0.1.0.dist-info/entry_points.txt,sha256=cwgQpbGT9zauIxxKwKDny5PEqBX-aOSp2giriKKe4_k,48
|
|
24
|
+
forge_agent_runtime-0.1.0.dist-info/licenses/LICENSE,sha256=DjMXvPnWtt0Iq8cSxPaicC7SGTQVV1DEUpGECqqtmB8,812
|
|
25
|
+
forge_agent_runtime-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mihaita Hingan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Forge Runtime public package interface."""
|
|
2
|
+
from forge_runtime.models import (
|
|
3
|
+
AgentDefinition,
|
|
4
|
+
AgentRequest,
|
|
5
|
+
AgentResult,
|
|
6
|
+
ExecutionPlan,
|
|
7
|
+
RunRecord,
|
|
8
|
+
RunStatus,
|
|
9
|
+
SkillDefinition,
|
|
10
|
+
TaskSpec,
|
|
11
|
+
TaskStatus,
|
|
12
|
+
)
|
|
13
|
+
from forge_runtime.orchestrator import ForgeMaster
|
|
14
|
+
from forge_runtime.registry import AgentForgeRegistry, RegistryError
|
|
15
|
+
|
|
16
|
+
__version__ = "0.1.0"
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"AgentDefinition",
|
|
20
|
+
"AgentForgeRegistry",
|
|
21
|
+
"AgentRequest",
|
|
22
|
+
"AgentResult",
|
|
23
|
+
"ExecutionPlan",
|
|
24
|
+
"ForgeMaster",
|
|
25
|
+
"RegistryError",
|
|
26
|
+
"RunRecord",
|
|
27
|
+
"RunStatus",
|
|
28
|
+
"SkillDefinition",
|
|
29
|
+
"TaskSpec",
|
|
30
|
+
"TaskStatus",
|
|
31
|
+
"__version__",
|
|
32
|
+
]
|
forge_runtime/api.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Optional local HTTP API for submitting and inspecting Forge runs."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import secrets
|
|
5
|
+
from ipaddress import ip_address
|
|
6
|
+
from typing import Annotated, Any
|
|
7
|
+
|
|
8
|
+
from fastapi import Depends, FastAPI, Header, HTTPException, Request
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
|
|
11
|
+
from forge_runtime import __version__
|
|
12
|
+
from forge_runtime.config import Settings
|
|
13
|
+
from forge_runtime.orchestrator import ForgeMaster
|
|
14
|
+
from forge_runtime.providers.factory import create_provider
|
|
15
|
+
from forge_runtime.registry import AgentForgeRegistry, RegistryError
|
|
16
|
+
from forge_runtime.storage import SQLiteStore
|
|
17
|
+
|
|
18
|
+
settings = Settings()
|
|
19
|
+
app = FastAPI(
|
|
20
|
+
title="Forge Runtime API",
|
|
21
|
+
version=__version__,
|
|
22
|
+
description="Local API for AgentForge-backed multi-agent orchestration.",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class CreateRunRequest(BaseModel):
|
|
27
|
+
goal: str = Field(min_length=1, max_length=20_000)
|
|
28
|
+
provider: str | None = None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def authorize(
|
|
32
|
+
request: Request,
|
|
33
|
+
x_forge_api_key: Annotated[str | None, Header()] = None,
|
|
34
|
+
) -> None:
|
|
35
|
+
"""Require a token for non-loopback clients, regardless of the server bind address."""
|
|
36
|
+
if settings.api_token:
|
|
37
|
+
if not x_forge_api_key or not secrets.compare_digest(
|
|
38
|
+
x_forge_api_key, settings.api_token
|
|
39
|
+
):
|
|
40
|
+
raise HTTPException(status_code=401, detail="Invalid or missing Forge API key.")
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
client_host = request.client.host if request.client is not None else ""
|
|
44
|
+
try:
|
|
45
|
+
is_loopback = ip_address(client_host).is_loopback
|
|
46
|
+
except ValueError:
|
|
47
|
+
is_loopback = False
|
|
48
|
+
if not is_loopback:
|
|
49
|
+
raise HTTPException(
|
|
50
|
+
status_code=401,
|
|
51
|
+
detail="Set FORGE_API_TOKEN and send X-Forge-API-Key for non-local access.",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_store() -> SQLiteStore:
|
|
56
|
+
return SQLiteStore(settings.database_url)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_registry() -> AgentForgeRegistry:
|
|
60
|
+
return AgentForgeRegistry(
|
|
61
|
+
local_path=settings.agentforge_local_path,
|
|
62
|
+
repository=settings.agentforge_repository,
|
|
63
|
+
ref=settings.agentforge_ref,
|
|
64
|
+
source=settings.agentforge_source,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def get_runtime(provider_name: str | None = None) -> ForgeMaster:
|
|
69
|
+
registry = get_registry()
|
|
70
|
+
provider = create_provider(provider_name or settings.default_provider, settings)
|
|
71
|
+
return ForgeMaster(registry=registry, provider=provider, settings=settings)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@app.get("/health", dependencies=[Depends(authorize)])
|
|
75
|
+
def health() -> dict[str, str]:
|
|
76
|
+
return {"status": "ok", "service": "forge-runtime", "version": __version__}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@app.get("/v1/agents", dependencies=[Depends(authorize)])
|
|
80
|
+
def list_agents() -> list[dict[str, Any]]:
|
|
81
|
+
try:
|
|
82
|
+
registry = get_registry()
|
|
83
|
+
registry.load()
|
|
84
|
+
except RegistryError as exc:
|
|
85
|
+
raise HTTPException(status_code=503, detail=str(exc)) from exc
|
|
86
|
+
return [
|
|
87
|
+
{
|
|
88
|
+
"name": agent.name,
|
|
89
|
+
"description": agent.description,
|
|
90
|
+
"mode": agent.mode,
|
|
91
|
+
"recommended_skills": list(agent.recommended_skills),
|
|
92
|
+
}
|
|
93
|
+
for agent in registry.agents.values()
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@app.get("/v1/skills", dependencies=[Depends(authorize)])
|
|
98
|
+
def list_skills() -> list[dict[str, str]]:
|
|
99
|
+
try:
|
|
100
|
+
registry = get_registry()
|
|
101
|
+
registry.load()
|
|
102
|
+
except RegistryError as exc:
|
|
103
|
+
raise HTTPException(status_code=503, detail=str(exc)) from exc
|
|
104
|
+
return [
|
|
105
|
+
{"name": skill.name, "description": skill.description, "path": skill.path}
|
|
106
|
+
for skill in registry.skills.values()
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@app.post("/v1/runs", status_code=201, dependencies=[Depends(authorize)])
|
|
111
|
+
async def create_run(payload: CreateRunRequest) -> dict[str, Any]:
|
|
112
|
+
try:
|
|
113
|
+
runtime = get_runtime(payload.provider)
|
|
114
|
+
run = await runtime.execute(payload.goal)
|
|
115
|
+
return run.model_dump(mode="json")
|
|
116
|
+
except RegistryError as exc:
|
|
117
|
+
raise HTTPException(status_code=503, detail=str(exc)) from exc
|
|
118
|
+
except ValueError as exc:
|
|
119
|
+
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
120
|
+
except Exception as exc:
|
|
121
|
+
raise HTTPException(
|
|
122
|
+
status_code=502,
|
|
123
|
+
detail=f"Orchestration failed: {type(exc).__name__}: {exc}",
|
|
124
|
+
) from exc
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@app.get("/v1/runs", dependencies=[Depends(authorize)])
|
|
128
|
+
def list_runs(limit: int = 20) -> list[dict[str, Any]]:
|
|
129
|
+
if limit < 1 or limit > 100:
|
|
130
|
+
raise HTTPException(status_code=422, detail="limit must be between 1 and 100.")
|
|
131
|
+
return get_store().list_runs(limit)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@app.get("/v1/runs/{run_id}", dependencies=[Depends(authorize)])
|
|
135
|
+
def get_run(run_id: str) -> dict[str, Any]:
|
|
136
|
+
run = get_store().get_run(run_id)
|
|
137
|
+
if run is None:
|
|
138
|
+
raise HTTPException(status_code=404, detail="Run not found.")
|
|
139
|
+
return run
|