agenthooksprotocol 0.2.0__tar.gz → 0.3.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 (104) hide show
  1. {agenthooksprotocol-0.2.0/src/agenthooksprotocol.egg-info → agenthooksprotocol-0.3.0}/PKG-INFO +106 -66
  2. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/README.md +105 -65
  3. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/pyproject.toml +1 -1
  4. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/_boundaries.py +66 -49
  5. agenthooksprotocol-0.3.0/src/agenthooksprotocol/_content.py +884 -0
  6. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/_hooks.py +277 -18
  7. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/_models/__init__.py +12248 -13693
  8. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/_models/content.py +2 -28
  9. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/_models/event.py +4 -4
  10. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/_models/tool.py +2 -0
  11. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/ahp-codegen.lock.json +8 -8
  12. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/attachment.py +6 -1
  13. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/compaction.py +69 -56
  14. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/content.py +1 -0
  15. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/effect.py +2 -1
  16. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/elicitation.py +12 -12
  17. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/event.py +2 -2
  18. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/generated.py +262 -554
  19. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/interop.py +83 -39
  20. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/lifecycle.py +5 -12
  21. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/lifecycle_client.py +5 -2
  22. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/lineage.py +15 -25
  23. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/runtime.py +75 -32
  24. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/schemas.json +421 -127
  25. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/tool.py +1 -0
  26. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0/src/agenthooksprotocol.egg-info}/PKG-INFO +106 -66
  27. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol.egg-info/SOURCES.txt +5 -1
  28. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/tests/test_attachments.py +279 -277
  29. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/tests/test_catalogue.py +27 -12
  30. agenthooksprotocol-0.3.0/tests/test_compaction.py +393 -0
  31. agenthooksprotocol-0.3.0/tests/test_compaction_wire.py +259 -0
  32. agenthooksprotocol-0.3.0/tests/test_content_lifetime.py +393 -0
  33. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/tests/test_content_receipts.py +14 -6
  34. agenthooksprotocol-0.3.0/tests/test_elicitation_adapter.py +83 -0
  35. agenthooksprotocol-0.3.0/tests/test_inline_messages.py +983 -0
  36. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/tests/test_integration.py +51 -0
  37. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/tests/test_integration_edges.py +15 -15
  38. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/tests/test_interop.py +74 -18
  39. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/tests/test_lifecycle.py +4 -4
  40. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/tests/test_lifecycle_auth.py +3 -3
  41. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/tests/test_owned_content.py +76 -42
  42. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/tests/test_owned_content_hooks.py +54 -51
  43. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/tests/test_public_content.py +195 -204
  44. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/tests/test_public_hooks.py +59 -0
  45. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/tests/test_schema_corrections.py +37 -8
  46. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/tests/test_sdk_unification.py +10 -5
  47. agenthooksprotocol-0.3.0/tests/test_shared_materialization.py +389 -0
  48. agenthooksprotocol-0.3.0/tests/test_upload_planning.py +361 -0
  49. agenthooksprotocol-0.2.0/src/agenthooksprotocol/_content.py +0 -801
  50. agenthooksprotocol-0.2.0/tests/test_compaction.py +0 -199
  51. agenthooksprotocol-0.2.0/tests/test_compaction_wire.py +0 -73
  52. agenthooksprotocol-0.2.0/tests/test_content_lifetime.py +0 -315
  53. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/LICENSE +0 -0
  54. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/setup.cfg +0 -0
  55. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/__init__.py +0 -0
  56. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/_diagnostics.py +0 -0
  57. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/_grants.py +0 -0
  58. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/_models/capability.py +0 -0
  59. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/_models/effect.py +0 -0
  60. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/_models/registration.py +0 -0
  61. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/_models/subscription.py +0 -0
  62. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/_models/transport.py +0 -0
  63. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/auth.py +0 -0
  64. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/candidate.py +0 -0
  65. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/capability.py +0 -0
  66. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/catalogue.py +0 -0
  67. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/codec.py +0 -0
  68. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/diagnostics.py +0 -0
  69. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/integrations/__init__.py +0 -0
  70. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/integrations/pydantic.py +0 -0
  71. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/lifecycle_server.py +0 -0
  72. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/observation_chain.py +0 -0
  73. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/path.py +0 -0
  74. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/permission.py +0 -0
  75. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/py.typed +0 -0
  76. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/registration.py +0 -0
  77. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/server/__init__.py +0 -0
  78. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/server/_framing.py +0 -0
  79. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/server/_json.py +0 -0
  80. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/server/asgi.py +0 -0
  81. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/server/attachments.py +0 -0
  82. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/server/hooks.py +0 -0
  83. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/server/stdio.py +0 -0
  84. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/state.py +0 -0
  85. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/subscription.py +0 -0
  86. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/transport.py +0 -0
  87. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/transports/__init__.py +0 -0
  88. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/transports/http.py +0 -0
  89. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol/transports/stdio.py +0 -0
  90. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol.egg-info/dependency_links.txt +0 -0
  91. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol.egg-info/requires.txt +0 -0
  92. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/src/agenthooksprotocol.egg-info/top_level.txt +0 -0
  93. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/tests/test_codec.py +0 -0
  94. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/tests/test_content.py +0 -0
  95. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/tests/test_fixture_public.py +0 -0
  96. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/tests/test_observation.py +0 -0
  97. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/tests/test_observation_chain_transport.py +0 -0
  98. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/tests/test_package.py +0 -0
  99. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/tests/test_public_examples.py +0 -0
  100. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/tests/test_public_server.py +0 -0
  101. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/tests/test_registration_auth.py +0 -0
  102. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/tests/test_safety.py +0 -0
  103. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.0}/tests/test_session_manifest.py +0 -0
  104. {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.3.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.2.0
3
+ Version: 0.3.0
4
4
  Summary: Official non-normative Python SDK for the Agent Hooks Protocol
5
5
  Author: Agent Hooks Protocol maintainers
6
6
  License:
@@ -327,30 +327,63 @@ Observation is one-way. It does not acquire effect authority and never waits for
327
327
 
328
328
  The attachment receiver verifies byte length and digest through EOF before committing caller-provided immutable storage. Receiver-allocated references are published only after authorization and successful storage commit. Applications choose storage durability and retention.
329
329
 
330
- ## Body-bound boundaries
330
+ ## Inline messages and specialized 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 `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.
332
+ Model-visible inputs contain canonical messages with a `role` and ordered
333
+ `parts`. Supported roles are `system`, `developer`, `user`, `assistant`, and
334
+ `tool`. A text part has `kind="text"`, `mediaType="text/plain"`, and inline
335
+ `text`. Serialized JSON is ordinary text. An attachment part has
336
+ `kind="attachment"`, a non-text/non-JSON media type, and an immutable binary
337
+ body. Construction supplies missing message/part IDs and marks them
338
+ `synthesized=True`; explicit IDs remain stable.
333
339
 
334
- Bindings are explicit paths within the canonical event:
335
-
336
- | Boundary | Binding |
337
- | --- | --- |
338
- | `user_elicitation_request` | `{"request": ("elicitation", "request")}` |
339
- | `user_elicitation_result` | `{"content": ("elicitation", "result")}` plus `original_request` |
340
- | `context_compact_before` | `{"instructions": ("instructions",)}` |
341
- | `context_compact_after` | `{"summary": ("summary",)}` |
342
-
343
- Pass `content=context` to the named boundary. An elicitation result needs the original canonical request snapshot; the SDK does not fabricate correlation, form/URL support, or MCP defaults. Metadata and omitted selections do not read body bytes. Changed bodies receive new immutable references, and a changed compaction input invalidates an earlier supplied summary.
344
-
345
- ### Lazy owned sources
346
-
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
-
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
-
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.
340
+ ```python
341
+ from agenthooksprotocol import Attachment
342
+
343
+ input = {
344
+ "trigger": "manual",
345
+ "items": [{"role": "user", "parts": [
346
+ {"kind": "text", "text": "Review this report."},
347
+ {"kind": "attachment", "mediaType": "application/pdf",
348
+ "body": Attachment.from_bytes(pdf_bytes)},
349
+ ]}],
350
+ }
351
+ result = await hooks.context_compact_before(input, uploads=authorized_uploads)
352
+ async with result:
353
+ data = await result.attachments.read("context.compact.before.items_parts[0][1]")
354
+ ```
352
355
 
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.
356
+ Named hook methods also accept canonical dictionaries with the same direct
357
+ attachment bodies. The runtime collects sources and creates receiver-specific
358
+ references internally. Construction and metadata/omit projection do not read
359
+ or upload attachments. Only schema-owned slots are projected; native payloads,
360
+ extensions, and application arguments are opaque. Selection is a ceiling for
361
+ each receiver's view: metadata/omit views contain no inline text or attachment
362
+ references. Independent receivers apply their own content selections.
363
+
364
+ Compaction `instructions` and `summary` are canonical text-part lists.
365
+ Replacement substitutes the list; merge appends in order. Model-visible message
366
+ list targets (`prompt`, `request`, `response`, and tool `output`) have the same
367
+ list operations. Tool output edits operate on `tool.after.items`; application
368
+ structures are serialized as text parts. Standalone `content` edits require the
369
+ boundary context: `user.message.outbound` uses a message list, and accepted form
370
+ answers at `user.elicitation.result` use an MCP answer object. Object targets such as tool input and workspace changes use shallow object merge. A response settles
371
+ atomically, and each serial receiver sees the committed state. Changed
372
+ compaction instructions invalidate an earlier supplied summary.
373
+
374
+ MCP elicitation `request` and `result` contain selected text parts whose `text`
375
+ is the serialized complete MCP JSON payload. The SDK validates parsed payloads,
376
+ mode grants, form answers, and correlation without inserting defaults. Result
377
+ edits require `original_request=` with the original canonical intercept request
378
+ snapshot; answer-object merge is shallow. Elicitation request bodies are immutable
379
+ to modification effects. No text/JSON upload callback is needed. Metadata and omit selections
380
+ cannot authorize body-dependent effects.
381
+
382
+ For custom transports, `begin(canonical_request)` separates acquisition,
383
+ cancellation, and acceptance; `exchange(canonical_request)` uses the same
384
+ settlement engine. Elicitation correlation can be supplied through
385
+ `ContentContext(original_request=..., bindings={"content": ("elicitation", "result")},
386
+ principal=authenticated_identity)` and `await pending.accept_content()`.
354
387
 
355
388
  ## Generated models and provenance
356
389
 
@@ -374,7 +407,7 @@ Integration tests also use a matching sibling `../agent-hooks-protocol` checkout
374
407
  Generated facade constructors retain nested model types through schema
375
408
  compositions. MCP connection `gaps` parameters accept lists of typed gap models,
376
409
  and HTTP, SSE, stdio, and custom connection objects expose typed location and gap
377
- attributes. `ModelVisibleItem` has composed content constructors with typed roles.
410
+ attributes. `ModelVisibleItem` is a canonical message with a typed role and ordered parts.
378
411
 
379
412
  Facade objects are mappings with read-only attributes.
380
413
  Optional attributes return `None` when absent; mapping membership still records
@@ -454,48 +487,55 @@ keys such as `toolName` and `tool_name` remain distinct wire/extension keys.
454
487
 
455
488
  ### Owned attachments
456
489
 
457
- The owned attachment APIs in this section are not included in the published 0.1.1 package.
458
-
459
490
  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
-
491
+ `Attachment.lazy(async_loader, aclose=async_cleanup)` to defer reading. Put
492
+ `attachment` directly in an attachment part's `body`. Named `Hooks` methods
493
+ accept both dictionary inputs and typed event inputs with the same owner.
494
+ Metadata belongs on the part. See the runnable
495
+ [file attachment example](examples/file_attachment.py).
496
+
497
+ Dispatch transfers ownership once. Successful results retain the exact owner,
498
+ including an unread loader, independently of `Hooks` shutdown. Callers close
499
+ returned content owners with `async with result:` or `await result.aclose()`.
500
+ Cleanup runs for unopened sources too. Create a new attachment for each
501
+ invocation; eager attachments can share the same immutable `bytes` object.
502
+
503
+ The default accepted size is 4 MiB and lazy loading timeout is 30 seconds.
504
+ Constructors accept `max_bytes`; lazy construction accepts `timeout`. Loaders
505
+ must bound their own allocations. `Attachment(async_stream, ...)` supports
506
+ bounded `read(size)`/`receive(max_bytes)` streams. Stream cleanup is shielded and
507
+ bounded to one second. Cancellation and failure never retry a loader. Mutable
508
+ buffers are rejected, and reads return immutable bytes.
509
+
510
+ Selected remote binary delivery requires an authorized uploader through
511
+ `uploads={backend_id: async_upload}`. Each uploader receives the exact owner's
512
+ immutable bytes and returns a committed receipt with `ref`, `size`, and `sha256`.
513
+ The SDK validates the receipt against the actual bytes before sending the event.
514
+ Upload callbacks remain caller-authorized and scoped to their backend; event
515
+ credentials do not authorize uploads to another destination.
516
+
517
+ `Hooks(..., max_concurrent_uploads=8)` bounds concurrent binary uploads.
518
+ The value must be a positive integer. Before serial interception
519
+ begins, the SDK prepares uploads for body-selected attachment slots in all
520
+ matched subscriptions, including observers. One attachment owner is read once
521
+ for multiple subscribers. Metadata, omit, and unmatched selections do not read
522
+ or upload its bytes. Result reads and metadata-only delivery require no uploader.
523
+
524
+ Only confirmed receipts are cached, for the exact attachment owner and backend
525
+ ID/subscription index. An uncalled interceptor receives a settled observation
526
+ with its cached reference after denial or stop. Upload failures are handled by
527
+ the affected subscription's failure policy when its delivery is reached; they
528
+ do not prevent independent destinations from preparing their uploads. An upload
529
+ can commit even if an earlier interceptor later removes the attachment or halts
530
+ the chain. Receiver applications manage separate storage durability and retention.
531
+ Cancellation cancels and joins pending upload work and closes invocation-owned
532
+ sources. Overlapping reads join one materialization and receive the same immutable
533
+ bytes or terminal error. Cancelling one reader leaves materialization active for
534
+ other readers; the last cancelled reader or owner closure cancels and joins it.
535
+ The attachment owns its single pending materialization independently of readers.
536
+ A cancelled reader returns and releases its upload permit while another reader
537
+ continues. The last reader or owner closure joins cleanup; completed workers are
538
+ not retained.
539
+
540
+ Attachments are the sole byte owners. Binary/file editing is not supported.
501
541
  Results are invocation-local, not session archives.
@@ -103,30 +103,63 @@ Observation is one-way. It does not acquire effect authority and never waits for
103
103
 
104
104
  The attachment receiver verifies byte length and digest through EOF before committing caller-provided immutable storage. Receiver-allocated references are published only after authorization and successful storage commit. Applications choose storage durability and retention.
105
105
 
106
- ## Body-bound boundaries
106
+ ## Inline messages and specialized 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 `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.
108
+ Model-visible inputs contain canonical messages with a `role` and ordered
109
+ `parts`. Supported roles are `system`, `developer`, `user`, `assistant`, and
110
+ `tool`. A text part has `kind="text"`, `mediaType="text/plain"`, and inline
111
+ `text`. Serialized JSON is ordinary text. An attachment part has
112
+ `kind="attachment"`, a non-text/non-JSON media type, and an immutable binary
113
+ body. Construction supplies missing message/part IDs and marks them
114
+ `synthesized=True`; explicit IDs remain stable.
109
115
 
110
- Bindings are explicit paths within the canonical event:
111
-
112
- | Boundary | Binding |
113
- | --- | --- |
114
- | `user_elicitation_request` | `{"request": ("elicitation", "request")}` |
115
- | `user_elicitation_result` | `{"content": ("elicitation", "result")}` plus `original_request` |
116
- | `context_compact_before` | `{"instructions": ("instructions",)}` |
117
- | `context_compact_after` | `{"summary": ("summary",)}` |
118
-
119
- Pass `content=context` to the named boundary. An elicitation result needs the original canonical request snapshot; the SDK does not fabricate correlation, form/URL support, or MCP defaults. Metadata and omitted selections do not read body bytes. Changed bodies receive new immutable references, and a changed compaction input invalidates an earlier supplied summary.
120
-
121
- ### Lazy owned sources
122
-
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
-
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
-
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.
116
+ ```python
117
+ from agenthooksprotocol import Attachment
118
+
119
+ input = {
120
+ "trigger": "manual",
121
+ "items": [{"role": "user", "parts": [
122
+ {"kind": "text", "text": "Review this report."},
123
+ {"kind": "attachment", "mediaType": "application/pdf",
124
+ "body": Attachment.from_bytes(pdf_bytes)},
125
+ ]}],
126
+ }
127
+ result = await hooks.context_compact_before(input, uploads=authorized_uploads)
128
+ async with result:
129
+ data = await result.attachments.read("context.compact.before.items_parts[0][1]")
130
+ ```
128
131
 
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.
132
+ Named hook methods also accept canonical dictionaries with the same direct
133
+ attachment bodies. The runtime collects sources and creates receiver-specific
134
+ references internally. Construction and metadata/omit projection do not read
135
+ or upload attachments. Only schema-owned slots are projected; native payloads,
136
+ extensions, and application arguments are opaque. Selection is a ceiling for
137
+ each receiver's view: metadata/omit views contain no inline text or attachment
138
+ references. Independent receivers apply their own content selections.
139
+
140
+ Compaction `instructions` and `summary` are canonical text-part lists.
141
+ Replacement substitutes the list; merge appends in order. Model-visible message
142
+ list targets (`prompt`, `request`, `response`, and tool `output`) have the same
143
+ list operations. Tool output edits operate on `tool.after.items`; application
144
+ structures are serialized as text parts. Standalone `content` edits require the
145
+ boundary context: `user.message.outbound` uses a message list, and accepted form
146
+ answers at `user.elicitation.result` use an MCP answer object. Object targets such as tool input and workspace changes use shallow object merge. A response settles
147
+ atomically, and each serial receiver sees the committed state. Changed
148
+ compaction instructions invalidate an earlier supplied summary.
149
+
150
+ MCP elicitation `request` and `result` contain selected text parts whose `text`
151
+ is the serialized complete MCP JSON payload. The SDK validates parsed payloads,
152
+ mode grants, form answers, and correlation without inserting defaults. Result
153
+ edits require `original_request=` with the original canonical intercept request
154
+ snapshot; answer-object merge is shallow. Elicitation request bodies are immutable
155
+ to modification effects. No text/JSON upload callback is needed. Metadata and omit selections
156
+ cannot authorize body-dependent effects.
157
+
158
+ For custom transports, `begin(canonical_request)` separates acquisition,
159
+ cancellation, and acceptance; `exchange(canonical_request)` uses the same
160
+ settlement engine. Elicitation correlation can be supplied through
161
+ `ContentContext(original_request=..., bindings={"content": ("elicitation", "result")},
162
+ principal=authenticated_identity)` and `await pending.accept_content()`.
130
163
 
131
164
  ## Generated models and provenance
132
165
 
@@ -150,7 +183,7 @@ Integration tests also use a matching sibling `../agent-hooks-protocol` checkout
150
183
  Generated facade constructors retain nested model types through schema
151
184
  compositions. MCP connection `gaps` parameters accept lists of typed gap models,
152
185
  and HTTP, SSE, stdio, and custom connection objects expose typed location and gap
153
- attributes. `ModelVisibleItem` has composed content constructors with typed roles.
186
+ attributes. `ModelVisibleItem` is a canonical message with a typed role and ordered parts.
154
187
 
155
188
  Facade objects are mappings with read-only attributes.
156
189
  Optional attributes return `None` when absent; mapping membership still records
@@ -230,48 +263,55 @@ keys such as `toolName` and `tool_name` remain distinct wire/extension keys.
230
263
 
231
264
  ### Owned attachments
232
265
 
233
- The owned attachment APIs in this section are not included in the published 0.1.1 package.
234
-
235
266
  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
-
267
+ `Attachment.lazy(async_loader, aclose=async_cleanup)` to defer reading. Put
268
+ `attachment` directly in an attachment part's `body`. Named `Hooks` methods
269
+ accept both dictionary inputs and typed event inputs with the same owner.
270
+ Metadata belongs on the part. See the runnable
271
+ [file attachment example](examples/file_attachment.py).
272
+
273
+ Dispatch transfers ownership once. Successful results retain the exact owner,
274
+ including an unread loader, independently of `Hooks` shutdown. Callers close
275
+ returned content owners with `async with result:` or `await result.aclose()`.
276
+ Cleanup runs for unopened sources too. Create a new attachment for each
277
+ invocation; eager attachments can share the same immutable `bytes` object.
278
+
279
+ The default accepted size is 4 MiB and lazy loading timeout is 30 seconds.
280
+ Constructors accept `max_bytes`; lazy construction accepts `timeout`. Loaders
281
+ must bound their own allocations. `Attachment(async_stream, ...)` supports
282
+ bounded `read(size)`/`receive(max_bytes)` streams. Stream cleanup is shielded and
283
+ bounded to one second. Cancellation and failure never retry a loader. Mutable
284
+ buffers are rejected, and reads return immutable bytes.
285
+
286
+ Selected remote binary delivery requires an authorized uploader through
287
+ `uploads={backend_id: async_upload}`. Each uploader receives the exact owner's
288
+ immutable bytes and returns a committed receipt with `ref`, `size`, and `sha256`.
289
+ The SDK validates the receipt against the actual bytes before sending the event.
290
+ Upload callbacks remain caller-authorized and scoped to their backend; event
291
+ credentials do not authorize uploads to another destination.
292
+
293
+ `Hooks(..., max_concurrent_uploads=8)` bounds concurrent binary uploads.
294
+ The value must be a positive integer. Before serial interception
295
+ begins, the SDK prepares uploads for body-selected attachment slots in all
296
+ matched subscriptions, including observers. One attachment owner is read once
297
+ for multiple subscribers. Metadata, omit, and unmatched selections do not read
298
+ or upload its bytes. Result reads and metadata-only delivery require no uploader.
299
+
300
+ Only confirmed receipts are cached, for the exact attachment owner and backend
301
+ ID/subscription index. An uncalled interceptor receives a settled observation
302
+ with its cached reference after denial or stop. Upload failures are handled by
303
+ the affected subscription's failure policy when its delivery is reached; they
304
+ do not prevent independent destinations from preparing their uploads. An upload
305
+ can commit even if an earlier interceptor later removes the attachment or halts
306
+ the chain. Receiver applications manage separate storage durability and retention.
307
+ Cancellation cancels and joins pending upload work and closes invocation-owned
308
+ sources. Overlapping reads join one materialization and receive the same immutable
309
+ bytes or terminal error. Cancelling one reader leaves materialization active for
310
+ other readers; the last cancelled reader or owner closure cancels and joins it.
311
+ The attachment owns its single pending materialization independently of readers.
312
+ A cancelled reader returns and releases its upload permit while another reader
313
+ continues. The last reader or owner closure joins cleanup; completed workers are
314
+ not retained.
315
+
316
+ Attachments are the sole byte owners. Binary/file editing is not supported.
277
317
  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.2.0"
7
+ version = "0.3.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"