handoffkit 0.3.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {handoffkit-0.3.0 → handoffkit-0.4.0}/MANIFEST.in +1 -0
- {handoffkit-0.3.0/handoffkit.egg-info → handoffkit-0.4.0}/PKG-INFO +70 -2
- {handoffkit-0.3.0 → handoffkit-0.4.0}/README.md +69 -1
- handoffkit-0.4.0/examples/context_handoff_demo.py +116 -0
- handoffkit-0.4.0/examples/memory_demo.py +55 -0
- handoffkit-0.4.0/examples/project_context_demo.py +66 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/__init__.py +18 -1
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/agent.py +47 -1
- handoffkit-0.4.0/handoffkit/context.py +182 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/handoff.py +3 -1
- handoffkit-0.4.0/handoffkit/memory.py +239 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/protocol.py +62 -60
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/protocols/hybrid_state.py +55 -52
- {handoffkit-0.3.0 → handoffkit-0.4.0/handoffkit.egg-info}/PKG-INFO +70 -2
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit.egg-info/SOURCES.txt +5 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/pyproject.toml +1 -1
- {handoffkit-0.3.0 → handoffkit-0.4.0}/tests/test_cli.py +1 -1
- {handoffkit-0.3.0 → handoffkit-0.4.0}/tests/test_contract_validation.py +2 -2
- {handoffkit-0.3.0 → handoffkit-0.4.0}/tests/test_handoff.py +34 -32
- handoffkit-0.4.0/tests/test_imports.py +24 -0
- handoffkit-0.4.0/tests/test_memory_context.py +180 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/tests/test_protocols.py +14 -0
- handoffkit-0.3.0/handoffkit/memory.py +0 -44
- handoffkit-0.3.0/tests/test_imports.py +0 -7
- {handoffkit-0.3.0 → handoffkit-0.4.0}/LICENSE +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/examples/coding_team.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/examples/fake_provider_tool_call_demo.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/examples/freemodel_best_model_demo.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/examples/freemodel_coding_team.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/examples/freemodel_openai_compatible.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/examples/handoff_demo.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/examples/ollama_agent.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/examples/real_task_calculator.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/examples/repo_inspired_protocols.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/examples/simple_agent.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/examples/tool_agent.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/examples/tool_execution_demo.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/examples/tool_schema_demo.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/cli.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/errors.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/protocols/__init__.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/protocols/compressed.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/protocols/hybrid_min.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/protocols/natural.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/providers/__init__.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/providers/base.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/providers/echo_provider.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/providers/ollama_provider.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/providers/openai_compatible.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/providers/openai_provider.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/py.typed +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/runner.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/safety.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/schemas.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/tool.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/tool_execution.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/tools/__init__.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/tools/filesystem.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/tools/shell.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/tools/text.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit.egg-info/dependency_links.txt +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit.egg-info/entry_points.txt +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit.egg-info/requires.txt +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit.egg-info/top_level.txt +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/setup.cfg +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/tests/test_agent.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/tests/test_coding_team.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/tests/test_ollama_provider_mock.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/tests/test_openai_model_selection.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/tests/test_openai_provider_mock.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/tests/test_real_task_demo.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/tests/test_shell_safety.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/tests/test_team.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/tests/test_tool.py +0 -0
- {handoffkit-0.3.0 → handoffkit-0.4.0}/tests/test_tool_execution.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: handoffkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: A lightweight Python framework for building multi-agent workflows with structured state transfer protocols.
|
|
5
5
|
Author: DaosPath
|
|
6
6
|
License-Expression: MIT
|
|
@@ -80,7 +80,21 @@ Tester
|
|
|
80
80
|
|
|
81
81
|
That makes agent workflows easier to inspect, test, replay, and improve.
|
|
82
82
|
|
|
83
|
-
## What 0.
|
|
83
|
+
## What 0.4.0 Adds
|
|
84
|
+
|
|
85
|
+
HandoffKit 0.4.0 adds a lightweight memory and project context engine:
|
|
86
|
+
|
|
87
|
+
- `MemoryItem`, `MemoryStore`, and `JsonMemoryStore` for structured memory,
|
|
88
|
+
- `ProjectIndexer` for scanning local project files,
|
|
89
|
+
- `ContextRetriever` for deterministic keyword retrieval,
|
|
90
|
+
- `ContextPack` for bundling relevant files and memories,
|
|
91
|
+
- `Agent.run_with_context()` for context-aware agent runs,
|
|
92
|
+
- `HandoffState.context_refs` so agents can pass explicit context references.
|
|
93
|
+
|
|
94
|
+
No vector database. No heavy dependency stack. Just transparent, inspectable
|
|
95
|
+
context for agent workflows.
|
|
96
|
+
|
|
97
|
+
## What 0.3.0 Added
|
|
84
98
|
|
|
85
99
|
HandoffKit 0.3.0 adds a real tool execution loop:
|
|
86
100
|
|
|
@@ -243,6 +257,56 @@ Safety:
|
|
|
243
257
|
- when `require_approval=True`, write and shell tools return
|
|
244
258
|
`approval_required` instead of executing.
|
|
245
259
|
|
|
260
|
+
## Memory + Project Context
|
|
261
|
+
|
|
262
|
+
HandoffKit can store durable project decisions, index local files, retrieve
|
|
263
|
+
relevant context, and hand explicit references from one agent to the next.
|
|
264
|
+
|
|
265
|
+
```python
|
|
266
|
+
from handoffkit import (
|
|
267
|
+
Agent,
|
|
268
|
+
ContextPack,
|
|
269
|
+
ContextRetriever,
|
|
270
|
+
JsonMemoryStore,
|
|
271
|
+
ProjectIndexer,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
memory = JsonMemoryStore("examples/output/memory.json")
|
|
275
|
+
memory.add(
|
|
276
|
+
"Calculator CLI should use argparse and pure functions.",
|
|
277
|
+
kind="decision",
|
|
278
|
+
tags=["calculator", "cli"],
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
documents = ProjectIndexer(".").index()
|
|
282
|
+
retriever = ContextRetriever(documents)
|
|
283
|
+
matches = retriever.search("calculator argparse tests", limit=3)
|
|
284
|
+
|
|
285
|
+
context = ContextPack(
|
|
286
|
+
query="Create a calculator CLI implementation plan.",
|
|
287
|
+
documents=matches,
|
|
288
|
+
memories=memory.search("calculator cli", limit=3),
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
agent = Agent("Architect", "Create concise implementation plans.")
|
|
292
|
+
result = agent.run_with_context(
|
|
293
|
+
"Create a concise architecture plan for a Python CLI calculator.",
|
|
294
|
+
context=context,
|
|
295
|
+
memory=memory,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
print(result.final_output)
|
|
299
|
+
print(context.to_markdown())
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Run the included demos:
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
python examples/memory_demo.py
|
|
306
|
+
python examples/project_context_demo.py
|
|
307
|
+
python examples/context_handoff_demo.py
|
|
308
|
+
```
|
|
309
|
+
|
|
246
310
|
## Real Task Demo
|
|
247
311
|
|
|
248
312
|
HandoffKit includes a reproducible real task demo:
|
|
@@ -340,6 +404,9 @@ python examples/coding_team.py
|
|
|
340
404
|
python examples/tool_schema_demo.py
|
|
341
405
|
python examples/tool_execution_demo.py
|
|
342
406
|
python examples/fake_provider_tool_call_demo.py
|
|
407
|
+
python examples/memory_demo.py
|
|
408
|
+
python examples/project_context_demo.py
|
|
409
|
+
python examples/context_handoff_demo.py
|
|
343
410
|
python examples/real_task_calculator.py
|
|
344
411
|
```
|
|
345
412
|
|
|
@@ -398,6 +465,7 @@ HandoffKit is a developer library, not a copy of that repository.
|
|
|
398
465
|
- structured tool calling loops,
|
|
399
466
|
- handoff quality metrics,
|
|
400
467
|
- memory integrations,
|
|
468
|
+
- project context retrieval,
|
|
401
469
|
- benchmark-inspired examples,
|
|
402
470
|
- multi-agent workflow templates.
|
|
403
471
|
|
|
@@ -51,7 +51,21 @@ Tester
|
|
|
51
51
|
|
|
52
52
|
That makes agent workflows easier to inspect, test, replay, and improve.
|
|
53
53
|
|
|
54
|
-
## What 0.
|
|
54
|
+
## What 0.4.0 Adds
|
|
55
|
+
|
|
56
|
+
HandoffKit 0.4.0 adds a lightweight memory and project context engine:
|
|
57
|
+
|
|
58
|
+
- `MemoryItem`, `MemoryStore`, and `JsonMemoryStore` for structured memory,
|
|
59
|
+
- `ProjectIndexer` for scanning local project files,
|
|
60
|
+
- `ContextRetriever` for deterministic keyword retrieval,
|
|
61
|
+
- `ContextPack` for bundling relevant files and memories,
|
|
62
|
+
- `Agent.run_with_context()` for context-aware agent runs,
|
|
63
|
+
- `HandoffState.context_refs` so agents can pass explicit context references.
|
|
64
|
+
|
|
65
|
+
No vector database. No heavy dependency stack. Just transparent, inspectable
|
|
66
|
+
context for agent workflows.
|
|
67
|
+
|
|
68
|
+
## What 0.3.0 Added
|
|
55
69
|
|
|
56
70
|
HandoffKit 0.3.0 adds a real tool execution loop:
|
|
57
71
|
|
|
@@ -214,6 +228,56 @@ Safety:
|
|
|
214
228
|
- when `require_approval=True`, write and shell tools return
|
|
215
229
|
`approval_required` instead of executing.
|
|
216
230
|
|
|
231
|
+
## Memory + Project Context
|
|
232
|
+
|
|
233
|
+
HandoffKit can store durable project decisions, index local files, retrieve
|
|
234
|
+
relevant context, and hand explicit references from one agent to the next.
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
from handoffkit import (
|
|
238
|
+
Agent,
|
|
239
|
+
ContextPack,
|
|
240
|
+
ContextRetriever,
|
|
241
|
+
JsonMemoryStore,
|
|
242
|
+
ProjectIndexer,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
memory = JsonMemoryStore("examples/output/memory.json")
|
|
246
|
+
memory.add(
|
|
247
|
+
"Calculator CLI should use argparse and pure functions.",
|
|
248
|
+
kind="decision",
|
|
249
|
+
tags=["calculator", "cli"],
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
documents = ProjectIndexer(".").index()
|
|
253
|
+
retriever = ContextRetriever(documents)
|
|
254
|
+
matches = retriever.search("calculator argparse tests", limit=3)
|
|
255
|
+
|
|
256
|
+
context = ContextPack(
|
|
257
|
+
query="Create a calculator CLI implementation plan.",
|
|
258
|
+
documents=matches,
|
|
259
|
+
memories=memory.search("calculator cli", limit=3),
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
agent = Agent("Architect", "Create concise implementation plans.")
|
|
263
|
+
result = agent.run_with_context(
|
|
264
|
+
"Create a concise architecture plan for a Python CLI calculator.",
|
|
265
|
+
context=context,
|
|
266
|
+
memory=memory,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
print(result.final_output)
|
|
270
|
+
print(context.to_markdown())
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Run the included demos:
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
python examples/memory_demo.py
|
|
277
|
+
python examples/project_context_demo.py
|
|
278
|
+
python examples/context_handoff_demo.py
|
|
279
|
+
```
|
|
280
|
+
|
|
217
281
|
## Real Task Demo
|
|
218
282
|
|
|
219
283
|
HandoffKit includes a reproducible real task demo:
|
|
@@ -311,6 +375,9 @@ python examples/coding_team.py
|
|
|
311
375
|
python examples/tool_schema_demo.py
|
|
312
376
|
python examples/tool_execution_demo.py
|
|
313
377
|
python examples/fake_provider_tool_call_demo.py
|
|
378
|
+
python examples/memory_demo.py
|
|
379
|
+
python examples/project_context_demo.py
|
|
380
|
+
python examples/context_handoff_demo.py
|
|
314
381
|
python examples/real_task_calculator.py
|
|
315
382
|
```
|
|
316
383
|
|
|
@@ -369,6 +436,7 @@ HandoffKit is a developer library, not a copy of that repository.
|
|
|
369
436
|
- structured tool calling loops,
|
|
370
437
|
- handoff quality metrics,
|
|
371
438
|
- memory integrations,
|
|
439
|
+
- project context retrieval,
|
|
372
440
|
- benchmark-inspired examples,
|
|
373
441
|
- multi-agent workflow templates.
|
|
374
442
|
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Context-aware Architect -> Coder -> Tester handoff demo."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from handoffkit import (
|
|
8
|
+
Agent,
|
|
9
|
+
ContextPack,
|
|
10
|
+
ContextRetriever,
|
|
11
|
+
HandoffProtocol,
|
|
12
|
+
JsonMemoryStore,
|
|
13
|
+
ProjectIndexer,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
17
|
+
OUTPUT_DIR = ROOT / "examples" / "output" / "context_handoff_demo"
|
|
18
|
+
SAMPLE_PROJECT = OUTPUT_DIR / "sample_project"
|
|
19
|
+
REPORTS_DIR = ROOT / "reports"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def write_sample_project() -> None:
|
|
23
|
+
"""Create deterministic files for the context demo."""
|
|
24
|
+
SAMPLE_PROJECT.mkdir(parents=True, exist_ok=True)
|
|
25
|
+
(SAMPLE_PROJECT / "README.md").write_text(
|
|
26
|
+
"Calculator CLI\n\nUse argparse and keep calculator operations pure.",
|
|
27
|
+
encoding="utf-8",
|
|
28
|
+
)
|
|
29
|
+
(SAMPLE_PROJECT / "test_plan.md").write_text(
|
|
30
|
+
"Test add, subtract, multiply, divide, and divide-by-zero behavior.",
|
|
31
|
+
encoding="utf-8",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def main() -> None:
|
|
36
|
+
"""Pass context references through a three-agent workflow."""
|
|
37
|
+
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
38
|
+
REPORTS_DIR.mkdir(parents=True, exist_ok=True)
|
|
39
|
+
write_sample_project()
|
|
40
|
+
|
|
41
|
+
memory = JsonMemoryStore(str(OUTPUT_DIR / "memory.json"))
|
|
42
|
+
memory.clear()
|
|
43
|
+
memory.add(
|
|
44
|
+
"Use argparse for the CLI and pure functions for arithmetic.",
|
|
45
|
+
kind="decision",
|
|
46
|
+
tags=["calculator", "architecture"],
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
documents = ProjectIndexer(str(SAMPLE_PROJECT)).index()
|
|
50
|
+
matches = ContextRetriever(documents).search("calculator argparse tests", limit=3)
|
|
51
|
+
context = ContextPack(
|
|
52
|
+
query="Create a Python CLI calculator plan.",
|
|
53
|
+
documents=matches,
|
|
54
|
+
memories=memory.search("calculator argparse", limit=3),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
architect = Agent("Architect", "Create implementation plans.")
|
|
58
|
+
coder = Agent("Coder", "Implement from structured state.")
|
|
59
|
+
tester = Agent("Tester", "Validate behavior and report evidence.")
|
|
60
|
+
protocol = HandoffProtocol(mode="hybrid_state")
|
|
61
|
+
|
|
62
|
+
architect_result = architect.run_with_context(
|
|
63
|
+
"Create a concise architecture plan for a Python CLI calculator.",
|
|
64
|
+
context=context,
|
|
65
|
+
memory=memory,
|
|
66
|
+
)
|
|
67
|
+
coder_state = protocol.transfer(
|
|
68
|
+
from_agent=architect,
|
|
69
|
+
to_agent=coder,
|
|
70
|
+
task="Implement the calculator CLI from the architecture plan.",
|
|
71
|
+
summary=architect_result.final_output,
|
|
72
|
+
decisions=["Use argparse.", "Keep arithmetic functions pure."],
|
|
73
|
+
important_files=[doc.path for doc in matches],
|
|
74
|
+
next_steps=["Create calculator.py.", "Create pytest coverage."],
|
|
75
|
+
context_refs=[doc.path for doc in matches],
|
|
76
|
+
)
|
|
77
|
+
tester_state = protocol.transfer(
|
|
78
|
+
from_agent=coder,
|
|
79
|
+
to_agent=tester,
|
|
80
|
+
task="Validate the calculator CLI implementation.",
|
|
81
|
+
summary="Coder received context-aware handoff and implementation constraints.",
|
|
82
|
+
decisions=coder_state.decisions,
|
|
83
|
+
important_files=coder_state.important_files,
|
|
84
|
+
next_steps=["Run pytest.", "Report failures with context references."],
|
|
85
|
+
context_refs=coder_state.context_refs,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
report = "\n".join(
|
|
89
|
+
[
|
|
90
|
+
"# Context Handoff Demo",
|
|
91
|
+
"",
|
|
92
|
+
"## Context Pack",
|
|
93
|
+
"",
|
|
94
|
+
context.to_markdown(),
|
|
95
|
+
"",
|
|
96
|
+
"## Coder Handoff",
|
|
97
|
+
"",
|
|
98
|
+
coder_state.to_json(),
|
|
99
|
+
"",
|
|
100
|
+
"## Tester Handoff",
|
|
101
|
+
"",
|
|
102
|
+
tester_state.to_json(),
|
|
103
|
+
"",
|
|
104
|
+
]
|
|
105
|
+
)
|
|
106
|
+
report_path = REPORTS_DIR / "context_handoff_demo.md"
|
|
107
|
+
report_path.write_text(report, encoding="utf-8")
|
|
108
|
+
|
|
109
|
+
print(f"Context documents: {[doc.path for doc in matches]}")
|
|
110
|
+
print(f"Coder context_refs: {coder_state.context_refs}")
|
|
111
|
+
print(f"Tester context_refs: {tester_state.context_refs}")
|
|
112
|
+
print(f"Report: {report_path}")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
if __name__ == "__main__":
|
|
116
|
+
main()
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Structured memory demo."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from handoffkit import JsonMemoryStore, MemoryReport
|
|
8
|
+
|
|
9
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
10
|
+
OUTPUT_DIR = ROOT / "examples" / "output" / "memory_demo"
|
|
11
|
+
REPORTS_DIR = ROOT / "reports"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main() -> None:
|
|
15
|
+
"""Create, search, and report structured memories."""
|
|
16
|
+
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
17
|
+
REPORTS_DIR.mkdir(parents=True, exist_ok=True)
|
|
18
|
+
|
|
19
|
+
store = JsonMemoryStore(str(OUTPUT_DIR / "memory.json"))
|
|
20
|
+
store.clear()
|
|
21
|
+
stored = [
|
|
22
|
+
store.add(
|
|
23
|
+
"Calculator CLI should use argparse and pure functions.",
|
|
24
|
+
kind="decision",
|
|
25
|
+
tags=["calculator", "cli"],
|
|
26
|
+
),
|
|
27
|
+
store.add(
|
|
28
|
+
"Tester agent must verify divide-by-zero behavior.",
|
|
29
|
+
kind="test-note",
|
|
30
|
+
tags=["calculator", "tests"],
|
|
31
|
+
),
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
query = "calculator cli tests"
|
|
35
|
+
matches = store.search(query, limit=5)
|
|
36
|
+
report = MemoryReport(
|
|
37
|
+
memories_stored=stored,
|
|
38
|
+
searches_executed=[query],
|
|
39
|
+
final_result=f"Found {len(matches)} relevant memories.",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
markdown_path = REPORTS_DIR / "memory_demo.md"
|
|
43
|
+
json_path = REPORTS_DIR / "memory_demo.json"
|
|
44
|
+
markdown_path.write_text(report.to_markdown(), encoding="utf-8")
|
|
45
|
+
json_path.write_text(report.to_json(), encoding="utf-8")
|
|
46
|
+
|
|
47
|
+
print(f"Memories stored: {len(stored)}")
|
|
48
|
+
print(f"Search query: {query}")
|
|
49
|
+
print(f"Matches: {[item.kind for item in matches]}")
|
|
50
|
+
print(f"Markdown report: {markdown_path}")
|
|
51
|
+
print(f"JSON report: {json_path}")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
if __name__ == "__main__":
|
|
55
|
+
main()
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Project context indexing and retrieval demo."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from handoffkit import ContextPack, ContextRetriever, ProjectIndexer
|
|
8
|
+
|
|
9
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
10
|
+
OUTPUT_DIR = ROOT / "examples" / "output" / "project_context_demo"
|
|
11
|
+
SAMPLE_PROJECT = OUTPUT_DIR / "sample_project"
|
|
12
|
+
REPORTS_DIR = ROOT / "reports"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def write_sample_project() -> None:
|
|
16
|
+
"""Create a tiny project for deterministic retrieval."""
|
|
17
|
+
SAMPLE_PROJECT.mkdir(parents=True, exist_ok=True)
|
|
18
|
+
(SAMPLE_PROJECT / "calculator.py").write_text(
|
|
19
|
+
"\n".join(
|
|
20
|
+
[
|
|
21
|
+
"def add(a: int, b: int) -> int:",
|
|
22
|
+
" return a + b",
|
|
23
|
+
"",
|
|
24
|
+
"def divide(a: int, b: int) -> float:",
|
|
25
|
+
" if b == 0:",
|
|
26
|
+
" raise ValueError('division by zero')",
|
|
27
|
+
" return a / b",
|
|
28
|
+
]
|
|
29
|
+
),
|
|
30
|
+
encoding="utf-8",
|
|
31
|
+
)
|
|
32
|
+
(SAMPLE_PROJECT / "README.md").write_text(
|
|
33
|
+
"Calculator CLI\n\nUse argparse. Test add, subtract, multiply, and divide.",
|
|
34
|
+
encoding="utf-8",
|
|
35
|
+
)
|
|
36
|
+
ignored = SAMPLE_PROJECT / "dist"
|
|
37
|
+
ignored.mkdir(exist_ok=True)
|
|
38
|
+
(ignored / "generated.txt").write_text("This file should not be indexed.", encoding="utf-8")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def main() -> None:
|
|
42
|
+
"""Index a sample project and write a context report."""
|
|
43
|
+
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
44
|
+
REPORTS_DIR.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
write_sample_project()
|
|
46
|
+
|
|
47
|
+
documents = ProjectIndexer(str(SAMPLE_PROJECT)).index()
|
|
48
|
+
retriever = ContextRetriever(documents)
|
|
49
|
+
query = "argparse divide tests"
|
|
50
|
+
matches = retriever.search(query, limit=3)
|
|
51
|
+
pack = ContextPack(query=query, documents=matches)
|
|
52
|
+
|
|
53
|
+
markdown_path = REPORTS_DIR / "project_context_demo.md"
|
|
54
|
+
json_path = REPORTS_DIR / "project_context_demo.json"
|
|
55
|
+
markdown_path.write_text(pack.to_markdown(), encoding="utf-8")
|
|
56
|
+
json_path.write_text(pack.to_json(), encoding="utf-8")
|
|
57
|
+
|
|
58
|
+
print(f"Documents indexed: {len(documents)}")
|
|
59
|
+
print(f"Query: {query}")
|
|
60
|
+
print(f"Matches: {[doc.path for doc in matches]}")
|
|
61
|
+
print(f"Markdown report: {markdown_path}")
|
|
62
|
+
print(f"JSON report: {json_path}")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
main()
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
"""Public API for HandoffKit."""
|
|
2
2
|
|
|
3
3
|
from handoffkit.agent import Agent
|
|
4
|
+
from handoffkit.context import (
|
|
5
|
+
ContextDocument,
|
|
6
|
+
ContextPack,
|
|
7
|
+
ContextRetriever,
|
|
8
|
+
ContextRunResult,
|
|
9
|
+
ProjectIndexer,
|
|
10
|
+
)
|
|
4
11
|
from handoffkit.errors import HandoffValidationError
|
|
5
12
|
from handoffkit.handoff import HandoffState
|
|
13
|
+
from handoffkit.memory import JsonMemoryStore, MemoryItem, MemoryReport, MemoryStore
|
|
6
14
|
from handoffkit.protocol import HandoffProtocol
|
|
7
15
|
from handoffkit.runner import Team, TeamRunResult
|
|
8
16
|
from handoffkit.tool import Tool, tool
|
|
@@ -13,13 +21,22 @@ from handoffkit.tool_execution import (
|
|
|
13
21
|
ToolResult,
|
|
14
22
|
)
|
|
15
23
|
|
|
16
|
-
__version__ = "0.
|
|
24
|
+
__version__ = "0.4.0"
|
|
17
25
|
|
|
18
26
|
__all__ = [
|
|
19
27
|
"Agent",
|
|
28
|
+
"ContextDocument",
|
|
29
|
+
"ContextPack",
|
|
30
|
+
"ContextRetriever",
|
|
31
|
+
"ContextRunResult",
|
|
20
32
|
"HandoffValidationError",
|
|
21
33
|
"HandoffProtocol",
|
|
22
34
|
"HandoffState",
|
|
35
|
+
"JsonMemoryStore",
|
|
36
|
+
"MemoryItem",
|
|
37
|
+
"MemoryReport",
|
|
38
|
+
"MemoryStore",
|
|
39
|
+
"ProjectIndexer",
|
|
23
40
|
"Team",
|
|
24
41
|
"TeamRunResult",
|
|
25
42
|
"Tool",
|
|
@@ -7,8 +7,9 @@ import shlex
|
|
|
7
7
|
from collections.abc import Callable, Sequence
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
|
+
from handoffkit.context import ContextPack, ContextRunResult
|
|
10
11
|
from handoffkit.handoff import HandoffState
|
|
11
|
-
from handoffkit.memory import AgentMemory
|
|
12
|
+
from handoffkit.memory import AgentMemory, MemoryStore
|
|
12
13
|
from handoffkit.providers import BaseProvider, EchoProvider
|
|
13
14
|
from handoffkit.tool import Tool, ensure_tool
|
|
14
15
|
from handoffkit.tool_execution import (
|
|
@@ -104,6 +105,29 @@ class Agent:
|
|
|
104
105
|
require_approval=require_approval,
|
|
105
106
|
)
|
|
106
107
|
|
|
108
|
+
def run_with_context(
|
|
109
|
+
self,
|
|
110
|
+
task: str,
|
|
111
|
+
context: ContextPack | None = None,
|
|
112
|
+
memory: MemoryStore | None = None,
|
|
113
|
+
tools: Sequence[Tool | Callable[..., Any]] | None = None,
|
|
114
|
+
) -> ContextRunResult:
|
|
115
|
+
"""Run the agent with retrieved project context and structured memory."""
|
|
116
|
+
memories = memory.search(task, limit=5) if memory else []
|
|
117
|
+
prompt = self._build_context_prompt(task, context=context, memories=memories)
|
|
118
|
+
self.memory.add("user", task, agent=self.name)
|
|
119
|
+
output = self.provider.generate(prompt)
|
|
120
|
+
self.memory.add("assistant", output, agent=self.name)
|
|
121
|
+
if tools:
|
|
122
|
+
tool_report = self.run_with_tools(task, tools=tools)
|
|
123
|
+
output = f"{output}\n\nTool execution: {tool_report.final_output}"
|
|
124
|
+
return ContextRunResult(
|
|
125
|
+
final_output=output,
|
|
126
|
+
context_used=context,
|
|
127
|
+
memories_used=memories,
|
|
128
|
+
success=True,
|
|
129
|
+
)
|
|
130
|
+
|
|
107
131
|
def _run_local_tool_mode(
|
|
108
132
|
self,
|
|
109
133
|
task: str,
|
|
@@ -254,6 +278,28 @@ class Agent:
|
|
|
254
278
|
'To finish, return {"final":"Done"}.'
|
|
255
279
|
)
|
|
256
280
|
|
|
281
|
+
def _build_context_prompt(
|
|
282
|
+
self,
|
|
283
|
+
task: str,
|
|
284
|
+
*,
|
|
285
|
+
context: ContextPack | None,
|
|
286
|
+
memories: list[Any],
|
|
287
|
+
) -> str:
|
|
288
|
+
"""Build prompt with context and memory summaries."""
|
|
289
|
+
context_text = context.to_markdown() if context else "No context pack."
|
|
290
|
+
memory_text = "\n".join(f"- {item.kind}: {item.content}" for item in memories)
|
|
291
|
+
if not memory_text:
|
|
292
|
+
memory_text = "No relevant structured memories."
|
|
293
|
+
return (
|
|
294
|
+
f"Agent: {self.name}\n"
|
|
295
|
+
f"Role: {self.role}\n"
|
|
296
|
+
f"Task: {task}\n\n"
|
|
297
|
+
f"Project context:\n{context_text}\n\n"
|
|
298
|
+
f"Relevant memory:\n{memory_text}\n\n"
|
|
299
|
+
"Use the provided context and memories. "
|
|
300
|
+
"Report useful progress, decisions, errors if any, and next steps."
|
|
301
|
+
)
|
|
302
|
+
|
|
257
303
|
def _build_prompt(self, task: str, *, handoff_state: HandoffState | None) -> str:
|
|
258
304
|
tool_lines = "\n".join(
|
|
259
305
|
f"- {tool.name}: {tool.description or 'No description'}"
|