awp-python 0.1.0a3__tar.gz → 0.1.0a5__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 (111) hide show
  1. awp_python-0.1.0a5/AGENTS.md +58 -0
  2. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/CHANGELOG.md +18 -8
  3. awp_python-0.1.0a5/CLAUDE.md +1 -0
  4. awp_python-0.1.0a5/PKG-INFO +130 -0
  5. awp_python-0.1.0a5/README.md +109 -0
  6. awp_python-0.1.0a5/conformance/README.md +53 -0
  7. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/conformance/core-agent-lockstep.json +11 -11
  8. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/conformance/core-agent-streaming.json +4 -4
  9. awp_python-0.1.0a5/conformance/frames.json +4 -0
  10. awp_python-0.1.0a5/conformance/manifest-lockstep.json +157 -0
  11. awp_python-0.1.0a5/conformance/manifest-streaming.json +160 -0
  12. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/pyproject.toml +5 -6
  13. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/aio.py +24 -27
  14. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/client.py +86 -32
  15. {awp_python-0.1.0a3/src/awp_sim → awp_python-0.1.0a5/src/awp}/demo.py +31 -7
  16. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/schema.py +7 -9
  17. awp_python-0.1.0a5/tests/test_aio.py +202 -0
  18. awp_python-0.1.0a5/tests/test_client.py +87 -0
  19. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/tests/test_client_traces.py +1 -3
  20. awp_python-0.1.0a5/tests/test_evidence.py +43 -0
  21. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/tests/test_schema.py +12 -9
  22. awp_python-0.1.0a5/tests/world.py +348 -0
  23. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/uv.lock +1 -1
  24. awp_python-0.1.0a3/PKG-INFO +0 -130
  25. awp_python-0.1.0a3/README.md +0 -109
  26. awp_python-0.1.0a3/conformance/README.md +0 -86
  27. awp_python-0.1.0a3/conformance/core-world-lockstep.json +0 -1669
  28. awp_python-0.1.0a3/conformance/core-world-streaming.json +0 -1671
  29. awp_python-0.1.0a3/conformance/features-lockstep.json +0 -1671
  30. awp_python-0.1.0a3/conformance/features-streaming.json +0 -1671
  31. awp_python-0.1.0a3/scripts/check_traces.py +0 -57
  32. awp_python-0.1.0a3/src/awp_sim/__init__.py +0 -8
  33. awp_python-0.1.0a3/src/awp_sim/__main__.py +0 -5
  34. awp_python-0.1.0a3/src/awp_sim/arm.py +0 -177
  35. awp_python-0.1.0a3/src/awp_sim/audit.py +0 -138
  36. awp_python-0.1.0a3/src/awp_sim/cli.py +0 -238
  37. awp_python-0.1.0a3/src/awp_sim/config.py +0 -226
  38. awp_python-0.1.0a3/src/awp_sim/loopback.py +0 -222
  39. awp_python-0.1.0a3/src/awp_sim/py.typed +0 -0
  40. awp_python-0.1.0a3/src/awp_sim/recorder.py +0 -72
  41. awp_python-0.1.0a3/src/awp_sim/replay.py +0 -134
  42. awp_python-0.1.0a3/src/awp_sim/scenarios.py +0 -427
  43. awp_python-0.1.0a3/src/awp_sim/server.py +0 -336
  44. awp_python-0.1.0a3/src/awp_sim/session.py +0 -173
  45. awp_python-0.1.0a3/src/awp_sim/world.py +0 -1679
  46. awp_python-0.1.0a3/tests/helpers.py +0 -62
  47. awp_python-0.1.0a3/tests/test_audit.py +0 -62
  48. awp_python-0.1.0a3/tests/test_cli.py +0 -26
  49. awp_python-0.1.0a3/tests/test_e2e.py +0 -118
  50. awp_python-0.1.0a3/tests/test_evidence.py +0 -224
  51. awp_python-0.1.0a3/tests/test_features.py +0 -372
  52. awp_python-0.1.0a3/tests/test_regressions.py +0 -243
  53. awp_python-0.1.0a3/tests/test_scenarios.py +0 -33
  54. awp_python-0.1.0a3/tests/test_streams.py +0 -156
  55. awp_python-0.1.0a3/tests/test_world.py +0 -513
  56. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/.gitignore +0 -0
  57. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/.gitmodules +0 -0
  58. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/.python-version +0 -0
  59. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/LICENSE +0 -0
  60. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/scripts/sync_spec.py +0 -0
  61. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/__init__.py +0 -0
  62. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/lifecycle.json +0 -0
  63. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/action-cancel-result.schema.json +0 -0
  64. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/action-ref.schema.json +0 -0
  65. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/action-schema.schema.json +0 -0
  66. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/action-status.schema.json +0 -0
  67. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/action-submit-result.schema.json +0 -0
  68. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/action-submit.schema.json +0 -0
  69. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/agent-manifest.schema.json +0 -0
  70. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/approval-requested.schema.json +0 -0
  71. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/approval-respond.schema.json +0 -0
  72. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/common.schema.json +0 -0
  73. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/embodiment.schema.json +0 -0
  74. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/empty-result.schema.json +0 -0
  75. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/error.schema.json +0 -0
  76. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/frame-inline.schema.json +0 -0
  77. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/frame-tree.schema.json +0 -0
  78. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/obs-report.schema.json +0 -0
  79. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/observation-channel.schema.json +0 -0
  80. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/ping-result.schema.json +0 -0
  81. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/ping.schema.json +0 -0
  82. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/profiles/gui-actions.schema.json +0 -0
  83. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/reset-result.schema.json +0 -0
  84. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/reset.schema.json +0 -0
  85. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/restore.schema.json +0 -0
  86. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/safety-policy.schema.json +0 -0
  87. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/session-open.schema.json +0 -0
  88. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/session-ready.schema.json +0 -0
  89. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/session-resume.schema.json +0 -0
  90. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/session-state.schema.json +0 -0
  91. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/session-telemetry.schema.json +0 -0
  92. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/session-transfer-result.schema.json +0 -0
  93. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/session-transfer.schema.json +0 -0
  94. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/snapshot-result.schema.json +0 -0
  95. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/subscribe-result.schema.json +0 -0
  96. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/subscribe.schema.json +0 -0
  97. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/task-update.schema.json +0 -0
  98. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/tick-result.schema.json +0 -0
  99. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/tick.schema.json +0 -0
  100. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/unsubscribe.schema.json +0 -0
  101. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/world-event.schema.json +0 -0
  102. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/_spec/schemas/world-manifest.schema.json +0 -0
  103. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/clock.py +0 -0
  104. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/errors.py +0 -0
  105. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/frames.py +0 -0
  106. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/jsonrpc.py +0 -0
  107. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/lifecycle.py +0 -0
  108. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/src/awp/py.typed +0 -0
  109. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/tests/__init__.py +0 -0
  110. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/tests/conftest.py +0 -0
  111. {awp_python-0.1.0a3 → awp_python-0.1.0a5}/tests/test_frames.py +0 -0
@@ -0,0 +1,58 @@
1
+ # AGENTS.md
2
+
3
+ awp-python is the Python agent SDK for the Agent World Protocol, published on PyPI as `awp-python`. `awp.ClientConnection` is the protocol as a sans-IO state machine, and `awp.aio.AsyncClient` drives it over a WebSocket. It targets one specification revision, which is pinned as the `spec/` submodule and named by `awp.SPEC_REVISION`.
4
+
5
+ ## Checks
6
+
7
+ ```bash
8
+ git submodule update --init
9
+ uv sync
10
+ uv run ruff check && uv run ruff format --check
11
+ uv run mypy
12
+ uv run pytest --cov
13
+ uv run python scripts/sync_spec.py --check
14
+ ```
15
+
16
+ CI runs all of these on Python 3.11–3.13. It also runs awp-conformance against `awp-demo` in both time models, and fails unless the claim is AWP-conformant.
17
+
18
+ ## Code
19
+
20
+ - Protocol behavior belongs in `ClientConnection`. `AsyncClient` adds only transport, timers, and awaiting.
21
+ - Cite a requirement ID (`AWP-XXX-NNN`) where the code implements it. Otherwise, comment only what the code can't say.
22
+ - Tests run against the scripted world in `tests/world.py`, not against awp-sim.
23
+ - `src/awp/_spec/` is copied from `spec/` by `scripts/sync_spec.py`. Never edit it by hand.
24
+
25
+ ## Conformance
26
+
27
+ `conformance/` holds the reports behind the README's claim, plus the evidence for their `manual` rows. The reports must come from the suite version CI runs. After bumping that pin in `.github/workflows/ci.yml`, regenerate them with the commands in `conformance/README.md`. Then update the versions named there and in the README.
28
+
29
+ ## Commits and pull requests
30
+
31
+ - Branch from `main` and open a pull request. Merge once CI passes.
32
+ - Write the title as one plain sentence in sentence case, with no trailing period, saying what changed: `Run awp-conformance 0.1.0a4 in CI`. When the change is part of a release, end it with the version: `(0.1.0a4)`.
33
+ - Add a body only when the title can't carry the reason: one or two short sentences.
34
+ - Write commits the way a person on the project would. No `Co-Authored-By` trailers, no "Generated with" lines, and no other mention of AI tools, in commits or in PRs.
35
+ - The PR title matches the commit title, and the description is a few lines at most.
36
+
37
+ ## Moving to a new draft revision
38
+
39
+ 1. Check out the revision's tag (`spec-v0.1-draft.N`) in `spec/`.
40
+ 2. Run `uv run python scripts/sync_spec.py`.
41
+ 3. Update `SPEC_REVISION` in `src/awp/__init__.py`.
42
+ 4. Fix whatever the tests report.
43
+
44
+ ## Releasing
45
+
46
+ 1. In a pull request:
47
+ - Set the version with `uv version <version>`, for example `0.1.0a5`.
48
+ - Add a `CHANGELOG.md` entry that starts "Targets specification revision `0.1-draft.N`." and lists the user-visible changes.
49
+ 2. Once it is merged, tag the merge commit and push the tag:
50
+
51
+ ```bash
52
+ git tag -a v0.1.0a5 -m "awp-python 0.1.0a5 (AWP 0.1-draft.N)"
53
+ git push origin v0.1.0a5
54
+ ```
55
+
56
+ 3. `release.yml` checks that the tag matches the version, then builds the package. It publishes through PyPI trusted publishing once someone approves the `pypi` environment. A maintainer gives that approval. Agents never approve deployments, and never publish with a token.
57
+
58
+ awp-sim depends on awp-python, so release awp-python first when both change.
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.0a5
4
+
5
+ Targets specification revision `0.1-draft.9`.
6
+
7
+ - `open_session` takes `embodiments` to bind several embodiments of one `multi_bind_group`, and `submit` then requires `embodiment_id` (AWP-EMB-005).
8
+ - A response that arrives after `AsyncClient.call` times out is still processed, so its `status_seq` and tick are not lost. `call`, `wait_for`, and `advance` accept `timeout=None`.
9
+ - `advance()` refuses while a `world.tick` is pending: under a `barrier` it may wait for other sessions (AWP-TIM-012). In lockstep the heartbeat timer runs even with `heartbeat=False`.
10
+ - Closing a session stops its heartbeat acknowledgements, stream connection, and resumption. A new session starts from none of the last one's channel, sequencing, or frame state, and `AsyncClient.latest` is cleared.
11
+ - `ClientConnection` gains `lockstep`, `heartbeat_s`, and `embodiments`. `forget` is gone.
12
+
13
+ ## 0.1.0a4
14
+
15
+ Targets specification revision `0.1-draft.9`.
16
+
17
+ - The reference world moves to its own package, [awp-sim](https://github.com/Hyperduality/awp-sim), and awp-python is the agent SDK alone.
18
+ - `awp-sim demo` becomes `awp-demo` (`awp.demo`). It reads `$AWP_URL` and `$AWP_TOKEN` when its flags are absent.
19
+ - Tests run against a scripted world over WebSockets instead of awp-sim, and CI runs awp-conformance against `awp-demo` in both time models.
20
+
3
21
  ## 0.1.0a3
4
22
 
5
23
  Targets specification revision `0.1-draft.9`.
@@ -25,13 +43,5 @@ First release, targeting specification revision `0.1-draft.7`.
25
43
  - The `ws` stream binding: `awp-sim serve --stream-binding ws` offers frames on a stream connection in the binary envelope, and `AsyncClient` uses it when offered, re-establishing it if only the stream drops (AWP-TRN-010..013).
26
44
  - Beyond Core, each enabled with `awp-sim serve --features`: `task`, `approval` (the `park` action; approvers connect with `--approver-token`), `blend`, `transfer`, `sim` (seeding, snapshot and restore, replay bundles with `--replay-dir`, and `awp-sim replay`), and `servo` (a command channel). `ClientConnection` gains `update_task`, `transfer`, `reset`, `snapshot`, `restore`, `respond_approval`, and `command`.
27
45
  - The lockstep session clock counts advances, so reset and restore, which now move the tick, never move it backward (AWP-TIM-013). Audit records hash frame payloads, not their base64 text.
28
-
29
46
  - `awp`: `ClientConnection.submit` refuses action types the session was not granted (AWP-AGT-003); `AsyncClient` treats three heartbeat intervals without a message from the world as a lost connection (AWP-SAF-002).
30
47
  - `awp_sim`: channels in modalities the agent did not declare are not granted (AWP-AGM-001); granted rates honor the agent's `max_obs_rate_hz` (AWP-AGM-002); a connection without a session is closed after at least 15 s (AWP-SES-012); `--max-duration-ms`. The demo agent does nothing when `move_to_pose` is not granted.
31
-
32
- ## 0.1.0.dev0 — unreleased
33
-
34
- First implementation, targeting specification revision `0.1-draft.5`.
35
-
36
- - `awp`: sans-IO `ClientConnection`, asyncio `AsyncClient`, binary and inline frame codec, canonical schema validation (receiver and sender forms), the lifecycle transition table, clock synchronization.
37
- - `awp_sim`: sans-IO world engine for lockstep and streaming, simulated arm, WebSocket server with bearer authentication, audit log, trace recorder, in-process loopback, twelve scripted scenarios, and the `awp-sim` command.
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -0,0 +1,130 @@
1
+ Metadata-Version: 2.5
2
+ Name: awp-python
3
+ Version: 0.1.0a5
4
+ Summary: Agent World Protocol agent SDK for Python.
5
+ Project-URL: Specification, https://www.agentworldprotocol.com
6
+ Project-URL: Source, https://github.com/Hyperduality/awp-python
7
+ Author: Hyperduality
8
+ License-Expression: Apache-2.0
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
16
+ Classifier: Typing :: Typed
17
+ Requires-Python: >=3.11
18
+ Requires-Dist: jsonschema>=4.23
19
+ Requires-Dist: websockets>=13.0
20
+ Description-Content-Type: text/markdown
21
+
22
+ # awp-python
23
+
24
+ The Python agent SDK for the [Agent World Protocol](https://www.agentworldprotocol.com). `ClientConnection` is the agent side of AWP as a sans-IO state machine, and `awp.aio.AsyncClient` drives it over a WebSocket.
25
+
26
+ It targets specification revision **`0.1-draft.9`**, pinned as the `spec/` submodule. This is an alpha, so the API will change along with the draft. The reference world, [awp-sim](https://github.com/Hyperduality/awp-sim), is a separate package built on this one.
27
+
28
+ ## Status
29
+
30
+ ![AWP: Core Agent, AWP-conformant against 0.1-draft.9](https://img.shields.io/badge/AWP-Core_Agent%2C_conformant_0.1--draft.9-555)
31
+
32
+ In both time models, the demo agent built on the client is **Core Agent: AWP-conformant against 0.1-draft.9**. [awp-conformance](https://github.com/Hyperduality/awp-conformance) reports no failure and nothing untested, and [`conformance/`](conformance/README.md) holds the reports and the evidence for their manual rows.
33
+
34
+ Implemented:
35
+
36
+ - lockstep and streaming sessions, with `world.tick` advances that wait for every per-tick channel;
37
+ - multi-bind sessions (`embodiments`);
38
+ - inline and `ws` stream bindings, and the binary frame codec (every spec vector);
39
+ - the action lifecycle, checked against the spec's transition table, and idempotent resubmission;
40
+ - heartbeats, clock synchronization, receiver reports, and resumption with replay and acknowledgement;
41
+ - canonical schema validation in receiver and sender forms;
42
+ - task, approval and standing approvals, transfer, reset, snapshots, and command channels.
43
+
44
+ Not implemented: stream bindings other than inline and `ws` (`webrtc`, `webtransport`, `shm`, `grpc`).
45
+
46
+ ## Install
47
+
48
+ ```bash
49
+ pip install --pre awp-python
50
+ ```
51
+
52
+ Requires Python 3.11+.
53
+
54
+ ## Using the client
55
+
56
+ With asyncio:
57
+
58
+ ```python
59
+ from awp import ClientConnection
60
+ from awp.aio import AsyncClient
61
+
62
+ conn = ClientConnection({"name": "my-agent", "version": "0.1.0", "vendor": "me"}, ["proprio/json"])
63
+ async with AsyncClient(conn, "ws://127.0.0.1:8710") as client:
64
+ await client.initialize()
65
+ await client.open_session("streaming", embodiment="arm_01", subscribe=["proprio"])
66
+ await client.wait_for(lambda e: "proprio" in client.latest)
67
+ record = await client.submit(
68
+ "move_to_pose",
69
+ {"pose": {"frame": "base", "p_m": [0.3, 0.2, 0.5], "q": [0, 0, 0, 1]}},
70
+ basis=client.latest["proprio"].frame, # the observation this intent rests on
71
+ valid_for_ms=200,
72
+ )
73
+ print((await client.wait_terminal(record.action_id)).state)
74
+ await client.close_session()
75
+ ```
76
+
77
+ Without asyncio, feed `ClientConnection` the decoded messages and send whatever it queues:
78
+
79
+ ```python
80
+ from awp import jsonrpc
81
+
82
+ events = conn.receive(jsonrpc.decode(text)) # typed events: ActionUpdated, FrameReceived, ...
83
+ for out in conn.outgoing(): # messages to send, in order
84
+ transport.send(jsonrpc.encode(out))
85
+ ```
86
+
87
+ The connection:
88
+
89
+ - tracks the action lifecycle against the spec's transition table;
90
+ - deduplicates replayed statuses;
91
+ - keeps the clock offset from heartbeats;
92
+ - reports any violation by the world as a `ProtocolViolation` event.
93
+
94
+ ## The demo agent
95
+
96
+ `awp-demo` is a scripted agent for awp-sim's arm. It moves, cancels a move, and triggers an envelope refusal. It rides out a lost connection by resuming the session, or by opening a new one if the world no longer holds it.
97
+
98
+ ```bash
99
+ pip install --pre awp-sim
100
+ awp-sim serve & # streaming world on ws://127.0.0.1:8710
101
+ awp-demo # or --url, --token ($AWP_URL, $AWP_TOKEN)
102
+ ```
103
+
104
+ ## Layout
105
+
106
+ ```
107
+ src/awp/ client protocol layer (sans-IO), asyncio adapter, frame codec, schemas, demo agent
108
+ src/awp/_spec/ schemas and lifecycle table bundled from spec/ (scripts/sync_spec.py)
109
+ tests/ unit tests, and a scripted world that serves over WebSockets
110
+ conformance/ conformance reports, the manifests they were run against, and evidence
111
+ spec/ agent-world-protocol, pinned at spec-v0.1-draft.9
112
+ ```
113
+
114
+ ## Development
115
+
116
+ ```bash
117
+ git clone --recurse-submodules https://github.com/Hyperduality/awp-python
118
+ cd awp-python
119
+ uv sync
120
+ uv run ruff check && uv run ruff format --check
121
+ uv run mypy
122
+ uv run pytest --cov
123
+ uv run python scripts/sync_spec.py --check
124
+ ```
125
+
126
+ [AGENTS.md](AGENTS.md) covers moving to a new draft revision and releasing.
127
+
128
+ ## License
129
+
130
+ Apache-2.0. See [LICENSE](LICENSE).
@@ -0,0 +1,109 @@
1
+ # awp-python
2
+
3
+ The Python agent SDK for the [Agent World Protocol](https://www.agentworldprotocol.com). `ClientConnection` is the agent side of AWP as a sans-IO state machine, and `awp.aio.AsyncClient` drives it over a WebSocket.
4
+
5
+ It targets specification revision **`0.1-draft.9`**, pinned as the `spec/` submodule. This is an alpha, so the API will change along with the draft. The reference world, [awp-sim](https://github.com/Hyperduality/awp-sim), is a separate package built on this one.
6
+
7
+ ## Status
8
+
9
+ ![AWP: Core Agent, AWP-conformant against 0.1-draft.9](https://img.shields.io/badge/AWP-Core_Agent%2C_conformant_0.1--draft.9-555)
10
+
11
+ In both time models, the demo agent built on the client is **Core Agent: AWP-conformant against 0.1-draft.9**. [awp-conformance](https://github.com/Hyperduality/awp-conformance) reports no failure and nothing untested, and [`conformance/`](conformance/README.md) holds the reports and the evidence for their manual rows.
12
+
13
+ Implemented:
14
+
15
+ - lockstep and streaming sessions, with `world.tick` advances that wait for every per-tick channel;
16
+ - multi-bind sessions (`embodiments`);
17
+ - inline and `ws` stream bindings, and the binary frame codec (every spec vector);
18
+ - the action lifecycle, checked against the spec's transition table, and idempotent resubmission;
19
+ - heartbeats, clock synchronization, receiver reports, and resumption with replay and acknowledgement;
20
+ - canonical schema validation in receiver and sender forms;
21
+ - task, approval and standing approvals, transfer, reset, snapshots, and command channels.
22
+
23
+ Not implemented: stream bindings other than inline and `ws` (`webrtc`, `webtransport`, `shm`, `grpc`).
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ pip install --pre awp-python
29
+ ```
30
+
31
+ Requires Python 3.11+.
32
+
33
+ ## Using the client
34
+
35
+ With asyncio:
36
+
37
+ ```python
38
+ from awp import ClientConnection
39
+ from awp.aio import AsyncClient
40
+
41
+ conn = ClientConnection({"name": "my-agent", "version": "0.1.0", "vendor": "me"}, ["proprio/json"])
42
+ async with AsyncClient(conn, "ws://127.0.0.1:8710") as client:
43
+ await client.initialize()
44
+ await client.open_session("streaming", embodiment="arm_01", subscribe=["proprio"])
45
+ await client.wait_for(lambda e: "proprio" in client.latest)
46
+ record = await client.submit(
47
+ "move_to_pose",
48
+ {"pose": {"frame": "base", "p_m": [0.3, 0.2, 0.5], "q": [0, 0, 0, 1]}},
49
+ basis=client.latest["proprio"].frame, # the observation this intent rests on
50
+ valid_for_ms=200,
51
+ )
52
+ print((await client.wait_terminal(record.action_id)).state)
53
+ await client.close_session()
54
+ ```
55
+
56
+ Without asyncio, feed `ClientConnection` the decoded messages and send whatever it queues:
57
+
58
+ ```python
59
+ from awp import jsonrpc
60
+
61
+ events = conn.receive(jsonrpc.decode(text)) # typed events: ActionUpdated, FrameReceived, ...
62
+ for out in conn.outgoing(): # messages to send, in order
63
+ transport.send(jsonrpc.encode(out))
64
+ ```
65
+
66
+ The connection:
67
+
68
+ - tracks the action lifecycle against the spec's transition table;
69
+ - deduplicates replayed statuses;
70
+ - keeps the clock offset from heartbeats;
71
+ - reports any violation by the world as a `ProtocolViolation` event.
72
+
73
+ ## The demo agent
74
+
75
+ `awp-demo` is a scripted agent for awp-sim's arm. It moves, cancels a move, and triggers an envelope refusal. It rides out a lost connection by resuming the session, or by opening a new one if the world no longer holds it.
76
+
77
+ ```bash
78
+ pip install --pre awp-sim
79
+ awp-sim serve & # streaming world on ws://127.0.0.1:8710
80
+ awp-demo # or --url, --token ($AWP_URL, $AWP_TOKEN)
81
+ ```
82
+
83
+ ## Layout
84
+
85
+ ```
86
+ src/awp/ client protocol layer (sans-IO), asyncio adapter, frame codec, schemas, demo agent
87
+ src/awp/_spec/ schemas and lifecycle table bundled from spec/ (scripts/sync_spec.py)
88
+ tests/ unit tests, and a scripted world that serves over WebSockets
89
+ conformance/ conformance reports, the manifests they were run against, and evidence
90
+ spec/ agent-world-protocol, pinned at spec-v0.1-draft.9
91
+ ```
92
+
93
+ ## Development
94
+
95
+ ```bash
96
+ git clone --recurse-submodules https://github.com/Hyperduality/awp-python
97
+ cd awp-python
98
+ uv sync
99
+ uv run ruff check && uv run ruff format --check
100
+ uv run mypy
101
+ uv run pytest --cov
102
+ uv run python scripts/sync_spec.py --check
103
+ ```
104
+
105
+ [AGENTS.md](AGENTS.md) covers moving to a new draft revision and releasing.
106
+
107
+ ## License
108
+
109
+ Apache-2.0. See [LICENSE](LICENSE).
@@ -0,0 +1,53 @@
1
+ # Conformance
2
+
3
+ ![AWP: Core Agent, AWP-conformant against 0.1-draft.9](https://img.shields.io/badge/AWP-Core_Agent%2C_conformant_0.1--draft.9-555)
4
+
5
+ | Class | Configuration | Claim | Report |
6
+ |---|---|---|---|
7
+ | Core Agent | `awp-demo`, [`manifest-lockstep.json`](manifest-lockstep.json) | Core Agent (lockstep): AWP-conformant against 0.1-draft.9 (awp-conformance 0.1.0a6) | [`core-agent-lockstep.json`](core-agent-lockstep.json) |
8
+ | Core Agent | `awp-demo`, [`manifest-streaming.json`](manifest-streaming.json) | Core Agent (streaming): AWP-conformant against 0.1-draft.9 (awp-conformance 0.1.0a6) | [`core-agent-streaming.json`](core-agent-streaming.json) |
9
+
10
+ The reports come from awp-conformance 0.1.0a6 run against awp-python 0.1.0a5. Neither has a failure or anything untested. The evidence for their `manual` rows is below (AWP-CNF-005).
11
+
12
+ ## Reproduce
13
+
14
+ The suite serves each manifest from its harness world and runs the demo agent against it. The manifests are awp-sim's (`awp-sim manifest --mode lockstep`, `--mode streaming`). [`frames.json`](frames.json) gives the harness one sample payload per channel.
15
+
16
+ ```bash
17
+ pip install --pre awp-python awp-conformance
18
+ awp-conformance agent --manifest conformance/manifest-lockstep.json --frames conformance/frames.json \
19
+ --mode lockstep --out report/ -- awp-demo --url '{url}' --token '{token}'
20
+ ```
21
+
22
+ For streaming, use `manifest-streaming.json` with `--mode streaming`. The tests the evidence below cites:
23
+
24
+ ```bash
25
+ uv run pytest tests/test_frames.py tests/test_evidence.py
26
+ ```
27
+
28
+ ## Manual evidence
29
+
30
+ ### AWP-DAT-001, AWP-DAT-009: loss accounting at the receiver (lockstep)
31
+
32
+ In streaming, the suite reads the agent's loss accounting from its `obs.report`. Lockstep sessions send no `obs.report`, so these rows are `manual` there.
33
+
34
+ - `test_the_receiver_counts_seq_gaps_as_loss_but_not_the_gap_before_a_resync` (`tests/test_evidence.py`) runs in each time model:
35
+ - on one channel, frames that skip two `seq` values count two missing frames in `ClientConnection.delivery()`;
36
+ - on another, a resync frame after a gap of five counts none.
37
+ - The client holds no delta state. Each frame reaches the application whole, with its `keyframe` and `resync` flags.
38
+
39
+ ### AWP-DAT-008: frame test vectors
40
+
41
+ `test_vectors` (`tests/test_frames.py`) runs `awp.frames` over every vector in `schemas/test-vectors/frames.json` of the `spec/` submodule, which is pinned at `spec-v0.1-draft.9`:
42
+
43
+ - the 10 valid vectors decode to their listed fields;
44
+ - the 10 marked `expect_error` are rejected with their listed error.
45
+
46
+ `test_roundtrip_without_vendor` re-encodes the 8 valid vectors that have no unknown extension or reserved bits, and each matches byte for byte.
47
+
48
+ ### AWP-VER-009: the draft revision is named
49
+
50
+ - The [README](../README.md) and the [Python SDK page](https://www.agentworldprotocol.com/sdks/python) name `0.1-draft.9`.
51
+ - `awp.SPEC_REVISION` is `"0.1-draft.9"`.
52
+ - The `spec/` submodule is pinned at the tag `spec-v0.1-draft.9`.
53
+ - Each report records `"specification": "0.1-draft.9"`, and its claim names the revision.
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "suite": {
3
3
  "name": "awp-conformance",
4
- "version": "0.1.0a3"
4
+ "version": "0.1.0a6"
5
5
  },
6
6
  "specification": "0.1-draft.9",
7
- "target": "/Users/archivec/Documents/Code/Hyperduality/awp-conformance/.venv/bin/awp-sim demo --url {url} --token {token}",
7
+ "target": "awp-demo --url {url} --token {token}",
8
8
  "side": "agent",
9
- "generated": "2026-09-25T19:40:36Z",
9
+ "generated": "2026-09-26T18:45:07Z",
10
10
  "claim": {
11
11
  "status": "conformant",
12
- "wording": "Core Agent (lockstep): AWP-conformant against 0.1-draft.9 (awp-conformance 0.1.0a3)",
12
+ "wording": "Core Agent (lockstep): AWP-conformant against 0.1-draft.9 (awp-conformance 0.1.0a6)",
13
13
  "classes": [
14
14
  "Core Agent"
15
15
  ],
@@ -108,7 +108,7 @@
108
108
  "id": "AWP-AGT-001",
109
109
  "level": "MUST",
110
110
  "outcome": "pass",
111
- "detail": "123 check(s): lockstep/baseline, lockstep/integer-range, lockstep/invalid-manifest, lockstep/malformed-frame, lockstep/no-action-grants, lockstep/reconnect, lockstep/refused-submission, lockstep/restarted-world, lockstep/silent-world"
111
+ "detail": "124 check(s): lockstep/baseline, lockstep/integer-range, lockstep/invalid-manifest, lockstep/malformed-frame, lockstep/no-action-grants, lockstep/reconnect, lockstep/refused-submission, lockstep/restarted-world, lockstep/silent-world"
112
112
  },
113
113
  {
114
114
  "id": "AWP-AGT-002",
@@ -138,7 +138,7 @@
138
138
  "id": "AWP-AGT-006",
139
139
  "level": "MUST",
140
140
  "outcome": "pass",
141
- "detail": "23 check(s): lockstep/baseline, lockstep/integer-range, lockstep/malformed-frame, lockstep/reconnect, lockstep/restarted-world, lockstep/silent-world"
141
+ "detail": "24 check(s): lockstep/baseline, lockstep/integer-range, lockstep/malformed-frame, lockstep/reconnect, lockstep/restarted-world, lockstep/silent-world"
142
142
  },
143
143
  {
144
144
  "id": "AWP-AGT-007",
@@ -396,7 +396,7 @@
396
396
  "id": "AWP-CTL-001",
397
397
  "level": "MUST",
398
398
  "outcome": "pass",
399
- "detail": "124 check(s): lockstep/baseline, lockstep/integer-range, lockstep/invalid-manifest, lockstep/malformed-frame, lockstep/no-action-grants, lockstep/reconnect, lockstep/refused-submission, lockstep/restarted-world, lockstep/silent-world"
399
+ "detail": "125 check(s): lockstep/baseline, lockstep/integer-range, lockstep/invalid-manifest, lockstep/malformed-frame, lockstep/no-action-grants, lockstep/reconnect, lockstep/refused-submission, lockstep/restarted-world, lockstep/silent-world"
400
400
  },
401
401
  {
402
402
  "id": "AWP-CTL-002",
@@ -426,7 +426,7 @@
426
426
  "id": "AWP-CTL-006",
427
427
  "level": "MUST",
428
428
  "outcome": "pass",
429
- "detail": "124 check(s): lockstep/baseline, lockstep/integer-range, lockstep/invalid-manifest, lockstep/malformed-frame, lockstep/no-action-grants, lockstep/reconnect, lockstep/refused-submission, lockstep/restarted-world, lockstep/silent-world"
429
+ "detail": "125 check(s): lockstep/baseline, lockstep/integer-range, lockstep/invalid-manifest, lockstep/malformed-frame, lockstep/no-action-grants, lockstep/reconnect, lockstep/refused-submission, lockstep/restarted-world, lockstep/silent-world"
430
430
  },
431
431
  {
432
432
  "id": "AWP-CTL-007",
@@ -444,7 +444,7 @@
444
444
  "id": "AWP-CTL-009",
445
445
  "level": "MUST",
446
446
  "outcome": "pass",
447
- "detail": "126 check(s): lockstep/baseline, lockstep/integer-range, lockstep/invalid-manifest, lockstep/malformed-frame, lockstep/no-action-grants, lockstep/reconnect, lockstep/refused-submission, lockstep/restarted-world, lockstep/silent-world"
447
+ "detail": "127 check(s): lockstep/baseline, lockstep/integer-range, lockstep/invalid-manifest, lockstep/malformed-frame, lockstep/no-action-grants, lockstep/reconnect, lockstep/refused-submission, lockstep/restarted-world, lockstep/silent-world"
448
448
  },
449
449
  {
450
450
  "id": "AWP-CTL-010",
@@ -1206,7 +1206,7 @@
1206
1206
  "id": "AWP-TIM-003",
1207
1207
  "level": "MUST",
1208
1208
  "outcome": "pass",
1209
- "detail": "8 check(s): lockstep/baseline, lockstep/malformed-frame, lockstep/reconnect, lockstep/restarted-world"
1209
+ "detail": "6 check(s): lockstep/baseline, lockstep/reconnect, lockstep/restarted-world"
1210
1210
  },
1211
1211
  {
1212
1212
  "id": "AWP-TIM-004",
@@ -1422,7 +1422,7 @@
1422
1422
  "id": "AWP-VER-004",
1423
1423
  "level": "MUST",
1424
1424
  "outcome": "pass",
1425
- "detail": "110 check(s): lockstep/baseline, lockstep/integer-range, lockstep/invalid-manifest, lockstep/malformed-frame, lockstep/no-action-grants, lockstep/reconnect, lockstep/refused-submission, lockstep/restarted-world, lockstep/silent-world"
1425
+ "detail": "111 check(s): lockstep/baseline, lockstep/integer-range, lockstep/invalid-manifest, lockstep/malformed-frame, lockstep/no-action-grants, lockstep/reconnect, lockstep/refused-submission, lockstep/restarted-world, lockstep/silent-world"
1426
1426
  },
1427
1427
  {
1428
1428
  "id": "AWP-VER-005",
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "suite": {
3
3
  "name": "awp-conformance",
4
- "version": "0.1.0a3"
4
+ "version": "0.1.0a6"
5
5
  },
6
6
  "specification": "0.1-draft.9",
7
- "target": "/Users/archivec/Documents/Code/Hyperduality/awp-conformance/.venv/bin/awp-sim demo --url {url} --token {token}",
7
+ "target": "awp-demo --url {url} --token {token}",
8
8
  "side": "agent",
9
- "generated": "2026-09-25T19:40:57Z",
9
+ "generated": "2026-09-26T18:45:09Z",
10
10
  "claim": {
11
11
  "status": "conformant",
12
- "wording": "Core Agent (streaming): AWP-conformant against 0.1-draft.9 (awp-conformance 0.1.0a3)",
12
+ "wording": "Core Agent (streaming): AWP-conformant against 0.1-draft.9 (awp-conformance 0.1.0a6)",
13
13
  "classes": [
14
14
  "Core Agent"
15
15
  ],
@@ -0,0 +1,4 @@
1
+ {
2
+ "proprio": { "p_m": [0, 0, 0.4], "v_mps": [0, 0, 0] },
3
+ "arm_state": { "phase": "idle", "target_m": null, "action_id": null }
4
+ }
@@ -0,0 +1,157 @@
1
+ {
2
+ "protocol_version": "0.1",
3
+ "world": {
4
+ "name": "awp-sim",
5
+ "version": "0.1.0a4",
6
+ "vendor": "hyperduality"
7
+ },
8
+ "time_models": [
9
+ "lockstep"
10
+ ],
11
+ "capabilities": {},
12
+ "initial_states": [
13
+ "home"
14
+ ],
15
+ "embodiments": [
16
+ {
17
+ "id": "arm_01",
18
+ "kind": "manipulator",
19
+ "action_types": [
20
+ "move_to_pose",
21
+ "stop"
22
+ ],
23
+ "channels": [
24
+ "proprio",
25
+ "arm_state"
26
+ ]
27
+ }
28
+ ],
29
+ "observation_channels": [
30
+ {
31
+ "id": "proprio",
32
+ "modality": "proprio/json",
33
+ "rate_hz": null,
34
+ "loss_class": "latest-wins",
35
+ "stale_after_ms": 50,
36
+ "schema": {
37
+ "fields": [
38
+ "p_m",
39
+ "v_mps"
40
+ ],
41
+ "frame": "base"
42
+ }
43
+ },
44
+ {
45
+ "id": "arm_state",
46
+ "modality": "text/event+json",
47
+ "rate_hz": null,
48
+ "loss_class": "reliable",
49
+ "schema": {
50
+ "type": "object",
51
+ "properties": {
52
+ "phase": {
53
+ "enum": [
54
+ "idle",
55
+ "moving",
56
+ "stopping",
57
+ "servo"
58
+ ]
59
+ },
60
+ "target_m": {
61
+ "type": [
62
+ "array",
63
+ "null"
64
+ ]
65
+ },
66
+ "action_id": {
67
+ "type": [
68
+ "string",
69
+ "null"
70
+ ]
71
+ }
72
+ },
73
+ "required": [
74
+ "phase",
75
+ "target_m",
76
+ "action_id"
77
+ ]
78
+ }
79
+ }
80
+ ],
81
+ "action_schemas": [
82
+ {
83
+ "type": "move_to_pose",
84
+ "params_schema": {
85
+ "$ref": "#/$defs/move_to_pose_params"
86
+ },
87
+ "duration": "extended",
88
+ "preemption": [
89
+ "replace",
90
+ "queue",
91
+ "reject"
92
+ ],
93
+ "concurrency_group": "arm_motion",
94
+ "max_queue": 4,
95
+ "max_abort_ms": 1500,
96
+ "max_duration_ms": 10000,
97
+ "description": "Move the end effector in a straight line to a position."
98
+ },
99
+ {
100
+ "type": "stop",
101
+ "params_schema": {
102
+ "type": "object",
103
+ "additionalProperties": false
104
+ },
105
+ "duration": "instant",
106
+ "preemption": "replace",
107
+ "concurrency_group": "arm_motion",
108
+ "description": "Decelerate to rest; replaces any motion."
109
+ }
110
+ ],
111
+ "safety_policy": {
112
+ "envelopes": [
113
+ {
114
+ "embodiment": "arm_01",
115
+ "spatial": {
116
+ "frame": "base",
117
+ "aabb_m": [
118
+ [
119
+ -0.5,
120
+ -0.5,
121
+ 0.0
122
+ ],
123
+ [
124
+ 0.5,
125
+ 0.5,
126
+ 0.8
127
+ ]
128
+ ]
129
+ },
130
+ "max_velocity_mps": 0.5,
131
+ "max_action_rate_hz": 20.0,
132
+ "enforcement": "command_check",
133
+ "on_violation": "reject"
134
+ }
135
+ ]
136
+ },
137
+ "$defs": {
138
+ "move_to_pose_params": {
139
+ "type": "object",
140
+ "properties": {
141
+ "pose": {
142
+ "$ref": "https://agentworldprotocol.com/schemas/v0.1/common.schema.json#/$defs/pose"
143
+ },
144
+ "max_velocity_mps": {
145
+ "type": "number",
146
+ "exclusiveMinimum": 0
147
+ }
148
+ },
149
+ "required": [
150
+ "pose"
151
+ ],
152
+ "additionalProperties": false
153
+ }
154
+ },
155
+ "tick_policy": "on_tick",
156
+ "tick_authority": "any_session"
157
+ }