peerhub 0.1.10__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 (161) hide show
  1. peerhub-0.1.10/LICENSE +21 -0
  2. peerhub-0.1.10/PKG-INFO +161 -0
  3. peerhub-0.1.10/README.md +139 -0
  4. peerhub-0.1.10/peerhub/__init__.py +5 -0
  5. peerhub-0.1.10/peerhub/adapters/__init__.py +7 -0
  6. peerhub-0.1.10/peerhub/adapters/agy_adapter.py +253 -0
  7. peerhub-0.1.10/peerhub/adapters/claude_adapter.py +277 -0
  8. peerhub-0.1.10/peerhub/adapters/codex_adapter.py +428 -0
  9. peerhub-0.1.10/peerhub/adapters/contract.py +657 -0
  10. peerhub-0.1.10/peerhub/adapters/discovery.py +93 -0
  11. peerhub-0.1.10/peerhub/adapters/registry.py +214 -0
  12. peerhub-0.1.10/peerhub/application/__init__.py +7 -0
  13. peerhub-0.1.10/peerhub/application/alert_raise.py +198 -0
  14. peerhub-0.1.10/peerhub/application/api.py +3136 -0
  15. peerhub-0.1.10/peerhub/application/arbiter_review.py +894 -0
  16. peerhub-0.1.10/peerhub/application/bootstrap.py +296 -0
  17. peerhub-0.1.10/peerhub/application/broadcast.py +415 -0
  18. peerhub-0.1.10/peerhub/application/broker_status.py +70 -0
  19. peerhub-0.1.10/peerhub/application/capability_config.py +559 -0
  20. peerhub-0.1.10/peerhub/application/capability_matching.py +574 -0
  21. peerhub-0.1.10/peerhub/application/commands.py +192 -0
  22. peerhub-0.1.10/peerhub/application/direct_ask.py +301 -0
  23. peerhub-0.1.10/peerhub/application/health_revalidation.py +596 -0
  24. peerhub-0.1.10/peerhub/application/leadership.py +655 -0
  25. peerhub-0.1.10/peerhub/application/lease_status.py +54 -0
  26. peerhub-0.1.10/peerhub/application/legacy.py +2553 -0
  27. peerhub-0.1.10/peerhub/application/lesson_broadcast.py +194 -0
  28. peerhub-0.1.10/peerhub/application/lesson_inject.py +174 -0
  29. peerhub-0.1.10/peerhub/application/peer_registry.py +448 -0
  30. peerhub-0.1.10/peerhub/application/process_lease_sweep.py +238 -0
  31. peerhub-0.1.10/peerhub/application/proposals.py +709 -0
  32. peerhub-0.1.10/peerhub/application/quarantine_review.py +153 -0
  33. peerhub-0.1.10/peerhub/application/retry.py +907 -0
  34. peerhub-0.1.10/peerhub/application/role_assignment.py +326 -0
  35. peerhub-0.1.10/peerhub/application/room_broadcast.py +145 -0
  36. peerhub-0.1.10/peerhub/application/session_saga.py +183 -0
  37. peerhub-0.1.10/peerhub/application/status.py +45 -0
  38. peerhub-0.1.10/peerhub/application/thread_new.py +67 -0
  39. peerhub-0.1.10/peerhub/application/workflows.py +1589 -0
  40. peerhub-0.1.10/peerhub/builtins/__init__.py +7 -0
  41. peerhub-0.1.10/peerhub/builtins/fake_adapter.py +302 -0
  42. peerhub-0.1.10/peerhub/cli.py +4158 -0
  43. peerhub-0.1.10/peerhub/client.py +72 -0
  44. peerhub-0.1.10/peerhub/core/__init__.py +1 -0
  45. peerhub-0.1.10/peerhub/core/binary_resolution.py +67 -0
  46. peerhub-0.1.10/peerhub/core/context.py +67 -0
  47. peerhub-0.1.10/peerhub/core/errors.py +624 -0
  48. peerhub-0.1.10/peerhub/core/evidence.py +127 -0
  49. peerhub-0.1.10/peerhub/core/execution.py +134 -0
  50. peerhub-0.1.10/peerhub/core/identity.py +106 -0
  51. peerhub-0.1.10/peerhub/core/ports.py +11 -0
  52. peerhub-0.1.10/peerhub/core/protocol.py +841 -0
  53. peerhub-0.1.10/peerhub/dispatch/__init__.py +11 -0
  54. peerhub-0.1.10/peerhub/dispatch/admission.py +582 -0
  55. peerhub-0.1.10/peerhub/dispatch/artifact_coordination.py +63 -0
  56. peerhub-0.1.10/peerhub/dispatch/artifacts.py +288 -0
  57. peerhub-0.1.10/peerhub/dispatch/attempt_lifecycle.py +755 -0
  58. peerhub-0.1.10/peerhub/dispatch/capability.py +588 -0
  59. peerhub-0.1.10/peerhub/dispatch/capability_policy.py +201 -0
  60. peerhub-0.1.10/peerhub/dispatch/completion.py +276 -0
  61. peerhub-0.1.10/peerhub/dispatch/contract.py +1434 -0
  62. peerhub-0.1.10/peerhub/dispatch/duty_lease.py +249 -0
  63. peerhub-0.1.10/peerhub/dispatch/heartbeat.py +289 -0
  64. peerhub-0.1.10/peerhub/dispatch/helpers.py +185 -0
  65. peerhub-0.1.10/peerhub/dispatch/materializer.py +787 -0
  66. peerhub-0.1.10/peerhub/dispatch/model.py +1460 -0
  67. peerhub-0.1.10/peerhub/dispatch/pipe.py +567 -0
  68. peerhub-0.1.10/peerhub/dispatch/process.py +758 -0
  69. peerhub-0.1.10/peerhub/dispatch/retry_authorization.py +898 -0
  70. peerhub-0.1.10/peerhub/dispatch/room_session.py +387 -0
  71. peerhub-0.1.10/peerhub/dispatch/service.py +1037 -0
  72. peerhub-0.1.10/peerhub/dispatch/session_lease.py +333 -0
  73. peerhub-0.1.10/peerhub/dispatch/terminal_duty.py +151 -0
  74. peerhub-0.1.10/peerhub/dispatch/tree_controller.py +615 -0
  75. peerhub-0.1.10/peerhub/dispatch/unit_of_work.py +453 -0
  76. peerhub-0.1.10/peerhub/events/contract.py +42 -0
  77. peerhub-0.1.10/peerhub/governance/__init__.py +1 -0
  78. peerhub-0.1.10/peerhub/governance/activity.py +114 -0
  79. peerhub-0.1.10/peerhub/governance/artifact_records.py +294 -0
  80. peerhub-0.1.10/peerhub/governance/broker.py +618 -0
  81. peerhub-0.1.10/peerhub/governance/consensus.py +679 -0
  82. peerhub-0.1.10/peerhub/governance/contract.py +624 -0
  83. peerhub-0.1.10/peerhub/governance/directives.py +164 -0
  84. peerhub-0.1.10/peerhub/governance/election_audit.py +313 -0
  85. peerhub-0.1.10/peerhub/governance/feedback.py +227 -0
  86. peerhub-0.1.10/peerhub/governance/file_locks.py +168 -0
  87. peerhub-0.1.10/peerhub/governance/invariant_requests.py +176 -0
  88. peerhub-0.1.10/peerhub/governance/lessons.py +234 -0
  89. peerhub-0.1.10/peerhub/governance/mutations.py +243 -0
  90. peerhub-0.1.10/peerhub/governance/operational_errors.py +264 -0
  91. peerhub-0.1.10/peerhub/governance/rooms.py +1241 -0
  92. peerhub-0.1.10/peerhub/governance/tasks.py +316 -0
  93. peerhub-0.1.10/peerhub/health/__init__.py +7 -0
  94. peerhub-0.1.10/peerhub/health/contract.py +1294 -0
  95. peerhub-0.1.10/peerhub/health/model.py +873 -0
  96. peerhub-0.1.10/peerhub/health/service.py +1897 -0
  97. peerhub-0.1.10/peerhub/persistence/__init__.py +1 -0
  98. peerhub-0.1.10/peerhub/persistence/migrations/0001_phase1_kernel.sql +162 -0
  99. peerhub-0.1.10/peerhub/persistence/migrations/0002_dispatch_session_lease.sql +59 -0
  100. peerhub-0.1.10/peerhub/persistence/migrations/0003_command_request_attempt.sql +448 -0
  101. peerhub-0.1.10/peerhub/persistence/migrations/0004_idempotency_aliases.sql +97 -0
  102. peerhub-0.1.10/peerhub/persistence/migrations/0005_health_routing.sql +272 -0
  103. peerhub-0.1.10/peerhub/persistence/migrations/0006_recovery_probe_single_flight.sql +19 -0
  104. peerhub-0.1.10/peerhub/persistence/migrations/0007_health_projection_readiness_context.sql +33 -0
  105. peerhub-0.1.10/peerhub/persistence/migrations/0008_dispatch_artifact_metadata.sql +107 -0
  106. peerhub-0.1.10/peerhub/persistence/migrations/0009_session_binding_generations.sql +20 -0
  107. peerhub-0.1.10/peerhub/persistence/migrations/0010_session_context_telemetry.sql +35 -0
  108. peerhub-0.1.10/peerhub/persistence/migrations/0011_admission_snapshot_configuration_digest.sql +96 -0
  109. peerhub-0.1.10/peerhub/persistence/migrations/0012_session_rotation_conversation_scope.sql +29 -0
  110. peerhub-0.1.10/peerhub/persistence/migrations/0013_session_context_conversation_scope.sql +30 -0
  111. peerhub-0.1.10/peerhub/persistence/migrations/0014_event_log_delivery_split.sql +54 -0
  112. peerhub-0.1.10/peerhub/persistence/migrations/0015_effect_receipts_delivery_fk.sql +49 -0
  113. peerhub-0.1.10/peerhub/persistence/migrations/0016_dispatch_artifact_manifests_event_log_fk.sql +67 -0
  114. peerhub-0.1.10/peerhub/persistence/migrations/0017_drop_legacy_outbox.sql +15 -0
  115. peerhub-0.1.10/peerhub/persistence/migrations/0018_capability_leases.sql +72 -0
  116. peerhub-0.1.10/peerhub/persistence/migrations/0019_route_decision_capability_tier.sql +26 -0
  117. peerhub-0.1.10/peerhub/persistence/migrations/0020_broadcast_correlation.sql +97 -0
  118. peerhub-0.1.10/peerhub/persistence/migrations/0021_broadcast_leg_timeout_state.sql +57 -0
  119. peerhub-0.1.10/peerhub/persistence/migrations/0022_retry_authority.sql +109 -0
  120. peerhub-0.1.10/peerhub/persistence/migrations/0023_evidence_artifacts.sql +26 -0
  121. peerhub-0.1.10/peerhub/persistence/migrations/0024_telemetry_quota_tracking.sql +58 -0
  122. peerhub-0.1.10/peerhub/persistence/migrations/0025_governed_target_listing.sql +22 -0
  123. peerhub-0.1.10/peerhub/persistence/migrations/0026_duty_leases.sql +28 -0
  124. peerhub-0.1.10/peerhub/persistence/migrations/0027_duty_lease_recovery.sql +16 -0
  125. peerhub-0.1.10/peerhub/persistence/migrations/0028_room_participation_sessions.sql +67 -0
  126. peerhub-0.1.10/peerhub/persistence/migrations/0029_recovery_probe_grant_lifecycle.sql +98 -0
  127. peerhub-0.1.10/peerhub/persistence/migrations/0030_administrative_recovery_budget.sql +16 -0
  128. peerhub-0.1.10/peerhub/persistence/migrations/__init__.py +1 -0
  129. peerhub-0.1.10/peerhub/persistence/sqlite.py +1901 -0
  130. peerhub-0.1.10/peerhub/persistence/sqlite_dispatch.py +2753 -0
  131. peerhub-0.1.10/peerhub/persistence/sqlite_events.py +185 -0
  132. peerhub-0.1.10/peerhub/persistence/sqlite_governance.py +980 -0
  133. peerhub-0.1.10/peerhub/persistence/sqlite_health.py +918 -0
  134. peerhub-0.1.10/peerhub/persistence/sqlite_helpers.py +38 -0
  135. peerhub-0.1.10/peerhub/persistence/sqlite_routing.py +196 -0
  136. peerhub-0.1.10/peerhub/persistence/sqlite_telemetry.py +847 -0
  137. peerhub-0.1.10/peerhub/routing/__init__.py +7 -0
  138. peerhub-0.1.10/peerhub/routing/capability_matching.py +698 -0
  139. peerhub-0.1.10/peerhub/routing/contract.py +727 -0
  140. peerhub-0.1.10/peerhub/routing/model.py +312 -0
  141. peerhub-0.1.10/peerhub/routing/service.py +251 -0
  142. peerhub-0.1.10/peerhub/runtime.py +434 -0
  143. peerhub-0.1.10/peerhub/state/__init__.py +1 -0
  144. peerhub-0.1.10/peerhub/state/contract.py +117 -0
  145. peerhub-0.1.10/peerhub/telemetry/__init__.py +8 -0
  146. peerhub-0.1.10/peerhub/telemetry/contract.py +454 -0
  147. peerhub-0.1.10/peerhub/telemetry/domain_rows.py +69 -0
  148. peerhub-0.1.10/peerhub/telemetry/presenter.py +613 -0
  149. peerhub-0.1.10/peerhub/telemetry/projections.py +664 -0
  150. peerhub-0.1.10/peerhub/telemetry/quota_polling.py +750 -0
  151. peerhub-0.1.10/peerhub/telemetry/statusline.py +106 -0
  152. peerhub-0.1.10/peerhub.egg-info/PKG-INFO +161 -0
  153. peerhub-0.1.10/peerhub.egg-info/SOURCES.txt +159 -0
  154. peerhub-0.1.10/peerhub.egg-info/dependency_links.txt +1 -0
  155. peerhub-0.1.10/peerhub.egg-info/entry_points.txt +2 -0
  156. peerhub-0.1.10/peerhub.egg-info/requires.txt +10 -0
  157. peerhub-0.1.10/peerhub.egg-info/top_level.txt +1 -0
  158. peerhub-0.1.10/pyproject.toml +43 -0
  159. peerhub-0.1.10/setup.cfg +4 -0
  160. peerhub-0.1.10/tests/test_event_dataclass_validation.py +73 -0
  161. peerhub-0.1.10/tests/test_sqlite_events.py +184 -0
peerhub-0.1.10/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 greatgc-flow
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,161 @@
1
+ Metadata-Version: 2.4
2
+ Name: peerhub
3
+ Version: 0.1.10
4
+ Summary: Local transactional coordination for collaborating AI peers
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/greatgc-flow/peerhub
7
+ Project-URL: Repository, https://github.com/greatgc-flow/peerhub
8
+ Project-URL: Issues, https://github.com/greatgc-flow/peerhub/issues
9
+ Requires-Python: >=3.11
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: psutil>=5.9.0
13
+ Requires-Dist: pydantic>=2.0
14
+ Requires-Dist: typing_extensions>=4.6.0
15
+ Provides-Extra: dev
16
+ Requires-Dist: pytest>=8.0; extra == "dev"
17
+ Requires-Dist: pytest-timeout>=2.3.0; extra == "dev"
18
+ Requires-Dist: pyright>=1.1.370; extra == "dev"
19
+ Requires-Dist: hypothesis>=6.0; extra == "dev"
20
+ Requires-Dist: alembic>=1.13.0; extra == "dev"
21
+ Dynamic: license-file
22
+
23
+ # peerhub
24
+
25
+ A lightweight, installable coordination layer for orchestrating multiple AI CLI agents (Claude, Codex, Antigravity, ...) as collaborating peers: dispatch, routing, consensus, and health. It's built to eventually replace an existing hand-rolled multi-peer coordination system (`hub.py`) with a proper, tested package.
26
+
27
+ ## Status (2026-08-17)
28
+
29
+ **`peerhub ask` works end-to-end today** — it genuinely dispatches a prompt to a real peer CLI (agy/claude/codex) through peerhub's own governance, admission, and process-supervision layers, and returns the response. See "Try it" below.
30
+
31
+ - **Implemented**:
32
+ - Coordination kernel: dispatch, process supervision, heartbeat/liveness, routing, health, telemetry, SQLite persistence.
33
+ - GovernanceBroker's outbox/delivery-tracking split is fully completed end-to-end (legacy mirror writes removed, tables dropped).
34
+ - Capability-lease enforcement (all 5 increments): required capability tier threaded end-to-end, with an atomic pre-spawn enforcement gate and explicitly documented evidence audit.
35
+ - Persistence UoW split (read/write separation) and a migration-runner sequence-derivation fix that fast-fails on FK violations.
36
+ - A typed command boundary (`ApplicationAPI`/`Client`) with Pydantic v2 strict validation at the wire edge.
37
+ - **Real peer adapters** for all 3 target CLIs — `RealAgyAdapter`, `RealClaudeAdapter`, `RealCodexAdapter` — each proven both standalone and through the full supervised `dispatch_and_execute()` pipeline (not a bypass), plus a `FakePeerAdapter` for tests. Selectable via a peer-kind registry (`peerhub.adapters.registry`).
38
+ - **A real CLI**: `peerhub --version`, `peerhub status [--workspace PATH] [--peer/--all]`, and `peerhub ask PEER PROMPT [options]` — the last one performs a genuine end-to-end dispatch (admission → routing → supervised process execution → decoded response), not a stub. See "Try it" below.
39
+ - A direct-ask admission bootstrap (`peerhub.direct-ask/v1`) that auto-provisions a real, measured-readiness health/routing configuration for a single requested peer on a fresh workspace — no manual policy setup required.
40
+ - Static type checking (Pyright, 0 errors) and CI (GitHub Actions: pytest + pyright on every push/PR).
41
+ - A ratified traceability convention and fact-refresh procedure (`docs/design/TRACEABILITY-CONVENTION-R1.md`, `docs/design/FACT-REFRESH-PROCEDURE-R1.md`) — the fact-refresh tool (`tools/peerhub_facts/`) is built, functional, and handles drift reporting via live CLI probes.
42
+ - The full T1 Phase 3 outer loop: `dispatch_with_retries()`, session resume, streaming, tool-call capture, and failover routing.
43
+ - Multi-peer broadcast primitive A: Correlation schema and a working `BroadcastCoordinator.fan_out()` loop (T3).
44
+ - `EvidenceArtifact` / 3-tier context partitioning (completed for Claude and Codex adapters).
45
+ - Health/quota tracking CLI surface: `peerhub status --peer/--all` and quota telemetry persistence.
46
+ - Ctrl-C during `peerhub ask` walks the real cancellation ladder (`SOFT_CANCEL` → `TERMINATE_TREE` → `KILL_TREE`) via a proper background-thread dispatch and cancellation hook.
47
+ - **Designed, but not yet built**:
48
+ - Health/quota tracking's periodic background polling (`TelemetryWorker`) — currently awaiting a user decision on the process-host model (e.g., poll-on-demand vs. daemon).
49
+ - Windows-native Brokered Read-Only Reducers — blocked pending a policy call on required OS privileges.
50
+ - **Explicitly deferred (with named triggers)**:
51
+ - Alembic runtime cutover: Ratified as HOLD. The bespoke runner remains the sole runtime migration engine. Will revisit only if peerhub adopts SQLAlchemy ORM or is about to become the primary dispatch path.
52
+ - Formal multi-peer consensus (voting machinery / Primitive B): Deferred until the first `r10_requires_finalized_for` decision class is actually routed to peerhub.
53
+ - Durable response transcripts for broadcast: Deferred until a dispatch-layer durability mechanism is ratified.
54
+ - Capability-lease enforcement evidence: Changing adapter receipts to claim positive enforcement is deferred until a machine-owned launcher, plan-bound digest, empirical negative probe, and post-plan corroboration gate exist.
55
+ - Parallel fan-out: Deferred (blocked on measuring SQLite write contention).
56
+ - **Not yet implemented / honest gaps**:
57
+ - Phase 4 shadow-by-ownership-cluster validation and same-revision comparison + rollback proof.
58
+ - Crash-linkage recovery (resuming an interrupted round after a coordinator crash).
59
+ - Detailed per-vendor error-taxonomy mapping, and PTY transport are deliberately out of scope for the current adapter slice.
60
+ - No shadow-mode validation yet (routing a subset of real traffic through peerhub in parallel with `hub.py` for comparison before any real cutover) — `hub.py` remains the authoritative system for real multi-peer coordination work today; `peerhub ask` is a real, working command, not yet a production replacement.
61
+
62
+ See [`docs/design/HUB-REPLACEMENT-ROADMAP-2026-08-09.md`](docs/design/HUB-REPLACEMENT-ROADMAP-2026-08-09.md) for the full phased plan toward functional hub.py parity, and [`docs/design/PEERHUB-P-DRIVE-ISOLATION-2026-08-09.md`](docs/design/PEERHUB-P-DRIVE-ISOLATION-2026-08-09.md) for how peerhub's own runtime state relates to (and is deliberately isolated from) the wider P: development environment this repo happens to live inside during development. For the Engram/peerhub architectural separation itself (Engram is a portable dev-environment package, peerhub is the standalone AI-collaboration layer that used to live inside it) — what's done, what's verified clean in both directions, and what's left on either side — see [Engram's `2026-09-03_separation-completion-backlog.md`](https://github.com/greatgc-flow/Engram/blob/main/_sys/data/sessions/2026-09-03_separation-completion-backlog.md).
63
+
64
+ **hub.py-replacement TDD (2026-08-27, in progress; last updated 2026-09-02)**: real, tested code now exists for gap-2 (consensus), gap-4 (duty-lease), gap-5 (task lifecycle), gap-6 (governance/lessons, capability matching) in full, and gap-3/gap-7 partially. **All 6 domains now have real, runnable CLI commands** — `peerhub consensus|task|lesson|room|duty|session`, see "Try it" below — and `LegacyTranslator` (`peerhub/application/legacy.py`) translates 71 of the 90 legacy `hub.py` action names into typed wire commands for these same domains (plus a new room-participation-session domain — `init-session`/`end-session`, backed by a new `RoomParticipationCoordinator`, a new private-mailbox domain — `send`/`check`/`mark-read`/`thread-promote`/`lesson-broadcast`, a final-arbiter escalation domain — `arbiter-review`, backed by `ArbiterReviewCoordinator`, a peer-node-registry domain — `register-node`/`list-nodes`, backed by `PeerRegistryService`, a durable role-assignment domain — `assign-role`/`release-role`/`role-status`, backed by `RoleAssignmentService`, a feedback-journal domain — `feedback-add`/`feedback-list`/`feedback-resolve`, backed by `FeedbackService`, an operational-error journal domain — `report-error`, backed by `OperationalErrorService`, a workspace-global leadership domain — `leader-claim`/`leader-yield`, backed by `LeadershipService`, the core room-status aggregate — `status`, backed by `RoomsService`, plus (since 2026-09-01/02) health/admission quick wins — `health-check`/`peer-status`/`peer-recover`/`health-precheck`/`check-gate`/`health-sweep`/`peer-quarantine`, session-lease visibility — `lease-status`/`lease-sweep`, room coordination — `update-status`/`thread-new`/`broadcast`, model/profile binding — `model-status`, governance proposals — `proposal-add`/`proposal-vote` backed by a new `ProposalCoordinator`, a durable artifact ledger — `artifact-claim`/`artifact-status`/`artifact-finalize` backed by a new `ArtifactRecordService`, a bounded effect-outbox view — `broker-status`, and capability-scored leadership — `discover`/`elect-leader` backed by `CapabilityMatchingCoordinator`/`CapabilityConfigService`/`ElectionAuditService`). **All 71 of those actions now execute end-to-end**: `ApplicationAPI` registers a `CommandDescriptor` for each one, backed by the same real services the native CLI uses, so a legacy action name genuinely runs through `LegacyTranslator.translate()` → `Client.submit()` → a persisted result, not just a name-to-wire-command translation. See [`docs/design/HUB-REPLACEMENT-TDD-PROGRESS-2026-08-27.md`](docs/design/HUB-REPLACEMENT-TDD-PROGRESS-2026-08-27.md) for exactly what's real vs. still missing — the remaining 19 of the 90 `LEGACY_CATALOG` actions are each a settled, cited, permanent waiver (host-environment-only tooling, an architecturally-incompatible generic write queue, upstream account/billing management peerhub deliberately does not fake, one policy divergence, and a known counting-formula nuance around `ask`/`ask-all`/`ask-coordinator`'s non-executing translator branches) — not open work. **See [`docs/design/PEERHUB-BACKLOG-2026-08-27.md`](docs/design/PEERHUB-BACKLOG-2026-08-27.md) for the full consolidated remaining-work backlog**, organized by how ready each item is (mechanical wiring vs. needs new component code vs. needs a design round vs. entirely undesigned domain), and [`docs/design/SESSION-LESSONS-INDEX-2026-09-02.md`](docs/design/SESSION-LESSONS-INDEX-2026-09-02.md) for a topic-indexed pointer into this session's recurring bug classes and process lessons (peer-dispatch reliability, codebase-specific pitfalls, design discipline), each linking back to its full account in the TDD progress log rather than restating it.
65
+
66
+ **hub.py-replacement design phase (2026-08-23 to 2026-08-26): DESIGN-complete, TDD-ready.** The 7 functional categories `hub.py` covers beyond basic dispatch — compat/cutover strategy, consensus, session/room/thread continuity, health/leadership/duty-lease, task lifecycle/approval, governance/learning, and diagnostics parity — each now have either a concrete `TargetState` JSON schema or a concrete dedicated design, all converged and ratified (52 of 53 remaining open items resolved by design-consistency reasoning, 1 genuine business decision resolved by the user, 1 shared infrastructure prerequisite scoped as its own task). Start at [`docs/design/HUB-REPLACEMENT-PRE-TDD-FINAL-RATIFICATION-2026-08-26.md`](docs/design/HUB-REPLACEMENT-PRE-TDD-FINAL-RATIFICATION-2026-08-26.md) (supersedes older per-doc "Unresolved" lists), then [`docs/design/HUB-REPLACEMENT-DESIGN-REINFORCEMENT-INDEX-2026-08-24.md`](docs/design/HUB-REPLACEMENT-DESIGN-REINFORCEMENT-INDEX-2026-08-24.md) for the full per-gap breakdown. **Update**: most of this design has since been implemented during the TDD phase below (consensus, task, lessons, duty-lease, and half of session/room/thread) — see [`docs/design/PEERHUB-BACKLOG-2026-08-27.md`](docs/design/PEERHUB-BACKLOG-2026-08-27.md) for exactly what from this design phase is still unimplemented.
67
+
68
+ The target architecture was designed and converged through a 9-round adversarial review (`ag`/`cx`/`cc`) documented in [`docs/design/ARCHITECTURE.md`](docs/design/ARCHITECTURE.md). The full debate record, including rejected alternatives and evidence citations, is in [`docs/design/peerhub-architecture-debate.md`](docs/design/peerhub-architecture-debate.md). Later design decisions are under [`docs/design/`](docs/design/), dated by filename.
69
+
70
+ ## Install
71
+
72
+ ### Option A: Install via Pip from GitHub Release (Recommended)
73
+
74
+ ```bash
75
+ pip install "git+https://github.com/greatgc-flow/peerhub.git@v0.1.10"
76
+ ```
77
+
78
+ ### Option B: Local Editable Development Install
79
+
80
+ ```bash
81
+ git clone https://github.com/greatgc-flow/peerhub.git
82
+ cd peerhub
83
+ pip install -e . # runtime only
84
+ pip install -e .[dev] # + pytest, pyright, hypothesis, alembic (needed to run tests/type-check locally)
85
+ ```
86
+
87
+ Requires Python >= 3.11. This installs the `peerhub` package and registers a `peerhub` entrypoint on your PATH (verified via a real sdist build + install: `pyproject.toml`'s `[project.scripts]` defines only `peerhub`, not a separate `hub` alias).
88
+
89
+ ## Try it
90
+
91
+ ```bash
92
+ peerhub --version
93
+
94
+ # Real-time multi-peer quota telemetry, headroom matrix, and active failover routing targets
95
+ peerhub diag
96
+ # Add a governed-domain state section (consensus/task/lesson) to the same command
97
+ peerhub diag --domains --workspace ./my-workspace
98
+
99
+ # Check a workspace (read-only; reports "uninitialized" if no database yet)
100
+ peerhub status --workspace ./my-workspace
101
+
102
+ # Auto-detect which built-in peer CLIs (agy/claude/codex) are installed and
103
+ # resolvable on PATH right now -- no workspace required
104
+ peerhub adapter discover
105
+ peerhub adapter discover --json # MEASURED/UNAVAILABLE/ABSENT per peer
106
+
107
+ # Genuinely dispatch a prompt to a real peer and get its response
108
+ peerhub ask ag "say hello in exactly three words" --capability-tier READ_ONLY
109
+ peerhub ask cc "..." --capability-tier READ_ONLY --profile <profile-id> # claude, if you have more than one profile configured
110
+ peerhub ask cx "..." --capability-tier WORKTREE_WRITE --json # structured output instead of plain text
111
+
112
+ # Multi-peer broadcast coordination across peers with unified consensus
113
+ peerhub broadcast "reply with exactly: pong" --peers ag,cx --capability-tier READ_ONLY
114
+
115
+ # Propose a consensus round
116
+ peerhub consensus propose --round-id r1 --title "Ship" --question "Ready?" --body "Decide" --proposer cx --required cx,ag --eligible cx,ag
117
+ # Create a task
118
+ peerhub task create --task-id t1 --summary "Ship" --spec "Do it" --creator cx
119
+ # Propose a governance lesson
120
+ peerhub lesson propose --lesson-id l1 --title "Rule" --rule "Do this" --category ops --severity HIGH --proposer cx --affected cx,ag
121
+ # Create a room
122
+ peerhub room create --room-id room1 --topic-id topic1 --title "Work" --creator cx --participants cx,ag
123
+ # Claim terminal duty
124
+ peerhub duty claim --room-id room1 --instance-id i1 --profile-id cx.standard --owner-principal-id p1 --authority-epoch 1
125
+ # Open a room-participation session
126
+ peerhub session open --workspace-scope-id ws1 --room-id room1 --actor-principal-id p1 --instance-id i1 --profile-id cx.standard --session-fingerprint fp1
127
+ ```
128
+
129
+ `ask` accepts `--capability-tier` (required: READ_ONLY, WORKTREE_WRITE, GIT_MUTATE, REMOTE_MUTATE),
130
+ `--workspace PATH` (default `.`), `--profile PROFILE_ID`,
131
+ `--timeout-seconds`/`--silence-timeout-seconds`/`--max-output-bytes`
132
+ (process limits), and `--json`. Exit codes: `0` verified response,
133
+ `2` usage/config/pre-spawn failure (unknown peer, executable not found,
134
+ readiness probe failed), `3` definite peer/protocol failure, `4`
135
+ uncertain execution (timeout, lost lease ownership), `130` interrupted.
136
+ It requires the real peer CLI (`agy.exe`/`claude.cmd`/`codex.cmd`) to be
137
+ installed and authenticated on your machine — `ask` will tell you clearly
138
+ if it can't find or run one, rather than failing silently.
139
+
140
+ Example `status` output against a workspace with one active lease:
141
+ ```
142
+ Workspace: /path/to/my-workspace
143
+ Database: /path/to/my-workspace/.peerhub/peerhub.sqlite3
144
+ Schema Migrations Applied: 24
145
+ Health Circuit ('system'): (no listing API exists yet -- not queryable from the CLI)
146
+ Active Leases: 1
147
+ Status: OK
148
+ ```
149
+
150
+ ## Run the tests
151
+
152
+ ```bash
153
+ pytest -q # fast suite, no real CLI calls
154
+ pytest -q -m slow # + the real-adapter/real-dispatch integration tests (needs real CLIs installed & authenticated, real wall-clock time)
155
+ pyright # static type check, should report 0 errors
156
+ ```
157
+
158
+ This repo's own convention (see `docs/design/FACT-REFRESH-PROCEDURE-R1.md`)
159
+ is to never cite a specific "current passing count" in this file — it
160
+ changes with nearly every commit. Run `pytest -q` yourself for the real,
161
+ current number.
@@ -0,0 +1,139 @@
1
+ # peerhub
2
+
3
+ A lightweight, installable coordination layer for orchestrating multiple AI CLI agents (Claude, Codex, Antigravity, ...) as collaborating peers: dispatch, routing, consensus, and health. It's built to eventually replace an existing hand-rolled multi-peer coordination system (`hub.py`) with a proper, tested package.
4
+
5
+ ## Status (2026-08-17)
6
+
7
+ **`peerhub ask` works end-to-end today** — it genuinely dispatches a prompt to a real peer CLI (agy/claude/codex) through peerhub's own governance, admission, and process-supervision layers, and returns the response. See "Try it" below.
8
+
9
+ - **Implemented**:
10
+ - Coordination kernel: dispatch, process supervision, heartbeat/liveness, routing, health, telemetry, SQLite persistence.
11
+ - GovernanceBroker's outbox/delivery-tracking split is fully completed end-to-end (legacy mirror writes removed, tables dropped).
12
+ - Capability-lease enforcement (all 5 increments): required capability tier threaded end-to-end, with an atomic pre-spawn enforcement gate and explicitly documented evidence audit.
13
+ - Persistence UoW split (read/write separation) and a migration-runner sequence-derivation fix that fast-fails on FK violations.
14
+ - A typed command boundary (`ApplicationAPI`/`Client`) with Pydantic v2 strict validation at the wire edge.
15
+ - **Real peer adapters** for all 3 target CLIs — `RealAgyAdapter`, `RealClaudeAdapter`, `RealCodexAdapter` — each proven both standalone and through the full supervised `dispatch_and_execute()` pipeline (not a bypass), plus a `FakePeerAdapter` for tests. Selectable via a peer-kind registry (`peerhub.adapters.registry`).
16
+ - **A real CLI**: `peerhub --version`, `peerhub status [--workspace PATH] [--peer/--all]`, and `peerhub ask PEER PROMPT [options]` — the last one performs a genuine end-to-end dispatch (admission → routing → supervised process execution → decoded response), not a stub. See "Try it" below.
17
+ - A direct-ask admission bootstrap (`peerhub.direct-ask/v1`) that auto-provisions a real, measured-readiness health/routing configuration for a single requested peer on a fresh workspace — no manual policy setup required.
18
+ - Static type checking (Pyright, 0 errors) and CI (GitHub Actions: pytest + pyright on every push/PR).
19
+ - A ratified traceability convention and fact-refresh procedure (`docs/design/TRACEABILITY-CONVENTION-R1.md`, `docs/design/FACT-REFRESH-PROCEDURE-R1.md`) — the fact-refresh tool (`tools/peerhub_facts/`) is built, functional, and handles drift reporting via live CLI probes.
20
+ - The full T1 Phase 3 outer loop: `dispatch_with_retries()`, session resume, streaming, tool-call capture, and failover routing.
21
+ - Multi-peer broadcast primitive A: Correlation schema and a working `BroadcastCoordinator.fan_out()` loop (T3).
22
+ - `EvidenceArtifact` / 3-tier context partitioning (completed for Claude and Codex adapters).
23
+ - Health/quota tracking CLI surface: `peerhub status --peer/--all` and quota telemetry persistence.
24
+ - Ctrl-C during `peerhub ask` walks the real cancellation ladder (`SOFT_CANCEL` → `TERMINATE_TREE` → `KILL_TREE`) via a proper background-thread dispatch and cancellation hook.
25
+ - **Designed, but not yet built**:
26
+ - Health/quota tracking's periodic background polling (`TelemetryWorker`) — currently awaiting a user decision on the process-host model (e.g., poll-on-demand vs. daemon).
27
+ - Windows-native Brokered Read-Only Reducers — blocked pending a policy call on required OS privileges.
28
+ - **Explicitly deferred (with named triggers)**:
29
+ - Alembic runtime cutover: Ratified as HOLD. The bespoke runner remains the sole runtime migration engine. Will revisit only if peerhub adopts SQLAlchemy ORM or is about to become the primary dispatch path.
30
+ - Formal multi-peer consensus (voting machinery / Primitive B): Deferred until the first `r10_requires_finalized_for` decision class is actually routed to peerhub.
31
+ - Durable response transcripts for broadcast: Deferred until a dispatch-layer durability mechanism is ratified.
32
+ - Capability-lease enforcement evidence: Changing adapter receipts to claim positive enforcement is deferred until a machine-owned launcher, plan-bound digest, empirical negative probe, and post-plan corroboration gate exist.
33
+ - Parallel fan-out: Deferred (blocked on measuring SQLite write contention).
34
+ - **Not yet implemented / honest gaps**:
35
+ - Phase 4 shadow-by-ownership-cluster validation and same-revision comparison + rollback proof.
36
+ - Crash-linkage recovery (resuming an interrupted round after a coordinator crash).
37
+ - Detailed per-vendor error-taxonomy mapping, and PTY transport are deliberately out of scope for the current adapter slice.
38
+ - No shadow-mode validation yet (routing a subset of real traffic through peerhub in parallel with `hub.py` for comparison before any real cutover) — `hub.py` remains the authoritative system for real multi-peer coordination work today; `peerhub ask` is a real, working command, not yet a production replacement.
39
+
40
+ See [`docs/design/HUB-REPLACEMENT-ROADMAP-2026-08-09.md`](docs/design/HUB-REPLACEMENT-ROADMAP-2026-08-09.md) for the full phased plan toward functional hub.py parity, and [`docs/design/PEERHUB-P-DRIVE-ISOLATION-2026-08-09.md`](docs/design/PEERHUB-P-DRIVE-ISOLATION-2026-08-09.md) for how peerhub's own runtime state relates to (and is deliberately isolated from) the wider P: development environment this repo happens to live inside during development. For the Engram/peerhub architectural separation itself (Engram is a portable dev-environment package, peerhub is the standalone AI-collaboration layer that used to live inside it) — what's done, what's verified clean in both directions, and what's left on either side — see [Engram's `2026-09-03_separation-completion-backlog.md`](https://github.com/greatgc-flow/Engram/blob/main/_sys/data/sessions/2026-09-03_separation-completion-backlog.md).
41
+
42
+ **hub.py-replacement TDD (2026-08-27, in progress; last updated 2026-09-02)**: real, tested code now exists for gap-2 (consensus), gap-4 (duty-lease), gap-5 (task lifecycle), gap-6 (governance/lessons, capability matching) in full, and gap-3/gap-7 partially. **All 6 domains now have real, runnable CLI commands** — `peerhub consensus|task|lesson|room|duty|session`, see "Try it" below — and `LegacyTranslator` (`peerhub/application/legacy.py`) translates 71 of the 90 legacy `hub.py` action names into typed wire commands for these same domains (plus a new room-participation-session domain — `init-session`/`end-session`, backed by a new `RoomParticipationCoordinator`, a new private-mailbox domain — `send`/`check`/`mark-read`/`thread-promote`/`lesson-broadcast`, a final-arbiter escalation domain — `arbiter-review`, backed by `ArbiterReviewCoordinator`, a peer-node-registry domain — `register-node`/`list-nodes`, backed by `PeerRegistryService`, a durable role-assignment domain — `assign-role`/`release-role`/`role-status`, backed by `RoleAssignmentService`, a feedback-journal domain — `feedback-add`/`feedback-list`/`feedback-resolve`, backed by `FeedbackService`, an operational-error journal domain — `report-error`, backed by `OperationalErrorService`, a workspace-global leadership domain — `leader-claim`/`leader-yield`, backed by `LeadershipService`, the core room-status aggregate — `status`, backed by `RoomsService`, plus (since 2026-09-01/02) health/admission quick wins — `health-check`/`peer-status`/`peer-recover`/`health-precheck`/`check-gate`/`health-sweep`/`peer-quarantine`, session-lease visibility — `lease-status`/`lease-sweep`, room coordination — `update-status`/`thread-new`/`broadcast`, model/profile binding — `model-status`, governance proposals — `proposal-add`/`proposal-vote` backed by a new `ProposalCoordinator`, a durable artifact ledger — `artifact-claim`/`artifact-status`/`artifact-finalize` backed by a new `ArtifactRecordService`, a bounded effect-outbox view — `broker-status`, and capability-scored leadership — `discover`/`elect-leader` backed by `CapabilityMatchingCoordinator`/`CapabilityConfigService`/`ElectionAuditService`). **All 71 of those actions now execute end-to-end**: `ApplicationAPI` registers a `CommandDescriptor` for each one, backed by the same real services the native CLI uses, so a legacy action name genuinely runs through `LegacyTranslator.translate()` → `Client.submit()` → a persisted result, not just a name-to-wire-command translation. See [`docs/design/HUB-REPLACEMENT-TDD-PROGRESS-2026-08-27.md`](docs/design/HUB-REPLACEMENT-TDD-PROGRESS-2026-08-27.md) for exactly what's real vs. still missing — the remaining 19 of the 90 `LEGACY_CATALOG` actions are each a settled, cited, permanent waiver (host-environment-only tooling, an architecturally-incompatible generic write queue, upstream account/billing management peerhub deliberately does not fake, one policy divergence, and a known counting-formula nuance around `ask`/`ask-all`/`ask-coordinator`'s non-executing translator branches) — not open work. **See [`docs/design/PEERHUB-BACKLOG-2026-08-27.md`](docs/design/PEERHUB-BACKLOG-2026-08-27.md) for the full consolidated remaining-work backlog**, organized by how ready each item is (mechanical wiring vs. needs new component code vs. needs a design round vs. entirely undesigned domain), and [`docs/design/SESSION-LESSONS-INDEX-2026-09-02.md`](docs/design/SESSION-LESSONS-INDEX-2026-09-02.md) for a topic-indexed pointer into this session's recurring bug classes and process lessons (peer-dispatch reliability, codebase-specific pitfalls, design discipline), each linking back to its full account in the TDD progress log rather than restating it.
43
+
44
+ **hub.py-replacement design phase (2026-08-23 to 2026-08-26): DESIGN-complete, TDD-ready.** The 7 functional categories `hub.py` covers beyond basic dispatch — compat/cutover strategy, consensus, session/room/thread continuity, health/leadership/duty-lease, task lifecycle/approval, governance/learning, and diagnostics parity — each now have either a concrete `TargetState` JSON schema or a concrete dedicated design, all converged and ratified (52 of 53 remaining open items resolved by design-consistency reasoning, 1 genuine business decision resolved by the user, 1 shared infrastructure prerequisite scoped as its own task). Start at [`docs/design/HUB-REPLACEMENT-PRE-TDD-FINAL-RATIFICATION-2026-08-26.md`](docs/design/HUB-REPLACEMENT-PRE-TDD-FINAL-RATIFICATION-2026-08-26.md) (supersedes older per-doc "Unresolved" lists), then [`docs/design/HUB-REPLACEMENT-DESIGN-REINFORCEMENT-INDEX-2026-08-24.md`](docs/design/HUB-REPLACEMENT-DESIGN-REINFORCEMENT-INDEX-2026-08-24.md) for the full per-gap breakdown. **Update**: most of this design has since been implemented during the TDD phase below (consensus, task, lessons, duty-lease, and half of session/room/thread) — see [`docs/design/PEERHUB-BACKLOG-2026-08-27.md`](docs/design/PEERHUB-BACKLOG-2026-08-27.md) for exactly what from this design phase is still unimplemented.
45
+
46
+ The target architecture was designed and converged through a 9-round adversarial review (`ag`/`cx`/`cc`) documented in [`docs/design/ARCHITECTURE.md`](docs/design/ARCHITECTURE.md). The full debate record, including rejected alternatives and evidence citations, is in [`docs/design/peerhub-architecture-debate.md`](docs/design/peerhub-architecture-debate.md). Later design decisions are under [`docs/design/`](docs/design/), dated by filename.
47
+
48
+ ## Install
49
+
50
+ ### Option A: Install via Pip from GitHub Release (Recommended)
51
+
52
+ ```bash
53
+ pip install "git+https://github.com/greatgc-flow/peerhub.git@v0.1.10"
54
+ ```
55
+
56
+ ### Option B: Local Editable Development Install
57
+
58
+ ```bash
59
+ git clone https://github.com/greatgc-flow/peerhub.git
60
+ cd peerhub
61
+ pip install -e . # runtime only
62
+ pip install -e .[dev] # + pytest, pyright, hypothesis, alembic (needed to run tests/type-check locally)
63
+ ```
64
+
65
+ Requires Python >= 3.11. This installs the `peerhub` package and registers a `peerhub` entrypoint on your PATH (verified via a real sdist build + install: `pyproject.toml`'s `[project.scripts]` defines only `peerhub`, not a separate `hub` alias).
66
+
67
+ ## Try it
68
+
69
+ ```bash
70
+ peerhub --version
71
+
72
+ # Real-time multi-peer quota telemetry, headroom matrix, and active failover routing targets
73
+ peerhub diag
74
+ # Add a governed-domain state section (consensus/task/lesson) to the same command
75
+ peerhub diag --domains --workspace ./my-workspace
76
+
77
+ # Check a workspace (read-only; reports "uninitialized" if no database yet)
78
+ peerhub status --workspace ./my-workspace
79
+
80
+ # Auto-detect which built-in peer CLIs (agy/claude/codex) are installed and
81
+ # resolvable on PATH right now -- no workspace required
82
+ peerhub adapter discover
83
+ peerhub adapter discover --json # MEASURED/UNAVAILABLE/ABSENT per peer
84
+
85
+ # Genuinely dispatch a prompt to a real peer and get its response
86
+ peerhub ask ag "say hello in exactly three words" --capability-tier READ_ONLY
87
+ peerhub ask cc "..." --capability-tier READ_ONLY --profile <profile-id> # claude, if you have more than one profile configured
88
+ peerhub ask cx "..." --capability-tier WORKTREE_WRITE --json # structured output instead of plain text
89
+
90
+ # Multi-peer broadcast coordination across peers with unified consensus
91
+ peerhub broadcast "reply with exactly: pong" --peers ag,cx --capability-tier READ_ONLY
92
+
93
+ # Propose a consensus round
94
+ peerhub consensus propose --round-id r1 --title "Ship" --question "Ready?" --body "Decide" --proposer cx --required cx,ag --eligible cx,ag
95
+ # Create a task
96
+ peerhub task create --task-id t1 --summary "Ship" --spec "Do it" --creator cx
97
+ # Propose a governance lesson
98
+ peerhub lesson propose --lesson-id l1 --title "Rule" --rule "Do this" --category ops --severity HIGH --proposer cx --affected cx,ag
99
+ # Create a room
100
+ peerhub room create --room-id room1 --topic-id topic1 --title "Work" --creator cx --participants cx,ag
101
+ # Claim terminal duty
102
+ peerhub duty claim --room-id room1 --instance-id i1 --profile-id cx.standard --owner-principal-id p1 --authority-epoch 1
103
+ # Open a room-participation session
104
+ peerhub session open --workspace-scope-id ws1 --room-id room1 --actor-principal-id p1 --instance-id i1 --profile-id cx.standard --session-fingerprint fp1
105
+ ```
106
+
107
+ `ask` accepts `--capability-tier` (required: READ_ONLY, WORKTREE_WRITE, GIT_MUTATE, REMOTE_MUTATE),
108
+ `--workspace PATH` (default `.`), `--profile PROFILE_ID`,
109
+ `--timeout-seconds`/`--silence-timeout-seconds`/`--max-output-bytes`
110
+ (process limits), and `--json`. Exit codes: `0` verified response,
111
+ `2` usage/config/pre-spawn failure (unknown peer, executable not found,
112
+ readiness probe failed), `3` definite peer/protocol failure, `4`
113
+ uncertain execution (timeout, lost lease ownership), `130` interrupted.
114
+ It requires the real peer CLI (`agy.exe`/`claude.cmd`/`codex.cmd`) to be
115
+ installed and authenticated on your machine — `ask` will tell you clearly
116
+ if it can't find or run one, rather than failing silently.
117
+
118
+ Example `status` output against a workspace with one active lease:
119
+ ```
120
+ Workspace: /path/to/my-workspace
121
+ Database: /path/to/my-workspace/.peerhub/peerhub.sqlite3
122
+ Schema Migrations Applied: 24
123
+ Health Circuit ('system'): (no listing API exists yet -- not queryable from the CLI)
124
+ Active Leases: 1
125
+ Status: OK
126
+ ```
127
+
128
+ ## Run the tests
129
+
130
+ ```bash
131
+ pytest -q # fast suite, no real CLI calls
132
+ pytest -q -m slow # + the real-adapter/real-dispatch integration tests (needs real CLIs installed & authenticated, real wall-clock time)
133
+ pyright # static type check, should report 0 errors
134
+ ```
135
+
136
+ This repo's own convention (see `docs/design/FACT-REFRESH-PROCEDURE-R1.md`)
137
+ is to never cite a specific "current passing count" in this file — it
138
+ changes with nearly every commit. Run `pytest -q` yourself for the real,
139
+ current number.
@@ -0,0 +1,5 @@
1
+ """PeerHub coordination engine."""
2
+
3
+ __version__ = "0.1.7"
4
+
5
+ __all__ = ("__version__",)
@@ -0,0 +1,7 @@
1
+ """PeerHub adapter-boundary contract package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "contract", # pyright: ignore[reportUnsupportedDunderAll]
7
+ ]
@@ -0,0 +1,253 @@
1
+ """Real Antigravity adapter implementation for Stage 3."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from collections.abc import Sequence
7
+
8
+ from peerhub.adapters.contract import (
9
+ AdapterRequest,
10
+ Capability,
11
+ DecodedOutput,
12
+ DecoderEvent,
13
+ DecoderEventKind,
14
+ InvocationPlan,
15
+ OutputChannel,
16
+ OutputDecoder,
17
+ PeerDescriptor,
18
+ ProfileDescriptor,
19
+ ProtocolAssessment,
20
+ PromptPolicy,
21
+ SessionAction,
22
+ SessionHint,
23
+ )
24
+ from peerhub.core.protocol import ErrorCode
25
+ from peerhub.core.execution import (
26
+ ProcessTerminalEvidence,
27
+ TransportKind,
28
+ TransportLimits,
29
+ )
30
+
31
+
32
+ def _split_canonical_lines(text: str) -> tuple[str, ...]:
33
+ if not text:
34
+ return ()
35
+ normalized = text.replace("\r\n", "\n").replace("\r", "\n")
36
+ lines = normalized.split("\n")
37
+ if normalized.endswith("\n"):
38
+ lines = lines[:-1]
39
+ return tuple(lines)
40
+
41
+
42
+ _AGY_PROFILE = ProfileDescriptor(
43
+ profile_id="ag.standard",
44
+ profile_class="tier",
45
+ supports_reasoning_effort=True,
46
+ )
47
+
48
+ _AGY_DESCRIPTOR = PeerDescriptor(
49
+ adapter_id="agy-peer",
50
+ adapter_version="1.0.0",
51
+ peer_kind="ag",
52
+ profiles=(_AGY_PROFILE,),
53
+ transports=frozenset({TransportKind.PIPE}),
54
+ capabilities=frozenset({Capability.SESSION}),
55
+ usage_provider_id=None,
56
+ readiness_probe_id="agy-readiness",
57
+ )
58
+
59
+
60
+ class AgyOutputDecoder:
61
+ """Decoder for agy.exe --output-format json."""
62
+
63
+ def __init__(self) -> None:
64
+ self._chunks: list[bytes] = []
65
+ self._finalized = False
66
+ self._events: list[DecoderEvent] = []
67
+
68
+ def feed(self, chunk: bytes, *, channel: OutputChannel = OutputChannel.STDOUT) -> tuple[DecoderEvent, ...]:
69
+ if self._finalized:
70
+ raise RuntimeError("feed called after finalize")
71
+ if type(chunk) is not bytes:
72
+ raise ValueError("chunk must be bytes")
73
+ self._chunks.append(chunk)
74
+ return ()
75
+
76
+ def finalize(self) -> DecodedOutput:
77
+ if self._finalized:
78
+ raise RuntimeError("finalize already called")
79
+ self._finalized = True
80
+
81
+ raw_bytes = b"".join(self._chunks)
82
+ canonical_text = ""
83
+ events: list[DecoderEvent] = []
84
+
85
+ try:
86
+ if raw_bytes:
87
+ decoded = raw_bytes.decode("utf-8")
88
+ start_idx = decoded.find("{")
89
+ end_idx = decoded.rfind("}")
90
+ if start_idx != -1 and end_idx != -1 and end_idx >= start_idx:
91
+ json_str = decoded[start_idx:end_idx + 1]
92
+ try:
93
+ parsed = json.loads(json_str)
94
+
95
+ conv_id = parsed.get("conversation_id")
96
+ if conv_id and isinstance(conv_id, str):
97
+ events.append(
98
+ DecoderEvent(
99
+ kind=DecoderEventKind.SESSION_IDENTITY,
100
+ payload={"session_id": conv_id},
101
+ )
102
+ )
103
+
104
+ response_text = parsed.get("response", "")
105
+ canonical_text = response_text or decoded
106
+ if response_text:
107
+ event = DecoderEvent(
108
+ kind=DecoderEventKind.ASSISTANT_TEXT,
109
+ payload={"text": response_text},
110
+ )
111
+ events.append(event)
112
+ if "error" in parsed:
113
+ error_val = parsed["error"]
114
+ err_type = ""
115
+ if isinstance(error_val, dict):
116
+ err_type = str(error_val.get("type", "")) # pyright: ignore[reportUnknownMemberType, reportUnknownArgumentType]
117
+ elif isinstance(error_val, str):
118
+ err_type = error_val
119
+
120
+ if err_type == "session_not_found":
121
+ events.append(DecoderEvent(kind=DecoderEventKind.VENDOR_ERROR, payload={"normalized_kind": "session_invalid", "evidence_source": "structured_vendor_output"}))
122
+ elif err_type == "invalid_model":
123
+ events.append(DecoderEvent(kind=DecoderEventKind.VENDOR_ERROR, payload={"normalized_kind": "invocation_plan_rejected", "evidence_source": "structured_vendor_output"}))
124
+ elif err_type == "rate_limit_exceeded":
125
+ events.append(DecoderEvent(kind=DecoderEventKind.VENDOR_ERROR, payload={"normalized_kind": "rate_limited", "evidence_source": "structured_vendor_output"}))
126
+ elif err_type == "network_error":
127
+ events.append(DecoderEvent(kind=DecoderEventKind.VENDOR_ERROR, payload={"normalized_kind": "network_unavailable", "evidence_source": "structured_vendor_output"}))
128
+ elif "auth" in err_type.lower() or "unauthorized" in err_type.lower():
129
+ events.append(DecoderEvent(kind=DecoderEventKind.VENDOR_ERROR, payload={"normalized_kind": "auth_unavailable", "evidence_source": "structured_vendor_output"}))
130
+ except Exception:
131
+ canonical_text = decoded
132
+ else:
133
+ canonical_text = decoded
134
+ except Exception:
135
+ # Not valid decoding or other error
136
+ if not canonical_text:
137
+ canonical_text = raw_bytes.decode("utf-8", errors="replace")
138
+
139
+ if "model_operand_invalid" in canonical_text and not any(e.kind == DecoderEventKind.VENDOR_ERROR for e in events):
140
+ events.append(DecoderEvent(kind=DecoderEventKind.VENDOR_ERROR, payload={"normalized_kind": "invocation_plan_rejected", "evidence_source": "known_terminal_pattern"}))
141
+
142
+ return DecodedOutput(
143
+ canonical_text=canonical_text,
144
+ canonical_lines=_split_canonical_lines(canonical_text),
145
+ events=tuple(events),
146
+ )
147
+
148
+
149
+ class RealAgyAdapter:
150
+ """Real adapter that shells out to agy.exe."""
151
+
152
+ descriptor = _AGY_DESCRIPTOR
153
+
154
+ def prompt_policy(self, profile: ProfileDescriptor) -> PromptPolicy:
155
+ if profile.profile_id != _AGY_PROFILE.profile_id:
156
+ raise ValueError(f"Unsupported profile {profile.profile_id}")
157
+ return PromptPolicy(
158
+ policy_id="ag-standard-policy",
159
+ max_inline_utf8_bytes=1000000,
160
+ artifact_reference_supported=False,
161
+ )
162
+
163
+ def plan_invocation(
164
+ self,
165
+ request: AdapterRequest,
166
+ profile: ProfileDescriptor,
167
+ session: SessionHint | None,
168
+ limits: TransportLimits,
169
+ ) -> InvocationPlan:
170
+
171
+ if profile.profile_id != _AGY_PROFILE.profile_id:
172
+ raise ValueError(f"Unsupported profile {profile.profile_id}")
173
+
174
+ prompt = request.prompt_content
175
+ if prompt is None:
176
+ raise ValueError("prompt_content is required")
177
+
178
+ if request.requested_session_action == SessionAction.RESUME:
179
+ if session is None or session.external_session_id is None:
180
+ raise ValueError("external_session_id is required for RESUME")
181
+ argv = ("agy.exe", "-p", prompt, "--output-format", "json", "--conversation", session.external_session_id)
182
+ redacted_display = "agy.exe -p <redacted> --output-format json --conversation <redacted>"
183
+ else:
184
+ argv = ("agy.exe", "-p", prompt, "--output-format", "json")
185
+ redacted_display = "agy.exe -p <redacted> --output-format json"
186
+
187
+ return InvocationPlan(
188
+ argv=argv,
189
+ cwd_reference=request.workspace_scope,
190
+ environment_delta={},
191
+ transport=TransportKind.PIPE,
192
+ stdin_payload=None,
193
+ limits=limits,
194
+ redacted_display=redacted_display,
195
+ artifacts=(),
196
+ session_action=request.requested_session_action,
197
+ )
198
+
199
+ def new_decoder(self, plan: InvocationPlan) -> OutputDecoder:
200
+ return AgyOutputDecoder()
201
+
202
+ def interpret_output(
203
+ self,
204
+ plan: InvocationPlan,
205
+ process: ProcessTerminalEvidence,
206
+ raw_chunks: Sequence[bytes],
207
+ ) -> ProtocolAssessment:
208
+ decoder = self.new_decoder(plan)
209
+ for chunk in raw_chunks:
210
+ decoder.feed(chunk, channel=OutputChannel.STDOUT)
211
+ decoded = decoder.finalize()
212
+ has_vendor_error = any(e.kind == DecoderEventKind.VENDOR_ERROR for e in decoded.events)
213
+
214
+ raw_bytes = b"".join(raw_chunks)
215
+ try:
216
+ decoded = raw_bytes.decode("utf-8")
217
+ start_idx = decoded.find("{")
218
+ end_idx = decoded.rfind("}")
219
+ if start_idx != -1 and end_idx != -1 and end_idx >= start_idx:
220
+ json_str = decoded[start_idx:end_idx + 1]
221
+ parsed = json.loads(json_str)
222
+ if "response" in parsed:
223
+ return ProtocolAssessment(
224
+ parsed=True,
225
+ response_present=True,
226
+ vendor_completion_marker=None,
227
+ suspected_truncation=False,
228
+ protocol_failure=None,
229
+ )
230
+ else:
231
+ return ProtocolAssessment(
232
+ parsed=True,
233
+ response_present=False,
234
+ vendor_completion_marker=None,
235
+ suspected_truncation=False,
236
+ protocol_failure=None if has_vendor_error else ErrorCode.INTERNAL_ERROR,
237
+ )
238
+
239
+ return ProtocolAssessment(
240
+ parsed=True,
241
+ response_present=False,
242
+ vendor_completion_marker=None,
243
+ suspected_truncation=False,
244
+ protocol_failure=None if has_vendor_error else ErrorCode.INTERNAL_ERROR,
245
+ )
246
+ except Exception:
247
+ return ProtocolAssessment(
248
+ parsed=False,
249
+ response_present=False,
250
+ vendor_completion_marker=None,
251
+ suspected_truncation=False,
252
+ protocol_failure=None if has_vendor_error else ErrorCode.INTERNAL_ERROR,
253
+ )