rdpge 0.1.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.
- rdpge-0.1.0/LICENSE +21 -0
- rdpge-0.1.0/PKG-INFO +357 -0
- rdpge-0.1.0/README.md +326 -0
- rdpge-0.1.0/pyproject.toml +58 -0
- rdpge-0.1.0/setup.cfg +4 -0
- rdpge-0.1.0/src/rdpge/__init__.py +47 -0
- rdpge-0.1.0/src/rdpge/agent.py +193 -0
- rdpge-0.1.0/src/rdpge/core/__init__.py +16 -0
- rdpge-0.1.0/src/rdpge/core/context.py +229 -0
- rdpge-0.1.0/src/rdpge/core/engine.py +513 -0
- rdpge-0.1.0/src/rdpge/core/executor.py +88 -0
- rdpge-0.1.0/src/rdpge/core/graph.py +57 -0
- rdpge-0.1.0/src/rdpge/core/models.py +73 -0
- rdpge-0.1.0/src/rdpge/llm/__init__.py +5 -0
- rdpge-0.1.0/src/rdpge/llm/adapters.py +117 -0
- rdpge-0.1.0/src/rdpge/llm/base.py +49 -0
- rdpge-0.1.0/src/rdpge/observe/__init__.py +5 -0
- rdpge-0.1.0/src/rdpge/observe/hooks.py +93 -0
- rdpge-0.1.0/src/rdpge/observe/trace.py +73 -0
- rdpge-0.1.0/src/rdpge/prompts/__init__.py +1 -0
- rdpge-0.1.0/src/rdpge/prompts/templates/system.j2 +206 -0
- rdpge-0.1.0/src/rdpge/storage/__init__.py +9 -0
- rdpge-0.1.0/src/rdpge/storage/base.py +138 -0
- rdpge-0.1.0/src/rdpge/tools/__init__.py +5 -0
- rdpge-0.1.0/src/rdpge/tools/base.py +214 -0
- rdpge-0.1.0/src/rdpge/tools/registry.py +86 -0
- rdpge-0.1.0/src/rdpge.egg-info/PKG-INFO +357 -0
- rdpge-0.1.0/src/rdpge.egg-info/SOURCES.txt +29 -0
- rdpge-0.1.0/src/rdpge.egg-info/dependency_links.txt +1 -0
- rdpge-0.1.0/src/rdpge.egg-info/requires.txt +9 -0
- rdpge-0.1.0/src/rdpge.egg-info/top_level.txt +1 -0
rdpge-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jahanzeb Ahmed
|
|
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. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
rdpge-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rdpge
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: RDPGE: Runtime Dynamic & Probabilistic Graph Execution — A novel paradigm for agentic AI
|
|
5
|
+
Author: Jahanzeb Ahmed
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Jahanzeb-git/rdpge
|
|
8
|
+
Project-URL: Documentation, https://github.com/Jahanzeb-git/rdpge#readme
|
|
9
|
+
Project-URL: Issues, https://github.com/Jahanzeb-git/rdpge/issues
|
|
10
|
+
Keywords: ai,agents,llm,agentic,graph,execution,framework
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: httpx>=0.27.0
|
|
23
|
+
Requires-Dist: pydantic>=2.0
|
|
24
|
+
Requires-Dist: jinja2>=3.1
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
28
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
29
|
+
Requires-Dist: mypy>=1.10; extra == "dev"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# RDPGE
|
|
33
|
+
|
|
34
|
+
**Runtime Dynamic & Probabilistic Graph Execution**
|
|
35
|
+
|
|
36
|
+
A novel agentic AI framework that treats LLM outputs as probabilistic events, not deterministic programs.
|
|
37
|
+
|
|
38
|
+
RDPGE replaces the flat ReAct loop with a dynamic execution graph — giving agents structured memory, selective context, and real-time situational awareness.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Why RDPGE?
|
|
43
|
+
|
|
44
|
+
Standard agentic frameworks (ReAct, function-calling loops) suffer from three core problems:
|
|
45
|
+
|
|
46
|
+
| Problem | What Happens | RDPGE's Solution |
|
|
47
|
+
|---------|-------------|-----------------|
|
|
48
|
+
| **Myopia** | The agent only sees recent context. Work done 15 steps ago fades from attention. | **Graph Manifest** — a live dashboard showing all tasks, distances, references, and step budget. Updated every turn. |
|
|
49
|
+
| **Greedy execution** | The agent does the first thing that seems useful without considering the bigger picture. | **Task-based organization** — work is structured into named tasks. The agent sees the full map before acting. |
|
|
50
|
+
| **Context waste** | All tool outputs stay in context forever, consuming tokens even when irrelevant. | **Context blurring** — inactive tasks' tool outputs are replaced with `[BLURRED]`. Restored on demand via edges. |
|
|
51
|
+
|
|
52
|
+
## Key Ideas
|
|
53
|
+
|
|
54
|
+
- **Code-as-interface** — The LLM outputs Python code, not JSON tool calls. Comments serve as chain-of-thought. An `action` dictionary is the structured interface.
|
|
55
|
+
- **Probabilistic graph** — Execution is tracked as a dynamic graph of nodes organized by tasks. The LLM creates tasks and nodes as needed.
|
|
56
|
+
- **Context blurring** — Only the active task's tool outputs are visible. Everything else is `[BLURRED]`. The LLM can restore any task's context by creating an edge.
|
|
57
|
+
- **Graph manifest** — Every turn, the LLM sees a live dashboard: active node, current task, step budget, task distances, and inter-task references.
|
|
58
|
+
- **Signal tools** — Built-in tools (`complete`, `ask_user`, `surrender`) that control execution flow explicitly.
|
|
59
|
+
|
|
60
|
+
## Install
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install rdpge
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Quick Start
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
import asyncio
|
|
70
|
+
from rdpge import Agent, tool
|
|
71
|
+
from rdpge.llm import OpenAIAdapter, LLMConfig
|
|
72
|
+
|
|
73
|
+
# 1. Define tools
|
|
74
|
+
@tool()
|
|
75
|
+
def read_file(path: str) -> str:
|
|
76
|
+
"""Read file contents from disk."""
|
|
77
|
+
with open(path) as f:
|
|
78
|
+
return f.read()
|
|
79
|
+
|
|
80
|
+
@tool()
|
|
81
|
+
def write_file(path: str, content: str) -> str:
|
|
82
|
+
"""Write content to a file on disk."""
|
|
83
|
+
with open(path, "w") as f:
|
|
84
|
+
f.write(content)
|
|
85
|
+
return f"Written {len(content)} bytes to {path}"
|
|
86
|
+
|
|
87
|
+
# 2. Create agent
|
|
88
|
+
agent = Agent(
|
|
89
|
+
llm=OpenAIAdapter(LLMConfig(
|
|
90
|
+
model="gpt-4o",
|
|
91
|
+
api_key="sk-...",
|
|
92
|
+
)),
|
|
93
|
+
tools=[read_file, write_file],
|
|
94
|
+
instructions="You are a code assistant.",
|
|
95
|
+
max_steps=25,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# 3. Run
|
|
99
|
+
async def main():
|
|
100
|
+
result = await agent.run("Read auth.py and fix the login bug")
|
|
101
|
+
print(f"Status: {result.status}")
|
|
102
|
+
print(f"Steps used: {result.steps}")
|
|
103
|
+
print(f"Reason: {result.reason}")
|
|
104
|
+
|
|
105
|
+
asyncio.run(main())
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Using with Together AI
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from rdpge.llm import OpenAIAdapter, LLMConfig
|
|
112
|
+
|
|
113
|
+
llm = OpenAIAdapter(LLMConfig(
|
|
114
|
+
model="Qwen/Qwen3-Coder-480B-A35B-Instruct",
|
|
115
|
+
api_key="your-together-key",
|
|
116
|
+
base_url="https://api.together.xyz/v1",
|
|
117
|
+
))
|
|
118
|
+
agent = Agent(llm=llm, tools=[...])
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Using with Anthropic
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from rdpge.llm import AnthropicAdapter, LLMConfig
|
|
125
|
+
|
|
126
|
+
llm = AnthropicAdapter(LLMConfig(
|
|
127
|
+
model="claude-sonnet-4-20250514",
|
|
128
|
+
api_key="sk-ant-...",
|
|
129
|
+
))
|
|
130
|
+
agent = Agent(llm=llm, tools=[...])
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## How It Works
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
User Request
|
|
137
|
+
│
|
|
138
|
+
▼
|
|
139
|
+
┌─────────────────────────────────────────────┐
|
|
140
|
+
│ EXECUTION LOOP │
|
|
141
|
+
│ │
|
|
142
|
+
│ 1. Build Context │
|
|
143
|
+
│ ├── System Prompt │
|
|
144
|
+
│ ├── Graph Manifest (live dashboard) │
|
|
145
|
+
│ └── Node History (with blurring) │
|
|
146
|
+
│ │
|
|
147
|
+
│ 2. LLM Generates Python Code │
|
|
148
|
+
│ └── action = {node, reason, tool_call} │
|
|
149
|
+
│ │
|
|
150
|
+
│ 3. Execute Code in Sandbox │
|
|
151
|
+
│ └── Extract action dict │
|
|
152
|
+
│ │
|
|
153
|
+
│ 4. Route Tool Call │
|
|
154
|
+
│ ├── Signal tool? → Handle internally │
|
|
155
|
+
│ └── Regular tool? → Execute via registry│
|
|
156
|
+
│ │
|
|
157
|
+
│ 5. Update Graph │
|
|
158
|
+
│ ├── Record node in task │
|
|
159
|
+
│ ├── Apply blurring to inactive tasks │
|
|
160
|
+
│ └── Save state (multi-turn) │
|
|
161
|
+
│ │
|
|
162
|
+
│ 6. Loop until: complete / surrender / │
|
|
163
|
+
│ ask_user / max_steps │
|
|
164
|
+
└─────────────────────────────────────────────┘
|
|
165
|
+
│
|
|
166
|
+
▼
|
|
167
|
+
AgentResult(status, reason, steps, trace, graph)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### The Graph Manifest
|
|
171
|
+
|
|
172
|
+
Every turn, the LLM sees this dashboard in its context:
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
Active Node: node-b2
|
|
176
|
+
Current Task: b
|
|
177
|
+
Step: 6 of 25
|
|
178
|
+
Active Edge: (none)
|
|
179
|
+
|
|
180
|
+
Task Map:
|
|
181
|
+
Task A: 2 steps ago | 0 references | 3 steps
|
|
182
|
+
Task B: active | 0 references | 2 steps
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
- **Distance** tells the LLM how far back a task is (recency).
|
|
186
|
+
- **References** show inter-task dependencies (importance).
|
|
187
|
+
- **Step counter** shows remaining budget (the hard execution limit).
|
|
188
|
+
|
|
189
|
+
### Context Blurring
|
|
190
|
+
|
|
191
|
+
When the LLM switches from Task A to Task B:
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
# Task A's tool outputs become:
|
|
195
|
+
Tool: [BLURRED]
|
|
196
|
+
|
|
197
|
+
# Task B's tool outputs remain fully visible:
|
|
198
|
+
Tool: def login(user, pwd): ...
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
The LLM can restore Task A's context by setting `edge: "node-a"` in its action.
|
|
202
|
+
|
|
203
|
+
## Signal Tools
|
|
204
|
+
|
|
205
|
+
Built-in tools that control execution flow:
|
|
206
|
+
|
|
207
|
+
| Signal | Args | Effect |
|
|
208
|
+
|--------|------|--------|
|
|
209
|
+
| `complete` | `{}` | Task is done. Loop ends. |
|
|
210
|
+
| `ask_user` | `{"question": str}` | Pauses execution. Returns the question to the caller. |
|
|
211
|
+
| `surrender` | `{"reason": str}` | Cannot accomplish the task. Loop ends. |
|
|
212
|
+
|
|
213
|
+
Developer-side abort:
|
|
214
|
+
```python
|
|
215
|
+
# From a UI button or hook callback:
|
|
216
|
+
agent.abort("User pressed cancel")
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Multi-Turn Sessions
|
|
220
|
+
|
|
221
|
+
RDPGE preserves full graph state between conversations:
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
# Turn 1
|
|
225
|
+
result = await agent.run("Read the codebase and find bugs")
|
|
226
|
+
# result.status == "awaiting_input" (agent used ask_user)
|
|
227
|
+
|
|
228
|
+
# Turn 2 — agent remembers everything from Turn 1
|
|
229
|
+
result = await agent.run("Focus on auth.py")
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Persistence
|
|
233
|
+
|
|
234
|
+
```python
|
|
235
|
+
from rdpge import Agent, InMemoryStore
|
|
236
|
+
|
|
237
|
+
# In-memory (default)
|
|
238
|
+
agent = Agent(llm=llm, tools=[...], store=InMemoryStore())
|
|
239
|
+
|
|
240
|
+
# Custom store (Redis, database, etc.)
|
|
241
|
+
# Implement the SessionStore protocol:
|
|
242
|
+
class RedisStore:
|
|
243
|
+
async def save(self, session_id: str, data: dict) -> None: ...
|
|
244
|
+
async def load(self, session_id: str) -> dict | None: ...
|
|
245
|
+
async def delete(self, session_id: str) -> None: ...
|
|
246
|
+
async def list_sessions(self) -> list[str]: ...
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Event Hooks
|
|
250
|
+
|
|
251
|
+
Monitor and react to agent behavior:
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
@agent.on("step_end")
|
|
255
|
+
async def log_step(data):
|
|
256
|
+
print(f"Step {data['step']}: {data['node_id']} → {data['tool']}")
|
|
257
|
+
|
|
258
|
+
@agent.on("complete")
|
|
259
|
+
async def on_done(data):
|
|
260
|
+
print(f"Finished in {data['steps']} steps")
|
|
261
|
+
|
|
262
|
+
@agent.on("surrender")
|
|
263
|
+
async def on_surrender(data):
|
|
264
|
+
print(f"Gave up: {data['reason']}")
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Available events: `step_start`, `step_end`, `tool_called`, `complete`, `ask_user`, `surrender`, `abort`, `error`
|
|
268
|
+
|
|
269
|
+
## Execution Traces
|
|
270
|
+
|
|
271
|
+
Every run produces a detailed trace:
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
result = await agent.run("Fix the bug")
|
|
275
|
+
summary = result.trace.summary()
|
|
276
|
+
|
|
277
|
+
print(summary)
|
|
278
|
+
# {
|
|
279
|
+
# "session_id": "a1b2c3d4",
|
|
280
|
+
# "total_steps": 5,
|
|
281
|
+
# "total_duration_ms": 12340,
|
|
282
|
+
# "tools_used": {"read_file": 2, "write_file": 1, "complete": 1, ...},
|
|
283
|
+
# "nodes": ["node-a1", "node-a2", "node-a3", "node-b1", "node-b2"],
|
|
284
|
+
# }
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## API Reference
|
|
288
|
+
|
|
289
|
+
### `Agent`
|
|
290
|
+
|
|
291
|
+
```python
|
|
292
|
+
Agent(
|
|
293
|
+
llm, # LLMProvider instance
|
|
294
|
+
tools: list = None, # List of @tool() or BaseTool instances
|
|
295
|
+
instructions: str = "", # Domain-specific instructions for the LLM
|
|
296
|
+
instructions_file: str = None,# Or load instructions from a file
|
|
297
|
+
max_steps: int = 25, # Hard execution limit
|
|
298
|
+
store = None, # SessionStore for persistence
|
|
299
|
+
signal_handlers: dict = None, # Override signal tool behavior
|
|
300
|
+
)
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**Methods:**
|
|
304
|
+
- `await agent.run(request)` → `AgentResult`
|
|
305
|
+
- `agent.abort(reason)` — stop execution from outside
|
|
306
|
+
- `agent.new_session()` — start fresh
|
|
307
|
+
- `await agent.load_session(session_id)` → `bool`
|
|
308
|
+
- `agent.on(event)` — decorator for event hooks
|
|
309
|
+
|
|
310
|
+
### `AgentResult`
|
|
311
|
+
|
|
312
|
+
```python
|
|
313
|
+
@dataclass
|
|
314
|
+
class AgentResult:
|
|
315
|
+
success: bool # True if task completed successfully
|
|
316
|
+
status: str # "completed" | "awaiting_input" | "surrendered" | "aborted" | "max_steps" | "error"
|
|
317
|
+
reason: str # Human-readable summary
|
|
318
|
+
steps: int # Number of steps executed
|
|
319
|
+
session_id: str # Session identifier
|
|
320
|
+
trace: SessionTrace # Execution trace
|
|
321
|
+
graph: GraphState # Final graph state
|
|
322
|
+
error: str = "" # Error details (if status == "error")
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### `@tool()`
|
|
326
|
+
|
|
327
|
+
```python
|
|
328
|
+
@tool()
|
|
329
|
+
def my_tool(param: str) -> str:
|
|
330
|
+
"""Tool description (used in the LLM prompt)."""
|
|
331
|
+
return result
|
|
332
|
+
|
|
333
|
+
# Or with explicit description:
|
|
334
|
+
@tool("Override the docstring description")
|
|
335
|
+
def my_tool(param: str) -> str:
|
|
336
|
+
...
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## Architecture
|
|
340
|
+
|
|
341
|
+
RDPGE is built on four principles:
|
|
342
|
+
|
|
343
|
+
1. **Respect probabilistic nature** — LLM outputs are unpredictable. The framework handles malformed outputs, retries, and edge cases structurally.
|
|
344
|
+
|
|
345
|
+
2. **Overcome myopia** — The graph manifest gives the LLM a bird's-eye view of its entire session, preventing short-sighted decisions.
|
|
346
|
+
|
|
347
|
+
3. **Ensure task accuracy** — Signal tools (`surrender`, `ask_user`) let the agent be honest about its limitations instead of producing garbage output.
|
|
348
|
+
|
|
349
|
+
4. **Complete observability** — Traces, hooks, and graph export provide full transparency into agent behavior at every level.
|
|
350
|
+
|
|
351
|
+
## License
|
|
352
|
+
|
|
353
|
+
MIT
|
|
354
|
+
|
|
355
|
+
## Author
|
|
356
|
+
|
|
357
|
+
**Jahanzeb Ahmed** — [GitHub](https://github.com/Jahanzeb-git)
|
rdpge-0.1.0/README.md
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# RDPGE
|
|
2
|
+
|
|
3
|
+
**Runtime Dynamic & Probabilistic Graph Execution**
|
|
4
|
+
|
|
5
|
+
A novel agentic AI framework that treats LLM outputs as probabilistic events, not deterministic programs.
|
|
6
|
+
|
|
7
|
+
RDPGE replaces the flat ReAct loop with a dynamic execution graph — giving agents structured memory, selective context, and real-time situational awareness.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Why RDPGE?
|
|
12
|
+
|
|
13
|
+
Standard agentic frameworks (ReAct, function-calling loops) suffer from three core problems:
|
|
14
|
+
|
|
15
|
+
| Problem | What Happens | RDPGE's Solution |
|
|
16
|
+
|---------|-------------|-----------------|
|
|
17
|
+
| **Myopia** | The agent only sees recent context. Work done 15 steps ago fades from attention. | **Graph Manifest** — a live dashboard showing all tasks, distances, references, and step budget. Updated every turn. |
|
|
18
|
+
| **Greedy execution** | The agent does the first thing that seems useful without considering the bigger picture. | **Task-based organization** — work is structured into named tasks. The agent sees the full map before acting. |
|
|
19
|
+
| **Context waste** | All tool outputs stay in context forever, consuming tokens even when irrelevant. | **Context blurring** — inactive tasks' tool outputs are replaced with `[BLURRED]`. Restored on demand via edges. |
|
|
20
|
+
|
|
21
|
+
## Key Ideas
|
|
22
|
+
|
|
23
|
+
- **Code-as-interface** — The LLM outputs Python code, not JSON tool calls. Comments serve as chain-of-thought. An `action` dictionary is the structured interface.
|
|
24
|
+
- **Probabilistic graph** — Execution is tracked as a dynamic graph of nodes organized by tasks. The LLM creates tasks and nodes as needed.
|
|
25
|
+
- **Context blurring** — Only the active task's tool outputs are visible. Everything else is `[BLURRED]`. The LLM can restore any task's context by creating an edge.
|
|
26
|
+
- **Graph manifest** — Every turn, the LLM sees a live dashboard: active node, current task, step budget, task distances, and inter-task references.
|
|
27
|
+
- **Signal tools** — Built-in tools (`complete`, `ask_user`, `surrender`) that control execution flow explicitly.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install rdpge
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
import asyncio
|
|
39
|
+
from rdpge import Agent, tool
|
|
40
|
+
from rdpge.llm import OpenAIAdapter, LLMConfig
|
|
41
|
+
|
|
42
|
+
# 1. Define tools
|
|
43
|
+
@tool()
|
|
44
|
+
def read_file(path: str) -> str:
|
|
45
|
+
"""Read file contents from disk."""
|
|
46
|
+
with open(path) as f:
|
|
47
|
+
return f.read()
|
|
48
|
+
|
|
49
|
+
@tool()
|
|
50
|
+
def write_file(path: str, content: str) -> str:
|
|
51
|
+
"""Write content to a file on disk."""
|
|
52
|
+
with open(path, "w") as f:
|
|
53
|
+
f.write(content)
|
|
54
|
+
return f"Written {len(content)} bytes to {path}"
|
|
55
|
+
|
|
56
|
+
# 2. Create agent
|
|
57
|
+
agent = Agent(
|
|
58
|
+
llm=OpenAIAdapter(LLMConfig(
|
|
59
|
+
model="gpt-4o",
|
|
60
|
+
api_key="sk-...",
|
|
61
|
+
)),
|
|
62
|
+
tools=[read_file, write_file],
|
|
63
|
+
instructions="You are a code assistant.",
|
|
64
|
+
max_steps=25,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# 3. Run
|
|
68
|
+
async def main():
|
|
69
|
+
result = await agent.run("Read auth.py and fix the login bug")
|
|
70
|
+
print(f"Status: {result.status}")
|
|
71
|
+
print(f"Steps used: {result.steps}")
|
|
72
|
+
print(f"Reason: {result.reason}")
|
|
73
|
+
|
|
74
|
+
asyncio.run(main())
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Using with Together AI
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from rdpge.llm import OpenAIAdapter, LLMConfig
|
|
81
|
+
|
|
82
|
+
llm = OpenAIAdapter(LLMConfig(
|
|
83
|
+
model="Qwen/Qwen3-Coder-480B-A35B-Instruct",
|
|
84
|
+
api_key="your-together-key",
|
|
85
|
+
base_url="https://api.together.xyz/v1",
|
|
86
|
+
))
|
|
87
|
+
agent = Agent(llm=llm, tools=[...])
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Using with Anthropic
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from rdpge.llm import AnthropicAdapter, LLMConfig
|
|
94
|
+
|
|
95
|
+
llm = AnthropicAdapter(LLMConfig(
|
|
96
|
+
model="claude-sonnet-4-20250514",
|
|
97
|
+
api_key="sk-ant-...",
|
|
98
|
+
))
|
|
99
|
+
agent = Agent(llm=llm, tools=[...])
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## How It Works
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
User Request
|
|
106
|
+
│
|
|
107
|
+
▼
|
|
108
|
+
┌─────────────────────────────────────────────┐
|
|
109
|
+
│ EXECUTION LOOP │
|
|
110
|
+
│ │
|
|
111
|
+
│ 1. Build Context │
|
|
112
|
+
│ ├── System Prompt │
|
|
113
|
+
│ ├── Graph Manifest (live dashboard) │
|
|
114
|
+
│ └── Node History (with blurring) │
|
|
115
|
+
│ │
|
|
116
|
+
│ 2. LLM Generates Python Code │
|
|
117
|
+
│ └── action = {node, reason, tool_call} │
|
|
118
|
+
│ │
|
|
119
|
+
│ 3. Execute Code in Sandbox │
|
|
120
|
+
│ └── Extract action dict │
|
|
121
|
+
│ │
|
|
122
|
+
│ 4. Route Tool Call │
|
|
123
|
+
│ ├── Signal tool? → Handle internally │
|
|
124
|
+
│ └── Regular tool? → Execute via registry│
|
|
125
|
+
│ │
|
|
126
|
+
│ 5. Update Graph │
|
|
127
|
+
│ ├── Record node in task │
|
|
128
|
+
│ ├── Apply blurring to inactive tasks │
|
|
129
|
+
│ └── Save state (multi-turn) │
|
|
130
|
+
│ │
|
|
131
|
+
│ 6. Loop until: complete / surrender / │
|
|
132
|
+
│ ask_user / max_steps │
|
|
133
|
+
└─────────────────────────────────────────────┘
|
|
134
|
+
│
|
|
135
|
+
▼
|
|
136
|
+
AgentResult(status, reason, steps, trace, graph)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### The Graph Manifest
|
|
140
|
+
|
|
141
|
+
Every turn, the LLM sees this dashboard in its context:
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
Active Node: node-b2
|
|
145
|
+
Current Task: b
|
|
146
|
+
Step: 6 of 25
|
|
147
|
+
Active Edge: (none)
|
|
148
|
+
|
|
149
|
+
Task Map:
|
|
150
|
+
Task A: 2 steps ago | 0 references | 3 steps
|
|
151
|
+
Task B: active | 0 references | 2 steps
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
- **Distance** tells the LLM how far back a task is (recency).
|
|
155
|
+
- **References** show inter-task dependencies (importance).
|
|
156
|
+
- **Step counter** shows remaining budget (the hard execution limit).
|
|
157
|
+
|
|
158
|
+
### Context Blurring
|
|
159
|
+
|
|
160
|
+
When the LLM switches from Task A to Task B:
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
# Task A's tool outputs become:
|
|
164
|
+
Tool: [BLURRED]
|
|
165
|
+
|
|
166
|
+
# Task B's tool outputs remain fully visible:
|
|
167
|
+
Tool: def login(user, pwd): ...
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The LLM can restore Task A's context by setting `edge: "node-a"` in its action.
|
|
171
|
+
|
|
172
|
+
## Signal Tools
|
|
173
|
+
|
|
174
|
+
Built-in tools that control execution flow:
|
|
175
|
+
|
|
176
|
+
| Signal | Args | Effect |
|
|
177
|
+
|--------|------|--------|
|
|
178
|
+
| `complete` | `{}` | Task is done. Loop ends. |
|
|
179
|
+
| `ask_user` | `{"question": str}` | Pauses execution. Returns the question to the caller. |
|
|
180
|
+
| `surrender` | `{"reason": str}` | Cannot accomplish the task. Loop ends. |
|
|
181
|
+
|
|
182
|
+
Developer-side abort:
|
|
183
|
+
```python
|
|
184
|
+
# From a UI button or hook callback:
|
|
185
|
+
agent.abort("User pressed cancel")
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Multi-Turn Sessions
|
|
189
|
+
|
|
190
|
+
RDPGE preserves full graph state between conversations:
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
# Turn 1
|
|
194
|
+
result = await agent.run("Read the codebase and find bugs")
|
|
195
|
+
# result.status == "awaiting_input" (agent used ask_user)
|
|
196
|
+
|
|
197
|
+
# Turn 2 — agent remembers everything from Turn 1
|
|
198
|
+
result = await agent.run("Focus on auth.py")
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Persistence
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
from rdpge import Agent, InMemoryStore
|
|
205
|
+
|
|
206
|
+
# In-memory (default)
|
|
207
|
+
agent = Agent(llm=llm, tools=[...], store=InMemoryStore())
|
|
208
|
+
|
|
209
|
+
# Custom store (Redis, database, etc.)
|
|
210
|
+
# Implement the SessionStore protocol:
|
|
211
|
+
class RedisStore:
|
|
212
|
+
async def save(self, session_id: str, data: dict) -> None: ...
|
|
213
|
+
async def load(self, session_id: str) -> dict | None: ...
|
|
214
|
+
async def delete(self, session_id: str) -> None: ...
|
|
215
|
+
async def list_sessions(self) -> list[str]: ...
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Event Hooks
|
|
219
|
+
|
|
220
|
+
Monitor and react to agent behavior:
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
@agent.on("step_end")
|
|
224
|
+
async def log_step(data):
|
|
225
|
+
print(f"Step {data['step']}: {data['node_id']} → {data['tool']}")
|
|
226
|
+
|
|
227
|
+
@agent.on("complete")
|
|
228
|
+
async def on_done(data):
|
|
229
|
+
print(f"Finished in {data['steps']} steps")
|
|
230
|
+
|
|
231
|
+
@agent.on("surrender")
|
|
232
|
+
async def on_surrender(data):
|
|
233
|
+
print(f"Gave up: {data['reason']}")
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Available events: `step_start`, `step_end`, `tool_called`, `complete`, `ask_user`, `surrender`, `abort`, `error`
|
|
237
|
+
|
|
238
|
+
## Execution Traces
|
|
239
|
+
|
|
240
|
+
Every run produces a detailed trace:
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
result = await agent.run("Fix the bug")
|
|
244
|
+
summary = result.trace.summary()
|
|
245
|
+
|
|
246
|
+
print(summary)
|
|
247
|
+
# {
|
|
248
|
+
# "session_id": "a1b2c3d4",
|
|
249
|
+
# "total_steps": 5,
|
|
250
|
+
# "total_duration_ms": 12340,
|
|
251
|
+
# "tools_used": {"read_file": 2, "write_file": 1, "complete": 1, ...},
|
|
252
|
+
# "nodes": ["node-a1", "node-a2", "node-a3", "node-b1", "node-b2"],
|
|
253
|
+
# }
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## API Reference
|
|
257
|
+
|
|
258
|
+
### `Agent`
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
Agent(
|
|
262
|
+
llm, # LLMProvider instance
|
|
263
|
+
tools: list = None, # List of @tool() or BaseTool instances
|
|
264
|
+
instructions: str = "", # Domain-specific instructions for the LLM
|
|
265
|
+
instructions_file: str = None,# Or load instructions from a file
|
|
266
|
+
max_steps: int = 25, # Hard execution limit
|
|
267
|
+
store = None, # SessionStore for persistence
|
|
268
|
+
signal_handlers: dict = None, # Override signal tool behavior
|
|
269
|
+
)
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
**Methods:**
|
|
273
|
+
- `await agent.run(request)` → `AgentResult`
|
|
274
|
+
- `agent.abort(reason)` — stop execution from outside
|
|
275
|
+
- `agent.new_session()` — start fresh
|
|
276
|
+
- `await agent.load_session(session_id)` → `bool`
|
|
277
|
+
- `agent.on(event)` — decorator for event hooks
|
|
278
|
+
|
|
279
|
+
### `AgentResult`
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
@dataclass
|
|
283
|
+
class AgentResult:
|
|
284
|
+
success: bool # True if task completed successfully
|
|
285
|
+
status: str # "completed" | "awaiting_input" | "surrendered" | "aborted" | "max_steps" | "error"
|
|
286
|
+
reason: str # Human-readable summary
|
|
287
|
+
steps: int # Number of steps executed
|
|
288
|
+
session_id: str # Session identifier
|
|
289
|
+
trace: SessionTrace # Execution trace
|
|
290
|
+
graph: GraphState # Final graph state
|
|
291
|
+
error: str = "" # Error details (if status == "error")
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### `@tool()`
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
@tool()
|
|
298
|
+
def my_tool(param: str) -> str:
|
|
299
|
+
"""Tool description (used in the LLM prompt)."""
|
|
300
|
+
return result
|
|
301
|
+
|
|
302
|
+
# Or with explicit description:
|
|
303
|
+
@tool("Override the docstring description")
|
|
304
|
+
def my_tool(param: str) -> str:
|
|
305
|
+
...
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Architecture
|
|
309
|
+
|
|
310
|
+
RDPGE is built on four principles:
|
|
311
|
+
|
|
312
|
+
1. **Respect probabilistic nature** — LLM outputs are unpredictable. The framework handles malformed outputs, retries, and edge cases structurally.
|
|
313
|
+
|
|
314
|
+
2. **Overcome myopia** — The graph manifest gives the LLM a bird's-eye view of its entire session, preventing short-sighted decisions.
|
|
315
|
+
|
|
316
|
+
3. **Ensure task accuracy** — Signal tools (`surrender`, `ask_user`) let the agent be honest about its limitations instead of producing garbage output.
|
|
317
|
+
|
|
318
|
+
4. **Complete observability** — Traces, hooks, and graph export provide full transparency into agent behavior at every level.
|
|
319
|
+
|
|
320
|
+
## License
|
|
321
|
+
|
|
322
|
+
MIT
|
|
323
|
+
|
|
324
|
+
## Author
|
|
325
|
+
|
|
326
|
+
**Jahanzeb Ahmed** — [GitHub](https://github.com/Jahanzeb-git)
|