handoffkit 0.6.0__tar.gz → 0.7.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 (96) hide show
  1. {handoffkit-0.6.0/handoffkit.egg-info → handoffkit-0.7.0}/PKG-INFO +124 -5
  2. {handoffkit-0.6.0 → handoffkit-0.7.0}/README.md +123 -4
  3. handoffkit-0.7.0/examples/contract_validation_demo.py +85 -0
  4. handoffkit-0.7.0/examples/handoff_quality_demo.py +46 -0
  5. {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/provider_tool_adapter_demo.py +1 -0
  6. handoffkit-0.7.0/examples/provider_tool_formats_demo.py +98 -0
  7. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/__init__.py +23 -1
  8. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/agent.py +21 -7
  9. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/cli.py +113 -0
  10. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/handoff.py +9 -18
  11. handoffkit-0.7.0/handoffkit/provider_adapters.py +260 -0
  12. handoffkit-0.7.0/handoffkit/quality.py +277 -0
  13. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/structured.py +6 -0
  14. handoffkit-0.7.0/handoffkit/validation.py +322 -0
  15. {handoffkit-0.6.0 → handoffkit-0.7.0/handoffkit.egg-info}/PKG-INFO +124 -5
  16. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit.egg-info/SOURCES.txt +9 -1
  17. {handoffkit-0.6.0 → handoffkit-0.7.0}/pyproject.toml +1 -1
  18. handoffkit-0.7.0/tests/test_070_examples.py +49 -0
  19. {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_cli.py +37 -1
  20. {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_imports.py +18 -0
  21. {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_provider_adapters.py +98 -1
  22. handoffkit-0.7.0/tests/test_quality.py +63 -0
  23. handoffkit-0.7.0/tests/test_validation.py +97 -0
  24. handoffkit-0.6.0/handoffkit/provider_adapters.py +0 -134
  25. {handoffkit-0.6.0 → handoffkit-0.7.0}/LICENSE +0 -0
  26. {handoffkit-0.6.0 → handoffkit-0.7.0}/MANIFEST.in +0 -0
  27. {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/coding_review_recipe.py +0 -0
  28. {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/coding_team.py +0 -0
  29. {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/context_handoff_demo.py +0 -0
  30. {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/extension_demo.py +0 -0
  31. {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/fake_provider_tool_call_demo.py +0 -0
  32. {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/freemodel_best_model_demo.py +0 -0
  33. {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/freemodel_coding_team.py +0 -0
  34. {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/freemodel_openai_compatible.py +0 -0
  35. {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/handoff_demo.py +0 -0
  36. {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/memory_demo.py +0 -0
  37. {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/ollama_agent.py +0 -0
  38. {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/project_context_demo.py +0 -0
  39. {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/real_task_calculator.py +0 -0
  40. {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/recipe_demo.py +0 -0
  41. {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/repo_inspired_protocols.py +0 -0
  42. {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/simple_agent.py +0 -0
  43. {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/structured_output_demo.py +0 -0
  44. {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/structured_recipe_demo.py +0 -0
  45. {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/tool_agent.py +0 -0
  46. {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/tool_execution_demo.py +0 -0
  47. {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/tool_schema_demo.py +0 -0
  48. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/builtins.py +0 -0
  49. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/context.py +0 -0
  50. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/errors.py +0 -0
  51. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/extensions.py +0 -0
  52. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/memory.py +0 -0
  53. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/protocol.py +0 -0
  54. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/protocols/__init__.py +0 -0
  55. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/protocols/compressed.py +0 -0
  56. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/protocols/hybrid_min.py +0 -0
  57. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/protocols/hybrid_state.py +0 -0
  58. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/protocols/natural.py +0 -0
  59. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/providers/__init__.py +0 -0
  60. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/providers/base.py +0 -0
  61. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/providers/echo_provider.py +0 -0
  62. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/providers/ollama_provider.py +0 -0
  63. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/providers/openai_compatible.py +0 -0
  64. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/providers/openai_provider.py +0 -0
  65. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/py.typed +0 -0
  66. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/recipes.py +0 -0
  67. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/runner.py +0 -0
  68. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/safety.py +0 -0
  69. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/schemas.py +0 -0
  70. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/tool.py +0 -0
  71. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/tool_execution.py +0 -0
  72. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/tools/__init__.py +0 -0
  73. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/tools/filesystem.py +0 -0
  74. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/tools/shell.py +0 -0
  75. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/tools/text.py +0 -0
  76. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit.egg-info/dependency_links.txt +0 -0
  77. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit.egg-info/entry_points.txt +0 -0
  78. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit.egg-info/requires.txt +0 -0
  79. {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit.egg-info/top_level.txt +0 -0
  80. {handoffkit-0.6.0 → handoffkit-0.7.0}/setup.cfg +0 -0
  81. {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_agent.py +0 -0
  82. {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_coding_team.py +0 -0
  83. {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_contract_validation.py +0 -0
  84. {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_handoff.py +0 -0
  85. {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_memory_context.py +0 -0
  86. {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_ollama_provider_mock.py +0 -0
  87. {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_openai_model_selection.py +0 -0
  88. {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_openai_provider_mock.py +0 -0
  89. {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_protocols.py +0 -0
  90. {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_real_task_demo.py +0 -0
  91. {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_recipes_extensions.py +0 -0
  92. {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_shell_safety.py +0 -0
  93. {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_structured_outputs.py +0 -0
  94. {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_team.py +0 -0
  95. {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_tool.py +0 -0
  96. {handoffkit-0.6.0 → handoffkit-0.7.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.6.0
3
+ Version: 0.7.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,6 +80,21 @@ Tester
80
80
 
81
81
  That makes agent workflows easier to inspect, test, replay, and improve.
82
82
 
83
+ ## What 0.7.0 Adds
84
+
85
+ HandoffKit 0.7.0 adds reusable validation and quality layers for mature
86
+ multi-agent workflows:
87
+
88
+ - `ValidationIssue` and `ValidationReport` for reusable contract reports,
89
+ - `HandoffStateValidator`, `StructuredOutputValidator`, and `ToolSchemaValidator`,
90
+ - `validate_report(...)` helpers while keeping existing `validate()` behavior,
91
+ - `HandoffQualityEvaluator` with deterministic handoff quality scoring,
92
+ - quality metrics for completeness, clarity, actionability, traceability, and error awareness,
93
+ - provider-native tool schemas for `handoffkit`, `openai`, and `anthropic`,
94
+ - provider-style tool call parsing with call ID preservation,
95
+ - `provider_adapter` support in `Agent.run_with_tools(...)`,
96
+ - offline quality, validator, and provider-format demos with JSON/Markdown reports.
97
+
83
98
  ## What 0.6.0 Adds
84
99
 
85
100
  HandoffKit 0.6.0 adds structured outputs and provider-neutral tool adapters:
@@ -353,6 +368,105 @@ whether a provider returns HandoffKit JSON or function-style tool calls. This is
353
368
  provider-neutral by design; HandoffKit does not require any external SDK for
354
369
  normal tests.
355
370
 
371
+ ## Handoff Quality Metrics
372
+
373
+ Quality checks are deterministic and offline. They help compare handoff states
374
+ without calling a provider or judging prose with another model.
375
+
376
+ ```python
377
+ from handoffkit import HandoffQualityEvaluator, HandoffState
378
+
379
+ state = HandoffState(
380
+ task="Create a Python CLI calculator.",
381
+ from_agent="Architect",
382
+ to_agent="Coder",
383
+ summary="Build an argparse calculator with pure operations and tests.",
384
+ decisions=["Use argparse.", "Keep operations pure."],
385
+ important_files=["calculator.py", "test_calculator.py"],
386
+ next_steps=["Implement calculator.py", "Run pytest -q"],
387
+ context_refs=["README.md#real-task-demo"],
388
+ metadata={"errors_checked": True},
389
+ )
390
+
391
+ report = HandoffQualityEvaluator().evaluate(state)
392
+ print(report.score, report.grade)
393
+ print(report.to_markdown())
394
+ ```
395
+
396
+ Metrics:
397
+
398
+ | Metric | Checks |
399
+ |---|---|
400
+ | `completeness` | required fields, summary, decisions, files, context, next steps |
401
+ | `clarity` | useful summary length and non-empty decisions |
402
+ | `actionability` | next steps written as concrete actions |
403
+ | `traceability` | important files and context references |
404
+ | `error_awareness` | recorded errors or explicit error-check metadata |
405
+
406
+ ## Contract Validators
407
+
408
+ Validators return `ValidationReport` objects that can be serialized, rendered as
409
+ Markdown, or raised as exceptions. Existing APIs stay compatible:
410
+ `HandoffState.validate()` still returns `self` or raises `HandoffValidationError`,
411
+ and `StructuredOutputSchema.validate()` still returns the input dictionary or
412
+ raises `OutputValidationError`.
413
+
414
+ ```python
415
+ from handoffkit import (
416
+ HandoffStateValidator,
417
+ StructuredOutputSchema,
418
+ StructuredOutputValidator,
419
+ ToolSchemaValidator,
420
+ tool,
421
+ )
422
+
423
+ @tool
424
+ def add(a: int, b: int) -> int:
425
+ """Add two integers."""
426
+ return a + b
427
+
428
+ schema = StructuredOutputSchema(
429
+ name="Plan",
430
+ fields={"title": str, "ready": bool},
431
+ required=["title", "ready"],
432
+ )
433
+
434
+ print(StructuredOutputValidator().validate(schema, {"title": "Demo", "ready": True}).to_json())
435
+ print(ToolSchemaValidator().validate(add).to_markdown())
436
+ ```
437
+
438
+ ## Provider Tool Formats
439
+
440
+ `ProviderToolAdapter` can render tools as HandoffKit, OpenAI function-style, or
441
+ Anthropic `tool_use` schemas. It also parses provider-native tool call payloads
442
+ back into HandoffKit `ToolCall` objects.
443
+
444
+ ```python
445
+ from handoffkit import ProviderToolAdapter, tool
446
+
447
+ @tool
448
+ def read_file(path: str) -> str:
449
+ """Read a file."""
450
+ return f"read:{path}"
451
+
452
+ openai_adapter = ProviderToolAdapter(provider_format="openai")
453
+ anthropic_adapter = ProviderToolAdapter(provider_format="anthropic")
454
+
455
+ print(openai_adapter.tools_to_provider_format([read_file]))
456
+ print(anthropic_adapter.tools_to_provider_format([read_file]))
457
+
458
+ calls = openai_adapter.parse_tool_calls({
459
+ "tool_calls": [
460
+ {
461
+ "id": "call_123",
462
+ "type": "function",
463
+ "function": {"name": "read_file", "arguments": "{\"path\":\"README.md\"}"},
464
+ }
465
+ ]
466
+ })
467
+ print(calls[0].tool_name, calls[0].arguments, calls[0].call_id)
468
+ ```
469
+
356
470
  ## Memory + Project Context
357
471
 
358
472
  HandoffKit can store durable project decisions, index local files, retrieve
@@ -559,6 +673,9 @@ handoffkit demo-recipe
559
673
  handoffkit demo-extension
560
674
  handoffkit demo-structured
561
675
  handoffkit demo-provider-tools
676
+ handoffkit demo-quality
677
+ handoffkit demo-validators
678
+ handoffkit demo-provider-formats
562
679
  ```
563
680
 
564
681
  ## Examples
@@ -572,6 +689,9 @@ python examples/tool_execution_demo.py
572
689
  python examples/fake_provider_tool_call_demo.py
573
690
  python examples/structured_output_demo.py
574
691
  python examples/provider_tool_adapter_demo.py
692
+ python examples/handoff_quality_demo.py
693
+ python examples/contract_validation_demo.py
694
+ python examples/provider_tool_formats_demo.py
575
695
  python examples/structured_recipe_demo.py
576
696
  python examples/recipe_demo.py
577
697
  python examples/coding_review_recipe.py
@@ -631,11 +751,10 @@ HandoffKit is a developer library, not a copy of that repository.
631
751
 
632
752
  ## Roadmap
633
753
 
634
- - richer contract validators,
754
+ - richer memory persistence adapters,
635
755
  - broader tool schema coverage,
636
- - provider adapters,
637
- - structured tool calling loops,
638
- - handoff quality metrics,
756
+ - provider-specific integration examples,
757
+ - benchmark-inspired quality comparisons,
639
758
  - memory integrations,
640
759
  - project context retrieval,
641
760
  - benchmark-inspired examples,
@@ -51,6 +51,21 @@ Tester
51
51
 
52
52
  That makes agent workflows easier to inspect, test, replay, and improve.
53
53
 
54
+ ## What 0.7.0 Adds
55
+
56
+ HandoffKit 0.7.0 adds reusable validation and quality layers for mature
57
+ multi-agent workflows:
58
+
59
+ - `ValidationIssue` and `ValidationReport` for reusable contract reports,
60
+ - `HandoffStateValidator`, `StructuredOutputValidator`, and `ToolSchemaValidator`,
61
+ - `validate_report(...)` helpers while keeping existing `validate()` behavior,
62
+ - `HandoffQualityEvaluator` with deterministic handoff quality scoring,
63
+ - quality metrics for completeness, clarity, actionability, traceability, and error awareness,
64
+ - provider-native tool schemas for `handoffkit`, `openai`, and `anthropic`,
65
+ - provider-style tool call parsing with call ID preservation,
66
+ - `provider_adapter` support in `Agent.run_with_tools(...)`,
67
+ - offline quality, validator, and provider-format demos with JSON/Markdown reports.
68
+
54
69
  ## What 0.6.0 Adds
55
70
 
56
71
  HandoffKit 0.6.0 adds structured outputs and provider-neutral tool adapters:
@@ -324,6 +339,105 @@ whether a provider returns HandoffKit JSON or function-style tool calls. This is
324
339
  provider-neutral by design; HandoffKit does not require any external SDK for
325
340
  normal tests.
326
341
 
342
+ ## Handoff Quality Metrics
343
+
344
+ Quality checks are deterministic and offline. They help compare handoff states
345
+ without calling a provider or judging prose with another model.
346
+
347
+ ```python
348
+ from handoffkit import HandoffQualityEvaluator, HandoffState
349
+
350
+ state = HandoffState(
351
+ task="Create a Python CLI calculator.",
352
+ from_agent="Architect",
353
+ to_agent="Coder",
354
+ summary="Build an argparse calculator with pure operations and tests.",
355
+ decisions=["Use argparse.", "Keep operations pure."],
356
+ important_files=["calculator.py", "test_calculator.py"],
357
+ next_steps=["Implement calculator.py", "Run pytest -q"],
358
+ context_refs=["README.md#real-task-demo"],
359
+ metadata={"errors_checked": True},
360
+ )
361
+
362
+ report = HandoffQualityEvaluator().evaluate(state)
363
+ print(report.score, report.grade)
364
+ print(report.to_markdown())
365
+ ```
366
+
367
+ Metrics:
368
+
369
+ | Metric | Checks |
370
+ |---|---|
371
+ | `completeness` | required fields, summary, decisions, files, context, next steps |
372
+ | `clarity` | useful summary length and non-empty decisions |
373
+ | `actionability` | next steps written as concrete actions |
374
+ | `traceability` | important files and context references |
375
+ | `error_awareness` | recorded errors or explicit error-check metadata |
376
+
377
+ ## Contract Validators
378
+
379
+ Validators return `ValidationReport` objects that can be serialized, rendered as
380
+ Markdown, or raised as exceptions. Existing APIs stay compatible:
381
+ `HandoffState.validate()` still returns `self` or raises `HandoffValidationError`,
382
+ and `StructuredOutputSchema.validate()` still returns the input dictionary or
383
+ raises `OutputValidationError`.
384
+
385
+ ```python
386
+ from handoffkit import (
387
+ HandoffStateValidator,
388
+ StructuredOutputSchema,
389
+ StructuredOutputValidator,
390
+ ToolSchemaValidator,
391
+ tool,
392
+ )
393
+
394
+ @tool
395
+ def add(a: int, b: int) -> int:
396
+ """Add two integers."""
397
+ return a + b
398
+
399
+ schema = StructuredOutputSchema(
400
+ name="Plan",
401
+ fields={"title": str, "ready": bool},
402
+ required=["title", "ready"],
403
+ )
404
+
405
+ print(StructuredOutputValidator().validate(schema, {"title": "Demo", "ready": True}).to_json())
406
+ print(ToolSchemaValidator().validate(add).to_markdown())
407
+ ```
408
+
409
+ ## Provider Tool Formats
410
+
411
+ `ProviderToolAdapter` can render tools as HandoffKit, OpenAI function-style, or
412
+ Anthropic `tool_use` schemas. It also parses provider-native tool call payloads
413
+ back into HandoffKit `ToolCall` objects.
414
+
415
+ ```python
416
+ from handoffkit import ProviderToolAdapter, tool
417
+
418
+ @tool
419
+ def read_file(path: str) -> str:
420
+ """Read a file."""
421
+ return f"read:{path}"
422
+
423
+ openai_adapter = ProviderToolAdapter(provider_format="openai")
424
+ anthropic_adapter = ProviderToolAdapter(provider_format="anthropic")
425
+
426
+ print(openai_adapter.tools_to_provider_format([read_file]))
427
+ print(anthropic_adapter.tools_to_provider_format([read_file]))
428
+
429
+ calls = openai_adapter.parse_tool_calls({
430
+ "tool_calls": [
431
+ {
432
+ "id": "call_123",
433
+ "type": "function",
434
+ "function": {"name": "read_file", "arguments": "{\"path\":\"README.md\"}"},
435
+ }
436
+ ]
437
+ })
438
+ print(calls[0].tool_name, calls[0].arguments, calls[0].call_id)
439
+ ```
440
+
327
441
  ## Memory + Project Context
328
442
 
329
443
  HandoffKit can store durable project decisions, index local files, retrieve
@@ -530,6 +644,9 @@ handoffkit demo-recipe
530
644
  handoffkit demo-extension
531
645
  handoffkit demo-structured
532
646
  handoffkit demo-provider-tools
647
+ handoffkit demo-quality
648
+ handoffkit demo-validators
649
+ handoffkit demo-provider-formats
533
650
  ```
534
651
 
535
652
  ## Examples
@@ -543,6 +660,9 @@ python examples/tool_execution_demo.py
543
660
  python examples/fake_provider_tool_call_demo.py
544
661
  python examples/structured_output_demo.py
545
662
  python examples/provider_tool_adapter_demo.py
663
+ python examples/handoff_quality_demo.py
664
+ python examples/contract_validation_demo.py
665
+ python examples/provider_tool_formats_demo.py
546
666
  python examples/structured_recipe_demo.py
547
667
  python examples/recipe_demo.py
548
668
  python examples/coding_review_recipe.py
@@ -602,11 +722,10 @@ HandoffKit is a developer library, not a copy of that repository.
602
722
 
603
723
  ## Roadmap
604
724
 
605
- - richer contract validators,
725
+ - richer memory persistence adapters,
606
726
  - broader tool schema coverage,
607
- - provider adapters,
608
- - structured tool calling loops,
609
- - handoff quality metrics,
727
+ - provider-specific integration examples,
728
+ - benchmark-inspired quality comparisons,
610
729
  - memory integrations,
611
730
  - project context retrieval,
612
731
  - benchmark-inspired examples,
@@ -0,0 +1,85 @@
1
+ """Contract validation demo without external APIs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from handoffkit import (
10
+ HandoffState,
11
+ HandoffStateValidator,
12
+ StructuredOutputSchema,
13
+ StructuredOutputValidator,
14
+ ToolSchemaValidator,
15
+ tool,
16
+ )
17
+
18
+ ROOT = Path(__file__).resolve().parents[1]
19
+ REPORTS_DIR = ROOT / "reports"
20
+
21
+
22
+ @tool
23
+ def add(a: int, b: int) -> int:
24
+ """Add two integers."""
25
+ return a + b
26
+
27
+
28
+ def build_state() -> HandoffState:
29
+ """Build a deterministic valid handoff state."""
30
+ return HandoffState(
31
+ task="Create a Python CLI calculator.",
32
+ from_agent="Architect",
33
+ to_agent="Coder",
34
+ summary="Architecture plan is ready for implementation.",
35
+ decisions=["Use argparse.", "Return non-zero on invalid input."],
36
+ important_files=["calculator.py"],
37
+ next_steps=["Implement calculator.py", "Run pytest -q"],
38
+ context_refs=["docs/calculator-contract.md"],
39
+ metadata={"errors_checked": True},
40
+ )
41
+
42
+
43
+ def _json_default(value: Any) -> Any:
44
+ if hasattr(value, "to_dict"):
45
+ return value.to_dict()
46
+ return str(value)
47
+
48
+
49
+ def main() -> None:
50
+ """Validate handoff, structured output, and tool contracts."""
51
+ REPORTS_DIR.mkdir(parents=True, exist_ok=True)
52
+ schema = StructuredOutputSchema(
53
+ name="CalculatorPlan",
54
+ fields={"title": str, "steps": list, "ready": bool},
55
+ required=["title", "steps", "ready"],
56
+ )
57
+ reports = {
58
+ "handoff": HandoffStateValidator().validate(build_state()),
59
+ "structured_output": StructuredOutputValidator().validate(
60
+ schema,
61
+ {"title": "CLI calculator", "steps": ["implement", "test"], "ready": True},
62
+ ),
63
+ "tool_schema": ToolSchemaValidator().validate(add),
64
+ }
65
+
66
+ markdown = "# Contract Validation Demo\n\n" + "\n\n".join(
67
+ f"## {name}\n\n{report.to_markdown()}" for name, report in reports.items()
68
+ )
69
+ payload = {name: report.to_dict() for name, report in reports.items()}
70
+
71
+ markdown_path = REPORTS_DIR / "contract_validation_demo.md"
72
+ json_path = REPORTS_DIR / "contract_validation_demo.json"
73
+ markdown_path.write_text(markdown, encoding="utf-8")
74
+ json_text = json.dumps(payload, ensure_ascii=False, indent=2, default=_json_default)
75
+ json_path.write_text(json_text, encoding="utf-8")
76
+
77
+ print(f"Handoff validation: {reports['handoff'].success}")
78
+ print(f"Structured output validation: {reports['structured_output'].success}")
79
+ print(f"Tool schema validation: {reports['tool_schema'].success}")
80
+ print(f"Markdown report: {markdown_path}")
81
+ print(f"JSON report: {json_path}")
82
+
83
+
84
+ if __name__ == "__main__":
85
+ main()
@@ -0,0 +1,46 @@
1
+ """Handoff quality metrics demo without external APIs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from handoffkit import HandoffQualityEvaluator, HandoffState
8
+
9
+ ROOT = Path(__file__).resolve().parents[1]
10
+ REPORTS_DIR = ROOT / "reports"
11
+
12
+
13
+ def build_state() -> HandoffState:
14
+ """Build a deterministic handoff state for quality scoring."""
15
+ return HandoffState(
16
+ task="Create a Python CLI calculator.",
17
+ from_agent="Architect",
18
+ to_agent="Coder",
19
+ summary="Build a dependency-free argparse calculator with pure operations and tests.",
20
+ decisions=["Use argparse for CLI parsing.", "Keep math operations in pure functions."],
21
+ important_files=["calculator.py", "test_calculator.py"],
22
+ next_steps=["Implement calculator.py", "Write pytest coverage", "Run pytest -q"],
23
+ context_refs=["README.md#real-task-demo"],
24
+ metadata={"errors_checked": True},
25
+ )
26
+
27
+
28
+ def main() -> None:
29
+ """Evaluate one handoff state and write Markdown/JSON reports."""
30
+ REPORTS_DIR.mkdir(parents=True, exist_ok=True)
31
+ report = HandoffQualityEvaluator().evaluate(build_state())
32
+
33
+ markdown_path = REPORTS_DIR / "handoff_quality_demo.md"
34
+ json_path = REPORTS_DIR / "handoff_quality_demo.json"
35
+ markdown_path.write_text(report.to_markdown(), encoding="utf-8")
36
+ json_path.write_text(report.to_json(), encoding="utf-8")
37
+
38
+ print(f"Handoff quality success: {report.success}")
39
+ print(f"Score: {report.score:.3f}")
40
+ print(f"Grade: {report.grade}")
41
+ print(f"Markdown report: {markdown_path}")
42
+ print(f"JSON report: {json_path}")
43
+
44
+
45
+ if __name__ == "__main__":
46
+ main()
@@ -25,6 +25,7 @@ def main() -> None:
25
25
  provider_response = {
26
26
  "tool_calls": [
27
27
  {
28
+ "id": "call_provider_adapter_demo",
28
29
  "function": {
29
30
  "name": "label",
30
31
  "arguments": '{"value":"provider-adapter"}',
@@ -0,0 +1,98 @@
1
+ """Provider tool format demo without external APIs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from handoffkit import ProviderToolAdapter, tool
10
+
11
+ ROOT = Path(__file__).resolve().parents[1]
12
+ REPORTS_DIR = ROOT / "reports"
13
+
14
+
15
+ @tool
16
+ def add(a: int, b: int) -> int:
17
+ """Add two integers."""
18
+ return a + b
19
+
20
+
21
+ def _json_default(value: Any) -> Any:
22
+ if hasattr(value, "to_dict"):
23
+ return value.to_dict()
24
+ return str(value)
25
+
26
+
27
+ def main() -> None:
28
+ """Render provider-native tool schemas and parse provider tool calls."""
29
+ REPORTS_DIR.mkdir(parents=True, exist_ok=True)
30
+ handoffkit_adapter = ProviderToolAdapter(provider_format="handoffkit")
31
+ openai_adapter = ProviderToolAdapter(provider_format="openai")
32
+ anthropic_adapter = ProviderToolAdapter(provider_format="anthropic")
33
+
34
+ schemas = {
35
+ "handoffkit": handoffkit_adapter.tools_to_provider_format([add]),
36
+ "openai": openai_adapter.tools_to_provider_format([add]),
37
+ "anthropic": anthropic_adapter.tools_to_provider_format([add]),
38
+ }
39
+ parsed_calls = {
40
+ "openai": [
41
+ call.to_dict()
42
+ for call in openai_adapter.parse_tool_calls(
43
+ {
44
+ "tool_calls": [
45
+ {
46
+ "id": "call_openai_1",
47
+ "type": "function",
48
+ "function": {"name": "add", "arguments": '{"a":2,"b":3}'},
49
+ }
50
+ ]
51
+ }
52
+ )
53
+ ],
54
+ "anthropic": [
55
+ call.to_dict()
56
+ for call in anthropic_adapter.parse_tool_calls(
57
+ {
58
+ "content": [
59
+ {
60
+ "type": "tool_use",
61
+ "id": "toolu_1",
62
+ "name": "add",
63
+ "input": {"a": 5, "b": 8},
64
+ }
65
+ ]
66
+ }
67
+ )
68
+ ],
69
+ }
70
+ payload = {"schemas": schemas, "parsed_calls": parsed_calls}
71
+ parsed_json = json.dumps(parsed_calls, ensure_ascii=False, indent=2)
72
+ markdown = (
73
+ "# Provider Tool Formats Demo\n\n"
74
+ "## Formats\n\n"
75
+ f"- handoffkit tools: {len(schemas['handoffkit'])}\n"
76
+ f"- openai tools: {len(schemas['openai'])}\n"
77
+ f"- anthropic tools: {len(schemas['anthropic'])}\n\n"
78
+ "## Parsed Calls\n\n"
79
+ f"```json\n{parsed_json}\n```\n"
80
+ )
81
+
82
+ markdown_path = REPORTS_DIR / "provider_tool_formats_demo.md"
83
+ json_path = REPORTS_DIR / "provider_tool_formats_demo.json"
84
+ markdown_path.write_text(markdown, encoding="utf-8")
85
+ json_text = json.dumps(payload, ensure_ascii=False, indent=2, default=_json_default)
86
+ json_path.write_text(json_text, encoding="utf-8")
87
+
88
+ print(f"HandoffKit format tools: {len(schemas['handoffkit'])}")
89
+ print(f"OpenAI format tools: {len(schemas['openai'])}")
90
+ print(f"Anthropic format tools: {len(schemas['anthropic'])}")
91
+ print(f"OpenAI call id: {parsed_calls['openai'][0]['call_id']}")
92
+ print(f"Anthropic call id: {parsed_calls['anthropic'][0]['call_id']}")
93
+ print(f"Markdown report: {markdown_path}")
94
+ print(f"JSON report: {json_path}")
95
+
96
+
97
+ if __name__ == "__main__":
98
+ main()
@@ -16,8 +16,14 @@ from handoffkit.protocol import HandoffProtocol
16
16
  from handoffkit.provider_adapters import (
17
17
  ProviderCapabilities,
18
18
  ProviderToolAdapter,
19
+ ProviderToolFormat,
19
20
  ToolCallParser,
20
21
  )
22
+ from handoffkit.quality import (
23
+ HandoffQualityEvaluator,
24
+ HandoffQualityMetric,
25
+ HandoffQualityReport,
26
+ )
21
27
  from handoffkit.recipes import (
22
28
  Recipe,
23
29
  RecipeRunner,
@@ -40,8 +46,15 @@ from handoffkit.tool_execution import (
40
46
  ToolRegistry,
41
47
  ToolResult,
42
48
  )
49
+ from handoffkit.validation import (
50
+ HandoffStateValidator,
51
+ StructuredOutputValidator,
52
+ ToolSchemaValidator,
53
+ ValidationIssue,
54
+ ValidationReport,
55
+ )
43
56
 
44
- __version__ = "0.6.0"
57
+ __version__ = "0.7.0"
45
58
 
46
59
  __all__ = [
47
60
  "Agent",
@@ -53,7 +66,11 @@ __all__ = [
53
66
  "ExtensionRegistry",
54
67
  "HandoffValidationError",
55
68
  "HandoffProtocol",
69
+ "HandoffQualityEvaluator",
70
+ "HandoffQualityMetric",
71
+ "HandoffQualityReport",
56
72
  "HandoffState",
73
+ "HandoffStateValidator",
57
74
  "JsonMemoryStore",
58
75
  "JsonOutputParser",
59
76
  "MemoryItem",
@@ -64,12 +81,14 @@ __all__ = [
64
81
  "ProjectIndexer",
65
82
  "ProviderCapabilities",
66
83
  "ProviderToolAdapter",
84
+ "ProviderToolFormat",
67
85
  "Recipe",
68
86
  "RecipeRunResult",
69
87
  "RecipeRunner",
70
88
  "RecipeStep",
71
89
  "StructuredOutputResult",
72
90
  "StructuredOutputSchema",
91
+ "StructuredOutputValidator",
73
92
  "Team",
74
93
  "TeamRunResult",
75
94
  "Tool",
@@ -78,6 +97,9 @@ __all__ = [
78
97
  "ToolExecutionReport",
79
98
  "ToolRegistry",
80
99
  "ToolResult",
100
+ "ToolSchemaValidator",
101
+ "ValidationIssue",
102
+ "ValidationReport",
81
103
  "WorkflowTemplate",
82
104
  "__version__",
83
105
  "tool",