agenthooksprotocol 0.2.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agenthooksprotocol-0.2.0/src/agenthooksprotocol.egg-info → agenthooksprotocol-0.4.0}/PKG-INFO +129 -83
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/README.md +128 -82
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/pyproject.toml +1 -1
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/__init__.py +11 -1
- agenthooksprotocol-0.4.0/src/agenthooksprotocol/_boundaries.py +1358 -0
- agenthooksprotocol-0.4.0/src/agenthooksprotocol/_content.py +907 -0
- agenthooksprotocol-0.2.0/src/agenthooksprotocol/elicitation.py → agenthooksprotocol-0.4.0/src/agenthooksprotocol/_elicitation.py +57 -20
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/_grants.py +6 -21
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/_hooks.py +374 -31
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/_models/__init__.py +9720 -8518
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/_models/content.py +4 -28
- agenthooksprotocol-0.4.0/src/agenthooksprotocol/_models/effect.py +124 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/_models/event.py +4 -4
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/_models/tool.py +2 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/ahp-codegen.lock.json +13 -13
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/attachment.py +6 -1
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/auth.py +8 -3
- agenthooksprotocol-0.4.0/src/agenthooksprotocol/codec.py +99 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/compaction.py +69 -56
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/content.py +1 -0
- agenthooksprotocol-0.4.0/src/agenthooksprotocol/contract.py +56 -0
- agenthooksprotocol-0.4.0/src/agenthooksprotocol/effect.py +511 -0
- agenthooksprotocol-0.4.0/src/agenthooksprotocol/elicitation.py +8 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/event.py +105 -2
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/generated.py +2022 -868
- agenthooksprotocol-0.4.0/src/agenthooksprotocol/integrations/pydantic.py +152 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/interop.py +89 -44
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/lifecycle.py +30 -19
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/lifecycle_client.py +5 -2
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/lineage.py +17 -27
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/registration.py +4 -1
- agenthooksprotocol-0.4.0/src/agenthooksprotocol/response.py +268 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/runtime.py +199 -73
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/schemas.json +2806 -405
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/server/asgi.py +2 -2
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/server/attachments.py +6 -2
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/server/hooks.py +13 -10
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/server/stdio.py +6 -4
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/state.py +3 -4
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/tool.py +1 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/transports/http.py +10 -8
- agenthooksprotocol-0.4.0/src/agenthooksprotocol/wire.py +49 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0/src/agenthooksprotocol.egg-info}/PKG-INFO +129 -83
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol.egg-info/SOURCES.txt +13 -1
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/tests/test_attachments.py +279 -276
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/tests/test_catalogue.py +27 -12
- agenthooksprotocol-0.4.0/tests/test_compaction.py +393 -0
- agenthooksprotocol-0.4.0/tests/test_compaction_wire.py +259 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/tests/test_content.py +4 -1
- agenthooksprotocol-0.4.0/tests/test_content_lifetime.py +393 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/tests/test_content_receipts.py +14 -6
- agenthooksprotocol-0.4.0/tests/test_correlated_core.py +293 -0
- agenthooksprotocol-0.4.0/tests/test_declared_contracts.py +506 -0
- agenthooksprotocol-0.4.0/tests/test_elicitation_adapter.py +83 -0
- agenthooksprotocol-0.4.0/tests/test_inline_messages.py +983 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/tests/test_integration.py +68 -17
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/tests/test_integration_edges.py +44 -16
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/tests/test_interop.py +78 -22
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/tests/test_lifecycle.py +29 -4
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/tests/test_lifecycle_auth.py +3 -3
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/tests/test_observation.py +2 -2
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/tests/test_owned_content.py +76 -42
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/tests/test_owned_content_hooks.py +54 -51
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/tests/test_public_content.py +195 -204
- agenthooksprotocol-0.4.0/tests/test_public_contract_surface.py +86 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/tests/test_public_hooks.py +59 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/tests/test_registration_auth.py +20 -25
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/tests/test_safety.py +22 -8
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/tests/test_schema_corrections.py +41 -12
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/tests/test_sdk_unification.py +13 -8
- agenthooksprotocol-0.4.0/tests/test_shared_materialization.py +389 -0
- agenthooksprotocol-0.4.0/tests/test_typed_contract_regressions.py +419 -0
- agenthooksprotocol-0.4.0/tests/test_upload_planning.py +361 -0
- agenthooksprotocol-0.2.0/src/agenthooksprotocol/_boundaries.py +0 -498
- agenthooksprotocol-0.2.0/src/agenthooksprotocol/_content.py +0 -801
- agenthooksprotocol-0.2.0/src/agenthooksprotocol/_models/effect.py +0 -48
- agenthooksprotocol-0.2.0/src/agenthooksprotocol/codec.py +0 -32
- agenthooksprotocol-0.2.0/src/agenthooksprotocol/effect.py +0 -150
- agenthooksprotocol-0.2.0/src/agenthooksprotocol/integrations/pydantic.py +0 -28
- agenthooksprotocol-0.2.0/tests/test_compaction.py +0 -199
- agenthooksprotocol-0.2.0/tests/test_compaction_wire.py +0 -73
- agenthooksprotocol-0.2.0/tests/test_content_lifetime.py +0 -315
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/LICENSE +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/setup.cfg +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/_diagnostics.py +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/_models/capability.py +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/_models/registration.py +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/_models/subscription.py +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/_models/transport.py +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/candidate.py +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/capability.py +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/catalogue.py +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/diagnostics.py +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/integrations/__init__.py +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/lifecycle_server.py +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/observation_chain.py +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/path.py +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/permission.py +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/py.typed +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/server/__init__.py +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/server/_framing.py +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/server/_json.py +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/subscription.py +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/transport.py +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/transports/__init__.py +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol/transports/stdio.py +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol.egg-info/dependency_links.txt +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol.egg-info/requires.txt +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/src/agenthooksprotocol.egg-info/top_level.txt +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/tests/test_codec.py +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/tests/test_fixture_public.py +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/tests/test_observation_chain_transport.py +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/tests/test_package.py +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/tests/test_public_examples.py +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/tests/test_public_server.py +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/tests/test_session_manifest.py +0 -0
- {agenthooksprotocol-0.2.0 → agenthooksprotocol-0.4.0}/tests/test_structural_facade.py +0 -0
{agenthooksprotocol-0.2.0/src/agenthooksprotocol.egg-info → agenthooksprotocol-0.4.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agenthooksprotocol
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Official non-normative Python SDK for the Agent Hooks Protocol
|
|
5
5
|
Author: Agent Hooks Protocol maintainers
|
|
6
6
|
License:
|
|
@@ -266,6 +266,7 @@ Capability builders are immutable: reusing a base declaration does not widen it.
|
|
|
266
266
|
|
|
267
267
|
Runnable, statically checked consumer programs live in [`examples/`](examples):
|
|
268
268
|
|
|
269
|
+
- `value_codecs.py`: independent argument/result/provenance codecs, admission-time invariants, and a form schema derived from its answer type.
|
|
269
270
|
- `typed_tool.py`: JSON registration, generated tool input, typed grants, initial state, real in-process ASGI handler, borrowed HTTP client, and explicit application decoding.
|
|
270
271
|
- `common_cases.py`: rewrite/allow, deny, invalid application shape, host policy, occurrence narrowing, native initial state, and fail-open/fail-closed reports. The host operation is an in-memory execution recorder, not a shell.
|
|
271
272
|
- `stdio_tool.py` / `stdio_backend.py`: a real owned backend process using the same public Handler, denial, and child cleanup.
|
|
@@ -282,9 +283,15 @@ Generated constructors build JSON-shaped protocol values. Generated `TypedDict`
|
|
|
282
283
|
|
|
283
284
|
Capabilities explicitly grant effects and target operations. Supply event capabilities at harness construction; a per-occurrence capability override can narrow them, never widen them. `initial_state` carries the host's native prior decision for one occurrence. A hook-supplied result is a candidate, not an assertion that an operation executed. The host owns its ordinary input validation and actual execution.
|
|
284
285
|
|
|
286
|
+
Supply `input_codec=`, `result_codec=`, and optional `provenance_codec=` to a named hook method or `dispatch` to declare caller types. Complete staged values are decoded and validated before publication. Object merge is shallow: omitted fields remain unchanged, literal null is retained, and nested values replace whole values. An invalid response publishes none of its effects; earlier accepted responses remain visible to later interceptors. A codec must preserve the wire data it receives, including opaque extra fields. Lossy serialization rejects admission rather than dropping fields.
|
|
287
|
+
|
|
288
|
+
`result.decoded_input` exposes the declared argument value. `result.decoded_candidate` is an application `Candidate[Result, Provenance]`, or `None` when there is no candidate. Its value can itself be `None`; optional provenance is omitted from `candidate.to_dict()` when absent. Raw `input` and `candidate` views remain detached snapshots. For explicitly selected opaque host slots, use `event_codecs={("params",): codec}` with event-relative paths. Unspecified native, provider, task, permission, and discovery values are not interpreted.
|
|
289
|
+
|
|
290
|
+
For forms, construct `PydanticFormContract(TypeAdapter(Answer))` from one answer declaration. `request(message)` derives the restricted MCP schema; `result_codec()` decodes the complete MCP result and its accepted answer while preserving opaque result metadata. Unsupported shapes and schema constraints are rejected during construction. Decline, cancel, and URL results have no form content. See [`examples/value_codecs.py`](examples/value_codecs.py).
|
|
291
|
+
|
|
285
292
|
After settlement, explicitly decode effective input with `result.decode_input(codec)`. A codec implements `encode(value)` and `decode(json_value)`; `IdentityCodec` copies JSON. The optional `integrations.pydantic.PydanticCodec` accepts a Pydantic `TypeAdapter`. A decoding error does not retroactively reject protocol effects: retain the settled result, raw effective input, accepted responses, and diagnostics, and report host-level rejection without executing.
|
|
286
293
|
|
|
287
|
-
`result.permission` is the generated `Permission` enum (`NONE`, `ALLOW`, `ASK`, `DENY`) read from canonical settlement, not an independent decision engine. `NONE` is not approval; `ASK` needs host approval; interruption never authorizes execution. `result.input` is detached accepted JSON
|
|
294
|
+
`result.permission` is the generated `Permission` enum (`NONE`, `ALLOW`, `ASK`, `DENY`) read from canonical settlement, not an independent decision engine. `NONE` is not approval; `ASK` needs host approval; interruption never authorizes execution. `result.input` is detached accepted JSON. Declared codecs validate accepted modifications; without a declared codec, the host supplies application validation. `decode_input(codec)` remains an explicit, post-settlement host check. Candidate, canonical event, accepted responses, and diagnostics remain accessible.
|
|
288
295
|
|
|
289
296
|
`state.initial(Permission.ALLOW)` represents a native decision **already made for this exact occurrence**, not an authorization shortcut. Its default candidate is absent (`null`); `state.Candidate(value=None)` instead means a supplied JSON-null result. Optional typed provenance records facts, not authenticated identity or execution proof. Canonical modifications still invalidate candidates/approval as specified.
|
|
290
297
|
|
|
@@ -327,36 +334,69 @@ Observation is one-way. It does not acquire effect authority and never waits for
|
|
|
327
334
|
|
|
328
335
|
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
336
|
|
|
330
|
-
##
|
|
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.
|
|
333
|
-
|
|
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.
|
|
337
|
+
## Inline messages and specialized boundaries
|
|
348
338
|
|
|
349
|
-
|
|
339
|
+
Model-visible inputs contain canonical messages with a `role` and ordered
|
|
340
|
+
`parts`. Supported roles are `system`, `developer`, `user`, `assistant`, and
|
|
341
|
+
`tool`. A text part has `kind="text"`, `mediaType="text/plain"`, and inline
|
|
342
|
+
`text`. Serialized JSON is ordinary text. An attachment part has
|
|
343
|
+
`kind="attachment"`, a non-text/non-JSON media type, and an immutable binary
|
|
344
|
+
body. Construction supplies missing message/part IDs and marks them
|
|
345
|
+
`synthesized=True`; explicit IDs remain stable.
|
|
350
346
|
|
|
351
|
-
|
|
347
|
+
```python
|
|
348
|
+
from agenthooksprotocol import Attachment
|
|
349
|
+
|
|
350
|
+
input = {
|
|
351
|
+
"trigger": "manual",
|
|
352
|
+
"items": [{"role": "user", "parts": [
|
|
353
|
+
{"kind": "text", "text": "Review this report."},
|
|
354
|
+
{"kind": "attachment", "mediaType": "application/pdf",
|
|
355
|
+
"body": Attachment.from_bytes(pdf_bytes)},
|
|
356
|
+
]}],
|
|
357
|
+
}
|
|
358
|
+
result = await hooks.context_compact_before(input, uploads=authorized_uploads)
|
|
359
|
+
async with result:
|
|
360
|
+
data = await result.attachments.read("context.compact.before.items_parts[0][1]")
|
|
361
|
+
```
|
|
352
362
|
|
|
353
|
-
|
|
363
|
+
Named hook methods also accept canonical dictionaries with the same direct
|
|
364
|
+
attachment bodies. The runtime collects sources and creates receiver-specific
|
|
365
|
+
references internally. Construction and metadata/omit projection do not read
|
|
366
|
+
or upload attachments. Only schema-owned slots are projected; native payloads,
|
|
367
|
+
extensions, and application arguments are opaque. Selection is a ceiling for
|
|
368
|
+
each receiver's view: metadata/omit views contain no inline text or attachment
|
|
369
|
+
references. Independent receivers apply their own content selections.
|
|
370
|
+
|
|
371
|
+
Compaction `instructions` and `summary` are canonical text-part lists.
|
|
372
|
+
Replacement substitutes the list; merge appends in order. Model-visible message
|
|
373
|
+
list targets (`prompt`, `request`, `response`, and tool `output`) have the same
|
|
374
|
+
list operations. Tool output edits operate on `tool.after.items`; application
|
|
375
|
+
structures are serialized as text parts. Standalone `content` edits require the
|
|
376
|
+
boundary context: `user.message.outbound` uses a message list, and accepted form
|
|
377
|
+
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
|
|
378
|
+
atomically, and each serial receiver sees the committed state. Changed
|
|
379
|
+
compaction instructions invalidate an earlier supplied summary.
|
|
380
|
+
|
|
381
|
+
MCP elicitation `request` and `result` contain selected text parts whose `text`
|
|
382
|
+
is the serialized complete MCP JSON payload. The SDK validates parsed payloads,
|
|
383
|
+
mode grants, form answers, and correlation without inserting defaults. Result
|
|
384
|
+
edits require `original_request=` with the original canonical intercept request
|
|
385
|
+
snapshot; answer-object merge is shallow. Elicitation request bodies are immutable
|
|
386
|
+
to modification effects. No text/JSON upload callback is needed. Metadata and omit selections
|
|
387
|
+
cannot authorize body-dependent effects.
|
|
388
|
+
|
|
389
|
+
For custom transports, `begin(canonical_request, value_codecs=ValueCodecs(...))` separates acquisition,
|
|
390
|
+
cancellation, and acceptance; `exchange(canonical_request)` uses the same
|
|
391
|
+
settlement engine. Elicitation correlation can be supplied through
|
|
392
|
+
`ContentContext(original_request=..., bindings={"content": ("elicitation", "result")},
|
|
393
|
+
principal=authenticated_identity)` and `await pending.accept_content()`.
|
|
354
394
|
|
|
355
395
|
## Generated models and provenance
|
|
356
396
|
|
|
357
397
|
Semantic namespaces (`event`, `tool`, `effect`, `capability`, and registration models) and all named event boundaries come from schema metadata, not a selected handwritten helper list. Constructors may supply fixed protocol literals; parsers never repair missing input. Open fields are retained for forward-compatible JSON round trips.
|
|
358
398
|
|
|
359
|
-
The low-level `generated` module exports the full schema's typed models, `parse_<root>` / `encode_<root>` codecs, `PROTOCOL_VERSION`, `SCHEMA_REVISION`, and JSON types. `ahp-codegen.lock.json` records immutable generator provenance. Change canonical schemas and generator source in the protocol repository rather than editing generated files.
|
|
399
|
+
The `wire` namespace exposes canonical JSON-shaped request, notification, effect, and response types with structural codecs. Server callbacks use these raw wire contracts. The low-level `generated` module exports the full schema's typed models, `parse_<root>` / `encode_<root>` codecs, `PROTOCOL_VERSION`, `SCHEMA_REVISION`, and JSON types. `ahp-codegen.lock.json` records immutable generator provenance. Change canonical schemas and generator source in the protocol repository rather than editing generated files.
|
|
360
400
|
|
|
361
401
|
## Development
|
|
362
402
|
|
|
@@ -374,12 +414,12 @@ Integration tests also use a matching sibling `../agent-hooks-protocol` checkout
|
|
|
374
414
|
Generated facade constructors retain nested model types through schema
|
|
375
415
|
compositions. MCP connection `gaps` parameters accept lists of typed gap models,
|
|
376
416
|
and HTTP, SSE, stdio, and custom connection objects expose typed location and gap
|
|
377
|
-
attributes. `ModelVisibleItem`
|
|
417
|
+
attributes. `ModelVisibleItem` is a canonical message with a typed role and ordered parts.
|
|
378
418
|
|
|
379
419
|
Facade objects are mappings with read-only attributes.
|
|
380
420
|
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.
|
|
421
|
+
presence. Omitted and explicit-empty response effects are both neutral; decoded effects provide an empty list without modifying the source wire object. Attributes conflicting with mapping methods use a trailing underscore
|
|
422
|
+
(such as `items_`), so `dict.items()` keeps working. Raw wire parsers return lossless mappings. `response.response_for_request(event_type, raw)` returns the generated contextual response selected by the originating event type, not by payload shape.
|
|
383
423
|
|
|
384
424
|
Use generated nested models for typed constructor arguments. Wire constructors, dictionary decoding, and runtime parsing
|
|
385
425
|
check location/evidence alternatives and other structural schema constraints. Application
|
|
@@ -413,13 +453,7 @@ the same structural descriptor engine as generated `parse_*` functions. The
|
|
|
413
453
|
original composition is retained, including required members, explicit nulls,
|
|
414
454
|
literals, closed enums, forbidden combinations, and union ambiguity. This is the
|
|
415
455
|
SDK's structural contract, not complete contextual protocol validation.
|
|
416
|
-
`from_dict`
|
|
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.
|
|
456
|
+
`from_dict` checks complete wire values and preserves optional-field presence. Decoded known nested values expose the same attributes as constructed values, such as `connection.gaps[0].reason`. Extension and application bags remain intact and do not acquire effect or attachment authority.
|
|
423
457
|
|
|
424
458
|
Invalid wire models raise `ValueError`; validation failures carry `result`,
|
|
425
459
|
`diagnostics`, and `raw` attributes. Use `parse_*` when you need the full result on
|
|
@@ -437,12 +471,7 @@ objects are validated at the dispatch/parse boundary. Direct dictionary
|
|
|
437
471
|
mutation, standard-library `json.loads`, TypedDict annotations, and type assertions
|
|
438
472
|
are not SDK validation entrypoints. Re-parse after manually mutating a mapping.
|
|
439
473
|
|
|
440
|
-
|
|
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`.
|
|
474
|
+
Nested model attributes expose declared protocol fields. For example, `capability.Capabilities(effects=["modify"], modify={}).modify.input` is `None` when that grant is absent. Family queries are available on generic and per-occurrence capability models.
|
|
446
475
|
|
|
447
476
|
Constructor aliases cannot silently overwrite wire keys. Supplying both
|
|
448
477
|
`tool_name=` and `toolName=` raises `TypeError`, even if the values agree. For
|
|
@@ -454,48 +483,65 @@ keys such as `toolName` and `tool_name` remain distinct wire/extension keys.
|
|
|
454
483
|
|
|
455
484
|
### Owned attachments
|
|
456
485
|
|
|
457
|
-
The owned attachment APIs in this section are not included in the published 0.1.1 package.
|
|
458
|
-
|
|
459
486
|
Use `Attachment.from_bytes(data)` for immutable Python `bytes`, or
|
|
460
|
-
`Attachment.lazy(async_loader, aclose=async_cleanup)` to defer reading.
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
`
|
|
470
|
-
`await result.
|
|
471
|
-
for unopened sources too.
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
The default accepted size is 4 MiB and lazy loading timeout is 30 seconds
|
|
475
|
-
|
|
476
|
-
must bound their own allocations. `Attachment(async_stream, ...)`
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
487
|
+
`Attachment.lazy(async_loader, aclose=async_cleanup)` to defer reading. Put
|
|
488
|
+
`attachment` directly in an attachment part's `body`. Named `Hooks` methods
|
|
489
|
+
accept both dictionary inputs and typed event inputs with the same owner.
|
|
490
|
+
For a custom `OwnedContentSource`, use the host-only `content.OwnedAttachment(source)` wrapper in an attachment part's body. This wrapper does not read the source and is not a wire value. The SDK transfers and closes its source under the same invocation ownership rules.
|
|
491
|
+
|
|
492
|
+
Metadata belongs on the part. See the runnable
|
|
493
|
+
[file attachment example](examples/file_attachment.py).
|
|
494
|
+
|
|
495
|
+
Dispatch transfers ownership once. Successful results retain the exact owner,
|
|
496
|
+
including an unread loader, independently of `Hooks` shutdown. Callers close
|
|
497
|
+
returned content owners with `async with result:` or `await result.aclose()`.
|
|
498
|
+
Cleanup runs for unopened sources too. Create a new attachment for each
|
|
499
|
+
invocation; eager attachments can share the same immutable `bytes` object.
|
|
500
|
+
|
|
501
|
+
The default accepted size is 4 MiB and lazy loading timeout is 30 seconds.
|
|
502
|
+
Constructors accept `max_bytes`; lazy construction accepts `timeout`. Loaders
|
|
503
|
+
must bound their own allocations. `Attachment(async_stream, ...)` supports
|
|
504
|
+
bounded `read(size)`/`receive(max_bytes)` streams. Stream cleanup is shielded and
|
|
505
|
+
bounded to one second. Cancellation and failure never retry a loader. Mutable
|
|
506
|
+
buffers are rejected, and reads return immutable bytes.
|
|
507
|
+
|
|
508
|
+
Selected remote binary delivery requires an authorized uploader through
|
|
509
|
+
`uploads={backend_id: async_upload}`. Each uploader receives the exact owner's
|
|
510
|
+
immutable bytes and returns a committed receipt with `ref`, `size`, and `sha256`.
|
|
511
|
+
The SDK validates the receipt against the actual bytes before sending the event.
|
|
512
|
+
Upload callbacks remain caller-authorized and scoped to their backend; event
|
|
513
|
+
credentials do not authorize uploads to another destination.
|
|
514
|
+
|
|
515
|
+
`Hooks(..., max_concurrent_uploads=8)` bounds concurrent binary uploads.
|
|
516
|
+
The value must be a positive integer. Before serial interception
|
|
517
|
+
begins, the SDK prepares uploads for body-selected attachment slots in all
|
|
518
|
+
matched subscriptions, including observers. One attachment owner is read once
|
|
519
|
+
for multiple subscribers. Metadata, omit, and unmatched selections do not read
|
|
520
|
+
or upload its bytes. Result reads and metadata-only delivery require no uploader.
|
|
521
|
+
|
|
522
|
+
Only confirmed receipts are reused, for the exact attachment owner and backend
|
|
523
|
+
ID/subscription index. An uncalled interceptor receives a settled observation
|
|
524
|
+
with its confirmed reference after denial or stop. Upload failures are handled by
|
|
525
|
+
the affected subscription's failure policy when its delivery is reached; they
|
|
526
|
+
do not prevent independent destinations from preparing their uploads. An upload
|
|
527
|
+
can commit even if an earlier interceptor later removes the attachment or halts
|
|
528
|
+
the chain. Receiver applications manage separate storage durability and retention.
|
|
529
|
+
Cancellation cancels and joins pending upload work and closes invocation-owned
|
|
530
|
+
sources. Overlapping reads join one materialization and receive the same immutable
|
|
531
|
+
bytes or terminal error. Cancelling one reader leaves materialization active for
|
|
532
|
+
other readers; the last cancelled reader or owner closure cancels and joins it.
|
|
533
|
+
The attachment owns its single pending materialization independently of readers.
|
|
534
|
+
A cancelled reader returns and releases its upload permit while another reader
|
|
535
|
+
continues. The last reader or owner closure joins cleanup; completed workers are
|
|
536
|
+
not retained.
|
|
537
|
+
|
|
538
|
+
Attachments are the sole byte owners. Binary/file editing is not supported.
|
|
501
539
|
Results are invocation-local, not session archives.
|
|
540
|
+
|
|
541
|
+
### Advanced validation and dispatch
|
|
542
|
+
|
|
543
|
+
`runtime.Validator` validates bundled canonical schemas and structural codecs. It does not establish request correlation, effect grants, host approval, or permission to execute. Use `Hooks` for complete interception admission.
|
|
544
|
+
|
|
545
|
+
`server.hooks.Engine` is the framework-neutral backend dispatcher. Supply an explicit validator or static harness manifest when needed. Engine callbacks return protocol effects; the receiving harness validates authority and settles them atomically.
|
|
546
|
+
|
|
547
|
+
Host session counters (`turns`, `modelRequests`, `toolCalls`, `inputTokens`, `outputTokens`, and extra counter names) are exact nonnegative integers. The SDK preserves supplied values and does not infer counters from events.
|
|
@@ -42,6 +42,7 @@ Capability builders are immutable: reusing a base declaration does not widen it.
|
|
|
42
42
|
|
|
43
43
|
Runnable, statically checked consumer programs live in [`examples/`](examples):
|
|
44
44
|
|
|
45
|
+
- `value_codecs.py`: independent argument/result/provenance codecs, admission-time invariants, and a form schema derived from its answer type.
|
|
45
46
|
- `typed_tool.py`: JSON registration, generated tool input, typed grants, initial state, real in-process ASGI handler, borrowed HTTP client, and explicit application decoding.
|
|
46
47
|
- `common_cases.py`: rewrite/allow, deny, invalid application shape, host policy, occurrence narrowing, native initial state, and fail-open/fail-closed reports. The host operation is an in-memory execution recorder, not a shell.
|
|
47
48
|
- `stdio_tool.py` / `stdio_backend.py`: a real owned backend process using the same public Handler, denial, and child cleanup.
|
|
@@ -58,9 +59,15 @@ Generated constructors build JSON-shaped protocol values. Generated `TypedDict`
|
|
|
58
59
|
|
|
59
60
|
Capabilities explicitly grant effects and target operations. Supply event capabilities at harness construction; a per-occurrence capability override can narrow them, never widen them. `initial_state` carries the host's native prior decision for one occurrence. A hook-supplied result is a candidate, not an assertion that an operation executed. The host owns its ordinary input validation and actual execution.
|
|
60
61
|
|
|
62
|
+
Supply `input_codec=`, `result_codec=`, and optional `provenance_codec=` to a named hook method or `dispatch` to declare caller types. Complete staged values are decoded and validated before publication. Object merge is shallow: omitted fields remain unchanged, literal null is retained, and nested values replace whole values. An invalid response publishes none of its effects; earlier accepted responses remain visible to later interceptors. A codec must preserve the wire data it receives, including opaque extra fields. Lossy serialization rejects admission rather than dropping fields.
|
|
63
|
+
|
|
64
|
+
`result.decoded_input` exposes the declared argument value. `result.decoded_candidate` is an application `Candidate[Result, Provenance]`, or `None` when there is no candidate. Its value can itself be `None`; optional provenance is omitted from `candidate.to_dict()` when absent. Raw `input` and `candidate` views remain detached snapshots. For explicitly selected opaque host slots, use `event_codecs={("params",): codec}` with event-relative paths. Unspecified native, provider, task, permission, and discovery values are not interpreted.
|
|
65
|
+
|
|
66
|
+
For forms, construct `PydanticFormContract(TypeAdapter(Answer))` from one answer declaration. `request(message)` derives the restricted MCP schema; `result_codec()` decodes the complete MCP result and its accepted answer while preserving opaque result metadata. Unsupported shapes and schema constraints are rejected during construction. Decline, cancel, and URL results have no form content. See [`examples/value_codecs.py`](examples/value_codecs.py).
|
|
67
|
+
|
|
61
68
|
After settlement, explicitly decode effective input with `result.decode_input(codec)`. A codec implements `encode(value)` and `decode(json_value)`; `IdentityCodec` copies JSON. The optional `integrations.pydantic.PydanticCodec` accepts a Pydantic `TypeAdapter`. A decoding error does not retroactively reject protocol effects: retain the settled result, raw effective input, accepted responses, and diagnostics, and report host-level rejection without executing.
|
|
62
69
|
|
|
63
|
-
`result.permission` is the generated `Permission` enum (`NONE`, `ALLOW`, `ASK`, `DENY`) read from canonical settlement, not an independent decision engine. `NONE` is not approval; `ASK` needs host approval; interruption never authorizes execution. `result.input` is detached accepted JSON
|
|
70
|
+
`result.permission` is the generated `Permission` enum (`NONE`, `ALLOW`, `ASK`, `DENY`) read from canonical settlement, not an independent decision engine. `NONE` is not approval; `ASK` needs host approval; interruption never authorizes execution. `result.input` is detached accepted JSON. Declared codecs validate accepted modifications; without a declared codec, the host supplies application validation. `decode_input(codec)` remains an explicit, post-settlement host check. Candidate, canonical event, accepted responses, and diagnostics remain accessible.
|
|
64
71
|
|
|
65
72
|
`state.initial(Permission.ALLOW)` represents a native decision **already made for this exact occurrence**, not an authorization shortcut. Its default candidate is absent (`null`); `state.Candidate(value=None)` instead means a supplied JSON-null result. Optional typed provenance records facts, not authenticated identity or execution proof. Canonical modifications still invalidate candidates/approval as specified.
|
|
66
73
|
|
|
@@ -103,36 +110,69 @@ Observation is one-way. It does not acquire effect authority and never waits for
|
|
|
103
110
|
|
|
104
111
|
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
112
|
|
|
106
|
-
##
|
|
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.
|
|
109
|
-
|
|
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.
|
|
113
|
+
## Inline messages and specialized boundaries
|
|
124
114
|
|
|
125
|
-
|
|
115
|
+
Model-visible inputs contain canonical messages with a `role` and ordered
|
|
116
|
+
`parts`. Supported roles are `system`, `developer`, `user`, `assistant`, and
|
|
117
|
+
`tool`. A text part has `kind="text"`, `mediaType="text/plain"`, and inline
|
|
118
|
+
`text`. Serialized JSON is ordinary text. An attachment part has
|
|
119
|
+
`kind="attachment"`, a non-text/non-JSON media type, and an immutable binary
|
|
120
|
+
body. Construction supplies missing message/part IDs and marks them
|
|
121
|
+
`synthesized=True`; explicit IDs remain stable.
|
|
126
122
|
|
|
127
|
-
|
|
123
|
+
```python
|
|
124
|
+
from agenthooksprotocol import Attachment
|
|
125
|
+
|
|
126
|
+
input = {
|
|
127
|
+
"trigger": "manual",
|
|
128
|
+
"items": [{"role": "user", "parts": [
|
|
129
|
+
{"kind": "text", "text": "Review this report."},
|
|
130
|
+
{"kind": "attachment", "mediaType": "application/pdf",
|
|
131
|
+
"body": Attachment.from_bytes(pdf_bytes)},
|
|
132
|
+
]}],
|
|
133
|
+
}
|
|
134
|
+
result = await hooks.context_compact_before(input, uploads=authorized_uploads)
|
|
135
|
+
async with result:
|
|
136
|
+
data = await result.attachments.read("context.compact.before.items_parts[0][1]")
|
|
137
|
+
```
|
|
128
138
|
|
|
129
|
-
|
|
139
|
+
Named hook methods also accept canonical dictionaries with the same direct
|
|
140
|
+
attachment bodies. The runtime collects sources and creates receiver-specific
|
|
141
|
+
references internally. Construction and metadata/omit projection do not read
|
|
142
|
+
or upload attachments. Only schema-owned slots are projected; native payloads,
|
|
143
|
+
extensions, and application arguments are opaque. Selection is a ceiling for
|
|
144
|
+
each receiver's view: metadata/omit views contain no inline text or attachment
|
|
145
|
+
references. Independent receivers apply their own content selections.
|
|
146
|
+
|
|
147
|
+
Compaction `instructions` and `summary` are canonical text-part lists.
|
|
148
|
+
Replacement substitutes the list; merge appends in order. Model-visible message
|
|
149
|
+
list targets (`prompt`, `request`, `response`, and tool `output`) have the same
|
|
150
|
+
list operations. Tool output edits operate on `tool.after.items`; application
|
|
151
|
+
structures are serialized as text parts. Standalone `content` edits require the
|
|
152
|
+
boundary context: `user.message.outbound` uses a message list, and accepted form
|
|
153
|
+
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
|
|
154
|
+
atomically, and each serial receiver sees the committed state. Changed
|
|
155
|
+
compaction instructions invalidate an earlier supplied summary.
|
|
156
|
+
|
|
157
|
+
MCP elicitation `request` and `result` contain selected text parts whose `text`
|
|
158
|
+
is the serialized complete MCP JSON payload. The SDK validates parsed payloads,
|
|
159
|
+
mode grants, form answers, and correlation without inserting defaults. Result
|
|
160
|
+
edits require `original_request=` with the original canonical intercept request
|
|
161
|
+
snapshot; answer-object merge is shallow. Elicitation request bodies are immutable
|
|
162
|
+
to modification effects. No text/JSON upload callback is needed. Metadata and omit selections
|
|
163
|
+
cannot authorize body-dependent effects.
|
|
164
|
+
|
|
165
|
+
For custom transports, `begin(canonical_request, value_codecs=ValueCodecs(...))` separates acquisition,
|
|
166
|
+
cancellation, and acceptance; `exchange(canonical_request)` uses the same
|
|
167
|
+
settlement engine. Elicitation correlation can be supplied through
|
|
168
|
+
`ContentContext(original_request=..., bindings={"content": ("elicitation", "result")},
|
|
169
|
+
principal=authenticated_identity)` and `await pending.accept_content()`.
|
|
130
170
|
|
|
131
171
|
## Generated models and provenance
|
|
132
172
|
|
|
133
173
|
Semantic namespaces (`event`, `tool`, `effect`, `capability`, and registration models) and all named event boundaries come from schema metadata, not a selected handwritten helper list. Constructors may supply fixed protocol literals; parsers never repair missing input. Open fields are retained for forward-compatible JSON round trips.
|
|
134
174
|
|
|
135
|
-
The low-level `generated` module exports the full schema's typed models, `parse_<root>` / `encode_<root>` codecs, `PROTOCOL_VERSION`, `SCHEMA_REVISION`, and JSON types. `ahp-codegen.lock.json` records immutable generator provenance. Change canonical schemas and generator source in the protocol repository rather than editing generated files.
|
|
175
|
+
The `wire` namespace exposes canonical JSON-shaped request, notification, effect, and response types with structural codecs. Server callbacks use these raw wire contracts. The low-level `generated` module exports the full schema's typed models, `parse_<root>` / `encode_<root>` codecs, `PROTOCOL_VERSION`, `SCHEMA_REVISION`, and JSON types. `ahp-codegen.lock.json` records immutable generator provenance. Change canonical schemas and generator source in the protocol repository rather than editing generated files.
|
|
136
176
|
|
|
137
177
|
## Development
|
|
138
178
|
|
|
@@ -150,12 +190,12 @@ Integration tests also use a matching sibling `../agent-hooks-protocol` checkout
|
|
|
150
190
|
Generated facade constructors retain nested model types through schema
|
|
151
191
|
compositions. MCP connection `gaps` parameters accept lists of typed gap models,
|
|
152
192
|
and HTTP, SSE, stdio, and custom connection objects expose typed location and gap
|
|
153
|
-
attributes. `ModelVisibleItem`
|
|
193
|
+
attributes. `ModelVisibleItem` is a canonical message with a typed role and ordered parts.
|
|
154
194
|
|
|
155
195
|
Facade objects are mappings with read-only attributes.
|
|
156
196
|
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.
|
|
197
|
+
presence. Omitted and explicit-empty response effects are both neutral; decoded effects provide an empty list without modifying the source wire object. Attributes conflicting with mapping methods use a trailing underscore
|
|
198
|
+
(such as `items_`), so `dict.items()` keeps working. Raw wire parsers return lossless mappings. `response.response_for_request(event_type, raw)` returns the generated contextual response selected by the originating event type, not by payload shape.
|
|
159
199
|
|
|
160
200
|
Use generated nested models for typed constructor arguments. Wire constructors, dictionary decoding, and runtime parsing
|
|
161
201
|
check location/evidence alternatives and other structural schema constraints. Application
|
|
@@ -189,13 +229,7 @@ the same structural descriptor engine as generated `parse_*` functions. The
|
|
|
189
229
|
original composition is retained, including required members, explicit nulls,
|
|
190
230
|
literals, closed enums, forbidden combinations, and union ambiguity. This is the
|
|
191
231
|
SDK's structural contract, not complete contextual protocol validation.
|
|
192
|
-
`from_dict`
|
|
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.
|
|
232
|
+
`from_dict` checks complete wire values and preserves optional-field presence. Decoded known nested values expose the same attributes as constructed values, such as `connection.gaps[0].reason`. Extension and application bags remain intact and do not acquire effect or attachment authority.
|
|
199
233
|
|
|
200
234
|
Invalid wire models raise `ValueError`; validation failures carry `result`,
|
|
201
235
|
`diagnostics`, and `raw` attributes. Use `parse_*` when you need the full result on
|
|
@@ -213,12 +247,7 @@ objects are validated at the dispatch/parse boundary. Direct dictionary
|
|
|
213
247
|
mutation, standard-library `json.loads`, TypedDict annotations, and type assertions
|
|
214
248
|
are not SDK validation entrypoints. Re-parse after manually mutating a mapping.
|
|
215
249
|
|
|
216
|
-
|
|
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`.
|
|
250
|
+
Nested model attributes expose declared protocol fields. For example, `capability.Capabilities(effects=["modify"], modify={}).modify.input` is `None` when that grant is absent. Family queries are available on generic and per-occurrence capability models.
|
|
222
251
|
|
|
223
252
|
Constructor aliases cannot silently overwrite wire keys. Supplying both
|
|
224
253
|
`tool_name=` and `toolName=` raises `TypeError`, even if the values agree. For
|
|
@@ -230,48 +259,65 @@ keys such as `toolName` and `tool_name` remain distinct wire/extension keys.
|
|
|
230
259
|
|
|
231
260
|
### Owned attachments
|
|
232
261
|
|
|
233
|
-
The owned attachment APIs in this section are not included in the published 0.1.1 package.
|
|
234
|
-
|
|
235
262
|
Use `Attachment.from_bytes(data)` for immutable Python `bytes`, or
|
|
236
|
-
`Attachment.lazy(async_loader, aclose=async_cleanup)` to defer reading.
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
`
|
|
246
|
-
`await result.
|
|
247
|
-
for unopened sources too.
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
The default accepted size is 4 MiB and lazy loading timeout is 30 seconds
|
|
251
|
-
|
|
252
|
-
must bound their own allocations. `Attachment(async_stream, ...)`
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
263
|
+
`Attachment.lazy(async_loader, aclose=async_cleanup)` to defer reading. Put
|
|
264
|
+
`attachment` directly in an attachment part's `body`. Named `Hooks` methods
|
|
265
|
+
accept both dictionary inputs and typed event inputs with the same owner.
|
|
266
|
+
For a custom `OwnedContentSource`, use the host-only `content.OwnedAttachment(source)` wrapper in an attachment part's body. This wrapper does not read the source and is not a wire value. The SDK transfers and closes its source under the same invocation ownership rules.
|
|
267
|
+
|
|
268
|
+
Metadata belongs on the part. See the runnable
|
|
269
|
+
[file attachment example](examples/file_attachment.py).
|
|
270
|
+
|
|
271
|
+
Dispatch transfers ownership once. Successful results retain the exact owner,
|
|
272
|
+
including an unread loader, independently of `Hooks` shutdown. Callers close
|
|
273
|
+
returned content owners with `async with result:` or `await result.aclose()`.
|
|
274
|
+
Cleanup runs for unopened sources too. Create a new attachment for each
|
|
275
|
+
invocation; eager attachments can share the same immutable `bytes` object.
|
|
276
|
+
|
|
277
|
+
The default accepted size is 4 MiB and lazy loading timeout is 30 seconds.
|
|
278
|
+
Constructors accept `max_bytes`; lazy construction accepts `timeout`. Loaders
|
|
279
|
+
must bound their own allocations. `Attachment(async_stream, ...)` supports
|
|
280
|
+
bounded `read(size)`/`receive(max_bytes)` streams. Stream cleanup is shielded and
|
|
281
|
+
bounded to one second. Cancellation and failure never retry a loader. Mutable
|
|
282
|
+
buffers are rejected, and reads return immutable bytes.
|
|
283
|
+
|
|
284
|
+
Selected remote binary delivery requires an authorized uploader through
|
|
285
|
+
`uploads={backend_id: async_upload}`. Each uploader receives the exact owner's
|
|
286
|
+
immutable bytes and returns a committed receipt with `ref`, `size`, and `sha256`.
|
|
287
|
+
The SDK validates the receipt against the actual bytes before sending the event.
|
|
288
|
+
Upload callbacks remain caller-authorized and scoped to their backend; event
|
|
289
|
+
credentials do not authorize uploads to another destination.
|
|
290
|
+
|
|
291
|
+
`Hooks(..., max_concurrent_uploads=8)` bounds concurrent binary uploads.
|
|
292
|
+
The value must be a positive integer. Before serial interception
|
|
293
|
+
begins, the SDK prepares uploads for body-selected attachment slots in all
|
|
294
|
+
matched subscriptions, including observers. One attachment owner is read once
|
|
295
|
+
for multiple subscribers. Metadata, omit, and unmatched selections do not read
|
|
296
|
+
or upload its bytes. Result reads and metadata-only delivery require no uploader.
|
|
297
|
+
|
|
298
|
+
Only confirmed receipts are reused, for the exact attachment owner and backend
|
|
299
|
+
ID/subscription index. An uncalled interceptor receives a settled observation
|
|
300
|
+
with its confirmed reference after denial or stop. Upload failures are handled by
|
|
301
|
+
the affected subscription's failure policy when its delivery is reached; they
|
|
302
|
+
do not prevent independent destinations from preparing their uploads. An upload
|
|
303
|
+
can commit even if an earlier interceptor later removes the attachment or halts
|
|
304
|
+
the chain. Receiver applications manage separate storage durability and retention.
|
|
305
|
+
Cancellation cancels and joins pending upload work and closes invocation-owned
|
|
306
|
+
sources. Overlapping reads join one materialization and receive the same immutable
|
|
307
|
+
bytes or terminal error. Cancelling one reader leaves materialization active for
|
|
308
|
+
other readers; the last cancelled reader or owner closure cancels and joins it.
|
|
309
|
+
The attachment owns its single pending materialization independently of readers.
|
|
310
|
+
A cancelled reader returns and releases its upload permit while another reader
|
|
311
|
+
continues. The last reader or owner closure joins cleanup; completed workers are
|
|
312
|
+
not retained.
|
|
313
|
+
|
|
314
|
+
Attachments are the sole byte owners. Binary/file editing is not supported.
|
|
277
315
|
Results are invocation-local, not session archives.
|
|
316
|
+
|
|
317
|
+
### Advanced validation and dispatch
|
|
318
|
+
|
|
319
|
+
`runtime.Validator` validates bundled canonical schemas and structural codecs. It does not establish request correlation, effect grants, host approval, or permission to execute. Use `Hooks` for complete interception admission.
|
|
320
|
+
|
|
321
|
+
`server.hooks.Engine` is the framework-neutral backend dispatcher. Supply an explicit validator or static harness manifest when needed. Engine callbacks return protocol effects; the receiving harness validates authority and settles them atomically.
|
|
322
|
+
|
|
323
|
+
Host session counters (`turns`, `modelRequests`, `toolCalls`, `inputTokens`, `outputTokens`, and extra counter names) are exact nonnegative integers. The SDK preserves supplied values and does not infer counters from events.
|