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.
Files changed (75) hide show
  1. {handoffkit-0.3.0 → handoffkit-0.4.0}/MANIFEST.in +1 -0
  2. {handoffkit-0.3.0/handoffkit.egg-info → handoffkit-0.4.0}/PKG-INFO +70 -2
  3. {handoffkit-0.3.0 → handoffkit-0.4.0}/README.md +69 -1
  4. handoffkit-0.4.0/examples/context_handoff_demo.py +116 -0
  5. handoffkit-0.4.0/examples/memory_demo.py +55 -0
  6. handoffkit-0.4.0/examples/project_context_demo.py +66 -0
  7. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/__init__.py +18 -1
  8. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/agent.py +47 -1
  9. handoffkit-0.4.0/handoffkit/context.py +182 -0
  10. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/handoff.py +3 -1
  11. handoffkit-0.4.0/handoffkit/memory.py +239 -0
  12. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/protocol.py +62 -60
  13. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/protocols/hybrid_state.py +55 -52
  14. {handoffkit-0.3.0 → handoffkit-0.4.0/handoffkit.egg-info}/PKG-INFO +70 -2
  15. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit.egg-info/SOURCES.txt +5 -0
  16. {handoffkit-0.3.0 → handoffkit-0.4.0}/pyproject.toml +1 -1
  17. {handoffkit-0.3.0 → handoffkit-0.4.0}/tests/test_cli.py +1 -1
  18. {handoffkit-0.3.0 → handoffkit-0.4.0}/tests/test_contract_validation.py +2 -2
  19. {handoffkit-0.3.0 → handoffkit-0.4.0}/tests/test_handoff.py +34 -32
  20. handoffkit-0.4.0/tests/test_imports.py +24 -0
  21. handoffkit-0.4.0/tests/test_memory_context.py +180 -0
  22. {handoffkit-0.3.0 → handoffkit-0.4.0}/tests/test_protocols.py +14 -0
  23. handoffkit-0.3.0/handoffkit/memory.py +0 -44
  24. handoffkit-0.3.0/tests/test_imports.py +0 -7
  25. {handoffkit-0.3.0 → handoffkit-0.4.0}/LICENSE +0 -0
  26. {handoffkit-0.3.0 → handoffkit-0.4.0}/examples/coding_team.py +0 -0
  27. {handoffkit-0.3.0 → handoffkit-0.4.0}/examples/fake_provider_tool_call_demo.py +0 -0
  28. {handoffkit-0.3.0 → handoffkit-0.4.0}/examples/freemodel_best_model_demo.py +0 -0
  29. {handoffkit-0.3.0 → handoffkit-0.4.0}/examples/freemodel_coding_team.py +0 -0
  30. {handoffkit-0.3.0 → handoffkit-0.4.0}/examples/freemodel_openai_compatible.py +0 -0
  31. {handoffkit-0.3.0 → handoffkit-0.4.0}/examples/handoff_demo.py +0 -0
  32. {handoffkit-0.3.0 → handoffkit-0.4.0}/examples/ollama_agent.py +0 -0
  33. {handoffkit-0.3.0 → handoffkit-0.4.0}/examples/real_task_calculator.py +0 -0
  34. {handoffkit-0.3.0 → handoffkit-0.4.0}/examples/repo_inspired_protocols.py +0 -0
  35. {handoffkit-0.3.0 → handoffkit-0.4.0}/examples/simple_agent.py +0 -0
  36. {handoffkit-0.3.0 → handoffkit-0.4.0}/examples/tool_agent.py +0 -0
  37. {handoffkit-0.3.0 → handoffkit-0.4.0}/examples/tool_execution_demo.py +0 -0
  38. {handoffkit-0.3.0 → handoffkit-0.4.0}/examples/tool_schema_demo.py +0 -0
  39. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/cli.py +0 -0
  40. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/errors.py +0 -0
  41. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/protocols/__init__.py +0 -0
  42. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/protocols/compressed.py +0 -0
  43. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/protocols/hybrid_min.py +0 -0
  44. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/protocols/natural.py +0 -0
  45. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/providers/__init__.py +0 -0
  46. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/providers/base.py +0 -0
  47. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/providers/echo_provider.py +0 -0
  48. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/providers/ollama_provider.py +0 -0
  49. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/providers/openai_compatible.py +0 -0
  50. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/providers/openai_provider.py +0 -0
  51. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/py.typed +0 -0
  52. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/runner.py +0 -0
  53. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/safety.py +0 -0
  54. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/schemas.py +0 -0
  55. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/tool.py +0 -0
  56. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/tool_execution.py +0 -0
  57. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/tools/__init__.py +0 -0
  58. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/tools/filesystem.py +0 -0
  59. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/tools/shell.py +0 -0
  60. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit/tools/text.py +0 -0
  61. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit.egg-info/dependency_links.txt +0 -0
  62. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit.egg-info/entry_points.txt +0 -0
  63. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit.egg-info/requires.txt +0 -0
  64. {handoffkit-0.3.0 → handoffkit-0.4.0}/handoffkit.egg-info/top_level.txt +0 -0
  65. {handoffkit-0.3.0 → handoffkit-0.4.0}/setup.cfg +0 -0
  66. {handoffkit-0.3.0 → handoffkit-0.4.0}/tests/test_agent.py +0 -0
  67. {handoffkit-0.3.0 → handoffkit-0.4.0}/tests/test_coding_team.py +0 -0
  68. {handoffkit-0.3.0 → handoffkit-0.4.0}/tests/test_ollama_provider_mock.py +0 -0
  69. {handoffkit-0.3.0 → handoffkit-0.4.0}/tests/test_openai_model_selection.py +0 -0
  70. {handoffkit-0.3.0 → handoffkit-0.4.0}/tests/test_openai_provider_mock.py +0 -0
  71. {handoffkit-0.3.0 → handoffkit-0.4.0}/tests/test_real_task_demo.py +0 -0
  72. {handoffkit-0.3.0 → handoffkit-0.4.0}/tests/test_shell_safety.py +0 -0
  73. {handoffkit-0.3.0 → handoffkit-0.4.0}/tests/test_team.py +0 -0
  74. {handoffkit-0.3.0 → handoffkit-0.4.0}/tests/test_tool.py +0 -0
  75. {handoffkit-0.3.0 → handoffkit-0.4.0}/tests/test_tool_execution.py +0 -0
@@ -4,6 +4,7 @@ graft examples
4
4
  graft tests
5
5
  prune examples/output
6
6
  prune tests/_tmp_tool_execution
7
+ prune tests/_tmp_memory_context
7
8
  global-exclude __pycache__
8
9
  global-exclude __pycache__/*
9
10
  global-exclude *.py[cod]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: handoffkit
3
- Version: 0.3.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.3.0 Adds
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.3.0 Adds
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.3.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'}"