agenthooksprotocol 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. {agenthooksprotocol-0.1.0/src/agenthooksprotocol.egg-info → agenthooksprotocol-0.2.0}/PKG-INFO +141 -10
  2. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/README.md +140 -9
  3. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/pyproject.toml +1 -1
  4. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/__init__.py +6 -0
  5. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_content.py +215 -86
  6. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_grants.py +60 -23
  7. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_hooks.py +177 -14
  8. agenthooksprotocol-0.2.0/src/agenthooksprotocol/_models/__init__.py +33474 -0
  9. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_models/capability.py +12 -120
  10. agenthooksprotocol-0.2.0/src/agenthooksprotocol/_models/content.py +64 -0
  11. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_models/effect.py +5 -9
  12. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_models/event.py +52 -30
  13. agenthooksprotocol-0.2.0/src/agenthooksprotocol/_models/registration.py +7 -0
  14. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_models/subscription.py +0 -10
  15. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_models/tool.py +0 -32
  16. agenthooksprotocol-0.2.0/src/agenthooksprotocol/_models/transport.py +24 -0
  17. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/ahp-codegen.lock.json +14 -6
  18. agenthooksprotocol-0.2.0/src/agenthooksprotocol/attachment.py +136 -0
  19. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/auth.py +6 -3
  20. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/capability.py +1 -39
  21. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/content.py +23 -1
  22. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/effect.py +9 -19
  23. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/event.py +27 -15
  24. agenthooksprotocol-0.2.0/src/agenthooksprotocol/generated.py +8872 -0
  25. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/interop.py +7 -11
  26. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/lifecycle.py +2 -11
  27. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/lifecycle_server.py +21 -0
  28. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/schemas.json +171 -123
  29. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/server/attachments.py +12 -5
  30. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/state.py +3 -3
  31. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/subscription.py +0 -5
  32. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/tool.py +0 -16
  33. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/transport.py +7 -0
  34. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/transports/http.py +10 -5
  35. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0/src/agenthooksprotocol.egg-info}/PKG-INFO +141 -10
  36. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol.egg-info/SOURCES.txt +6 -1
  37. agenthooksprotocol-0.2.0/tests/test_attachments.py +731 -0
  38. agenthooksprotocol-0.2.0/tests/test_content_lifetime.py +315 -0
  39. agenthooksprotocol-0.2.0/tests/test_content_receipts.py +155 -0
  40. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_integration_edges.py +1 -1
  41. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_interop.py +15 -3
  42. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_lifecycle.py +2 -2
  43. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_lifecycle_auth.py +68 -1
  44. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_owned_content.py +8 -5
  45. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_public_content.py +26 -3
  46. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_public_server.py +1 -1
  47. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_schema_corrections.py +2 -2
  48. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_sdk_unification.py +7 -5
  49. agenthooksprotocol-0.2.0/tests/test_structural_facade.py +151 -0
  50. agenthooksprotocol-0.1.0/src/agenthooksprotocol/_models/__init__.py +0 -18414
  51. agenthooksprotocol-0.1.0/src/agenthooksprotocol/_models/content.py +0 -58
  52. agenthooksprotocol-0.1.0/src/agenthooksprotocol/_models/registration.py +0 -20
  53. agenthooksprotocol-0.1.0/src/agenthooksprotocol/_models/transport.py +0 -8
  54. agenthooksprotocol-0.1.0/src/agenthooksprotocol/generated.py +0 -11595
  55. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/LICENSE +0 -0
  56. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/setup.cfg +0 -0
  57. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_boundaries.py +0 -0
  58. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_diagnostics.py +0 -0
  59. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/candidate.py +0 -0
  60. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/catalogue.py +0 -0
  61. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/codec.py +0 -0
  62. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/compaction.py +0 -0
  63. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/diagnostics.py +0 -0
  64. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/elicitation.py +0 -0
  65. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/integrations/__init__.py +0 -0
  66. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/integrations/pydantic.py +0 -0
  67. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/lifecycle_client.py +0 -0
  68. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/lineage.py +0 -0
  69. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/observation_chain.py +0 -0
  70. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/path.py +0 -0
  71. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/permission.py +0 -0
  72. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/py.typed +0 -0
  73. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/registration.py +0 -0
  74. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/runtime.py +0 -0
  75. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/server/__init__.py +0 -0
  76. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/server/_framing.py +0 -0
  77. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/server/_json.py +0 -0
  78. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/server/asgi.py +0 -0
  79. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/server/hooks.py +0 -0
  80. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/server/stdio.py +0 -0
  81. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/transports/__init__.py +0 -0
  82. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/transports/stdio.py +0 -0
  83. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol.egg-info/dependency_links.txt +0 -0
  84. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol.egg-info/requires.txt +0 -0
  85. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol.egg-info/top_level.txt +0 -0
  86. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_catalogue.py +0 -0
  87. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_codec.py +0 -0
  88. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_compaction.py +0 -0
  89. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_compaction_wire.py +0 -0
  90. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_content.py +0 -0
  91. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_fixture_public.py +0 -0
  92. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_integration.py +0 -0
  93. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_observation.py +0 -0
  94. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_observation_chain_transport.py +0 -0
  95. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_owned_content_hooks.py +0 -0
  96. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_package.py +0 -0
  97. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_public_examples.py +0 -0
  98. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_public_hooks.py +0 -0
  99. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_registration_auth.py +0 -0
  100. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_safety.py +0 -0
  101. {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_session_manifest.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agenthooksprotocol
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Official non-normative Python SDK for the Agent Hooks Protocol
5
5
  Author: Agent Hooks Protocol maintainers
6
6
  License:
@@ -228,10 +228,10 @@ A typed, asynchronous SDK for the [Agent Hooks Protocol](https://github.com/agen
228
228
 
229
229
  ## Install
230
230
 
231
- The distribution name is `agenthooksprotocol`. Until the first PyPI release, install a pinned Git commit:
231
+ Install the published `agenthooksprotocol` package from [PyPI](https://pypi.org/project/agenthooksprotocol/):
232
232
 
233
233
  ```sh
234
- python -m pip install 'agenthooksprotocol[http] @ git+https://github.com/agenthooksprotocol/python-sdk.git@<commit-sha>'
234
+ python -m pip install 'agenthooksprotocol[http]'
235
235
  ```
236
236
 
237
237
  HTTPX is an opt-in dependency (`[http]`). Pydantic is an independent opt-in integration (`[pydantic]`); neither a web framework nor Pydantic is needed for the core SDK or stdio.
@@ -260,7 +260,7 @@ async def review(existing_arguments):
260
260
  return result # Host validation and authorization still precede execution.
261
261
  ```
262
262
 
263
- Generated `Input` objects are distinct from canonical wire events. They flatten only protocol-owned wrappers, never application arguments, and `to_wire()` performs the shared mapping. `path` and `origin` remain separate required facts. Optional host IDs, timestamps, parent IDs, and other facts are retained; `event_id=` overrides an input ID. The SDK supplies type/source and the configured session manifest. Low-level `dispatch` still accepts canonical dictionaries.
263
+ Generated `Input` objects are distinct from canonical wire events. They flatten only protocol-owned wrappers, never application arguments, and `to_wire()` performs the shared mapping. `path` and `origin` remain separate required facts. Optional host IDs, timestamps, parent IDs, and other facts are retained; `event_id=` overrides an input ID. The SDK supplies type/source and the configured session manifest. Low-level `dispatch` accepts canonical dictionaries.
264
264
 
265
265
  Capability builders are immutable: reusing a base declaration does not widen it. `intercept()` deliberately advertises **both intercept and observe**; `observe()` is observation-only. `modify_input(replace=True)` supplies both the effect grant and operation block. Empty operations and observation effect grants are rejected; boundary compatibility is validated before delivery. Form and URL elicitation grants are explicit (`elicitation_form()` / `elicitation_url()`). Raw manifests retain their exact modes; omission never implies permission. Per-call `capabilities=` only narrows configured grants.
266
266
 
@@ -269,7 +269,7 @@ Runnable, statically checked consumer programs live in [`examples/`](examples):
269
269
  - `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
270
  - `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
271
  - `stdio_tool.py` / `stdio_backend.py`: a real owned backend process using the same public Handler, denial, and child cleanup.
272
- - `upload.py`: streamed binary upload, metadata no-read behavior, independent upload credentials, verified descriptor, and caller-owned immutable storage.
272
+ - `upload.py`: streamed binary upload, metadata no-read behavior, independent upload credentials, verified upload receipt, and caller-owned immutable storage.
273
273
 
274
274
  - `lifecycle.py`: caller-owned task groups, operation-owned observations, interrupted requests, idempotent close, and proof that an owned subprocess was reaped.
275
275
  - `http_auth.py`: real loopback HTTP, canonical backend callbacks, registration `tokenEnv` resolution, and separate content-upload authorization. Its standard-library HTTP routing is example host code, not a production server.
@@ -313,7 +313,7 @@ The default `auth.EnvironmentAuthProvider` resolves bearer `tokenEnv` or optiona
313
313
 
314
314
  ## Static host manifest
315
315
 
316
- Configure `Hooks(config, *, source, manifest=full_manifest, transport=None, auth_provider=None)` with a complete canonical static manifest, **or** use the existing `capabilities=event_declarations` option. These options are mutually exclusive. The full manifest supplies the same event/mode/effect authority used for admission; it is not backend discovery. `hooks.manifest` returns a detached snapshot.
316
+ Configure `Hooks(config, *, source, manifest=full_manifest, transport=None, auth_provider=None)` with a complete canonical static manifest, **or** use `capabilities=event_declarations`. These options are mutually exclusive. The full manifest supplies the same event/mode/effect authority used for admission; it is not backend discovery. `hooks.manifest` returns a detached snapshot.
317
317
 
318
318
  The event-map shorthand advertises only explicitly declared events and modes. Its derived manifest records a gap for every omitted canonical event and for host-level facilities that the map cannot establish (transports, authentication, tool paths, content categories, limits, and managed policy). Empty lists do not mean universal support. Use a full manifest to declare those facilities accurately; explicit full manifests are preserved rather than augmented with assumed support.
319
319
 
@@ -329,7 +329,7 @@ The attachment receiver verifies byte length and digest through EOF before commi
329
329
 
330
330
  ## Body-bound boundaries
331
331
 
332
- Use `ContentContext` for MCP elicitation and compaction bodies. Its asynchronous `resolve(reference)` and `upload(bytes)` functions are trusted storage/transport adapters, not application-schema callbacks. `upload` must return a verified receiver-allocated immutable descriptor after storage commit. The SDK verifies referenced bytes and replacement descriptors before publishing an accepted state.
332
+ Use `ContentContext` for MCP elicitation and compaction bodies. Its asynchronous `resolve(reference)` and `upload(bytes)` functions are trusted storage/transport adapters, not application-schema callbacks. `upload` must return a verified `ContentUploadReceipt` (`ref`, `size`, `sha256`) after storage commit. The SDK checks receipt length and digest against the actual uploaded bytes. `resolve` receives only `{ "ref": ... }` and must resolve immutable bytes within its authorized storage scope; event metadata is not an integrity authority. The SDK detects changes to previously resolved bytes.
333
333
 
334
334
  Bindings are explicit paths within the canonical event:
335
335
 
@@ -346,11 +346,11 @@ Pass `content=context` to the named boundary. An elicitation result needs the or
346
346
 
347
347
  `OwnedContentSource(stream, max_bytes=..., timeout=...)` wraps an async native `read(size)`/`receive(size)` stream with `aclose()`. Construction does not read. Size/hash expectations are optional and verified, not trusted. Generated inputs expose named `bind_<slot>_source(source)` methods; repeated content slots additionally require `index=`. For example, `input.bind_items_source(source, index=0)` binds an existing metadata item without putting the stream into wire JSON.
348
348
 
349
- Pass the bound input to a named hook method with `uploads={backend_id: authorized_upload_callback}`. Each callback receives immutable bytes and returns a receiver-allocated canonical content reference; it can use `auth.AuthenticatedHTTPTransport.upload(..., authentication=upload_binding)`. Metadata, omit, and unmatched receivers consume no bytes and require no uploader. Selected body delivery snapshots once within limits, hashes actual raw bytes, uploads independently for each authorized destination, and verifies references before event delivery. Missing receiver authorization fails before reading. Source bindings derive from shared generated metadata; advanced `ContentSources` and existing canonical references/resolvers remain available.
349
+ Pass the bound input to a named hook method with `uploads={backend_id: authorized_upload_callback}`. Each callback receives immutable bytes and returns a receiver-allocated canonical upload receipt; it can use `auth.AuthenticatedHTTPTransport.upload(..., authentication=upload_binding)`. Metadata, omit, and unmatched receivers consume no bytes and require no uploader. Selected body delivery snapshots once within limits, hashes actual raw bytes, uploads independently for each authorized destination, and verifies receipts before event delivery. Missing receiver authorization fails before reading. Use `ContentSources` to supply explicit source bindings, or `ContentContext` to resolve canonical references.
350
350
 
351
- Ownership transfers to the source wrapper, then to the hook operation when passed. Streams close on success, failure, cancellation, and unused selection; snapshots release when the call finishes. Sources are single-operation values, not concurrently reusable. Call `aclose()` or use an async context manager for a source that never reaches a hook call. Cleanup is shielded and bounded separately from the operation budget.
351
+ Ownership transfers to the source wrapper, then to the hook operation when passed. Streams close on success, failure, cancellation, and unused selection; snapshots release when the call finishes. Closed wrappers also release their reader, so retaining a source or result does not retain the reader’s input buffer. Sources are single-operation values, not concurrently reusable. Call `aclose()` or use an async context manager for a source that never reaches a hook call. Cleanup is shielded and bounded separately from the operation budget.
352
352
 
353
- For custom transport integrations, `begin(canonical_request)` returns a `PendingInvocation` with separate acquisition, cancellation, and acceptance. `exchange(canonical_request)` uses the same settlement engine. Content-aware pending invocations use `await accept_content()`; this keeps upload completion and cancellation inside the publication boundary. The ordinary named-method path does this automatically.
353
+ For custom transport integrations, `begin(canonical_request)` returns a `PendingInvocation` with separate acquisition, cancellation, and acceptance. `exchange(canonical_request)` uses the same settlement engine. Content-aware pending invocations use `await accept_content()`; this keeps upload completion and cancellation inside the publication boundary. The ordinary named-method path does this automatically. Pending invocations retain selected bytes only until acceptance, cancellation, acquisition/finalization failure, or harness close. Failed finalization may be retried and resolves fresh bytes. Harness tracking is weak: dropping a pending invocation does not keep its prepared payload alive. Terminal handles also detach their resolver/uploader context, so retained handles do not retain the caller’s backing store. Returned references continue to resolve in caller-owned storage; cleanup never clears that persistent store.
354
354
 
355
355
  ## Generated models and provenance
356
356
 
@@ -368,3 +368,134 @@ uv run python -m build
368
368
  ```
369
369
 
370
370
  Integration tests also use a matching sibling `../agent-hooks-protocol` checkout for shared fixtures and public synthetic certificates. The installed runtime uses its bundled schemas and does not need that checkout. No SDK API executes host tools on your behalf.
371
+
372
+ ### Typed composed payloads
373
+
374
+ Generated facade constructors retain nested model types through schema
375
+ compositions. MCP connection `gaps` parameters accept lists of typed gap models,
376
+ and HTTP, SSE, stdio, and custom connection objects expose typed location and gap
377
+ attributes. `ModelVisibleItem` has composed content constructors with typed roles.
378
+
379
+ Facade objects are mappings with read-only attributes.
380
+ Optional attributes return `None` when absent; mapping membership still records
381
+ presence. Attributes conflicting with mapping methods use a trailing underscore
382
+ (such as `items_`), so `dict.items()` keeps working. Wire parsers return lossless mappings, not hydrated facade instances.
383
+
384
+ Use generated nested models for typed constructor arguments. Wire constructors, dictionary decoding, and runtime parsing
385
+ check location/evidence alternatives and other structural schema constraints. Application
386
+ JSON, native payloads, and extension values remain intentionally dynamic.
387
+
388
+ Upload receipts and event references are distinct. To attach a successful upload,
389
+ use `content.reference(receipt)` (or `{"ref": receipt["ref"]}`). Never attach the
390
+ receipt itself: body-selected content carries only the ref, with no outer `size`
391
+ or `sha256`. Metadata-only content and body gaps may still disclose size/digest.
392
+
393
+ ### Structural model decoding and effect-family support
394
+
395
+ ```python
396
+ from agenthooksprotocol import ContentReference, capability, effect
397
+
398
+ caps = capability.Capabilities.from_dict({"effects": ["deny", "vendor.custom"]})
399
+ assert caps.supports(capability.EffectName.DENY)
400
+ assert caps.supports("vendor.custom")
401
+ assert effect.EffectName is capability.EffectName
402
+ reference = ContentReference.from_dict({"ref": "opaque", "vendor": {"version": 2}})
403
+ ```
404
+
405
+ `supports(effect)` is available on generic and incoming capability models. It
406
+ checks the advertised `effects` list only. Nested grants do not imply family
407
+ support. The result is **not authorization**: it does not check target, operation,
408
+ mode, contextual restrictions, or permission. `EffectName` contains schema-derived
409
+ known identifiers; custom family identifiers remain ordinary strings.
410
+
411
+ Wire-model keyword constructors and `Model.from_dict(mapping)` validate with
412
+ the same structural descriptor engine as generated `parse_*` functions. The
413
+ original composition is retained, including required members, explicit nulls,
414
+ literals, closed enums, forbidden combinations, and union ambiguity. This is the
415
+ SDK's structural contract, not complete contextual protocol validation.
416
+ `from_dict` does not supply omitted wire literals or defaults. It validates once
417
+ and privately hydrates nested typed models without recursively calling public
418
+ constructors. For example, a decoded HTTP connection supports
419
+ `connection.gaps[0].reason`, just like a constructed connection. Union selection
420
+ reuses memoized descriptor checks from that decode; subtrees are not repeatedly
421
+ validated during hydration. Extension/application values remain ordinary JSON
422
+ values. Descriptors and hydration metadata are cached.
423
+
424
+ Invalid wire models raise `ValueError`; validation failures carry `result`,
425
+ `diagnostics`, and `raw` attributes. Use `parse_*` when you need the full result on
426
+ both success and failure, including unknown-variant warnings and lossless raw
427
+ values. Open extension values remain intact. Finite `Decimal` values are retained;
428
+ non-finite numbers and non-JSON values are rejected. Parsing raw JSON through
429
+ `parse_*` retains decimal precision; decoding it first with standard-library
430
+ `json.loads` cannot recover precision already lost to a float.
431
+
432
+ Provide valid enum/literal values and all required wire members to `from_dict`. A null candidate, a candidate with
433
+ `{"value": null}`, and a candidate with `{"value": 0}` remain distinct; omitting a
434
+ required candidate is an error. Generated input projections and capability grant
435
+ builders are construction helpers, not wire decode entrypoints; their final wire
436
+ objects are validated at the dispatch/parse boundary. Direct dictionary
437
+ mutation, standard-library `json.loads`, TypedDict annotations, and type assertions
438
+ are not SDK validation entrypoints. Re-parse after manually mutating a mapping.
439
+
440
+ Wire constructors also privately hydrate nested mappings: for example,
441
+ `capability.Capabilities(effects=["modify"], modify={}).modify.input` returns
442
+ `None`, not an attribute error. Already typed child models retain their identity;
443
+ plain nested mappings become typed models after the constructor's single cached
444
+ validation pass. Family queries include all generated per-occurrence capability
445
+ models, such as `capability.ToolBeforeCapabilities`.
446
+
447
+ Constructor aliases cannot silently overwrite wire keys. Supplying both
448
+ `tool_name=` and `toolName=` raises `TypeError`, even if the values agree. For
449
+ optional/defaulted fields, a lone wire spelling (for example `addressForm=` or
450
+ `protocolVersion=`) is used and validated rather than replaced by a default.
451
+ Required constructor parameters use their documented Python spelling;
452
+ use `from_dict` to decode complete wire-keyed objects. In `from_dict`, distinct
453
+ keys such as `toolName` and `tool_name` remain distinct wire/extension keys.
454
+
455
+ ### Owned attachments
456
+
457
+ The owned attachment APIs in this section are not included in the published 0.1.1 package.
458
+
459
+ Use `Attachment.from_bytes(data)` for immutable Python `bytes`, or
460
+ `Attachment.lazy(async_loader, aclose=async_cleanup)` to defer reading. Bind it
461
+ with the typed input's `bind_*_source` method on an item without a body reference.
462
+ Content IDs, media types, and other metadata belong on the content item. See the
463
+ runnable [file attachment example](examples/file_attachment.py).
464
+
465
+ Dispatch transfers ownership once. Metadata-only and unmatched deliveries do not
466
+ invoke the loader. Selected body deliveries share one immutable snapshot, with
467
+ receiver-specific references. Successful results own the bytes **or still-unread
468
+ loader**, independent of `Hooks` shutdown. Use `async with result:` or
469
+ `await result.aclose()` and read with
470
+ `await result.attachments.read("context.compact.before.items[0]")`. Cleanup runs
471
+ for unopened sources too. Do not reuse an attachment across invocations; create a
472
+ new attachment (which can share the same immutable `bytes`) instead.
473
+
474
+ The default accepted size is 4 MiB and lazy loading timeout is 30 seconds;
475
+ constructors accept `max_bytes` and lazy construction accepts `timeout`. Loaders
476
+ must bound their own allocations. `Attachment(async_stream, ...)` also supports
477
+ the bounded `read(size)`/`receive(max_bytes)` source contract. Stream cleanup
478
+ is shielded and bounded to one second. Cancellation and failure never retry a
479
+ loader. Python mutable buffers are rejected, and reads
480
+ return immutable bytes.
481
+
482
+ Remote body delivery requires an authorized receiver uploader through
483
+ `uploads={backend_id: async_upload}`. The uploader supplies a reference that the
484
+ receiver can read.
485
+ Metadata-only delivery and result reads require no uploader.
486
+
487
+ Attachments own their immutable bytes directly. A lazy loader's `bytes` object
488
+ becomes the attachment snapshot; upload and result reads borrow that same object.
489
+ Receiver applications manage upload storage.
490
+
491
+ Compaction text and elicitation JSON effects read selected attachment
492
+ owners. An accepted replacement creates a new effective attachment; subsequent
493
+ deliveries and the result use that owner. Original attachment size bounds also
494
+ apply to replacements. Generated candidates are available through
495
+ `await result.attachments.read("candidate")`. Binary/file editing is not supported.
496
+ Elicitation results require their original request correlation context;
497
+ `ContentContext.attachments` can provide the original request owner without a
498
+ resolver. A resolver is an optional
499
+ adapter for externally supplied references.
500
+
501
+ Results are invocation-local, not session archives.
@@ -4,10 +4,10 @@ A typed, asynchronous SDK for the [Agent Hooks Protocol](https://github.com/agen
4
4
 
5
5
  ## Install
6
6
 
7
- The distribution name is `agenthooksprotocol`. Until the first PyPI release, install a pinned Git commit:
7
+ Install the published `agenthooksprotocol` package from [PyPI](https://pypi.org/project/agenthooksprotocol/):
8
8
 
9
9
  ```sh
10
- python -m pip install 'agenthooksprotocol[http] @ git+https://github.com/agenthooksprotocol/python-sdk.git@<commit-sha>'
10
+ python -m pip install 'agenthooksprotocol[http]'
11
11
  ```
12
12
 
13
13
  HTTPX is an opt-in dependency (`[http]`). Pydantic is an independent opt-in integration (`[pydantic]`); neither a web framework nor Pydantic is needed for the core SDK or stdio.
@@ -36,7 +36,7 @@ async def review(existing_arguments):
36
36
  return result # Host validation and authorization still precede execution.
37
37
  ```
38
38
 
39
- Generated `Input` objects are distinct from canonical wire events. They flatten only protocol-owned wrappers, never application arguments, and `to_wire()` performs the shared mapping. `path` and `origin` remain separate required facts. Optional host IDs, timestamps, parent IDs, and other facts are retained; `event_id=` overrides an input ID. The SDK supplies type/source and the configured session manifest. Low-level `dispatch` still accepts canonical dictionaries.
39
+ Generated `Input` objects are distinct from canonical wire events. They flatten only protocol-owned wrappers, never application arguments, and `to_wire()` performs the shared mapping. `path` and `origin` remain separate required facts. Optional host IDs, timestamps, parent IDs, and other facts are retained; `event_id=` overrides an input ID. The SDK supplies type/source and the configured session manifest. Low-level `dispatch` accepts canonical dictionaries.
40
40
 
41
41
  Capability builders are immutable: reusing a base declaration does not widen it. `intercept()` deliberately advertises **both intercept and observe**; `observe()` is observation-only. `modify_input(replace=True)` supplies both the effect grant and operation block. Empty operations and observation effect grants are rejected; boundary compatibility is validated before delivery. Form and URL elicitation grants are explicit (`elicitation_form()` / `elicitation_url()`). Raw manifests retain their exact modes; omission never implies permission. Per-call `capabilities=` only narrows configured grants.
42
42
 
@@ -45,7 +45,7 @@ Runnable, statically checked consumer programs live in [`examples/`](examples):
45
45
  - `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
46
  - `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
47
  - `stdio_tool.py` / `stdio_backend.py`: a real owned backend process using the same public Handler, denial, and child cleanup.
48
- - `upload.py`: streamed binary upload, metadata no-read behavior, independent upload credentials, verified descriptor, and caller-owned immutable storage.
48
+ - `upload.py`: streamed binary upload, metadata no-read behavior, independent upload credentials, verified upload receipt, and caller-owned immutable storage.
49
49
 
50
50
  - `lifecycle.py`: caller-owned task groups, operation-owned observations, interrupted requests, idempotent close, and proof that an owned subprocess was reaped.
51
51
  - `http_auth.py`: real loopback HTTP, canonical backend callbacks, registration `tokenEnv` resolution, and separate content-upload authorization. Its standard-library HTTP routing is example host code, not a production server.
@@ -89,7 +89,7 @@ The default `auth.EnvironmentAuthProvider` resolves bearer `tokenEnv` or optiona
89
89
 
90
90
  ## Static host manifest
91
91
 
92
- Configure `Hooks(config, *, source, manifest=full_manifest, transport=None, auth_provider=None)` with a complete canonical static manifest, **or** use the existing `capabilities=event_declarations` option. These options are mutually exclusive. The full manifest supplies the same event/mode/effect authority used for admission; it is not backend discovery. `hooks.manifest` returns a detached snapshot.
92
+ Configure `Hooks(config, *, source, manifest=full_manifest, transport=None, auth_provider=None)` with a complete canonical static manifest, **or** use `capabilities=event_declarations`. These options are mutually exclusive. The full manifest supplies the same event/mode/effect authority used for admission; it is not backend discovery. `hooks.manifest` returns a detached snapshot.
93
93
 
94
94
  The event-map shorthand advertises only explicitly declared events and modes. Its derived manifest records a gap for every omitted canonical event and for host-level facilities that the map cannot establish (transports, authentication, tool paths, content categories, limits, and managed policy). Empty lists do not mean universal support. Use a full manifest to declare those facilities accurately; explicit full manifests are preserved rather than augmented with assumed support.
95
95
 
@@ -105,7 +105,7 @@ The attachment receiver verifies byte length and digest through EOF before commi
105
105
 
106
106
  ## Body-bound boundaries
107
107
 
108
- Use `ContentContext` for MCP elicitation and compaction bodies. Its asynchronous `resolve(reference)` and `upload(bytes)` functions are trusted storage/transport adapters, not application-schema callbacks. `upload` must return a verified receiver-allocated immutable descriptor after storage commit. The SDK verifies referenced bytes and replacement descriptors before publishing an accepted state.
108
+ Use `ContentContext` for MCP elicitation and compaction bodies. Its asynchronous `resolve(reference)` and `upload(bytes)` functions are trusted storage/transport adapters, not application-schema callbacks. `upload` must return a verified `ContentUploadReceipt` (`ref`, `size`, `sha256`) after storage commit. The SDK checks receipt length and digest against the actual uploaded bytes. `resolve` receives only `{ "ref": ... }` and must resolve immutable bytes within its authorized storage scope; event metadata is not an integrity authority. The SDK detects changes to previously resolved bytes.
109
109
 
110
110
  Bindings are explicit paths within the canonical event:
111
111
 
@@ -122,11 +122,11 @@ Pass `content=context` to the named boundary. An elicitation result needs the or
122
122
 
123
123
  `OwnedContentSource(stream, max_bytes=..., timeout=...)` wraps an async native `read(size)`/`receive(size)` stream with `aclose()`. Construction does not read. Size/hash expectations are optional and verified, not trusted. Generated inputs expose named `bind_<slot>_source(source)` methods; repeated content slots additionally require `index=`. For example, `input.bind_items_source(source, index=0)` binds an existing metadata item without putting the stream into wire JSON.
124
124
 
125
- Pass the bound input to a named hook method with `uploads={backend_id: authorized_upload_callback}`. Each callback receives immutable bytes and returns a receiver-allocated canonical content reference; it can use `auth.AuthenticatedHTTPTransport.upload(..., authentication=upload_binding)`. Metadata, omit, and unmatched receivers consume no bytes and require no uploader. Selected body delivery snapshots once within limits, hashes actual raw bytes, uploads independently for each authorized destination, and verifies references before event delivery. Missing receiver authorization fails before reading. Source bindings derive from shared generated metadata; advanced `ContentSources` and existing canonical references/resolvers remain available.
125
+ Pass the bound input to a named hook method with `uploads={backend_id: authorized_upload_callback}`. Each callback receives immutable bytes and returns a receiver-allocated canonical upload receipt; it can use `auth.AuthenticatedHTTPTransport.upload(..., authentication=upload_binding)`. Metadata, omit, and unmatched receivers consume no bytes and require no uploader. Selected body delivery snapshots once within limits, hashes actual raw bytes, uploads independently for each authorized destination, and verifies receipts before event delivery. Missing receiver authorization fails before reading. Use `ContentSources` to supply explicit source bindings, or `ContentContext` to resolve canonical references.
126
126
 
127
- Ownership transfers to the source wrapper, then to the hook operation when passed. Streams close on success, failure, cancellation, and unused selection; snapshots release when the call finishes. Sources are single-operation values, not concurrently reusable. Call `aclose()` or use an async context manager for a source that never reaches a hook call. Cleanup is shielded and bounded separately from the operation budget.
127
+ Ownership transfers to the source wrapper, then to the hook operation when passed. Streams close on success, failure, cancellation, and unused selection; snapshots release when the call finishes. Closed wrappers also release their reader, so retaining a source or result does not retain the reader’s input buffer. Sources are single-operation values, not concurrently reusable. Call `aclose()` or use an async context manager for a source that never reaches a hook call. Cleanup is shielded and bounded separately from the operation budget.
128
128
 
129
- For custom transport integrations, `begin(canonical_request)` returns a `PendingInvocation` with separate acquisition, cancellation, and acceptance. `exchange(canonical_request)` uses the same settlement engine. Content-aware pending invocations use `await accept_content()`; this keeps upload completion and cancellation inside the publication boundary. The ordinary named-method path does this automatically.
129
+ For custom transport integrations, `begin(canonical_request)` returns a `PendingInvocation` with separate acquisition, cancellation, and acceptance. `exchange(canonical_request)` uses the same settlement engine. Content-aware pending invocations use `await accept_content()`; this keeps upload completion and cancellation inside the publication boundary. The ordinary named-method path does this automatically. Pending invocations retain selected bytes only until acceptance, cancellation, acquisition/finalization failure, or harness close. Failed finalization may be retried and resolves fresh bytes. Harness tracking is weak: dropping a pending invocation does not keep its prepared payload alive. Terminal handles also detach their resolver/uploader context, so retained handles do not retain the caller’s backing store. Returned references continue to resolve in caller-owned storage; cleanup never clears that persistent store.
130
130
 
131
131
  ## Generated models and provenance
132
132
 
@@ -144,3 +144,134 @@ uv run python -m build
144
144
  ```
145
145
 
146
146
  Integration tests also use a matching sibling `../agent-hooks-protocol` checkout for shared fixtures and public synthetic certificates. The installed runtime uses its bundled schemas and does not need that checkout. No SDK API executes host tools on your behalf.
147
+
148
+ ### Typed composed payloads
149
+
150
+ Generated facade constructors retain nested model types through schema
151
+ compositions. MCP connection `gaps` parameters accept lists of typed gap models,
152
+ and HTTP, SSE, stdio, and custom connection objects expose typed location and gap
153
+ attributes. `ModelVisibleItem` has composed content constructors with typed roles.
154
+
155
+ Facade objects are mappings with read-only attributes.
156
+ Optional attributes return `None` when absent; mapping membership still records
157
+ presence. Attributes conflicting with mapping methods use a trailing underscore
158
+ (such as `items_`), so `dict.items()` keeps working. Wire parsers return lossless mappings, not hydrated facade instances.
159
+
160
+ Use generated nested models for typed constructor arguments. Wire constructors, dictionary decoding, and runtime parsing
161
+ check location/evidence alternatives and other structural schema constraints. Application
162
+ JSON, native payloads, and extension values remain intentionally dynamic.
163
+
164
+ Upload receipts and event references are distinct. To attach a successful upload,
165
+ use `content.reference(receipt)` (or `{"ref": receipt["ref"]}`). Never attach the
166
+ receipt itself: body-selected content carries only the ref, with no outer `size`
167
+ or `sha256`. Metadata-only content and body gaps may still disclose size/digest.
168
+
169
+ ### Structural model decoding and effect-family support
170
+
171
+ ```python
172
+ from agenthooksprotocol import ContentReference, capability, effect
173
+
174
+ caps = capability.Capabilities.from_dict({"effects": ["deny", "vendor.custom"]})
175
+ assert caps.supports(capability.EffectName.DENY)
176
+ assert caps.supports("vendor.custom")
177
+ assert effect.EffectName is capability.EffectName
178
+ reference = ContentReference.from_dict({"ref": "opaque", "vendor": {"version": 2}})
179
+ ```
180
+
181
+ `supports(effect)` is available on generic and incoming capability models. It
182
+ checks the advertised `effects` list only. Nested grants do not imply family
183
+ support. The result is **not authorization**: it does not check target, operation,
184
+ mode, contextual restrictions, or permission. `EffectName` contains schema-derived
185
+ known identifiers; custom family identifiers remain ordinary strings.
186
+
187
+ Wire-model keyword constructors and `Model.from_dict(mapping)` validate with
188
+ the same structural descriptor engine as generated `parse_*` functions. The
189
+ original composition is retained, including required members, explicit nulls,
190
+ literals, closed enums, forbidden combinations, and union ambiguity. This is the
191
+ SDK's structural contract, not complete contextual protocol validation.
192
+ `from_dict` does not supply omitted wire literals or defaults. It validates once
193
+ and privately hydrates nested typed models without recursively calling public
194
+ constructors. For example, a decoded HTTP connection supports
195
+ `connection.gaps[0].reason`, just like a constructed connection. Union selection
196
+ reuses memoized descriptor checks from that decode; subtrees are not repeatedly
197
+ validated during hydration. Extension/application values remain ordinary JSON
198
+ values. Descriptors and hydration metadata are cached.
199
+
200
+ Invalid wire models raise `ValueError`; validation failures carry `result`,
201
+ `diagnostics`, and `raw` attributes. Use `parse_*` when you need the full result on
202
+ both success and failure, including unknown-variant warnings and lossless raw
203
+ values. Open extension values remain intact. Finite `Decimal` values are retained;
204
+ non-finite numbers and non-JSON values are rejected. Parsing raw JSON through
205
+ `parse_*` retains decimal precision; decoding it first with standard-library
206
+ `json.loads` cannot recover precision already lost to a float.
207
+
208
+ Provide valid enum/literal values and all required wire members to `from_dict`. A null candidate, a candidate with
209
+ `{"value": null}`, and a candidate with `{"value": 0}` remain distinct; omitting a
210
+ required candidate is an error. Generated input projections and capability grant
211
+ builders are construction helpers, not wire decode entrypoints; their final wire
212
+ objects are validated at the dispatch/parse boundary. Direct dictionary
213
+ mutation, standard-library `json.loads`, TypedDict annotations, and type assertions
214
+ are not SDK validation entrypoints. Re-parse after manually mutating a mapping.
215
+
216
+ Wire constructors also privately hydrate nested mappings: for example,
217
+ `capability.Capabilities(effects=["modify"], modify={}).modify.input` returns
218
+ `None`, not an attribute error. Already typed child models retain their identity;
219
+ plain nested mappings become typed models after the constructor's single cached
220
+ validation pass. Family queries include all generated per-occurrence capability
221
+ models, such as `capability.ToolBeforeCapabilities`.
222
+
223
+ Constructor aliases cannot silently overwrite wire keys. Supplying both
224
+ `tool_name=` and `toolName=` raises `TypeError`, even if the values agree. For
225
+ optional/defaulted fields, a lone wire spelling (for example `addressForm=` or
226
+ `protocolVersion=`) is used and validated rather than replaced by a default.
227
+ Required constructor parameters use their documented Python spelling;
228
+ use `from_dict` to decode complete wire-keyed objects. In `from_dict`, distinct
229
+ keys such as `toolName` and `tool_name` remain distinct wire/extension keys.
230
+
231
+ ### Owned attachments
232
+
233
+ The owned attachment APIs in this section are not included in the published 0.1.1 package.
234
+
235
+ Use `Attachment.from_bytes(data)` for immutable Python `bytes`, or
236
+ `Attachment.lazy(async_loader, aclose=async_cleanup)` to defer reading. Bind it
237
+ with the typed input's `bind_*_source` method on an item without a body reference.
238
+ Content IDs, media types, and other metadata belong on the content item. See the
239
+ runnable [file attachment example](examples/file_attachment.py).
240
+
241
+ Dispatch transfers ownership once. Metadata-only and unmatched deliveries do not
242
+ invoke the loader. Selected body deliveries share one immutable snapshot, with
243
+ receiver-specific references. Successful results own the bytes **or still-unread
244
+ loader**, independent of `Hooks` shutdown. Use `async with result:` or
245
+ `await result.aclose()` and read with
246
+ `await result.attachments.read("context.compact.before.items[0]")`. Cleanup runs
247
+ for unopened sources too. Do not reuse an attachment across invocations; create a
248
+ new attachment (which can share the same immutable `bytes`) instead.
249
+
250
+ The default accepted size is 4 MiB and lazy loading timeout is 30 seconds;
251
+ constructors accept `max_bytes` and lazy construction accepts `timeout`. Loaders
252
+ must bound their own allocations. `Attachment(async_stream, ...)` also supports
253
+ the bounded `read(size)`/`receive(max_bytes)` source contract. Stream cleanup
254
+ is shielded and bounded to one second. Cancellation and failure never retry a
255
+ loader. Python mutable buffers are rejected, and reads
256
+ return immutable bytes.
257
+
258
+ Remote body delivery requires an authorized receiver uploader through
259
+ `uploads={backend_id: async_upload}`. The uploader supplies a reference that the
260
+ receiver can read.
261
+ Metadata-only delivery and result reads require no uploader.
262
+
263
+ Attachments own their immutable bytes directly. A lazy loader's `bytes` object
264
+ becomes the attachment snapshot; upload and result reads borrow that same object.
265
+ Receiver applications manage upload storage.
266
+
267
+ Compaction text and elicitation JSON effects read selected attachment
268
+ owners. An accepted replacement creates a new effective attachment; subsequent
269
+ deliveries and the result use that owner. Original attachment size bounds also
270
+ apply to replacements. Generated candidates are available through
271
+ `await result.attachments.read("candidate")`. Binary/file editing is not supported.
272
+ Elicitation results require their original request correlation context;
273
+ `ContentContext.attachments` can provide the original request owner without a
274
+ resolver. A resolver is an optional
275
+ adapter for externally supplied references.
276
+
277
+ Results are invocation-local, not session archives.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "agenthooksprotocol"
7
- version = "0.1.0"
7
+ version = "0.2.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"
@@ -15,6 +15,10 @@ from . import (
15
15
  tool,
16
16
  transport,
17
17
  )
18
+ from .attachment import (
19
+ Attachment as Attachment,
20
+ AttachmentContents as AttachmentContents,
21
+ )
18
22
  from ._content import ContentContext as ContentContext
19
23
  from ._models import * # noqa: F403
20
24
  from ._models import __all__ as _model_names
@@ -36,6 +40,8 @@ from .runtime import (
36
40
  )
37
41
 
38
42
  __all__ = list(_model_names) + [
43
+ "Attachment",
44
+ "AttachmentContents",
39
45
  "ContentContext",
40
46
  "OwnedContentSource",
41
47
  "ContentSources",