datagol-agent-harness 0.2.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.
- datagol_agent_harness-0.2.0/PKG-INFO +809 -0
- datagol_agent_harness-0.2.0/README.md +756 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/__init__.py +114 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/builtin/__init__.py +19 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/builtin/bash.py +58 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/builtin/file_output.py +31 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/builtin/filesystem.py +79 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/builtin/memory.py +104 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/builtin/web.py +32 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/core.py +392 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/evals/__init__.py +83 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/evals/cli.py +250 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/evals/datasets/__init__.py +23 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/evals/datasets/guardrails.py +34 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/evals/datasets/loader.py +85 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/evals/datasets/memory.py +41 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/evals/datasets/multi_agent.py +40 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/evals/datasets/registry.py +41 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/evals/datasets/skills.py +63 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/evals/datasets/tool_calling.py +57 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/evals/evaluators/__init__.py +171 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/evals/evaluators/correctness.py +170 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/evals/evaluators/delegation.py +42 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/evals/evaluators/llm_judge.py +116 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/evals/evaluators/skills.py +58 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/evals/evaluators/tools.py +194 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/evals/evaluators/trajectory.py +106 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/evals/runner.py +238 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/evals/target.py +220 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/extensions/__init__.py +37 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/extensions/base.py +157 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/extensions/langsmith.py +436 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/extensions/result_spill.py +548 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/hooks.py +135 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/mcp.py +437 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/memory.py +613 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/permissions.py +169 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/providers/__init__.py +14 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/providers/anthropic.py +138 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/providers/base.py +87 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/providers/openai.py +310 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/py.typed +0 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/runtime.py +415 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/sandbox.py +439 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/skills.py +221 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/streaming.py +307 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/tools.py +288 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness/types.py +222 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness.egg-info/PKG-INFO +809 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness.egg-info/SOURCES.txt +56 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness.egg-info/dependency_links.txt +1 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness.egg-info/requires.txt +36 -0
- datagol_agent_harness-0.2.0/datagol_agent_harness.egg-info/top_level.txt +1 -0
- datagol_agent_harness-0.2.0/pyproject.toml +80 -0
- datagol_agent_harness-0.2.0/setup.cfg +4 -0
- datagol_agent_harness-0.2.0/tests/test_evals.py +410 -0
- datagol_agent_harness-0.2.0/tests/test_harness_enhancements.py +352 -0
- datagol_agent_harness-0.2.0/tests/test_langsmith_extension.py +343 -0
|
@@ -0,0 +1,809 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: datagol-agent-harness
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A lightweight, production-grade LLM agent toolkit in Python: streaming, MCP, lazy skills, memory, and sandboxing.
|
|
5
|
+
Author-email: Jyotish Bora <jyotish.bora@datagol.ai>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/inncretech/datagol-agent-harness
|
|
8
|
+
Project-URL: Repository, https://github.com/inncretech/datagol-agent-harness
|
|
9
|
+
Project-URL: Issues, https://github.com/inncretech/datagol-agent-harness/issues
|
|
10
|
+
Project-URL: Documentation, https://github.com/inncretech/datagol-agent-harness#readme
|
|
11
|
+
Keywords: agent,ai-agents,llm,anthropic,claude,openai,mcp,model-context-protocol,sandboxing,langsmith,multi-agent,streaming
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
21
|
+
Classifier: Operating System :: MacOS
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Requires-Dist: anthropic>=0.83.0
|
|
25
|
+
Requires-Dist: rich>=13.0.0
|
|
26
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
27
|
+
Requires-Dist: httpx>=0.27.0
|
|
28
|
+
Provides-Extra: langsmith
|
|
29
|
+
Requires-Dist: langsmith>=0.1.0; extra == "langsmith"
|
|
30
|
+
Provides-Extra: openai
|
|
31
|
+
Requires-Dist: openai>=1.50.0; extra == "openai"
|
|
32
|
+
Requires-Dist: tiktoken>=0.7.0; extra == "openai"
|
|
33
|
+
Provides-Extra: mcp
|
|
34
|
+
Requires-Dist: mcp[cli]>=1.0.0; extra == "mcp"
|
|
35
|
+
Provides-Extra: docker
|
|
36
|
+
Requires-Dist: docker>=7.0.0; extra == "docker"
|
|
37
|
+
Provides-Extra: server
|
|
38
|
+
Requires-Dist: fastapi>=0.110.0; extra == "server"
|
|
39
|
+
Requires-Dist: uvicorn[standard]>=0.27.0; extra == "server"
|
|
40
|
+
Requires-Dist: python-dotenv>=1.0.0; extra == "server"
|
|
41
|
+
Provides-Extra: dev
|
|
42
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
43
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
44
|
+
Provides-Extra: all
|
|
45
|
+
Requires-Dist: langsmith>=0.1.0; extra == "all"
|
|
46
|
+
Requires-Dist: openai>=1.50.0; extra == "all"
|
|
47
|
+
Requires-Dist: tiktoken>=0.7.0; extra == "all"
|
|
48
|
+
Requires-Dist: mcp[cli]>=1.0.0; extra == "all"
|
|
49
|
+
Requires-Dist: docker>=7.0.0; extra == "all"
|
|
50
|
+
Requires-Dist: fastapi>=0.110.0; extra == "all"
|
|
51
|
+
Requires-Dist: uvicorn[standard]>=0.27.0; extra == "all"
|
|
52
|
+
Requires-Dist: python-dotenv>=1.0.0; extra == "all"
|
|
53
|
+
|
|
54
|
+
# DataGOL Agent Harness
|
|
55
|
+
|
|
56
|
+
[](https://pypi.org/project/datagol-agent-harness/)
|
|
57
|
+
[](https://pypi.org/project/datagol-agent-harness/)
|
|
58
|
+
[](https://opensource.org/licenses/MIT)
|
|
59
|
+
|
|
60
|
+
**A provider-agnostic, library-first Python toolkit for building production-grade LLM agents.**
|
|
61
|
+
|
|
62
|
+
Rather than locking developers into rigid state-machine graphs or opaque persona prompts, `datagol-agent-harness` gives you a composable set of building blocks: real-time streaming, typed tool registration with schema inference, lazy-loaded skills, multi-agent delegation, 4-tier memory, sandboxed code execution, Model Context Protocol (MCP) tool bridges, native LangSmith tracing, and evaluation suites.
|
|
63
|
+
|
|
64
|
+
### Why DataGOL Agent Harness?
|
|
65
|
+
|
|
66
|
+
- **No Graph Boilerplate:** Write simple async Python functions instead of complex state graphs.
|
|
67
|
+
- **Provider-Agnostic:** First-class support for Anthropic (Claude 3.5/3.7/Sonnet/Opus) and OpenAI (GPT-4o/GPT-5).
|
|
68
|
+
- **Streaming-First:** First-class typed event stream (`run_stream`) for WebSocket and SSE frontends.
|
|
69
|
+
- **Lazy Instruction Packs (Skills):** Load specialized guidelines only when needed via YAML-frontmatter `SKILL.md` packs.
|
|
70
|
+
- **Enterprise Guardrails & Sandboxing:** Execute untrusted code safely via Process, Docker, or macOS Seatbelt isolation with granular budget and iteration caps.
|
|
71
|
+
- **MCP Native:** Connect to any Model Context Protocol server (stdio subprocess or remote SSE) in 3 lines of code.
|
|
72
|
+
- **Full Observability & Evals:** Zero-overhead lifecycle hooks, middleware transforms, native LangSmith tracing, and benchmark evaluations.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
- [Setup](#setup)
|
|
77
|
+
- [1. Creating an agent](#1-creating-an-agent)
|
|
78
|
+
- [2. Registering tools](#2-registering-tools)
|
|
79
|
+
- [3. Streaming agents](#3-streaming-agents)
|
|
80
|
+
- [4. Multi-agent orchestration](#4-multi-agent-orchestration)
|
|
81
|
+
- [5. Skills](#5-skills)
|
|
82
|
+
- [6. Memory](#6-memory)
|
|
83
|
+
- [7. Permissions and guardrails](#7-permissions-and-guardrails)
|
|
84
|
+
- [8. Hooks and middleware](#8-hooks-and-middleware)
|
|
85
|
+
- [9. MCP servers](#9-mcp-servers)
|
|
86
|
+
- [10. Session persistence](#10-session-persistence)
|
|
87
|
+
- [11. Shipping a web app](#11-shipping-a-web-app)
|
|
88
|
+
- [12. Evaluations with LangSmith](#12-evaluations-with-langsmith)
|
|
89
|
+
- [API reference (quick)](#api-reference-quick)
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Setup
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# From PyPI
|
|
97
|
+
pip install datagol-agent-harness
|
|
98
|
+
|
|
99
|
+
# With optional extras (LangSmith, OpenAI, MCP, Docker, or everything)
|
|
100
|
+
pip install "datagol-agent-harness[langsmith]"
|
|
101
|
+
pip install "datagol-agent-harness[openai]"
|
|
102
|
+
pip install "datagol-agent-harness[all]"
|
|
103
|
+
|
|
104
|
+
# For local development (editable)
|
|
105
|
+
pip install -e ".[all]"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Set your key (a root `.env` is auto-loaded by the `examples` package):
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
ANTHROPIC_API_KEY=sk-ant-...
|
|
112
|
+
# or, for OpenAI-backed agents:
|
|
113
|
+
OPENAI_API_KEY=sk-...
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Run examples as modules from the repo root (not as plain scripts):
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
python -m examples.simple_chat
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 1. Creating an agent
|
|
125
|
+
|
|
126
|
+
`Agent` is the core agentic loop: LLM reasoning → tool execution → repeat,
|
|
127
|
+
until the model stops with `end_turn`.
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
import asyncio
|
|
131
|
+
from datagol_agent_harness import Agent, AgentConfig
|
|
132
|
+
|
|
133
|
+
agent = Agent(
|
|
134
|
+
config=AgentConfig(
|
|
135
|
+
model="claude-sonnet-4-6", # or "gpt-5"
|
|
136
|
+
provider="anthropic", # or "openai"
|
|
137
|
+
system_prompt="You are a helpful assistant.",
|
|
138
|
+
max_tokens=8192,
|
|
139
|
+
max_iterations=50, # loop guardrail
|
|
140
|
+
temperature=0.0,
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
print(asyncio.run(agent.run("What is 17 + 25?")))
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`AgentConfig` fields (all optional, these are the defaults):
|
|
148
|
+
|
|
149
|
+
| Field | Default | Purpose |
|
|
150
|
+
|---|---|---|
|
|
151
|
+
| `model` | `"claude-sonnet-4-6"` | Model id passed to the provider |
|
|
152
|
+
| `provider` | `"anthropic"` | `"anthropic"` or `"openai"` |
|
|
153
|
+
| `max_tokens` | `8192` | Per-response token cap |
|
|
154
|
+
| `max_iterations` | `50` | Max loop iterations before `MaxIterationsError` |
|
|
155
|
+
| `system_prompt` | `"You are a helpful assistant."` | System prompt |
|
|
156
|
+
| `temperature` | `0.0` | Sampling temperature |
|
|
157
|
+
| `max_result_chars` | `12000` | Tool-result eviction threshold in memory |
|
|
158
|
+
|
|
159
|
+
You can also inject your own pieces — everything is a constructor parameter:
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
from datagol_agent_harness import (
|
|
163
|
+
Agent, AgentConfig, ConversationMemory, HookManager,
|
|
164
|
+
MiddlewarePipeline, PermissionManager, ToolRegistry,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
agent = Agent(
|
|
168
|
+
config=AgentConfig(system_prompt="..."),
|
|
169
|
+
tools=ToolRegistry(), # your own registry
|
|
170
|
+
memory=ConversationMemory(), # your own memory
|
|
171
|
+
permissions=PermissionManager(), # your own permission policy
|
|
172
|
+
hooks=HookManager(), # lifecycle hooks
|
|
173
|
+
middleware=MiddlewarePipeline(), # request/response transforms
|
|
174
|
+
)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Conversation loop
|
|
178
|
+
|
|
179
|
+
`agent.run()` keeps the conversation in memory, so calling it repeatedly is a
|
|
180
|
+
multi-turn chat:
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
async def chat(agent):
|
|
184
|
+
while True:
|
|
185
|
+
text = input("You: ").strip()
|
|
186
|
+
if text in ("", "quit"):
|
|
187
|
+
break
|
|
188
|
+
print("Agent:", await agent.run(text))
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## 2. Registering tools
|
|
194
|
+
|
|
195
|
+
A tool is any Python function. The registry converts its signature, type
|
|
196
|
+
hints, and docstring into a JSON schema for the model, and dispatches calls
|
|
197
|
+
back to the function.
|
|
198
|
+
|
|
199
|
+
### Decorator registration
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
from datagol_agent_harness import PermissionLevel
|
|
203
|
+
|
|
204
|
+
@agent.tools.register(permission=PermissionLevel.ALLOW)
|
|
205
|
+
def add(a: int, b: int) -> int:
|
|
206
|
+
"""Add two numbers.""" # becomes the tool description
|
|
207
|
+
return a + b
|
|
208
|
+
|
|
209
|
+
@agent.tools.register(permission=PermissionLevel.ASK)
|
|
210
|
+
async def read_file(path: str) -> str:
|
|
211
|
+
"""Read a file from disk.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
path: Absolute path to the file. # becomes the param description
|
|
215
|
+
"""
|
|
216
|
+
with open(path) as f:
|
|
217
|
+
return f.read()
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Rules of thumb:
|
|
221
|
+
|
|
222
|
+
- The **first line of the docstring** is the tool description the model sees.
|
|
223
|
+
- An `Args:` section documents parameters.
|
|
224
|
+
- Type hints drive the JSON schema: `str`, `int`, `float`, `bool`, `list[X]`,
|
|
225
|
+
`dict`. Optional params (with defaults) are not required.
|
|
226
|
+
- **Pitfall:** avoid `list[str] | None` style unions — the schema generator
|
|
227
|
+
can't express them and will fall back to `"type": "string"`. Give optional
|
|
228
|
+
params a plain default and type instead, or use `register_with_schema`
|
|
229
|
+
(below) for exact control.
|
|
230
|
+
- Sync and async handlers both work. Errors never crash the agent — they are
|
|
231
|
+
returned to the model as `is_error` tool results.
|
|
232
|
+
|
|
233
|
+
### Explicit schema registration
|
|
234
|
+
|
|
235
|
+
When you need a schema the signature can't express (nested objects, arrays
|
|
236
|
+
with item types, `oneOf`-style choices):
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
async def propose_items(query: str = "", items=None, intro_text: str = "") -> str:
|
|
240
|
+
if (query and items) or (not query and not items):
|
|
241
|
+
return "Error: provide exactly one of `query` or `items`."
|
|
242
|
+
resolved = [query] if query else items
|
|
243
|
+
return f"Presented {len(resolved)} item(s)."
|
|
244
|
+
|
|
245
|
+
agent.tools.register_with_schema(
|
|
246
|
+
name="propose_items",
|
|
247
|
+
description="Propose items, via query (user's words) or items (your strings).",
|
|
248
|
+
input_schema={
|
|
249
|
+
"type": "object",
|
|
250
|
+
"properties": {
|
|
251
|
+
"query": {"type": "string", "description": "User's words, untouched."},
|
|
252
|
+
"items": {
|
|
253
|
+
"type": "array",
|
|
254
|
+
"items": {"type": "string"},
|
|
255
|
+
"description": "Item strings you wrote yourself.",
|
|
256
|
+
},
|
|
257
|
+
"intro_text": {"type": "string"},
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
handler=propose_items,
|
|
261
|
+
permission=PermissionLevel.ALLOW,
|
|
262
|
+
)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Built-in tools
|
|
266
|
+
|
|
267
|
+
`datagol_agent_harness.builtin` ships ready-made tool sets:
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
from datagol_agent_harness.builtin import register_all_tools
|
|
271
|
+
|
|
272
|
+
register_all_tools(agent.tools)
|
|
273
|
+
# read_file, write_file, list_directory, run_bash, fetch_url, memory tools...
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Or individually: `register_filesystem_tools`, `register_bash_tools`,
|
|
277
|
+
`register_web_tools`, `register_memory_tools`.
|
|
278
|
+
|
|
279
|
+
### Inspecting and executing
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
agent.tools.list_tools() # ['add', 'read_file', ...]
|
|
283
|
+
agent.tools.get_tool_params() # Anthropic-format tool schemas
|
|
284
|
+
result = await agent.tools.execute(call) # manual dispatch (ToolCall -> ToolResult)
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## 3. Streaming agents
|
|
290
|
+
|
|
291
|
+
`StreamingAgent` yields typed events as the model generates, instead of
|
|
292
|
+
returning one final string. It mirrors `Agent`'s constructor.
|
|
293
|
+
|
|
294
|
+
```python
|
|
295
|
+
import asyncio
|
|
296
|
+
from datagol_agent_harness import AgentConfig, StreamingAgent, StreamEventType
|
|
297
|
+
|
|
298
|
+
agent = StreamingAgent(config=AgentConfig(system_prompt="You are helpful."))
|
|
299
|
+
|
|
300
|
+
async def main():
|
|
301
|
+
async for event in agent.run_stream("Tell me a story"):
|
|
302
|
+
if event.type == StreamEventType.TEXT_DELTA:
|
|
303
|
+
print(event.data, end="", flush=True)
|
|
304
|
+
elif event.type == StreamEventType.TOOL_CALL_START:
|
|
305
|
+
print(f"\n> calling {event.data.name}({event.data.input})")
|
|
306
|
+
elif event.type == StreamEventType.TOOL_RESULT:
|
|
307
|
+
if event.data.is_error:
|
|
308
|
+
print(f" tool error: {event.data.content}")
|
|
309
|
+
elif event.type == StreamEventType.TURN_COMPLETE:
|
|
310
|
+
print()
|
|
311
|
+
|
|
312
|
+
asyncio.run(main())
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Event types: `TEXT_DELTA`, `TEXT_COMPLETE`, `TOOL_CALL_START`,
|
|
316
|
+
`TOOL_CALL_COMPLETE`, `TOOL_RESULT`, `THINKING_DELTA`, `TURN_COMPLETE`,
|
|
317
|
+
`ERROR`.
|
|
318
|
+
|
|
319
|
+
Usage stats accumulate on the guardrails engine:
|
|
320
|
+
|
|
321
|
+
```python
|
|
322
|
+
agent.guardrails.usage_summary
|
|
323
|
+
# {'iterations': 3, 'input_tokens': 5120, 'output_tokens': 640, 'estimated_cost': '$0.0250'}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## 4. Multi-agent orchestration
|
|
329
|
+
|
|
330
|
+
There is no special "multi-agent" class — a sub-agent is just an `Agent`
|
|
331
|
+
wrapped in a tool. The orchestrator's LLM decides when to delegate; the tool
|
|
332
|
+
runs the specialist to completion and returns its text as the tool result.
|
|
333
|
+
|
|
334
|
+
```python
|
|
335
|
+
import asyncio
|
|
336
|
+
from datagol_agent_harness import Agent, AgentConfig, PermissionLevel
|
|
337
|
+
from datagol_agent_harness.builtin.web import register_web_tools
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
async def run_research_agent(query: str) -> str:
|
|
341
|
+
"""A specialist with its own prompt, tools, and iteration budget."""
|
|
342
|
+
researcher = Agent(
|
|
343
|
+
config=AgentConfig(
|
|
344
|
+
system_prompt="You are a research specialist. Cite sources.",
|
|
345
|
+
max_iterations=10,
|
|
346
|
+
)
|
|
347
|
+
)
|
|
348
|
+
register_web_tools(researcher.tools)
|
|
349
|
+
researcher.permissions.set_permission("fetch_url", PermissionLevel.ALLOW)
|
|
350
|
+
return await researcher.run(query)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
orchestrator = Agent(
|
|
354
|
+
config=AgentConfig(
|
|
355
|
+
system_prompt=(
|
|
356
|
+
"You manage a team of specialists. Delegate research to "
|
|
357
|
+
"delegate_research; synthesize results for the user."
|
|
358
|
+
),
|
|
359
|
+
max_iterations=20,
|
|
360
|
+
)
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
@orchestrator.tools.register(permission=PermissionLevel.ALLOW)
|
|
364
|
+
async def delegate_research(query: str) -> str:
|
|
365
|
+
"""Delegate a research question to the research specialist.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
query: The research question.
|
|
369
|
+
"""
|
|
370
|
+
return await run_research_agent(query)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
print(asyncio.run(orchestrator.run("Compare SQLite and DuckDB for analytics.")))
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Patterns that work well:
|
|
377
|
+
|
|
378
|
+
- **Give each specialist a tight system prompt and a small tool set.** The
|
|
379
|
+
orchestrator's prompt should list its delegation tools and when to use them.
|
|
380
|
+
- **Cap `max_iterations` per specialist** so a runaway sub-agent can't burn
|
|
381
|
+
the budget.
|
|
382
|
+
- Sub-agents are created per call (stateless). If a specialist needs
|
|
383
|
+
continuity, keep one instance and reuse it across calls.
|
|
384
|
+
- Sub-agents can themselves delegate — the pattern composes.
|
|
385
|
+
|
|
386
|
+
A full working version with three specialists lives in
|
|
387
|
+
`examples/multi_agent.py` (`python -m examples.multi_agent`).
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
## 5. Skills
|
|
392
|
+
|
|
393
|
+
A skill is a markdown file with YAML frontmatter — a way to ship reusable
|
|
394
|
+
instruction packs that load **lazily**: the agent only sees each skill's
|
|
395
|
+
name + description in its system prompt, and pulls the full body into context
|
|
396
|
+
by calling the `Skill` tool when a request matches.
|
|
397
|
+
|
|
398
|
+
### Authoring a skill
|
|
399
|
+
|
|
400
|
+
`skills/commit-message/SKILL.md`:
|
|
401
|
+
|
|
402
|
+
```markdown
|
|
403
|
+
---
|
|
404
|
+
name: commit-message
|
|
405
|
+
description: Write a conventional commit message for a described change.
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
You write commit messages. Rules:
|
|
409
|
+
- Conventional Commits format: type(scope): subject
|
|
410
|
+
- Subject in imperative mood, <= 72 chars, no trailing period
|
|
411
|
+
- Add a body only when the "why" isn't obvious from the subject
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
A path can be a folder containing `SKILL.md`, or a markdown file directly.
|
|
415
|
+
|
|
416
|
+
### Loading skills into an agent
|
|
417
|
+
|
|
418
|
+
```python
|
|
419
|
+
from datagol_agent_harness import Agent, AgentConfig
|
|
420
|
+
|
|
421
|
+
agent = Agent(
|
|
422
|
+
config=AgentConfig(
|
|
423
|
+
system_prompt=(
|
|
424
|
+
"You are an engineering assistant. When a request matches a "
|
|
425
|
+
"skill in <available-skills>, call the Skill tool first to load "
|
|
426
|
+
"its instructions, then follow them."
|
|
427
|
+
),
|
|
428
|
+
),
|
|
429
|
+
skills=["./skills/commit-message", "./skills/code-review"],
|
|
430
|
+
)
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
The `skills=` argument accepts a list of paths or a `SkillManager`:
|
|
434
|
+
|
|
435
|
+
```python
|
|
436
|
+
from datagol_agent_harness import SkillManager
|
|
437
|
+
|
|
438
|
+
manager = SkillManager.from_paths(["./skills/commit-message"])
|
|
439
|
+
agent = Agent(config=AgentConfig(system_prompt="..."), skills=manager)
|
|
440
|
+
|
|
441
|
+
for s in agent.skills.list():
|
|
442
|
+
print(s.name, "-", s.description)
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### Reacting to skill invocation
|
|
446
|
+
|
|
447
|
+
A `SKILL_INVOKED` hook fires when the model loads a skill:
|
|
448
|
+
|
|
449
|
+
```python
|
|
450
|
+
from datagol_agent_harness import HookContext, HookEvent
|
|
451
|
+
|
|
452
|
+
async def on_skill(ctx: HookContext):
|
|
453
|
+
if ctx.data.get("found"):
|
|
454
|
+
print(f"loaded skill: {ctx.data['skill']} ({ctx.data['body_chars']} chars)")
|
|
455
|
+
|
|
456
|
+
agent.hooks.on(HookEvent.SKILL_INVOKED, on_skill)
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
Full demo: `examples/skills_agent.py`, with sample skills under
|
|
460
|
+
`examples/skills/`.
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
## 6. Memory
|
|
465
|
+
|
|
466
|
+
Three layers, all optional:
|
|
467
|
+
|
|
468
|
+
### Conversation memory (default)
|
|
469
|
+
|
|
470
|
+
Every agent has a `ConversationMemory` that stores the message list and trims
|
|
471
|
+
itself when approaching the context limit. You rarely touch it directly, but
|
|
472
|
+
you can:
|
|
473
|
+
|
|
474
|
+
```python
|
|
475
|
+
agent.memory.get_messages() # current message list
|
|
476
|
+
agent.memory.set_messages(saved) # restore
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### Agent memory — markdown notes on disk
|
|
480
|
+
|
|
481
|
+
`AgentMemory` persists titled, tagged notes as markdown files. You expose it
|
|
482
|
+
to the model through tools you write:
|
|
483
|
+
|
|
484
|
+
```python
|
|
485
|
+
from datagol_agent_harness import AgentMemory, PermissionLevel
|
|
486
|
+
|
|
487
|
+
memory = AgentMemory(storage_dir=".agent_memory/agent")
|
|
488
|
+
|
|
489
|
+
@agent.tools.register(permission=PermissionLevel.ALLOW)
|
|
490
|
+
async def save_memory(title: str, content: str, tags: str = "") -> str:
|
|
491
|
+
"""Save a note to persistent memory.
|
|
492
|
+
|
|
493
|
+
Args:
|
|
494
|
+
title: Short title for the memory.
|
|
495
|
+
content: The information to remember.
|
|
496
|
+
tags: Comma-separated tags.
|
|
497
|
+
"""
|
|
498
|
+
tag_list = [t.strip() for t in tags.split(",") if t.strip()]
|
|
499
|
+
filename = memory.save(title, content, tags=tag_list)
|
|
500
|
+
return f"Saved '{title}' -> {filename}"
|
|
501
|
+
|
|
502
|
+
@agent.tools.register(permission=PermissionLevel.ALLOW)
|
|
503
|
+
async def search_memory(query: str = "") -> str:
|
|
504
|
+
"""Search saved memories by keyword."""
|
|
505
|
+
results = memory.search(query=query)
|
|
506
|
+
return "\n".join(f"- {r['title']} [{r['filename']}]" for r in results) or "None found."
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Long-term memory — structured facts
|
|
510
|
+
|
|
511
|
+
`LongTermMemory` stores categorized facts with ids:
|
|
512
|
+
|
|
513
|
+
```python
|
|
514
|
+
from datagol_agent_harness import LongTermMemory
|
|
515
|
+
|
|
516
|
+
long_term = LongTermMemory(storage_dir=".agent_memory/long_term")
|
|
517
|
+
fact_id = long_term.save("User prefers dark mode", category="preference")
|
|
518
|
+
long_term.search(query="dark mode")
|
|
519
|
+
long_term.update(fact_id, "User prefers light mode")
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
Tell the agent about these tools in the system prompt and when to use them
|
|
523
|
+
("proactively save useful information"). A complete two-layer example is in
|
|
524
|
+
`examples/memory_agent.py`.
|
|
525
|
+
|
|
526
|
+
---
|
|
527
|
+
|
|
528
|
+
## 7. Permissions and guardrails
|
|
529
|
+
|
|
530
|
+
### Permission levels
|
|
531
|
+
|
|
532
|
+
Every tool has a level: `ALLOW` (run silently), `ASK` (prompt the user
|
|
533
|
+
y/n/always), or `DENY` (never run). The default at registration is `ASK`.
|
|
534
|
+
|
|
535
|
+
```python
|
|
536
|
+
from datagol_agent_harness import PermissionLevel
|
|
537
|
+
|
|
538
|
+
# At registration
|
|
539
|
+
@agent.tools.register(permission=PermissionLevel.ALLOW)
|
|
540
|
+
def add(a: int, b: int) -> int: ...
|
|
541
|
+
|
|
542
|
+
# Or override later
|
|
543
|
+
agent.permissions.set_permission("run_bash", PermissionLevel.ASK)
|
|
544
|
+
agent.permissions.grant_session("read_file") # pre-approve for this session
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
`ASK` prompts on stdin in terminal apps. In a server context, register tools
|
|
548
|
+
as `ALLOW` (or build your own `PermissionManager` subclass that asks over
|
|
549
|
+
your transport).
|
|
550
|
+
|
|
551
|
+
### Guardrails
|
|
552
|
+
|
|
553
|
+
```python
|
|
554
|
+
from datagol_agent_harness import GuardrailsEngine, MaxIterationsError, CostLimitError
|
|
555
|
+
|
|
556
|
+
agent.guardrails.max_cost_dollars = 0.50 # raises CostLimitError past this
|
|
557
|
+
agent.guardrails.usage_summary # tokens + estimated cost so far
|
|
558
|
+
agent.guardrails.reset() # reset counters
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
`max_iterations` comes from `AgentConfig` and raises `MaxIterationsError`.
|
|
562
|
+
|
|
563
|
+
---
|
|
564
|
+
|
|
565
|
+
## 8. Hooks and middleware
|
|
566
|
+
|
|
567
|
+
### Hooks — observe the lifecycle
|
|
568
|
+
|
|
569
|
+
```python
|
|
570
|
+
from datagol_agent_harness import HookContext, HookEvent
|
|
571
|
+
|
|
572
|
+
@agent.hooks.before_tool
|
|
573
|
+
async def log_call(ctx: HookContext):
|
|
574
|
+
tc = ctx.data["tool_call"]
|
|
575
|
+
print(f"-> {tc.name}({tc.input})")
|
|
576
|
+
|
|
577
|
+
@agent.hooks.after_tool
|
|
578
|
+
async def log_result(ctx: HookContext):
|
|
579
|
+
result = ctx.data["result"]
|
|
580
|
+
if result.is_error:
|
|
581
|
+
print(f"tool failed: {result.content}")
|
|
582
|
+
|
|
583
|
+
agent.hooks.on(HookEvent.LLM_RESPONSE, lambda ctx: print(ctx.data["stop_reason"]))
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
Events: `AGENT_START`, `AGENT_END`, `LOOP_ITERATION_START`,
|
|
587
|
+
`LOOP_ITERATION_END`, `LLM_REQUEST`, `LLM_RESPONSE`, `TOOL_CALL_START`,
|
|
588
|
+
`TOOL_CALL_END`, `SKILL_INVOKED`, `SANDBOX_EXEC`, `CHECKPOINT`, `ERROR`.
|
|
589
|
+
|
|
590
|
+
### Middleware — transform requests and results
|
|
591
|
+
|
|
592
|
+
Subclass `Middleware` to mutate messages, tool calls, or results as they flow:
|
|
593
|
+
|
|
594
|
+
```python
|
|
595
|
+
from datagol_agent_harness import Middleware
|
|
596
|
+
|
|
597
|
+
class RedactSecrets(Middleware):
|
|
598
|
+
async def before_tool_execution(self, tool_call):
|
|
599
|
+
if "api_key" in str(tool_call.input):
|
|
600
|
+
tool_call.input = {k: ("***" if "key" in k else v)
|
|
601
|
+
for k, v in tool_call.input.items()}
|
|
602
|
+
return tool_call
|
|
603
|
+
|
|
604
|
+
agent.middleware.add(RedactSecrets())
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
Overridable stages: `before_llm_call`, `after_llm_call`,
|
|
608
|
+
`before_tool_execution`, `after_tool_execution`.
|
|
609
|
+
|
|
610
|
+
---
|
|
611
|
+
|
|
612
|
+
## 9. MCP servers
|
|
613
|
+
|
|
614
|
+
`MCPManager` connects to Model Context Protocol servers (stdio subprocess or
|
|
615
|
+
remote SSE) and registers their tools into your agent's registry, so MCP
|
|
616
|
+
tools look exactly like native tools to the model.
|
|
617
|
+
|
|
618
|
+
```python
|
|
619
|
+
from datagol_agent_harness import Agent, AgentConfig, MCPManager
|
|
620
|
+
|
|
621
|
+
mcp = MCPManager()
|
|
622
|
+
await mcp.connect(
|
|
623
|
+
"filesystem",
|
|
624
|
+
command="npx",
|
|
625
|
+
args=["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
|
|
626
|
+
)
|
|
627
|
+
# or a remote server:
|
|
628
|
+
# await mcp.connect("remote", url="http://localhost:8000/sse")
|
|
629
|
+
|
|
630
|
+
agent = Agent(config=AgentConfig(system_prompt="..."), mcp=mcp)
|
|
631
|
+
mcp.register_tools(agent.tools)
|
|
632
|
+
|
|
633
|
+
print(await agent.run("What's in /tmp?"))
|
|
634
|
+
await mcp.disconnect_all()
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
See `examples/mcp_agent.py` for the interactive version.
|
|
638
|
+
|
|
639
|
+
---
|
|
640
|
+
|
|
641
|
+
## 10. Session persistence
|
|
642
|
+
|
|
643
|
+
Save and restore an agent's conversation:
|
|
644
|
+
|
|
645
|
+
```python
|
|
646
|
+
session_id = await agent.save_session() # -> writes .agent_sessions/
|
|
647
|
+
|
|
648
|
+
# later, or in another process:
|
|
649
|
+
from datagol_agent_harness import Agent
|
|
650
|
+
restored = await Agent.load_session(session_id, config=AgentConfig(system_prompt="..."))
|
|
651
|
+
await restored.run("Where were we?")
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
For managed multi-session services (checkpointing, pause/resume, expiry),
|
|
655
|
+
see `AgentRuntime` in `datagol_agent_harness/runtime.py`.
|
|
656
|
+
|
|
657
|
+
---
|
|
658
|
+
|
|
659
|
+
## 11. Shipping a web app
|
|
660
|
+
|
|
661
|
+
The harness is transport-agnostic. The reference implementation in
|
|
662
|
+
`examples/web_app` demonstrates:
|
|
663
|
+
|
|
664
|
+
1. **Streaming agent execution** translated to Server-Sent Events (SSE) or WebSockets:
|
|
665
|
+
|
|
666
|
+
```python
|
|
667
|
+
from fastapi import FastAPI
|
|
668
|
+
from fastapi.responses import StreamingResponse
|
|
669
|
+
from datagol_agent_harness import AgentConfig, StreamingAgent, StreamEventType
|
|
670
|
+
import json
|
|
671
|
+
|
|
672
|
+
app = FastAPI()
|
|
673
|
+
agent = StreamingAgent(config=AgentConfig(system_prompt="You are helpful."))
|
|
674
|
+
|
|
675
|
+
@app.post("/api/stream")
|
|
676
|
+
async def stream(req: dict):
|
|
677
|
+
async def event_generator():
|
|
678
|
+
async for event in agent.run_stream(req["message"]):
|
|
679
|
+
if event.type == StreamEventType.TEXT_DELTA:
|
|
680
|
+
yield f"data: {json.dumps({'type': 'text_delta', 'content': event.data})}\n\n"
|
|
681
|
+
elif event.type == StreamEventType.TOOL_CALL_START:
|
|
682
|
+
yield f"data: {json.dumps({'type': 'tool_call', 'name': event.data.name})}\n\n"
|
|
683
|
+
yield "data: [DONE]\n\n"
|
|
684
|
+
|
|
685
|
+
return StreamingResponse(event_generator(), media_type="text/event-stream")
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
2. **Register tools as `PermissionLevel.ALLOW`** — in a server context, tools should be pre-approved or gated via an authorization middleware rather than interactive stdin prompts.
|
|
689
|
+
3. **Built-in UI and MCP integrations** — the web app serves a complete browser UI that displays tool calls, token usage, MCP servers, and dynamic skills.
|
|
690
|
+
|
|
691
|
+
Run the full example from the repo root:
|
|
692
|
+
|
|
693
|
+
```bash
|
|
694
|
+
uvicorn examples.web_app.server:app --port 8000 --reload
|
|
695
|
+
# open http://localhost:8000
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
---
|
|
699
|
+
|
|
700
|
+
## 12. Evaluations with LangSmith
|
|
701
|
+
|
|
702
|
+
The harness includes first-class evaluation capabilities powered by the
|
|
703
|
+
**LangSmith evaluation framework** (`evaluate` / `aevaluate`). You can benchmark
|
|
704
|
+
tool selection, skill routing, multi-agent delegation, and guardrails either
|
|
705
|
+
locally (offline, zero-cost) or in the LangSmith Web UI.
|
|
706
|
+
|
|
707
|
+
### Running an evaluation
|
|
708
|
+
|
|
709
|
+
```python
|
|
710
|
+
from datagol_agent_harness import Agent, AgentConfig, PermissionLevel
|
|
711
|
+
from datagol_agent_harness.evals import (
|
|
712
|
+
build_example,
|
|
713
|
+
default_evaluators,
|
|
714
|
+
evaluate_agent,
|
|
715
|
+
tool_selection_evaluator,
|
|
716
|
+
contains_evaluator,
|
|
717
|
+
)
|
|
718
|
+
|
|
719
|
+
# 1. Define your agent factory or instance
|
|
720
|
+
def make_agent(inputs):
|
|
721
|
+
agent = Agent(config=AgentConfig(model="claude-sonnet-4-6"))
|
|
722
|
+
@agent.tools.register(permission=PermissionLevel.ALLOW)
|
|
723
|
+
def calculate(expression: str) -> str:
|
|
724
|
+
return str(eval(expression, {"__builtins__": None}, {}))
|
|
725
|
+
return agent
|
|
726
|
+
|
|
727
|
+
# 2. Define test cases or use built-in suites ("tool_calling", "skills", "all")
|
|
728
|
+
dataset = [
|
|
729
|
+
build_example(
|
|
730
|
+
inputs={"prompt": "What is 144 / 12?"},
|
|
731
|
+
outputs={
|
|
732
|
+
"expected_tools": ["calculate"],
|
|
733
|
+
"contains_all": ["12"],
|
|
734
|
+
"max_allowed_iterations": 3,
|
|
735
|
+
},
|
|
736
|
+
)
|
|
737
|
+
]
|
|
738
|
+
|
|
739
|
+
# 3. Evaluate (offline for fast CI/CD tests, or live to sync to LangSmith)
|
|
740
|
+
summary = evaluate_agent(
|
|
741
|
+
agent=make_agent,
|
|
742
|
+
dataset=dataset,
|
|
743
|
+
evaluators=[tool_selection_evaluator, contains_evaluator],
|
|
744
|
+
experiment_prefix="math-agent-benchmark",
|
|
745
|
+
offline=True, # set False or omit when LANGSMITH_API_KEY is present
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
print(f"Pass rate: {summary.pass_rate * 100:.1f}%")
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
### Running evals from the CLI
|
|
752
|
+
|
|
753
|
+
```bash
|
|
754
|
+
# Run tool calling benchmarks offline (no API keys required)
|
|
755
|
+
python -m datagol_agent_harness.evals.cli --suite tool_calling --offline
|
|
756
|
+
|
|
757
|
+
# Run against Anthropic and upload results + traces to LangSmith
|
|
758
|
+
export LANGSMITH_API_KEY="lsv2_pt_..."
|
|
759
|
+
python -m datagol_agent_harness.evals.cli --suite skills --model claude-sonnet-4-6 --upload
|
|
760
|
+
|
|
761
|
+
# Run all benchmark suites with concurrency
|
|
762
|
+
python -m datagol_agent_harness.evals.cli --suite all --concurrency 2
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
When uploaded, the CLI prints a clickable LangSmith URL to inspect row-level scores,
|
|
766
|
+
side-by-side prompt diffs, and the complete nested execution tree for every turn.
|
|
767
|
+
|
|
768
|
+
---
|
|
769
|
+
|
|
770
|
+
## API reference (quick)
|
|
771
|
+
|
|
772
|
+
| Class / function | Module | Purpose |
|
|
773
|
+
|---|---|---|
|
|
774
|
+
| `Agent` | `datagol_agent_harness` | Core agentic loop (`await agent.run(msg)`) |
|
|
775
|
+
| `StreamingAgent` | `datagol_agent_harness` | Event-streaming variant (`run_stream`) |
|
|
776
|
+
| `AgentConfig` | `datagol_agent_harness` | Model, provider, limits, prompt |
|
|
777
|
+
| `ToolRegistry` | `datagol_agent_harness` | `register`, `register_with_schema`, `execute` |
|
|
778
|
+
| `PermissionLevel` | `datagol_agent_harness` | `ALLOW` / `ASK` / `DENY` |
|
|
779
|
+
| `PermissionManager` | `datagol_agent_harness` | Per-tool overrides, session grants |
|
|
780
|
+
| `GuardrailsEngine` | `datagol_agent_harness` | Iteration/cost limits, usage stats |
|
|
781
|
+
| `ConversationMemory` | `datagol_agent_harness` | Message list with auto-trimming |
|
|
782
|
+
| `AgentMemory` / `LongTermMemory` | `datagol_agent_harness` | Disk-backed notes / facts |
|
|
783
|
+
| `PersistentMemory` | `datagol_agent_harness` | Session save/load |
|
|
784
|
+
| `HookManager` / `HookEvent` | `datagol_agent_harness` | Lifecycle hooks |
|
|
785
|
+
| `Middleware` / `MiddlewarePipeline` | `datagol_agent_harness` | Request/result transforms |
|
|
786
|
+
| `SkillManager` | `datagol_agent_harness` | Lazy skill loading |
|
|
787
|
+
| `MCPManager` | `datagol_agent_harness` | MCP server connections |
|
|
788
|
+
| `Sandbox` | `datagol_agent_harness` | Sandboxed code execution |
|
|
789
|
+
| `AgentRuntime` | `datagol_agent_harness` | Managed sessions, checkpoints |
|
|
790
|
+
| `Extension` / `LangSmithExtension` | `datagol_agent_harness` | Pluggable runtime extensions / LangSmith tracing |
|
|
791
|
+
| `evaluate_agent` | `datagol_agent_harness.evals` | LangSmith evaluation runner |
|
|
792
|
+
| `AgentTarget` | `datagol_agent_harness.evals` | Target adapter with telemetry & trace linking |
|
|
793
|
+
| `default_evaluators` | `datagol_agent_harness.evals` | Standard suite of evaluators |
|
|
794
|
+
| `register_all_tools` | `datagol_agent_harness.builtin` | Filesystem, bash, web, memory tools |
|
|
795
|
+
|
|
796
|
+
## Runnable examples
|
|
797
|
+
|
|
798
|
+
| Example | Shows |
|
|
799
|
+
|---|---|
|
|
800
|
+
| `python -m examples.simple_chat` | Streaming interactive chat + basic tools |
|
|
801
|
+
| `python -m examples.multi_agent` | Orchestrator + specialist agents |
|
|
802
|
+
| `python -m examples.langsmith_tracing` | LangSmith lifecycle tracing + multi-agent nesting |
|
|
803
|
+
| `python -m examples.run_evals` | Agent evaluation suite with LangSmith |
|
|
804
|
+
| `python -m examples.skills_agent` | Lazy skill loading |
|
|
805
|
+
| `python -m examples.memory_agent` | Two-layer persistent memory |
|
|
806
|
+
| `python -m examples.mcp_agent` | MCP tool integration |
|
|
807
|
+
| `python -m examples.coding_agent` | Full coding assistant |
|
|
808
|
+
| `python -m examples.sandboxed_coder` | Sandboxed execution |
|
|
809
|
+
| `uvicorn examples.web_app.server:app --port 8000` | Web app with streaming UI, MCP, and skills |
|