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.
- peerhub-0.1.10/LICENSE +21 -0
- peerhub-0.1.10/PKG-INFO +161 -0
- peerhub-0.1.10/README.md +139 -0
- peerhub-0.1.10/peerhub/__init__.py +5 -0
- peerhub-0.1.10/peerhub/adapters/__init__.py +7 -0
- peerhub-0.1.10/peerhub/adapters/agy_adapter.py +253 -0
- peerhub-0.1.10/peerhub/adapters/claude_adapter.py +277 -0
- peerhub-0.1.10/peerhub/adapters/codex_adapter.py +428 -0
- peerhub-0.1.10/peerhub/adapters/contract.py +657 -0
- peerhub-0.1.10/peerhub/adapters/discovery.py +93 -0
- peerhub-0.1.10/peerhub/adapters/registry.py +214 -0
- peerhub-0.1.10/peerhub/application/__init__.py +7 -0
- peerhub-0.1.10/peerhub/application/alert_raise.py +198 -0
- peerhub-0.1.10/peerhub/application/api.py +3136 -0
- peerhub-0.1.10/peerhub/application/arbiter_review.py +894 -0
- peerhub-0.1.10/peerhub/application/bootstrap.py +296 -0
- peerhub-0.1.10/peerhub/application/broadcast.py +415 -0
- peerhub-0.1.10/peerhub/application/broker_status.py +70 -0
- peerhub-0.1.10/peerhub/application/capability_config.py +559 -0
- peerhub-0.1.10/peerhub/application/capability_matching.py +574 -0
- peerhub-0.1.10/peerhub/application/commands.py +192 -0
- peerhub-0.1.10/peerhub/application/direct_ask.py +301 -0
- peerhub-0.1.10/peerhub/application/health_revalidation.py +596 -0
- peerhub-0.1.10/peerhub/application/leadership.py +655 -0
- peerhub-0.1.10/peerhub/application/lease_status.py +54 -0
- peerhub-0.1.10/peerhub/application/legacy.py +2553 -0
- peerhub-0.1.10/peerhub/application/lesson_broadcast.py +194 -0
- peerhub-0.1.10/peerhub/application/lesson_inject.py +174 -0
- peerhub-0.1.10/peerhub/application/peer_registry.py +448 -0
- peerhub-0.1.10/peerhub/application/process_lease_sweep.py +238 -0
- peerhub-0.1.10/peerhub/application/proposals.py +709 -0
- peerhub-0.1.10/peerhub/application/quarantine_review.py +153 -0
- peerhub-0.1.10/peerhub/application/retry.py +907 -0
- peerhub-0.1.10/peerhub/application/role_assignment.py +326 -0
- peerhub-0.1.10/peerhub/application/room_broadcast.py +145 -0
- peerhub-0.1.10/peerhub/application/session_saga.py +183 -0
- peerhub-0.1.10/peerhub/application/status.py +45 -0
- peerhub-0.1.10/peerhub/application/thread_new.py +67 -0
- peerhub-0.1.10/peerhub/application/workflows.py +1589 -0
- peerhub-0.1.10/peerhub/builtins/__init__.py +7 -0
- peerhub-0.1.10/peerhub/builtins/fake_adapter.py +302 -0
- peerhub-0.1.10/peerhub/cli.py +4158 -0
- peerhub-0.1.10/peerhub/client.py +72 -0
- peerhub-0.1.10/peerhub/core/__init__.py +1 -0
- peerhub-0.1.10/peerhub/core/binary_resolution.py +67 -0
- peerhub-0.1.10/peerhub/core/context.py +67 -0
- peerhub-0.1.10/peerhub/core/errors.py +624 -0
- peerhub-0.1.10/peerhub/core/evidence.py +127 -0
- peerhub-0.1.10/peerhub/core/execution.py +134 -0
- peerhub-0.1.10/peerhub/core/identity.py +106 -0
- peerhub-0.1.10/peerhub/core/ports.py +11 -0
- peerhub-0.1.10/peerhub/core/protocol.py +841 -0
- peerhub-0.1.10/peerhub/dispatch/__init__.py +11 -0
- peerhub-0.1.10/peerhub/dispatch/admission.py +582 -0
- peerhub-0.1.10/peerhub/dispatch/artifact_coordination.py +63 -0
- peerhub-0.1.10/peerhub/dispatch/artifacts.py +288 -0
- peerhub-0.1.10/peerhub/dispatch/attempt_lifecycle.py +755 -0
- peerhub-0.1.10/peerhub/dispatch/capability.py +588 -0
- peerhub-0.1.10/peerhub/dispatch/capability_policy.py +201 -0
- peerhub-0.1.10/peerhub/dispatch/completion.py +276 -0
- peerhub-0.1.10/peerhub/dispatch/contract.py +1434 -0
- peerhub-0.1.10/peerhub/dispatch/duty_lease.py +249 -0
- peerhub-0.1.10/peerhub/dispatch/heartbeat.py +289 -0
- peerhub-0.1.10/peerhub/dispatch/helpers.py +185 -0
- peerhub-0.1.10/peerhub/dispatch/materializer.py +787 -0
- peerhub-0.1.10/peerhub/dispatch/model.py +1460 -0
- peerhub-0.1.10/peerhub/dispatch/pipe.py +567 -0
- peerhub-0.1.10/peerhub/dispatch/process.py +758 -0
- peerhub-0.1.10/peerhub/dispatch/retry_authorization.py +898 -0
- peerhub-0.1.10/peerhub/dispatch/room_session.py +387 -0
- peerhub-0.1.10/peerhub/dispatch/service.py +1037 -0
- peerhub-0.1.10/peerhub/dispatch/session_lease.py +333 -0
- peerhub-0.1.10/peerhub/dispatch/terminal_duty.py +151 -0
- peerhub-0.1.10/peerhub/dispatch/tree_controller.py +615 -0
- peerhub-0.1.10/peerhub/dispatch/unit_of_work.py +453 -0
- peerhub-0.1.10/peerhub/events/contract.py +42 -0
- peerhub-0.1.10/peerhub/governance/__init__.py +1 -0
- peerhub-0.1.10/peerhub/governance/activity.py +114 -0
- peerhub-0.1.10/peerhub/governance/artifact_records.py +294 -0
- peerhub-0.1.10/peerhub/governance/broker.py +618 -0
- peerhub-0.1.10/peerhub/governance/consensus.py +679 -0
- peerhub-0.1.10/peerhub/governance/contract.py +624 -0
- peerhub-0.1.10/peerhub/governance/directives.py +164 -0
- peerhub-0.1.10/peerhub/governance/election_audit.py +313 -0
- peerhub-0.1.10/peerhub/governance/feedback.py +227 -0
- peerhub-0.1.10/peerhub/governance/file_locks.py +168 -0
- peerhub-0.1.10/peerhub/governance/invariant_requests.py +176 -0
- peerhub-0.1.10/peerhub/governance/lessons.py +234 -0
- peerhub-0.1.10/peerhub/governance/mutations.py +243 -0
- peerhub-0.1.10/peerhub/governance/operational_errors.py +264 -0
- peerhub-0.1.10/peerhub/governance/rooms.py +1241 -0
- peerhub-0.1.10/peerhub/governance/tasks.py +316 -0
- peerhub-0.1.10/peerhub/health/__init__.py +7 -0
- peerhub-0.1.10/peerhub/health/contract.py +1294 -0
- peerhub-0.1.10/peerhub/health/model.py +873 -0
- peerhub-0.1.10/peerhub/health/service.py +1897 -0
- peerhub-0.1.10/peerhub/persistence/__init__.py +1 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0001_phase1_kernel.sql +162 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0002_dispatch_session_lease.sql +59 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0003_command_request_attempt.sql +448 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0004_idempotency_aliases.sql +97 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0005_health_routing.sql +272 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0006_recovery_probe_single_flight.sql +19 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0007_health_projection_readiness_context.sql +33 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0008_dispatch_artifact_metadata.sql +107 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0009_session_binding_generations.sql +20 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0010_session_context_telemetry.sql +35 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0011_admission_snapshot_configuration_digest.sql +96 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0012_session_rotation_conversation_scope.sql +29 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0013_session_context_conversation_scope.sql +30 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0014_event_log_delivery_split.sql +54 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0015_effect_receipts_delivery_fk.sql +49 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0016_dispatch_artifact_manifests_event_log_fk.sql +67 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0017_drop_legacy_outbox.sql +15 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0018_capability_leases.sql +72 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0019_route_decision_capability_tier.sql +26 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0020_broadcast_correlation.sql +97 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0021_broadcast_leg_timeout_state.sql +57 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0022_retry_authority.sql +109 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0023_evidence_artifacts.sql +26 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0024_telemetry_quota_tracking.sql +58 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0025_governed_target_listing.sql +22 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0026_duty_leases.sql +28 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0027_duty_lease_recovery.sql +16 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0028_room_participation_sessions.sql +67 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0029_recovery_probe_grant_lifecycle.sql +98 -0
- peerhub-0.1.10/peerhub/persistence/migrations/0030_administrative_recovery_budget.sql +16 -0
- peerhub-0.1.10/peerhub/persistence/migrations/__init__.py +1 -0
- peerhub-0.1.10/peerhub/persistence/sqlite.py +1901 -0
- peerhub-0.1.10/peerhub/persistence/sqlite_dispatch.py +2753 -0
- peerhub-0.1.10/peerhub/persistence/sqlite_events.py +185 -0
- peerhub-0.1.10/peerhub/persistence/sqlite_governance.py +980 -0
- peerhub-0.1.10/peerhub/persistence/sqlite_health.py +918 -0
- peerhub-0.1.10/peerhub/persistence/sqlite_helpers.py +38 -0
- peerhub-0.1.10/peerhub/persistence/sqlite_routing.py +196 -0
- peerhub-0.1.10/peerhub/persistence/sqlite_telemetry.py +847 -0
- peerhub-0.1.10/peerhub/routing/__init__.py +7 -0
- peerhub-0.1.10/peerhub/routing/capability_matching.py +698 -0
- peerhub-0.1.10/peerhub/routing/contract.py +727 -0
- peerhub-0.1.10/peerhub/routing/model.py +312 -0
- peerhub-0.1.10/peerhub/routing/service.py +251 -0
- peerhub-0.1.10/peerhub/runtime.py +434 -0
- peerhub-0.1.10/peerhub/state/__init__.py +1 -0
- peerhub-0.1.10/peerhub/state/contract.py +117 -0
- peerhub-0.1.10/peerhub/telemetry/__init__.py +8 -0
- peerhub-0.1.10/peerhub/telemetry/contract.py +454 -0
- peerhub-0.1.10/peerhub/telemetry/domain_rows.py +69 -0
- peerhub-0.1.10/peerhub/telemetry/presenter.py +613 -0
- peerhub-0.1.10/peerhub/telemetry/projections.py +664 -0
- peerhub-0.1.10/peerhub/telemetry/quota_polling.py +750 -0
- peerhub-0.1.10/peerhub/telemetry/statusline.py +106 -0
- peerhub-0.1.10/peerhub.egg-info/PKG-INFO +161 -0
- peerhub-0.1.10/peerhub.egg-info/SOURCES.txt +159 -0
- peerhub-0.1.10/peerhub.egg-info/dependency_links.txt +1 -0
- peerhub-0.1.10/peerhub.egg-info/entry_points.txt +2 -0
- peerhub-0.1.10/peerhub.egg-info/requires.txt +10 -0
- peerhub-0.1.10/peerhub.egg-info/top_level.txt +1 -0
- peerhub-0.1.10/pyproject.toml +43 -0
- peerhub-0.1.10/setup.cfg +4 -0
- peerhub-0.1.10/tests/test_event_dataclass_validation.py +73 -0
- 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.
|
peerhub-0.1.10/PKG-INFO
ADDED
|
@@ -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.
|
peerhub-0.1.10/README.md
ADDED
|
@@ -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,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
|
+
)
|