agenthooksprotocol 0.1.1__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 (96) hide show
  1. {agenthooksprotocol-0.1.1/src/agenthooksprotocol.egg-info → agenthooksprotocol-0.2.0}/PKG-INFO +61 -18
  2. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/README.md +60 -17
  3. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/pyproject.toml +1 -1
  4. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/__init__.py +6 -0
  5. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_content.py +181 -64
  6. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_hooks.py +142 -7
  7. agenthooksprotocol-0.2.0/src/agenthooksprotocol/attachment.py +136 -0
  8. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/content.py +4 -0
  9. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0/src/agenthooksprotocol.egg-info}/PKG-INFO +61 -18
  10. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol.egg-info/SOURCES.txt +2 -0
  11. agenthooksprotocol-0.2.0/tests/test_attachments.py +731 -0
  12. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/tests/test_content_lifetime.py +3 -2
  13. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/tests/test_public_content.py +1 -1
  14. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/LICENSE +0 -0
  15. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/setup.cfg +0 -0
  16. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_boundaries.py +0 -0
  17. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_diagnostics.py +0 -0
  18. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_grants.py +0 -0
  19. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_models/__init__.py +0 -0
  20. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_models/capability.py +0 -0
  21. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_models/content.py +0 -0
  22. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_models/effect.py +0 -0
  23. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_models/event.py +0 -0
  24. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_models/registration.py +0 -0
  25. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_models/subscription.py +0 -0
  26. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_models/tool.py +0 -0
  27. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_models/transport.py +0 -0
  28. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/ahp-codegen.lock.json +0 -0
  29. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/auth.py +0 -0
  30. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/candidate.py +0 -0
  31. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/capability.py +0 -0
  32. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/catalogue.py +0 -0
  33. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/codec.py +0 -0
  34. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/compaction.py +0 -0
  35. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/diagnostics.py +0 -0
  36. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/effect.py +0 -0
  37. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/elicitation.py +0 -0
  38. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/event.py +0 -0
  39. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/generated.py +0 -0
  40. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/integrations/__init__.py +0 -0
  41. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/integrations/pydantic.py +0 -0
  42. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/interop.py +0 -0
  43. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/lifecycle.py +0 -0
  44. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/lifecycle_client.py +0 -0
  45. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/lifecycle_server.py +0 -0
  46. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/lineage.py +0 -0
  47. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/observation_chain.py +0 -0
  48. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/path.py +0 -0
  49. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/permission.py +0 -0
  50. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/py.typed +0 -0
  51. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/registration.py +0 -0
  52. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/runtime.py +0 -0
  53. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/schemas.json +0 -0
  54. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/server/__init__.py +0 -0
  55. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/server/_framing.py +0 -0
  56. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/server/_json.py +0 -0
  57. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/server/asgi.py +0 -0
  58. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/server/attachments.py +0 -0
  59. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/server/hooks.py +0 -0
  60. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/server/stdio.py +0 -0
  61. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/state.py +0 -0
  62. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/subscription.py +0 -0
  63. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/tool.py +0 -0
  64. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/transport.py +0 -0
  65. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/transports/__init__.py +0 -0
  66. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/transports/http.py +0 -0
  67. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/transports/stdio.py +0 -0
  68. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol.egg-info/dependency_links.txt +0 -0
  69. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol.egg-info/requires.txt +0 -0
  70. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol.egg-info/top_level.txt +0 -0
  71. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/tests/test_catalogue.py +0 -0
  72. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/tests/test_codec.py +0 -0
  73. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/tests/test_compaction.py +0 -0
  74. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/tests/test_compaction_wire.py +0 -0
  75. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/tests/test_content.py +0 -0
  76. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/tests/test_content_receipts.py +0 -0
  77. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/tests/test_fixture_public.py +0 -0
  78. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/tests/test_integration.py +0 -0
  79. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/tests/test_integration_edges.py +0 -0
  80. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/tests/test_interop.py +0 -0
  81. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/tests/test_lifecycle.py +0 -0
  82. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/tests/test_lifecycle_auth.py +0 -0
  83. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/tests/test_observation.py +0 -0
  84. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/tests/test_observation_chain_transport.py +0 -0
  85. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/tests/test_owned_content.py +0 -0
  86. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/tests/test_owned_content_hooks.py +0 -0
  87. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/tests/test_package.py +0 -0
  88. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/tests/test_public_examples.py +0 -0
  89. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/tests/test_public_hooks.py +0 -0
  90. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/tests/test_public_server.py +0 -0
  91. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/tests/test_registration_auth.py +0 -0
  92. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/tests/test_safety.py +0 -0
  93. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/tests/test_schema_corrections.py +0 -0
  94. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/tests/test_sdk_unification.py +0 -0
  95. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/tests/test_session_manifest.py +0 -0
  96. {agenthooksprotocol-0.1.1 → agenthooksprotocol-0.2.0}/tests/test_structural_facade.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agenthooksprotocol
3
- Version: 0.1.1
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
 
@@ -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
 
@@ -346,7 +346,7 @@ 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 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. 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
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
 
@@ -376,14 +376,12 @@ compositions. MCP connection `gaps` parameters accept lists of typed gap models,
376
376
  and HTTP, SSE, stdio, and custom connection objects expose typed location and gap
377
377
  attributes. `ModelVisibleItem` has composed content constructors with typed roles.
378
378
 
379
- Facade objects remain mappings and additionally provide read-only attributes.
379
+ Facade objects are mappings with read-only attributes.
380
380
  Optional attributes return `None` when absent; mapping membership still records
381
381
  presence. Attributes conflicting with mapping methods use a trailing underscore
382
- (such as `items_`), so `dict.items()` keeps working. Wire parsers continue to
383
- return lossless mappings, not hydrated facade instances.
382
+ (such as `items_`), so `dict.items()` keeps working. Wire parsers return lossless mappings, not hydrated facade instances.
384
383
 
385
- Migration: use generated nested models instead of arbitrary mappings for typed
386
- constructor arguments. Wire constructors, dictionary decoding, and runtime parsing
384
+ Use generated nested models for typed constructor arguments. Wire constructors, dictionary decoding, and runtime parsing
387
385
  check location/evidence alternatives and other structural schema constraints. Application
388
386
  JSON, native payloads, and extension values remain intentionally dynamic.
389
387
 
@@ -410,7 +408,7 @@ support. The result is **not authorization**: it does not check target, operatio
410
408
  mode, contextual restrictions, or permission. `EffectName` contains schema-derived
411
409
  known identifiers; custom family identifiers remain ordinary strings.
412
410
 
413
- Wire-model keyword constructors and `Model.from_dict(mapping)` now validate with
411
+ Wire-model keyword constructors and `Model.from_dict(mapping)` validate with
414
412
  the same structural descriptor engine as generated `parse_*` functions. The
415
413
  original composition is retained, including required members, explicit nulls,
416
414
  literals, closed enums, forbidden combinations, and union ambiguity. This is the
@@ -431,17 +429,14 @@ non-finite numbers and non-JSON values are rejected. Parsing raw JSON through
431
429
  `parse_*` retains decimal precision; decoding it first with standard-library
432
430
  `json.loads` cannot recover precision already lost to a float.
433
431
 
434
- **Migration:** constructors previously allowed structurally invalid mappings;
435
- those calls now fail immediately. Provide valid enum/literal values and all
436
- required wire members to `from_dict`. A null candidate, a candidate with
432
+ Provide valid enum/literal values and all required wire members to `from_dict`. A null candidate, a candidate with
437
433
  `{"value": null}`, and a candidate with `{"value": 0}` remain distinct; omitting a
438
434
  required candidate is an error. Generated input projections and capability grant
439
435
  builders are construction helpers, not wire decode entrypoints; their final wire
440
- objects are validated at the existing dispatch/parse boundary. Direct dictionary
436
+ objects are validated at the dispatch/parse boundary. Direct dictionary
441
437
  mutation, standard-library `json.loads`, TypedDict annotations, and type assertions
442
438
  are not SDK validation entrypoints. Re-parse after manually mutating a mapping.
443
439
 
444
-
445
440
  Wire constructors also privately hydrate nested mappings: for example,
446
441
  `capability.Capabilities(effects=["modify"], modify={}).modify.input` returns
447
442
  `None`, not an attribute error. Already typed child models retain their identity;
@@ -453,6 +448,54 @@ Constructor aliases cannot silently overwrite wire keys. Supplying both
453
448
  `tool_name=` and `toolName=` raises `TypeError`, even if the values agree. For
454
449
  optional/defaulted fields, a lone wire spelling (for example `addressForm=` or
455
450
  `protocolVersion=`) is used and validated rather than replaced by a default.
456
- Required constructor parameters still use their documented Python spelling;
451
+ Required constructor parameters use their documented Python spelling;
457
452
  use `from_dict` to decode complete wire-keyed objects. In `from_dict`, distinct
458
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
 
@@ -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
 
@@ -122,7 +122,7 @@ 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 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. 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
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
 
@@ -152,14 +152,12 @@ compositions. MCP connection `gaps` parameters accept lists of typed gap models,
152
152
  and HTTP, SSE, stdio, and custom connection objects expose typed location and gap
153
153
  attributes. `ModelVisibleItem` has composed content constructors with typed roles.
154
154
 
155
- Facade objects remain mappings and additionally provide read-only attributes.
155
+ Facade objects are mappings with read-only attributes.
156
156
  Optional attributes return `None` when absent; mapping membership still records
157
157
  presence. Attributes conflicting with mapping methods use a trailing underscore
158
- (such as `items_`), so `dict.items()` keeps working. Wire parsers continue to
159
- return lossless mappings, not hydrated facade instances.
158
+ (such as `items_`), so `dict.items()` keeps working. Wire parsers return lossless mappings, not hydrated facade instances.
160
159
 
161
- Migration: use generated nested models instead of arbitrary mappings for typed
162
- constructor arguments. Wire constructors, dictionary decoding, and runtime parsing
160
+ Use generated nested models for typed constructor arguments. Wire constructors, dictionary decoding, and runtime parsing
163
161
  check location/evidence alternatives and other structural schema constraints. Application
164
162
  JSON, native payloads, and extension values remain intentionally dynamic.
165
163
 
@@ -186,7 +184,7 @@ support. The result is **not authorization**: it does not check target, operatio
186
184
  mode, contextual restrictions, or permission. `EffectName` contains schema-derived
187
185
  known identifiers; custom family identifiers remain ordinary strings.
188
186
 
189
- Wire-model keyword constructors and `Model.from_dict(mapping)` now validate with
187
+ Wire-model keyword constructors and `Model.from_dict(mapping)` validate with
190
188
  the same structural descriptor engine as generated `parse_*` functions. The
191
189
  original composition is retained, including required members, explicit nulls,
192
190
  literals, closed enums, forbidden combinations, and union ambiguity. This is the
@@ -207,17 +205,14 @@ non-finite numbers and non-JSON values are rejected. Parsing raw JSON through
207
205
  `parse_*` retains decimal precision; decoding it first with standard-library
208
206
  `json.loads` cannot recover precision already lost to a float.
209
207
 
210
- **Migration:** constructors previously allowed structurally invalid mappings;
211
- those calls now fail immediately. Provide valid enum/literal values and all
212
- required wire members to `from_dict`. A null candidate, a candidate with
208
+ Provide valid enum/literal values and all required wire members to `from_dict`. A null candidate, a candidate with
213
209
  `{"value": null}`, and a candidate with `{"value": 0}` remain distinct; omitting a
214
210
  required candidate is an error. Generated input projections and capability grant
215
211
  builders are construction helpers, not wire decode entrypoints; their final wire
216
- objects are validated at the existing dispatch/parse boundary. Direct dictionary
212
+ objects are validated at the dispatch/parse boundary. Direct dictionary
217
213
  mutation, standard-library `json.loads`, TypedDict annotations, and type assertions
218
214
  are not SDK validation entrypoints. Re-parse after manually mutating a mapping.
219
215
 
220
-
221
216
  Wire constructors also privately hydrate nested mappings: for example,
222
217
  `capability.Capabilities(effects=["modify"], modify={}).modify.input` returns
223
218
  `None`, not an attribute error. Already typed child models retain their identity;
@@ -229,6 +224,54 @@ Constructor aliases cannot silently overwrite wire keys. Supplying both
229
224
  `tool_name=` and `toolName=` raises `TypeError`, even if the values agree. For
230
225
  optional/defaulted fields, a lone wire spelling (for example `addressForm=` or
231
226
  `protocolVersion=`) is used and validated rather than replaced by a default.
232
- Required constructor parameters still use their documented Python spelling;
227
+ Required constructor parameters use their documented Python spelling;
233
228
  use `from_dict` to decode complete wire-keyed objects. In `from_dict`, distinct
234
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.1"
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",