agenthooksprotocol 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 (113) hide show
  1. {agenthooksprotocol-0.3.0/src/agenthooksprotocol.egg-info → agenthooksprotocol-0.4.0}/PKG-INFO +27 -21
  2. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/README.md +26 -20
  3. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/pyproject.toml +1 -1
  4. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/__init__.py +11 -1
  5. agenthooksprotocol-0.4.0/src/agenthooksprotocol/_boundaries.py +1358 -0
  6. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/_content.py +69 -46
  7. agenthooksprotocol-0.3.0/src/agenthooksprotocol/elicitation.py → agenthooksprotocol-0.4.0/src/agenthooksprotocol/_elicitation.py +50 -13
  8. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/_grants.py +6 -21
  9. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/_hooks.py +117 -33
  10. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/_models/__init__.py +3169 -522
  11. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/_models/content.py +2 -0
  12. agenthooksprotocol-0.4.0/src/agenthooksprotocol/_models/effect.py +124 -0
  13. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/ahp-codegen.lock.json +10 -10
  14. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/auth.py +8 -3
  15. agenthooksprotocol-0.4.0/src/agenthooksprotocol/codec.py +99 -0
  16. agenthooksprotocol-0.4.0/src/agenthooksprotocol/contract.py +56 -0
  17. agenthooksprotocol-0.4.0/src/agenthooksprotocol/effect.py +511 -0
  18. agenthooksprotocol-0.4.0/src/agenthooksprotocol/elicitation.py +8 -0
  19. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/event.py +103 -0
  20. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/generated.py +1793 -347
  21. agenthooksprotocol-0.4.0/src/agenthooksprotocol/integrations/pydantic.py +152 -0
  22. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/interop.py +8 -7
  23. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/lifecycle.py +25 -7
  24. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/lineage.py +2 -2
  25. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/registration.py +4 -1
  26. agenthooksprotocol-0.4.0/src/agenthooksprotocol/response.py +268 -0
  27. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/runtime.py +155 -72
  28. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/schemas.json +2465 -358
  29. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/server/asgi.py +2 -2
  30. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/server/attachments.py +6 -2
  31. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/server/hooks.py +13 -10
  32. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/server/stdio.py +6 -4
  33. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/state.py +3 -4
  34. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/transports/http.py +10 -8
  35. agenthooksprotocol-0.4.0/src/agenthooksprotocol/wire.py +49 -0
  36. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0/src/agenthooksprotocol.egg-info}/PKG-INFO +27 -21
  37. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol.egg-info/SOURCES.txt +8 -0
  38. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_attachments.py +2 -1
  39. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_content.py +4 -1
  40. agenthooksprotocol-0.4.0/tests/test_correlated_core.py +293 -0
  41. agenthooksprotocol-0.4.0/tests/test_declared_contracts.py +506 -0
  42. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_inline_messages.py +3 -3
  43. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_integration.py +19 -19
  44. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_integration_edges.py +29 -1
  45. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_interop.py +4 -4
  46. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_lifecycle.py +25 -0
  47. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_observation.py +2 -2
  48. agenthooksprotocol-0.4.0/tests/test_public_contract_surface.py +86 -0
  49. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_registration_auth.py +20 -25
  50. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_safety.py +22 -8
  51. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_schema_corrections.py +4 -4
  52. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_sdk_unification.py +3 -3
  53. agenthooksprotocol-0.4.0/tests/test_typed_contract_regressions.py +419 -0
  54. agenthooksprotocol-0.3.0/src/agenthooksprotocol/_boundaries.py +0 -515
  55. agenthooksprotocol-0.3.0/src/agenthooksprotocol/_models/effect.py +0 -48
  56. agenthooksprotocol-0.3.0/src/agenthooksprotocol/codec.py +0 -32
  57. agenthooksprotocol-0.3.0/src/agenthooksprotocol/effect.py +0 -151
  58. agenthooksprotocol-0.3.0/src/agenthooksprotocol/integrations/pydantic.py +0 -28
  59. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/LICENSE +0 -0
  60. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/setup.cfg +0 -0
  61. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/_diagnostics.py +0 -0
  62. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/_models/capability.py +0 -0
  63. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/_models/event.py +0 -0
  64. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/_models/registration.py +0 -0
  65. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/_models/subscription.py +0 -0
  66. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/_models/tool.py +0 -0
  67. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/_models/transport.py +0 -0
  68. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/attachment.py +0 -0
  69. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/candidate.py +0 -0
  70. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/capability.py +0 -0
  71. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/catalogue.py +0 -0
  72. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/compaction.py +0 -0
  73. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/content.py +0 -0
  74. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/diagnostics.py +0 -0
  75. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/integrations/__init__.py +0 -0
  76. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/lifecycle_client.py +0 -0
  77. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/lifecycle_server.py +0 -0
  78. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/observation_chain.py +0 -0
  79. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/path.py +0 -0
  80. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/permission.py +0 -0
  81. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/py.typed +0 -0
  82. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/server/__init__.py +0 -0
  83. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/server/_framing.py +0 -0
  84. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/server/_json.py +0 -0
  85. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/subscription.py +0 -0
  86. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/tool.py +0 -0
  87. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/transport.py +0 -0
  88. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/transports/__init__.py +0 -0
  89. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/transports/stdio.py +0 -0
  90. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol.egg-info/dependency_links.txt +0 -0
  91. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol.egg-info/requires.txt +0 -0
  92. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol.egg-info/top_level.txt +0 -0
  93. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_catalogue.py +0 -0
  94. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_codec.py +0 -0
  95. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_compaction.py +0 -0
  96. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_compaction_wire.py +0 -0
  97. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_content_lifetime.py +0 -0
  98. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_content_receipts.py +0 -0
  99. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_elicitation_adapter.py +0 -0
  100. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_fixture_public.py +0 -0
  101. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_lifecycle_auth.py +0 -0
  102. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_observation_chain_transport.py +0 -0
  103. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_owned_content.py +0 -0
  104. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_owned_content_hooks.py +0 -0
  105. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_package.py +0 -0
  106. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_public_content.py +0 -0
  107. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_public_examples.py +0 -0
  108. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_public_hooks.py +0 -0
  109. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_public_server.py +0 -0
  110. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_session_manifest.py +0 -0
  111. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_shared_materialization.py +0 -0
  112. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_structural_facade.py +0 -0
  113. {agenthooksprotocol-0.3.0 → agenthooksprotocol-0.4.0}/tests/test_upload_planning.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agenthooksprotocol
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Official non-normative Python SDK for the Agent Hooks Protocol
5
5
  Author: Agent Hooks Protocol maintainers
6
6
  License:
@@ -266,6 +266,7 @@ Capability builders are immutable: reusing a base declaration does not widen it.
266
266
 
267
267
  Runnable, statically checked consumer programs live in [`examples/`](examples):
268
268
 
269
+ - `value_codecs.py`: independent argument/result/provenance codecs, admission-time invariants, and a form schema derived from its answer type.
269
270
  - `typed_tool.py`: JSON registration, generated tool input, typed grants, initial state, real in-process ASGI handler, borrowed HTTP client, and explicit application decoding.
270
271
  - `common_cases.py`: rewrite/allow, deny, invalid application shape, host policy, occurrence narrowing, native initial state, and fail-open/fail-closed reports. The host operation is an in-memory execution recorder, not a shell.
271
272
  - `stdio_tool.py` / `stdio_backend.py`: a real owned backend process using the same public Handler, denial, and child cleanup.
@@ -282,9 +283,15 @@ Generated constructors build JSON-shaped protocol values. Generated `TypedDict`
282
283
 
283
284
  Capabilities explicitly grant effects and target operations. Supply event capabilities at harness construction; a per-occurrence capability override can narrow them, never widen them. `initial_state` carries the host's native prior decision for one occurrence. A hook-supplied result is a candidate, not an assertion that an operation executed. The host owns its ordinary input validation and actual execution.
284
285
 
286
+ Supply `input_codec=`, `result_codec=`, and optional `provenance_codec=` to a named hook method or `dispatch` to declare caller types. Complete staged values are decoded and validated before publication. Object merge is shallow: omitted fields remain unchanged, literal null is retained, and nested values replace whole values. An invalid response publishes none of its effects; earlier accepted responses remain visible to later interceptors. A codec must preserve the wire data it receives, including opaque extra fields. Lossy serialization rejects admission rather than dropping fields.
287
+
288
+ `result.decoded_input` exposes the declared argument value. `result.decoded_candidate` is an application `Candidate[Result, Provenance]`, or `None` when there is no candidate. Its value can itself be `None`; optional provenance is omitted from `candidate.to_dict()` when absent. Raw `input` and `candidate` views remain detached snapshots. For explicitly selected opaque host slots, use `event_codecs={("params",): codec}` with event-relative paths. Unspecified native, provider, task, permission, and discovery values are not interpreted.
289
+
290
+ For forms, construct `PydanticFormContract(TypeAdapter(Answer))` from one answer declaration. `request(message)` derives the restricted MCP schema; `result_codec()` decodes the complete MCP result and its accepted answer while preserving opaque result metadata. Unsupported shapes and schema constraints are rejected during construction. Decline, cancel, and URL results have no form content. See [`examples/value_codecs.py`](examples/value_codecs.py).
291
+
285
292
  After settlement, explicitly decode effective input with `result.decode_input(codec)`. A codec implements `encode(value)` and `decode(json_value)`; `IdentityCodec` copies JSON. The optional `integrations.pydantic.PydanticCodec` accepts a Pydantic `TypeAdapter`. A decoding error does not retroactively reject protocol effects: retain the settled result, raw effective input, accepted responses, and diagnostics, and report host-level rejection without executing.
286
293
 
287
- `result.permission` is the generated `Permission` enum (`NONE`, `ALLOW`, `ASK`, `DENY`) read from canonical settlement, not an independent decision engine. `NONE` is not approval; `ASK` needs host approval; interruption never authorizes execution. `result.input` is detached accepted JSON, not a claim that backend modifications satisfy an application's original type. `decode_input(codec)` remains an explicit, post-settlement host check. Candidate, canonical event, accepted responses, and diagnostics remain accessible.
294
+ `result.permission` is the generated `Permission` enum (`NONE`, `ALLOW`, `ASK`, `DENY`) read from canonical settlement, not an independent decision engine. `NONE` is not approval; `ASK` needs host approval; interruption never authorizes execution. `result.input` is detached accepted JSON. Declared codecs validate accepted modifications; without a declared codec, the host supplies application validation. `decode_input(codec)` remains an explicit, post-settlement host check. Candidate, canonical event, accepted responses, and diagnostics remain accessible.
288
295
 
289
296
  `state.initial(Permission.ALLOW)` represents a native decision **already made for this exact occurrence**, not an authorization shortcut. Its default candidate is absent (`null`); `state.Candidate(value=None)` instead means a supplied JSON-null result. Optional typed provenance records facts, not authenticated identity or execution proof. Canonical modifications still invalidate candidates/approval as specified.
290
297
 
@@ -379,7 +386,7 @@ snapshot; answer-object merge is shallow. Elicitation request bodies are immutab
379
386
  to modification effects. No text/JSON upload callback is needed. Metadata and omit selections
380
387
  cannot authorize body-dependent effects.
381
388
 
382
- For custom transports, `begin(canonical_request)` separates acquisition,
389
+ For custom transports, `begin(canonical_request, value_codecs=ValueCodecs(...))` separates acquisition,
383
390
  cancellation, and acceptance; `exchange(canonical_request)` uses the same
384
391
  settlement engine. Elicitation correlation can be supplied through
385
392
  `ContentContext(original_request=..., bindings={"content": ("elicitation", "result")},
@@ -389,7 +396,7 @@ principal=authenticated_identity)` and `await pending.accept_content()`.
389
396
 
390
397
  Semantic namespaces (`event`, `tool`, `effect`, `capability`, and registration models) and all named event boundaries come from schema metadata, not a selected handwritten helper list. Constructors may supply fixed protocol literals; parsers never repair missing input. Open fields are retained for forward-compatible JSON round trips.
391
398
 
392
- The low-level `generated` module exports the full schema's typed models, `parse_<root>` / `encode_<root>` codecs, `PROTOCOL_VERSION`, `SCHEMA_REVISION`, and JSON types. `ahp-codegen.lock.json` records immutable generator provenance. Change canonical schemas and generator source in the protocol repository rather than editing generated files.
399
+ The `wire` namespace exposes canonical JSON-shaped request, notification, effect, and response types with structural codecs. Server callbacks use these raw wire contracts. The low-level `generated` module exports the full schema's typed models, `parse_<root>` / `encode_<root>` codecs, `PROTOCOL_VERSION`, `SCHEMA_REVISION`, and JSON types. `ahp-codegen.lock.json` records immutable generator provenance. Change canonical schemas and generator source in the protocol repository rather than editing generated files.
393
400
 
394
401
  ## Development
395
402
 
@@ -411,8 +418,8 @@ attributes. `ModelVisibleItem` is a canonical message with a typed role and orde
411
418
 
412
419
  Facade objects are mappings with read-only attributes.
413
420
  Optional attributes return `None` when absent; mapping membership still records
414
- presence. Attributes conflicting with mapping methods use a trailing underscore
415
- (such as `items_`), so `dict.items()` keeps working. Wire parsers return lossless mappings, not hydrated facade instances.
421
+ presence. Omitted and explicit-empty response effects are both neutral; decoded effects provide an empty list without modifying the source wire object. Attributes conflicting with mapping methods use a trailing underscore
422
+ (such as `items_`), so `dict.items()` keeps working. Raw wire parsers return lossless mappings. `response.response_for_request(event_type, raw)` returns the generated contextual response selected by the originating event type, not by payload shape.
416
423
 
417
424
  Use generated nested models for typed constructor arguments. Wire constructors, dictionary decoding, and runtime parsing
418
425
  check location/evidence alternatives and other structural schema constraints. Application
@@ -446,13 +453,7 @@ the same structural descriptor engine as generated `parse_*` functions. The
446
453
  original composition is retained, including required members, explicit nulls,
447
454
  literals, closed enums, forbidden combinations, and union ambiguity. This is the
448
455
  SDK's structural contract, not complete contextual protocol validation.
449
- `from_dict` does not supply omitted wire literals or defaults. It validates once
450
- and privately hydrates nested typed models without recursively calling public
451
- constructors. For example, a decoded HTTP connection supports
452
- `connection.gaps[0].reason`, just like a constructed connection. Union selection
453
- reuses memoized descriptor checks from that decode; subtrees are not repeatedly
454
- validated during hydration. Extension/application values remain ordinary JSON
455
- values. Descriptors and hydration metadata are cached.
456
+ `from_dict` checks complete wire values and preserves optional-field presence. Decoded known nested values expose the same attributes as constructed values, such as `connection.gaps[0].reason`. Extension and application bags remain intact and do not acquire effect or attachment authority.
456
457
 
457
458
  Invalid wire models raise `ValueError`; validation failures carry `result`,
458
459
  `diagnostics`, and `raw` attributes. Use `parse_*` when you need the full result on
@@ -470,12 +471,7 @@ objects are validated at the dispatch/parse boundary. Direct dictionary
470
471
  mutation, standard-library `json.loads`, TypedDict annotations, and type assertions
471
472
  are not SDK validation entrypoints. Re-parse after manually mutating a mapping.
472
473
 
473
- Wire constructors also privately hydrate nested mappings: for example,
474
- `capability.Capabilities(effects=["modify"], modify={}).modify.input` returns
475
- `None`, not an attribute error. Already typed child models retain their identity;
476
- plain nested mappings become typed models after the constructor's single cached
477
- validation pass. Family queries include all generated per-occurrence capability
478
- models, such as `capability.ToolBeforeCapabilities`.
474
+ Nested model attributes expose declared protocol fields. For example, `capability.Capabilities(effects=["modify"], modify={}).modify.input` is `None` when that grant is absent. Family queries are available on generic and per-occurrence capability models.
479
475
 
480
476
  Constructor aliases cannot silently overwrite wire keys. Supplying both
481
477
  `tool_name=` and `toolName=` raises `TypeError`, even if the values agree. For
@@ -491,6 +487,8 @@ Use `Attachment.from_bytes(data)` for immutable Python `bytes`, or
491
487
  `Attachment.lazy(async_loader, aclose=async_cleanup)` to defer reading. Put
492
488
  `attachment` directly in an attachment part's `body`. Named `Hooks` methods
493
489
  accept both dictionary inputs and typed event inputs with the same owner.
490
+ For a custom `OwnedContentSource`, use the host-only `content.OwnedAttachment(source)` wrapper in an attachment part's body. This wrapper does not read the source and is not a wire value. The SDK transfers and closes its source under the same invocation ownership rules.
491
+
494
492
  Metadata belongs on the part. See the runnable
495
493
  [file attachment example](examples/file_attachment.py).
496
494
 
@@ -521,9 +519,9 @@ matched subscriptions, including observers. One attachment owner is read once
521
519
  for multiple subscribers. Metadata, omit, and unmatched selections do not read
522
520
  or upload its bytes. Result reads and metadata-only delivery require no uploader.
523
521
 
524
- Only confirmed receipts are cached, for the exact attachment owner and backend
522
+ Only confirmed receipts are reused, for the exact attachment owner and backend
525
523
  ID/subscription index. An uncalled interceptor receives a settled observation
526
- with its cached reference after denial or stop. Upload failures are handled by
524
+ with its confirmed reference after denial or stop. Upload failures are handled by
527
525
  the affected subscription's failure policy when its delivery is reached; they
528
526
  do not prevent independent destinations from preparing their uploads. An upload
529
527
  can commit even if an earlier interceptor later removes the attachment or halts
@@ -539,3 +537,11 @@ not retained.
539
537
 
540
538
  Attachments are the sole byte owners. Binary/file editing is not supported.
541
539
  Results are invocation-local, not session archives.
540
+
541
+ ### Advanced validation and dispatch
542
+
543
+ `runtime.Validator` validates bundled canonical schemas and structural codecs. It does not establish request correlation, effect grants, host approval, or permission to execute. Use `Hooks` for complete interception admission.
544
+
545
+ `server.hooks.Engine` is the framework-neutral backend dispatcher. Supply an explicit validator or static harness manifest when needed. Engine callbacks return protocol effects; the receiving harness validates authority and settles them atomically.
546
+
547
+ Host session counters (`turns`, `modelRequests`, `toolCalls`, `inputTokens`, `outputTokens`, and extra counter names) are exact nonnegative integers. The SDK preserves supplied values and does not infer counters from events.
@@ -42,6 +42,7 @@ Capability builders are immutable: reusing a base declaration does not widen it.
42
42
 
43
43
  Runnable, statically checked consumer programs live in [`examples/`](examples):
44
44
 
45
+ - `value_codecs.py`: independent argument/result/provenance codecs, admission-time invariants, and a form schema derived from its answer type.
45
46
  - `typed_tool.py`: JSON registration, generated tool input, typed grants, initial state, real in-process ASGI handler, borrowed HTTP client, and explicit application decoding.
46
47
  - `common_cases.py`: rewrite/allow, deny, invalid application shape, host policy, occurrence narrowing, native initial state, and fail-open/fail-closed reports. The host operation is an in-memory execution recorder, not a shell.
47
48
  - `stdio_tool.py` / `stdio_backend.py`: a real owned backend process using the same public Handler, denial, and child cleanup.
@@ -58,9 +59,15 @@ Generated constructors build JSON-shaped protocol values. Generated `TypedDict`
58
59
 
59
60
  Capabilities explicitly grant effects and target operations. Supply event capabilities at harness construction; a per-occurrence capability override can narrow them, never widen them. `initial_state` carries the host's native prior decision for one occurrence. A hook-supplied result is a candidate, not an assertion that an operation executed. The host owns its ordinary input validation and actual execution.
60
61
 
62
+ Supply `input_codec=`, `result_codec=`, and optional `provenance_codec=` to a named hook method or `dispatch` to declare caller types. Complete staged values are decoded and validated before publication. Object merge is shallow: omitted fields remain unchanged, literal null is retained, and nested values replace whole values. An invalid response publishes none of its effects; earlier accepted responses remain visible to later interceptors. A codec must preserve the wire data it receives, including opaque extra fields. Lossy serialization rejects admission rather than dropping fields.
63
+
64
+ `result.decoded_input` exposes the declared argument value. `result.decoded_candidate` is an application `Candidate[Result, Provenance]`, or `None` when there is no candidate. Its value can itself be `None`; optional provenance is omitted from `candidate.to_dict()` when absent. Raw `input` and `candidate` views remain detached snapshots. For explicitly selected opaque host slots, use `event_codecs={("params",): codec}` with event-relative paths. Unspecified native, provider, task, permission, and discovery values are not interpreted.
65
+
66
+ For forms, construct `PydanticFormContract(TypeAdapter(Answer))` from one answer declaration. `request(message)` derives the restricted MCP schema; `result_codec()` decodes the complete MCP result and its accepted answer while preserving opaque result metadata. Unsupported shapes and schema constraints are rejected during construction. Decline, cancel, and URL results have no form content. See [`examples/value_codecs.py`](examples/value_codecs.py).
67
+
61
68
  After settlement, explicitly decode effective input with `result.decode_input(codec)`. A codec implements `encode(value)` and `decode(json_value)`; `IdentityCodec` copies JSON. The optional `integrations.pydantic.PydanticCodec` accepts a Pydantic `TypeAdapter`. A decoding error does not retroactively reject protocol effects: retain the settled result, raw effective input, accepted responses, and diagnostics, and report host-level rejection without executing.
62
69
 
63
- `result.permission` is the generated `Permission` enum (`NONE`, `ALLOW`, `ASK`, `DENY`) read from canonical settlement, not an independent decision engine. `NONE` is not approval; `ASK` needs host approval; interruption never authorizes execution. `result.input` is detached accepted JSON, not a claim that backend modifications satisfy an application's original type. `decode_input(codec)` remains an explicit, post-settlement host check. Candidate, canonical event, accepted responses, and diagnostics remain accessible.
70
+ `result.permission` is the generated `Permission` enum (`NONE`, `ALLOW`, `ASK`, `DENY`) read from canonical settlement, not an independent decision engine. `NONE` is not approval; `ASK` needs host approval; interruption never authorizes execution. `result.input` is detached accepted JSON. Declared codecs validate accepted modifications; without a declared codec, the host supplies application validation. `decode_input(codec)` remains an explicit, post-settlement host check. Candidate, canonical event, accepted responses, and diagnostics remain accessible.
64
71
 
65
72
  `state.initial(Permission.ALLOW)` represents a native decision **already made for this exact occurrence**, not an authorization shortcut. Its default candidate is absent (`null`); `state.Candidate(value=None)` instead means a supplied JSON-null result. Optional typed provenance records facts, not authenticated identity or execution proof. Canonical modifications still invalidate candidates/approval as specified.
66
73
 
@@ -155,7 +162,7 @@ snapshot; answer-object merge is shallow. Elicitation request bodies are immutab
155
162
  to modification effects. No text/JSON upload callback is needed. Metadata and omit selections
156
163
  cannot authorize body-dependent effects.
157
164
 
158
- For custom transports, `begin(canonical_request)` separates acquisition,
165
+ For custom transports, `begin(canonical_request, value_codecs=ValueCodecs(...))` separates acquisition,
159
166
  cancellation, and acceptance; `exchange(canonical_request)` uses the same
160
167
  settlement engine. Elicitation correlation can be supplied through
161
168
  `ContentContext(original_request=..., bindings={"content": ("elicitation", "result")},
@@ -165,7 +172,7 @@ principal=authenticated_identity)` and `await pending.accept_content()`.
165
172
 
166
173
  Semantic namespaces (`event`, `tool`, `effect`, `capability`, and registration models) and all named event boundaries come from schema metadata, not a selected handwritten helper list. Constructors may supply fixed protocol literals; parsers never repair missing input. Open fields are retained for forward-compatible JSON round trips.
167
174
 
168
- The low-level `generated` module exports the full schema's typed models, `parse_<root>` / `encode_<root>` codecs, `PROTOCOL_VERSION`, `SCHEMA_REVISION`, and JSON types. `ahp-codegen.lock.json` records immutable generator provenance. Change canonical schemas and generator source in the protocol repository rather than editing generated files.
175
+ The `wire` namespace exposes canonical JSON-shaped request, notification, effect, and response types with structural codecs. Server callbacks use these raw wire contracts. The low-level `generated` module exports the full schema's typed models, `parse_<root>` / `encode_<root>` codecs, `PROTOCOL_VERSION`, `SCHEMA_REVISION`, and JSON types. `ahp-codegen.lock.json` records immutable generator provenance. Change canonical schemas and generator source in the protocol repository rather than editing generated files.
169
176
 
170
177
  ## Development
171
178
 
@@ -187,8 +194,8 @@ attributes. `ModelVisibleItem` is a canonical message with a typed role and orde
187
194
 
188
195
  Facade objects are mappings with read-only attributes.
189
196
  Optional attributes return `None` when absent; mapping membership still records
190
- presence. Attributes conflicting with mapping methods use a trailing underscore
191
- (such as `items_`), so `dict.items()` keeps working. Wire parsers return lossless mappings, not hydrated facade instances.
197
+ presence. Omitted and explicit-empty response effects are both neutral; decoded effects provide an empty list without modifying the source wire object. Attributes conflicting with mapping methods use a trailing underscore
198
+ (such as `items_`), so `dict.items()` keeps working. Raw wire parsers return lossless mappings. `response.response_for_request(event_type, raw)` returns the generated contextual response selected by the originating event type, not by payload shape.
192
199
 
193
200
  Use generated nested models for typed constructor arguments. Wire constructors, dictionary decoding, and runtime parsing
194
201
  check location/evidence alternatives and other structural schema constraints. Application
@@ -222,13 +229,7 @@ the same structural descriptor engine as generated `parse_*` functions. The
222
229
  original composition is retained, including required members, explicit nulls,
223
230
  literals, closed enums, forbidden combinations, and union ambiguity. This is the
224
231
  SDK's structural contract, not complete contextual protocol validation.
225
- `from_dict` does not supply omitted wire literals or defaults. It validates once
226
- and privately hydrates nested typed models without recursively calling public
227
- constructors. For example, a decoded HTTP connection supports
228
- `connection.gaps[0].reason`, just like a constructed connection. Union selection
229
- reuses memoized descriptor checks from that decode; subtrees are not repeatedly
230
- validated during hydration. Extension/application values remain ordinary JSON
231
- values. Descriptors and hydration metadata are cached.
232
+ `from_dict` checks complete wire values and preserves optional-field presence. Decoded known nested values expose the same attributes as constructed values, such as `connection.gaps[0].reason`. Extension and application bags remain intact and do not acquire effect or attachment authority.
232
233
 
233
234
  Invalid wire models raise `ValueError`; validation failures carry `result`,
234
235
  `diagnostics`, and `raw` attributes. Use `parse_*` when you need the full result on
@@ -246,12 +247,7 @@ objects are validated at the dispatch/parse boundary. Direct dictionary
246
247
  mutation, standard-library `json.loads`, TypedDict annotations, and type assertions
247
248
  are not SDK validation entrypoints. Re-parse after manually mutating a mapping.
248
249
 
249
- Wire constructors also privately hydrate nested mappings: for example,
250
- `capability.Capabilities(effects=["modify"], modify={}).modify.input` returns
251
- `None`, not an attribute error. Already typed child models retain their identity;
252
- plain nested mappings become typed models after the constructor's single cached
253
- validation pass. Family queries include all generated per-occurrence capability
254
- models, such as `capability.ToolBeforeCapabilities`.
250
+ Nested model attributes expose declared protocol fields. For example, `capability.Capabilities(effects=["modify"], modify={}).modify.input` is `None` when that grant is absent. Family queries are available on generic and per-occurrence capability models.
255
251
 
256
252
  Constructor aliases cannot silently overwrite wire keys. Supplying both
257
253
  `tool_name=` and `toolName=` raises `TypeError`, even if the values agree. For
@@ -267,6 +263,8 @@ Use `Attachment.from_bytes(data)` for immutable Python `bytes`, or
267
263
  `Attachment.lazy(async_loader, aclose=async_cleanup)` to defer reading. Put
268
264
  `attachment` directly in an attachment part's `body`. Named `Hooks` methods
269
265
  accept both dictionary inputs and typed event inputs with the same owner.
266
+ For a custom `OwnedContentSource`, use the host-only `content.OwnedAttachment(source)` wrapper in an attachment part's body. This wrapper does not read the source and is not a wire value. The SDK transfers and closes its source under the same invocation ownership rules.
267
+
270
268
  Metadata belongs on the part. See the runnable
271
269
  [file attachment example](examples/file_attachment.py).
272
270
 
@@ -297,9 +295,9 @@ matched subscriptions, including observers. One attachment owner is read once
297
295
  for multiple subscribers. Metadata, omit, and unmatched selections do not read
298
296
  or upload its bytes. Result reads and metadata-only delivery require no uploader.
299
297
 
300
- Only confirmed receipts are cached, for the exact attachment owner and backend
298
+ Only confirmed receipts are reused, for the exact attachment owner and backend
301
299
  ID/subscription index. An uncalled interceptor receives a settled observation
302
- with its cached reference after denial or stop. Upload failures are handled by
300
+ with its confirmed reference after denial or stop. Upload failures are handled by
303
301
  the affected subscription's failure policy when its delivery is reached; they
304
302
  do not prevent independent destinations from preparing their uploads. An upload
305
303
  can commit even if an earlier interceptor later removes the attachment or halts
@@ -315,3 +313,11 @@ not retained.
315
313
 
316
314
  Attachments are the sole byte owners. Binary/file editing is not supported.
317
315
  Results are invocation-local, not session archives.
316
+
317
+ ### Advanced validation and dispatch
318
+
319
+ `runtime.Validator` validates bundled canonical schemas and structural codecs. It does not establish request correlation, effect grants, host approval, or permission to execute. Use `Hooks` for complete interception admission.
320
+
321
+ `server.hooks.Engine` is the framework-neutral backend dispatcher. Supply an explicit validator or static harness manifest when needed. Engine callbacks return protocol effects; the receiving harness validates authority and settles them atomically.
322
+
323
+ Host session counters (`turns`, `modelRequests`, `toolCalls`, `inputTokens`, `outputTokens`, and extra counter names) are exact nonnegative integers. The SDK preserves supplied values and does not infer counters from events.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "agenthooksprotocol"
7
- version = "0.3.0"
7
+ version = "0.4.0"
8
8
  description = "Official non-normative Python SDK for the Agent Hooks Protocol"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -33,12 +33,19 @@ from .content import (
33
33
  OwnedContentSource as OwnedContentSource,
34
34
  ContentSources as ContentSources,
35
35
  )
36
- from .codec import Codec as Codec, IdentityCodec as IdentityCodec
36
+ from .codec import (
37
+ ValueCodecs as ValueCodecs,
38
+ Codec as Codec,
39
+ IdentityCodec as IdentityCodec,
40
+ Candidate as Candidate,
41
+ )
37
42
  from .runtime import (
38
43
  ProtocolError as ProtocolError,
39
44
  OperationCancelledError as OperationCancelledError,
40
45
  )
41
46
 
47
+ wire = generated
48
+
42
49
  __all__ = list(_model_names) + [
43
50
  "Attachment",
44
51
  "AttachmentContents",
@@ -65,6 +72,9 @@ __all__ = list(_model_names) + [
65
72
  "PendingInvocation",
66
73
  "ProtocolError",
67
74
  "Codec",
75
+ "ValueCodecs",
76
+ "Candidate",
68
77
  "IdentityCodec",
69
78
  "generated",
79
+ "wire",
70
80
  ]