hermes-voip 0.1.1__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.
- hermes_voip-0.1.1/.claude/hooks/enforce-worktree.mjs +65 -0
- hermes_voip-0.1.1/.claude/settings.json +42 -0
- hermes_voip-0.1.1/.claude/skills/adr/SKILL.md +29 -0
- hermes_voip-0.1.1/.claude/skills/memory/SKILL.md +50 -0
- hermes_voip-0.1.1/.claude/skills/orchestrate/SKILL.md +317 -0
- hermes_voip-0.1.1/.claude/skills/orchestrate/wave.workflow.js +416 -0
- hermes_voip-0.1.1/.claude/skills/worktree-lane/SKILL.md +74 -0
- hermes_voip-0.1.1/.devcontainer/.gitignore +1 -0
- hermes_voip-0.1.1/.devcontainer/Dockerfile +135 -0
- hermes_voip-0.1.1/.devcontainer/devcontainer.json +46 -0
- hermes_voip-0.1.1/.devcontainer/docker-compose.yml +41 -0
- hermes_voip-0.1.1/.devcontainer/post-create.sh +142 -0
- hermes_voip-0.1.1/.devcontainer/post-start.sh +13 -0
- hermes_voip-0.1.1/.env.example +501 -0
- hermes_voip-0.1.1/.github/workflows/gate.yml +205 -0
- hermes_voip-0.1.1/.github/workflows/gitleaks.yml +23 -0
- hermes_voip-0.1.1/.github/workflows/publish.yml +274 -0
- hermes_voip-0.1.1/.github/workflows/supply-chain.yml +71 -0
- hermes_voip-0.1.1/.gitignore +65 -0
- hermes_voip-0.1.1/.gitleaks.toml +33 -0
- hermes_voip-0.1.1/.mcp.json +16 -0
- hermes_voip-0.1.1/.pre-commit-config.yaml +43 -0
- hermes_voip-0.1.1/.python-version +1 -0
- hermes_voip-0.1.1/AGENTS.md +199 -0
- hermes_voip-0.1.1/CHANGELOG.md +176 -0
- hermes_voip-0.1.1/CLAUDE.md +48 -0
- hermes_voip-0.1.1/LICENSE +194 -0
- hermes_voip-0.1.1/NOTICE +5 -0
- hermes_voip-0.1.1/PKG-INFO +712 -0
- hermes_voip-0.1.1/README.md +678 -0
- hermes_voip-0.1.1/THIRD_PARTY_NOTICES.md +142 -0
- hermes_voip-0.1.1/conftest.py +23 -0
- hermes_voip-0.1.1/docs/CLAUDE.md +7 -0
- hermes_voip-0.1.1/docs/adr/0000-template.md +26 -0
- hermes_voip-0.1.1/docs/adr/0001-python-uv-toolchain.md +50 -0
- hermes_voip-0.1.1/docs/adr/0002-voip-plugin-architecture.md +123 -0
- hermes_voip-0.1.1/docs/adr/0003-cascaded-media-architecture.md +177 -0
- hermes_voip-0.1.1/docs/adr/0004-provider-interface-abstraction.md +398 -0
- hermes_voip-0.1.1/docs/adr/0005-telephony-media-and-transport.md +239 -0
- hermes_voip-0.1.1/docs/adr/0006-streaming-stt-provider.md +190 -0
- hermes_voip-0.1.1/docs/adr/0007-streaming-tts-provider.md +345 -0
- hermes_voip-0.1.1/docs/adr/0008-vad-endpointing-barge-in.md +203 -0
- hermes_voip-0.1.1/docs/adr/0009-prompt-injection-guard.md +234 -0
- hermes_voip-0.1.1/docs/adr/0010-dtmf-handling.md +272 -0
- hermes_voip-0.1.1/docs/adr/0011-multi-registration-and-call-control.md +124 -0
- hermes_voip-0.1.1/docs/adr/0012-default-model-pins.md +90 -0
- hermes_voip-0.1.1/docs/adr/0013-sdes-srtp-media-encryption.md +181 -0
- hermes_voip-0.1.1/docs/adr/0014-adapter-subclasses-real-base-via-lazy-split.md +93 -0
- hermes_voip-0.1.1/docs/adr/0015-symmetric-rtp-comedia-latching.md +84 -0
- hermes_voip-0.1.1/docs/adr/0016-webrtc-transport.md +391 -0
- hermes_voip-0.1.1/docs/adr/0017-outbound-tts-resample-in-send-path.md +158 -0
- hermes_voip-0.1.1/docs/adr/0018-video-call-support.md +606 -0
- hermes_voip-0.1.1/docs/adr/0019-outbound-calling-uac-originate.md +445 -0
- hermes_voip-0.1.1/docs/adr/0020-voip-caller-modes.md +547 -0
- hermes_voip-0.1.1/docs/adr/0021-voip-caller-groups.md +478 -0
- hermes_voip-0.1.1/docs/adr/0022-g722-wideband-codec.md +143 -0
- hermes_voip-0.1.1/docs/adr/0023-echo-robust-barge-in.md +164 -0
- hermes_voip-0.1.1/docs/adr/0024-supply-chain-audit-all-extras.md +90 -0
- hermes_voip-0.1.1/docs/adr/0025-tts-failover-and-elevenlabs-model-id-guard.md +161 -0
- hermes_voip-0.1.1/docs/adr/0026-call-termination-hermes-session-signal.md +195 -0
- hermes_voip-0.1.1/docs/adr/0027-elevenlabs-v3-audio-tags-model-conditional.md +114 -0
- hermes_voip-0.1.1/docs/adr/0028-barge-in-clean-stop-fade.md +132 -0
- hermes_voip-0.1.1/docs/adr/0029-agent-triggered-outbound-calls.md +194 -0
- hermes_voip-0.1.1/docs/adr/0030-dead-air-comfort-filler.md +169 -0
- hermes_voip-0.1.1/docs/adr/0031-intercom-caller-mode-and-in-call-dtmf-actuation.md +200 -0
- hermes_voip-0.1.1/docs/adr/0032-webrtc-media-wiring-and-opus.md +171 -0
- hermes_voip-0.1.1/docs/adr/0033-in-process-aec-aggressive-barge-in.md +206 -0
- hermes_voip-0.1.1/docs/adr/0034-ice-turn-trickle-consent.md +253 -0
- hermes_voip-0.1.1/docs/adr/0035-caller-group-channel-routing.md +138 -0
- hermes_voip-0.1.1/docs/adr/0036-dtmf-sip-info-and-in-band.md +163 -0
- hermes_voip-0.1.1/docs/adr/0037-hermes-plugin-manifest-and-install-models.md +181 -0
- hermes_voip-0.1.1/docs/adr/0038-wss-signalling-wiring.md +202 -0
- hermes_voip-0.1.1/docs/adr/0042-webrtc-inbound-real-gateway-fixes.md +118 -0
- hermes_voip-0.1.1/docs/adr/0043-ipv6-first-ice-and-default-stun.md +87 -0
- hermes_voip-0.1.1/docs/adr/0044-webrtc-video.md +215 -0
- hermes_voip-0.1.1/docs/adr/0045-multi-intercom-openings.md +130 -0
- hermes_voip-0.1.1/docs/adr/0046-best-practices-alignment.md +133 -0
- hermes_voip-0.1.1/docs/adr/0047-bundled-call-skills.md +99 -0
- hermes_voip-0.1.1/docs/adr/0048-transfer-attended.md +123 -0
- hermes_voip-0.1.1/docs/adr/0049-outbound-webrtc-opus.md +130 -0
- hermes_voip-0.1.1/docs/adr/0050-webrtc-dtls-active-answerer.md +79 -0
- hermes_voip-0.1.1/docs/adr/0052-rich-inbound-call-context.md +123 -0
- hermes_voip-0.1.1/docs/adr/0053-sip-dtls-srtp-media.md +255 -0
- hermes_voip-0.1.1/docs/adr/0054-comfort-filler-random-periodic-multilanguage.md +138 -0
- hermes_voip-0.1.1/docs/adr/0055-sip-signalling-robustness-refresh-recovery-and-cancel.md +147 -0
- hermes_voip-0.1.1/docs/adr/0056-media-quality-loss-resilience.md +170 -0
- hermes_voip-0.1.1/docs/adr/0057-conversational-ux-silence-goodbye-streaming.md +240 -0
- hermes_voip-0.1.1/docs/adr/0058-sip-digest-sha256-md5sess.md +73 -0
- hermes_voip-0.1.1/docs/adr/0059-adapter-production-safety-lifecycle.md +163 -0
- hermes_voip-0.1.1/docs/adr/0060-s3-tables-iceberg-call-events-recordings.md +356 -0
- hermes_voip-0.1.1/docs/adr/0061-rtcp-sr-rr-rtcp-mux.md +216 -0
- hermes_voip-0.1.1/docs/adr/0062-pyjwt-cve-override.md +228 -0
- hermes_voip-0.1.1/docs/adr/0063-adapter-media-activation-and-error-sanitization.md +142 -0
- hermes_voip-0.1.1/docs/adr/0064-call-progress-detection-fax-amd.md +251 -0
- hermes_voip-0.1.1/docs/adr/0065-answer-time-dialog-registration-ack-aware-abort.md +132 -0
- hermes_voip-0.1.1/docs/adr/0066-srtcp-transform.md +140 -0
- hermes_voip-0.1.1/docs/adr/0067-outbound-sdes-srtp-offering.md +183 -0
- hermes_voip-0.1.1/docs/adr/0068-v3-audio-tag-prompt-encouragement.md +137 -0
- hermes_voip-0.1.1/docs/adr/0069-outbound-sip-cancel.md +126 -0
- hermes_voip-0.1.1/docs/adr/0070-secure-media-mandate.md +116 -0
- hermes_voip-0.1.1/docs/adr/0071-rfc4028-session-timers.md +171 -0
- hermes_voip-0.1.1/docs/adr/0072-autonomous-orchestration-loop.md +163 -0
- hermes_voip-0.1.1/docs/adr/0073-sdes-answer-selects-strongest-suite.md +52 -0
- hermes_voip-0.1.1/docs/adr/0074-proactive-place-call-operator-origin.md +86 -0
- hermes_voip-0.1.1/docs/adr/0075-structured-lifecycle-rtcp-log-events.md +72 -0
- hermes_voip-0.1.1/docs/adr/0076-refuse-spoken-safe-decline-line.md +98 -0
- hermes_voip-0.1.1/docs/adr/0077-dtmf-feed-result-type.md +125 -0
- hermes_voip-0.1.1/docs/adr/0078-negotiate-audio-answerer-preference.md +111 -0
- hermes_voip-0.1.1/docs/adr/0079-reinvite-nonaudio-sdp-rejection.md +48 -0
- hermes_voip-0.1.1/docs/adr/0080-registration-sips-on-secure-transport-and-digest-reactive-contracts.md +135 -0
- hermes_voip-0.1.1/docs/adr/0081-malformed-message-skip-vs-framing-failure.md +161 -0
- hermes_voip-0.1.1/docs/adr/CLAUDE.md +7 -0
- hermes_voip-0.1.1/docs/backlog.md +1090 -0
- hermes_voip-0.1.1/docs/plan/IMPLEMENTATION-PLAN.md +249 -0
- hermes_voip-0.1.1/docs/plan/MULTIREG-CALLCONTROL-PLAN.md +49 -0
- hermes_voip-0.1.1/docs/reference/elevenlabs-v3-audio-tags.md +113 -0
- hermes_voip-0.1.1/docs/runbooks/0001-sip-extension-credentials.md +76 -0
- hermes_voip-0.1.1/docs/runbooks/0002-voip-live-validation.md +787 -0
- hermes_voip-0.1.1/docs/runbooks/0003-supply-chain-audit.md +143 -0
- hermes_voip-0.1.1/docs/runbooks/0004-voip-tts-voice.md +230 -0
- hermes_voip-0.1.1/docs/runbooks/0005-voip-rtp-inactivity-timeout.md +88 -0
- hermes_voip-0.1.1/docs/runbooks/0006-voip-comfort-filler.md +170 -0
- hermes_voip-0.1.1/docs/runbooks/0007-voip-outbound-calling.md +262 -0
- hermes_voip-0.1.1/docs/runbooks/0008-voip-intercom-and-dtmf.md +308 -0
- hermes_voip-0.1.1/docs/runbooks/0009-voip-webrtc-media.md +493 -0
- hermes_voip-0.1.1/docs/runbooks/0010-voip-caller-modes.md +285 -0
- hermes_voip-0.1.1/docs/runbooks/0011-voip-enable-plugin.md +189 -0
- hermes_voip-0.1.1/docs/runbooks/0012-voip-inbound-call-context.md +106 -0
- hermes_voip-0.1.1/docs/runbooks/0013-voip-incident-oncall.md +615 -0
- hermes_voip-0.1.1/docs/runbooks/0014-voip-slo-metrics.md +467 -0
- hermes_voip-0.1.1/docs/runbooks/0015-voip-silence-reprompt-and-goodbye.md +134 -0
- hermes_voip-0.1.1/docs/runbooks/0016-orchestration-loop.md +196 -0
- hermes_voip-0.1.1/docs/runbooks/0017-devcontainer-resources.md +84 -0
- hermes_voip-0.1.1/docs/runbooks/0018-voip-acoustic-echo-cancellation.md +149 -0
- hermes_voip-0.1.1/docs/runbooks/0019-release-process.md +298 -0
- hermes_voip-0.1.1/docs/runbooks/CLAUDE.md +12 -0
- hermes_voip-0.1.1/docs/stack.md +26 -0
- hermes_voip-0.1.1/packaging/hermes-plugins/hermes-voip/__init__.py +26 -0
- hermes_voip-0.1.1/packaging/hermes-plugins/hermes-voip/plugin.yaml +147 -0
- hermes_voip-0.1.1/pyproject.toml +323 -0
- hermes_voip-0.1.1/src/hermes_voip/__init__.py +75 -0
- hermes_voip-0.1.1/src/hermes_voip/_header_list.py +53 -0
- hermes_voip-0.1.1/src/hermes_voip/_lazy_singleton.py +239 -0
- hermes_voip-0.1.1/src/hermes_voip/adapter.py +7683 -0
- hermes_voip-0.1.1/src/hermes_voip/aio.py +138 -0
- hermes_voip-0.1.1/src/hermes_voip/call.py +792 -0
- hermes_voip-0.1.1/src/hermes_voip/call_context.py +584 -0
- hermes_voip-0.1.1/src/hermes_voip/call_end.py +150 -0
- hermes_voip-0.1.1/src/hermes_voip/caller_modes.py +1476 -0
- hermes_voip-0.1.1/src/hermes_voip/config.py +2324 -0
- hermes_voip-0.1.1/src/hermes_voip/dialog.py +364 -0
- hermes_voip-0.1.1/src/hermes_voip/digest.py +367 -0
- hermes_voip-0.1.1/src/hermes_voip/dtmf.py +548 -0
- hermes_voip-0.1.1/src/hermes_voip/dtmf_config.py +163 -0
- hermes_voip-0.1.1/src/hermes_voip/dtmf_confirm.py +215 -0
- hermes_voip-0.1.1/src/hermes_voip/dtmf_sipinfo.py +111 -0
- hermes_voip-0.1.1/src/hermes_voip/guard/__init__.py +32 -0
- hermes_voip-0.1.1/src/hermes_voip/guard/_onnx_runtime.py +171 -0
- hermes_voip-0.1.1/src/hermes_voip/guard/normalize.py +388 -0
- hermes_voip-0.1.1/src/hermes_voip/guard/onnx.py +296 -0
- hermes_voip-0.1.1/src/hermes_voip/hermes_surface.py +127 -0
- hermes_voip-0.1.1/src/hermes_voip/incall.py +317 -0
- hermes_voip-0.1.1/src/hermes_voip/intercom.py +362 -0
- hermes_voip-0.1.1/src/hermes_voip/keepalive.py +108 -0
- hermes_voip-0.1.1/src/hermes_voip/manager.py +574 -0
- hermes_voip-0.1.1/src/hermes_voip/manifest.py +329 -0
- hermes_voip-0.1.1/src/hermes_voip/media/__init__.py +6 -0
- hermes_voip-0.1.1/src/hermes_voip/media/aec.py +369 -0
- hermes_voip-0.1.1/src/hermes_voip/media/audio.py +398 -0
- hermes_voip-0.1.1/src/hermes_voip/media/call_loop.py +2092 -0
- hermes_voip-0.1.1/src/hermes_voip/media/call_progress.py +634 -0
- hermes_voip-0.1.1/src/hermes_voip/media/dtls.py +908 -0
- hermes_voip-0.1.1/src/hermes_voip/media/endpoint.py +148 -0
- hermes_voip-0.1.1/src/hermes_voip/media/engine.py +3716 -0
- hermes_voip-0.1.1/src/hermes_voip/media/g722.py +456 -0
- hermes_voip-0.1.1/src/hermes_voip/media/ice.py +760 -0
- hermes_voip-0.1.1/src/hermes_voip/media/opus.py +436 -0
- hermes_voip-0.1.1/src/hermes_voip/media/sip_dtls_session.py +541 -0
- hermes_voip-0.1.1/src/hermes_voip/media/srtcp.py +599 -0
- hermes_voip-0.1.1/src/hermes_voip/media/srtp.py +921 -0
- hermes_voip-0.1.1/src/hermes_voip/media/vad.py +525 -0
- hermes_voip-0.1.1/src/hermes_voip/media/video_rtp.py +527 -0
- hermes_voip-0.1.1/src/hermes_voip/media/webrtc_session.py +560 -0
- hermes_voip-0.1.1/src/hermes_voip/message.py +351 -0
- hermes_voip-0.1.1/src/hermes_voip/multi_intercom.py +606 -0
- hermes_voip-0.1.1/src/hermes_voip/notice_filter.py +154 -0
- hermes_voip-0.1.1/src/hermes_voip/originate.py +190 -0
- hermes_voip-0.1.1/src/hermes_voip/outbound_allow.py +74 -0
- hermes_voip-0.1.1/src/hermes_voip/plugin.py +388 -0
- hermes_voip-0.1.1/src/hermes_voip/plugin.yaml +147 -0
- hermes_voip-0.1.1/src/hermes_voip/provider_error.py +137 -0
- hermes_voip-0.1.1/src/hermes_voip/providers/__init__.py +44 -0
- hermes_voip-0.1.1/src/hermes_voip/providers/asr.py +57 -0
- hermes_voip-0.1.1/src/hermes_voip/providers/audio.py +50 -0
- hermes_voip-0.1.1/src/hermes_voip/providers/build.py +376 -0
- hermes_voip-0.1.1/src/hermes_voip/providers/guard.py +71 -0
- hermes_voip-0.1.1/src/hermes_voip/providers/onnx_compat.py +71 -0
- hermes_voip-0.1.1/src/hermes_voip/providers/policy.py +212 -0
- hermes_voip-0.1.1/src/hermes_voip/providers/registry.py +49 -0
- hermes_voip-0.1.1/src/hermes_voip/providers/transport.py +51 -0
- hermes_voip-0.1.1/src/hermes_voip/providers/tts.py +74 -0
- hermes_voip-0.1.1/src/hermes_voip/py.typed +0 -0
- hermes_voip-0.1.1/src/hermes_voip/refer.py +535 -0
- hermes_voip-0.1.1/src/hermes_voip/registration.py +419 -0
- hermes_voip-0.1.1/src/hermes_voip/rtcp.py +1091 -0
- hermes_voip-0.1.1/src/hermes_voip/rtp.py +422 -0
- hermes_voip-0.1.1/src/hermes_voip/sdp.py +2101 -0
- hermes_voip-0.1.1/src/hermes_voip/session_timer.py +344 -0
- hermes_voip-0.1.1/src/hermes_voip/sip.py +30 -0
- hermes_voip-0.1.1/src/hermes_voip/skills/enquire-price-availability/SKILL.md +37 -0
- hermes_voip-0.1.1/src/hermes_voip/skills/intercom-open-for-delivery/SKILL.md +42 -0
- hermes_voip-0.1.1/src/hermes_voip/skills/make-reservation/SKILL.md +38 -0
- hermes_voip-0.1.1/src/hermes_voip/skills/reception/SKILL.md +36 -0
- hermes_voip-0.1.1/src/hermes_voip/skills/take-message/SKILL.md +37 -0
- hermes_voip-0.1.1/src/hermes_voip/skills.py +205 -0
- hermes_voip-0.1.1/src/hermes_voip/spoken_text.py +281 -0
- hermes_voip-0.1.1/src/hermes_voip/stt/__init__.py +31 -0
- hermes_voip-0.1.1/src/hermes_voip/stt/deepgram.py +414 -0
- hermes_voip-0.1.1/src/hermes_voip/stt/resample.py +172 -0
- hermes_voip-0.1.1/src/hermes_voip/stt/sherpa_onnx.py +501 -0
- hermes_voip-0.1.1/src/hermes_voip/tools.py +326 -0
- hermes_voip-0.1.1/src/hermes_voip/transport/__init__.py +31 -0
- hermes_voip-0.1.1/src/hermes_voip/transport/connection.py +978 -0
- hermes_voip-0.1.1/src/hermes_voip/transport/framing.py +137 -0
- hermes_voip-0.1.1/src/hermes_voip/transport/transaction.py +201 -0
- hermes_voip-0.1.1/src/hermes_voip/transport/ws_connection.py +523 -0
- hermes_voip-0.1.1/src/hermes_voip/tts/__init__.py +44 -0
- hermes_voip-0.1.1/src/hermes_voip/tts/_stream.py +250 -0
- hermes_voip-0.1.1/src/hermes_voip/tts/elevenlabs.py +674 -0
- hermes_voip-0.1.1/src/hermes_voip/tts/failover.py +392 -0
- hermes_voip-0.1.1/src/hermes_voip/tts/segment.py +305 -0
- hermes_voip-0.1.1/src/hermes_voip/tts/sherpa_kokoro.py +345 -0
- hermes_voip-0.1.1/src/hermes_voip/voip_tools.py +1588 -0
- hermes_voip-0.1.1/tests/__init__.py +8 -0
- hermes_voip-0.1.1/tests/conftest.py +17 -0
- hermes_voip-0.1.1/tests/e2e/__init__.py +10 -0
- hermes_voip-0.1.1/tests/e2e/_fake_gateway.py +568 -0
- hermes_voip-0.1.1/tests/e2e/_fake_webrtc_gateway.py +847 -0
- hermes_voip-0.1.1/tests/e2e/test_concurrent_inbound_calls.py +579 -0
- hermes_voip-0.1.1/tests/e2e/test_inbound_call.py +803 -0
- hermes_voip-0.1.1/tests/e2e/test_inbound_webrtc_call.py +693 -0
- hermes_voip-0.1.1/tests/e2e/test_outbound_call.py +2041 -0
- hermes_voip-0.1.1/tests/fixtures/regen_g722_kat.py +47 -0
- hermes_voip-0.1.1/tests/g722_kat_vectors.py +378 -0
- hermes_voip-0.1.1/tests/providers/__init__.py +1 -0
- hermes_voip-0.1.1/tests/providers/test_build.py +667 -0
- hermes_voip-0.1.1/tests/stt/test_deepgram.py +422 -0
- hermes_voip-0.1.1/tests/stt/test_resample.py +154 -0
- hermes_voip-0.1.1/tests/stt/test_sherpa_onnx.py +596 -0
- hermes_voip-0.1.1/tests/test_adapter.py +3371 -0
- hermes_voip-0.1.1/tests/test_adapter_attended_consult.py +121 -0
- hermes_voip-0.1.1/tests/test_adapter_call_progress.py +406 -0
- hermes_voip-0.1.1/tests/test_adapter_caller_modes.py +2278 -0
- hermes_voip-0.1.1/tests/test_adapter_observability.py +476 -0
- hermes_voip-0.1.1/tests/test_adapter_production_safety.py +641 -0
- hermes_voip-0.1.1/tests/test_adapter_reconnect.py +420 -0
- hermes_voip-0.1.1/tests/test_adapter_rtcp.py +912 -0
- hermes_voip-0.1.1/tests/test_adapter_secure_media.py +501 -0
- hermes_voip-0.1.1/tests/test_adapter_session_timers.py +1230 -0
- hermes_voip-0.1.1/tests/test_adapter_sip_dtls.py +1566 -0
- hermes_voip-0.1.1/tests/test_adapter_webrtc.py +1229 -0
- hermes_voip-0.1.1/tests/test_adapter_wss_signalling.py +728 -0
- hermes_voip-0.1.1/tests/test_adr_unique_numbers.py +43 -0
- hermes_voip-0.1.1/tests/test_aec_barge_in_integration.py +191 -0
- hermes_voip-0.1.1/tests/test_aio.py +149 -0
- hermes_voip-0.1.1/tests/test_audio_content.py +580 -0
- hermes_voip-0.1.1/tests/test_barge_in_gate.py +475 -0
- hermes_voip-0.1.1/tests/test_best_practices_alignment.py +397 -0
- hermes_voip-0.1.1/tests/test_call.py +860 -0
- hermes_voip-0.1.1/tests/test_call_context.py +522 -0
- hermes_voip-0.1.1/tests/test_call_dtmf_sipinfo.py +269 -0
- hermes_voip-0.1.1/tests/test_call_end.py +106 -0
- hermes_voip-0.1.1/tests/test_call_loop.py +4715 -0
- hermes_voip-0.1.1/tests/test_call_loop_call_progress.py +499 -0
- hermes_voip-0.1.1/tests/test_call_loop_dtmf.py +192 -0
- hermes_voip-0.1.1/tests/test_caller_groups.py +1355 -0
- hermes_voip-0.1.1/tests/test_caller_modes.py +495 -0
- hermes_voip-0.1.1/tests/test_caller_privilege.py +183 -0
- hermes_voip-0.1.1/tests/test_codec_capability.py +120 -0
- hermes_voip-0.1.1/tests/test_config.py +2336 -0
- hermes_voip-0.1.1/tests/test_config_aec.py +114 -0
- hermes_voip-0.1.1/tests/test_config_call_progress.py +50 -0
- hermes_voip-0.1.1/tests/test_convpath_review.py +562 -0
- hermes_voip-0.1.1/tests/test_database_exposure_guard.py +419 -0
- hermes_voip-0.1.1/tests/test_dialog.py +473 -0
- hermes_voip-0.1.1/tests/test_digest.py +754 -0
- hermes_voip-0.1.1/tests/test_dtmf.py +373 -0
- hermes_voip-0.1.1/tests/test_dtmf_config.py +125 -0
- hermes_voip-0.1.1/tests/test_dtmf_confirm.py +254 -0
- hermes_voip-0.1.1/tests/test_dtmf_inband.py +184 -0
- hermes_voip-0.1.1/tests/test_dtmf_mode_resolution.py +160 -0
- hermes_voip-0.1.1/tests/test_dtmf_receive.py +434 -0
- hermes_voip-0.1.1/tests/test_dtmf_sipinfo.py +91 -0
- hermes_voip-0.1.1/tests/test_g722_codec.py +139 -0
- hermes_voip-0.1.1/tests/test_guard_classifier_miss.py +98 -0
- hermes_voip-0.1.1/tests/test_guard_model_manifest.py +63 -0
- hermes_voip-0.1.1/tests/test_guard_normalize.py +185 -0
- hermes_voip-0.1.1/tests/test_guard_onnx.py +289 -0
- hermes_voip-0.1.1/tests/test_guard_onnx_model.py +59 -0
- hermes_voip-0.1.1/tests/test_header_list.py +43 -0
- hermes_voip-0.1.1/tests/test_hermes_contract.py +224 -0
- hermes_voip-0.1.1/tests/test_inbound_stt_chain.py +360 -0
- hermes_voip-0.1.1/tests/test_incall.py +423 -0
- hermes_voip-0.1.1/tests/test_intercom.py +361 -0
- hermes_voip-0.1.1/tests/test_keepalive.py +178 -0
- hermes_voip-0.1.1/tests/test_lazy_singleton.py +277 -0
- hermes_voip-0.1.1/tests/test_manager.py +741 -0
- hermes_voip-0.1.1/tests/test_media_aec.py +382 -0
- hermes_voip-0.1.1/tests/test_media_audio.py +507 -0
- hermes_voip-0.1.1/tests/test_media_call_progress.py +464 -0
- hermes_voip-0.1.1/tests/test_media_concurrency.py +1105 -0
- hermes_voip-0.1.1/tests/test_media_concurrency_hermes.py +711 -0
- hermes_voip-0.1.1/tests/test_media_dtls.py +741 -0
- hermes_voip-0.1.1/tests/test_media_endpoint.py +151 -0
- hermes_voip-0.1.1/tests/test_media_engine.py +2240 -0
- hermes_voip-0.1.1/tests/test_media_engine_aec.py +260 -0
- hermes_voip-0.1.1/tests/test_media_engine_bargein.py +360 -0
- hermes_voip-0.1.1/tests/test_media_engine_g722.py +452 -0
- hermes_voip-0.1.1/tests/test_media_engine_ice.py +217 -0
- hermes_voip-0.1.1/tests/test_media_engine_inband_dtmf.py +304 -0
- hermes_voip-0.1.1/tests/test_media_engine_opus.py +213 -0
- hermes_voip-0.1.1/tests/test_media_engine_pacing.py +352 -0
- hermes_voip-0.1.1/tests/test_media_engine_plc.py +318 -0
- hermes_voip-0.1.1/tests/test_media_engine_rtcp.py +1080 -0
- hermes_voip-0.1.1/tests/test_media_engine_srtcp.py +403 -0
- hermes_voip-0.1.1/tests/test_media_ice.py +418 -0
- hermes_voip-0.1.1/tests/test_media_opus.py +212 -0
- hermes_voip-0.1.1/tests/test_media_singletons_threadsafe.py +249 -0
- hermes_voip-0.1.1/tests/test_media_sip_dtls_session.py +525 -0
- hermes_voip-0.1.1/tests/test_media_srtcp.py +726 -0
- hermes_voip-0.1.1/tests/test_media_srtp.py +889 -0
- hermes_voip-0.1.1/tests/test_media_vad.py +493 -0
- hermes_voip-0.1.1/tests/test_media_watchdog.py +276 -0
- hermes_voip-0.1.1/tests/test_media_webrtc_session.py +642 -0
- hermes_voip-0.1.1/tests/test_message.py +380 -0
- hermes_voip-0.1.1/tests/test_model_licence_gate.py +246 -0
- hermes_voip-0.1.1/tests/test_multi_intercom.py +612 -0
- hermes_voip-0.1.1/tests/test_multi_intercom_webhook.py +179 -0
- hermes_voip-0.1.1/tests/test_notice_filter.py +294 -0
- hermes_voip-0.1.1/tests/test_onnx_compat.py +70 -0
- hermes_voip-0.1.1/tests/test_originate.py +219 -0
- hermes_voip-0.1.1/tests/test_outbound_allow.py +62 -0
- hermes_voip-0.1.1/tests/test_outbound_uri_injection.py +142 -0
- hermes_voip-0.1.1/tests/test_plugin_manifest.py +642 -0
- hermes_voip-0.1.1/tests/test_provider_error.py +106 -0
- hermes_voip-0.1.1/tests/test_providers.py +159 -0
- hermes_voip-0.1.1/tests/test_providers_async.py +186 -0
- hermes_voip-0.1.1/tests/test_providers_audio.py +22 -0
- hermes_voip-0.1.1/tests/test_providers_conformance.py +114 -0
- hermes_voip-0.1.1/tests/test_providers_exports.py +82 -0
- hermes_voip-0.1.1/tests/test_providers_policy.py +144 -0
- hermes_voip-0.1.1/tests/test_providers_registry.py +41 -0
- hermes_voip-0.1.1/tests/test_refer.py +610 -0
- hermes_voip-0.1.1/tests/test_register.py +767 -0
- hermes_voip-0.1.1/tests/test_register_skills.py +152 -0
- hermes_voip-0.1.1/tests/test_registration.py +651 -0
- hermes_voip-0.1.1/tests/test_rtcp.py +772 -0
- hermes_voip-0.1.1/tests/test_rtp.py +465 -0
- hermes_voip-0.1.1/tests/test_runbook_0013_drift.py +108 -0
- hermes_voip-0.1.1/tests/test_sdp.py +2834 -0
- hermes_voip-0.1.1/tests/test_sdp_video.py +462 -0
- hermes_voip-0.1.1/tests/test_session_timer.py +394 -0
- hermes_voip-0.1.1/tests/test_sip.py +29 -0
- hermes_voip-0.1.1/tests/test_sip_request.py +78 -0
- hermes_voip-0.1.1/tests/test_spoken_text.py +392 -0
- hermes_voip-0.1.1/tests/test_stt_tts_model_manifest.py +110 -0
- hermes_voip-0.1.1/tests/test_supply_chain_schedule.py +204 -0
- hermes_voip-0.1.1/tests/test_tools.py +444 -0
- hermes_voip-0.1.1/tests/test_tts_elevenlabs.py +903 -0
- hermes_voip-0.1.1/tests/test_tts_failover.py +674 -0
- hermes_voip-0.1.1/tests/test_tts_pcm16.py +63 -0
- hermes_voip-0.1.1/tests/test_tts_segment.py +254 -0
- hermes_voip-0.1.1/tests/test_tts_sherpa_kokoro.py +351 -0
- hermes_voip-0.1.1/tests/test_tts_sherpa_kokoro_real.py +56 -0
- hermes_voip-0.1.1/tests/test_video_rtp.py +468 -0
- hermes_voip-0.1.1/tests/test_voip_tools.py +1020 -0
- hermes_voip-0.1.1/tests/test_voip_tools_place_call.py +483 -0
- hermes_voip-0.1.1/tests/test_voip_tools_transfer_attended.py +435 -0
- hermes_voip-0.1.1/tests/test_wheel_packaging.py +211 -0
- hermes_voip-0.1.1/tests/transport/__init__.py +1 -0
- hermes_voip-0.1.1/tests/transport/_loopback.py +188 -0
- hermes_voip-0.1.1/tests/transport/test_connection.py +1114 -0
- hermes_voip-0.1.1/tests/transport/test_framing.py +227 -0
- hermes_voip-0.1.1/tests/transport/test_keepalive.py +326 -0
- hermes_voip-0.1.1/tests/transport/test_keepalive_ping.py +155 -0
- hermes_voip-0.1.1/tests/transport/test_transaction.py +214 -0
- hermes_voip-0.1.1/tests/transport/test_ws_connection.py +944 -0
- hermes_voip-0.1.1/tools/__init__.py +1 -0
- hermes_voip-0.1.1/tools/check_database_exposure.py +397 -0
- hermes_voip-0.1.1/uv.lock +2154 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PreToolUse hook: blocks Edit/Write/NotebookEdit calls that target the root
|
|
4
|
+
* checkout. AGENTS.md rule 8: all work happens in worktree lanes under
|
|
5
|
+
* .worktrees/; the root checkout is a pristine mirror of main.
|
|
6
|
+
*
|
|
7
|
+
* Allowed: paths under <root>/.worktrees/** and paths outside the repository
|
|
8
|
+
* entirely (e.g. ~/.claude memory, /tmp scratch). Blocked: everything else
|
|
9
|
+
* inside the root checkout's working tree.
|
|
10
|
+
*
|
|
11
|
+
* Exit codes per the hooks contract: 0 = allow, 2 = block (stderr is fed back
|
|
12
|
+
* to the model). Any unexpected failure allows the call — this hook is defence
|
|
13
|
+
* in depth, not the rule itself.
|
|
14
|
+
*/
|
|
15
|
+
import { execFileSync } from "node:child_process";
|
|
16
|
+
import { readFileSync } from "node:fs";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
|
|
19
|
+
let input;
|
|
20
|
+
try {
|
|
21
|
+
input = JSON.parse(readFileSync(0, "utf8"));
|
|
22
|
+
} catch {
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const toolInput = input.tool_input ?? {};
|
|
27
|
+
const target = toolInput.file_path ?? toolInput.notebook_path;
|
|
28
|
+
if (typeof target !== "string" || target === "") {
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const cwd =
|
|
33
|
+
typeof input.cwd === "string" && input.cwd !== "" ? input.cwd : process.cwd();
|
|
34
|
+
|
|
35
|
+
let mainRoot;
|
|
36
|
+
try {
|
|
37
|
+
// The common git dir is <main-root>/.git for every linked worktree.
|
|
38
|
+
const commonDir = execFileSync(
|
|
39
|
+
"git",
|
|
40
|
+
["rev-parse", "--path-format=absolute", "--git-common-dir"],
|
|
41
|
+
{ cwd, encoding: "utf8" },
|
|
42
|
+
).trim();
|
|
43
|
+
mainRoot = path.dirname(commonDir);
|
|
44
|
+
} catch {
|
|
45
|
+
process.exit(0); // not inside a git repository — nothing to enforce
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const resolved = path.resolve(cwd, target);
|
|
49
|
+
const rel = path.relative(mainRoot, resolved);
|
|
50
|
+
const insideRoot =
|
|
51
|
+
rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
|
|
52
|
+
const insideLane =
|
|
53
|
+
rel.startsWith(`.worktrees${path.sep}`) ||
|
|
54
|
+
rel.startsWith(`.claude${path.sep}worktrees${path.sep}`);
|
|
55
|
+
|
|
56
|
+
if (insideRoot && !insideLane) {
|
|
57
|
+
console.error(
|
|
58
|
+
`Blocked: ${resolved} is in the root checkout. AGENTS.md rule 8: never edit the ` +
|
|
59
|
+
`root checkout — create a worktree lane (worktree-lane skill) and edit ` +
|
|
60
|
+
`${mainRoot}/.worktrees/<lane>/... instead.`,
|
|
61
|
+
);
|
|
62
|
+
process.exit(2);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
process.exit(0);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"enableAllProjectMcpServers": true,
|
|
3
|
+
"permissions": {
|
|
4
|
+
"deny": [
|
|
5
|
+
"Read(/.env)",
|
|
6
|
+
"Read(/.env.*)",
|
|
7
|
+
"Read(/.memory/**)",
|
|
8
|
+
"Read(/**/.venv/**)",
|
|
9
|
+
"Read(/**/__pycache__/**)",
|
|
10
|
+
"Read(/**/.mypy_cache/**)",
|
|
11
|
+
"Read(/**/.ruff_cache/**)",
|
|
12
|
+
"Read(/**/.pytest_cache/**)",
|
|
13
|
+
"Read(/**/*.egg-info/**)",
|
|
14
|
+
"Read(/**/htmlcov/**)",
|
|
15
|
+
"Read(/**/dist/**)",
|
|
16
|
+
"Read(/**/build/**)"
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
"hooks": {
|
|
20
|
+
"SessionStart": [
|
|
21
|
+
{
|
|
22
|
+
"hooks": [
|
|
23
|
+
{
|
|
24
|
+
"type": "command",
|
|
25
|
+
"command": "echo 'Persistent project memory is active: recall prior decisions with qdrant-find before non-trivial work; store new decisions, corrections and gotchas with qdrant-store (conventions: the memory skill). Reminder: repo is PUBLIC — keep the gateway host/extension/password/device-model out of every tracked file.'"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"PreToolUse": [
|
|
31
|
+
{
|
|
32
|
+
"matcher": "Edit|Write|NotebookEdit",
|
|
33
|
+
"hooks": [
|
|
34
|
+
{
|
|
35
|
+
"type": "command",
|
|
36
|
+
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/enforce-worktree.mjs\""
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: adr
|
|
3
|
+
description: Record an architecture decision as an ADR in docs/adr/. Use whenever a non-trivial design or tooling decision is made (technology choice, data model, protocol, security posture) — before or alongside the implementing commit.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Writing an ADR
|
|
7
|
+
|
|
8
|
+
Implements AGENTS.md rule 30: non-trivial decisions are recorded, not implied by code.
|
|
9
|
+
|
|
10
|
+
## Procedure
|
|
11
|
+
|
|
12
|
+
1. Find the next number: `ls docs/adr/` — files are `NNNN-kebab-title.md`, zero-padded to
|
|
13
|
+
four digits. `0000-template.md` is reserved for the template.
|
|
14
|
+
2. Copy `docs/adr/0000-template.md` to `docs/adr/NNNN-<kebab-title>.md`.
|
|
15
|
+
3. Fill every section. "Alternatives considered" must name real alternatives and the
|
|
16
|
+
specific reason each was rejected — "didn't fit" is not a reason.
|
|
17
|
+
4. Status starts at `Accepted` (we record decisions when made, not proposals). If a later
|
|
18
|
+
ADR reverses it, edit the old one's status to `Superseded by ADR-NNNN` in the same commit
|
|
19
|
+
that adds the new one.
|
|
20
|
+
5. Commit the ADR with the work it justifies, or as its own `docs(adr):` commit if the
|
|
21
|
+
decision precedes implementation.
|
|
22
|
+
|
|
23
|
+
## Quality bar
|
|
24
|
+
|
|
25
|
+
- Present tense, factual, self-contained — a reader gets the full picture without the chat
|
|
26
|
+
transcript that produced it.
|
|
27
|
+
- Numbers and names, not vibes: versions, benchmarks, prices, URLs.
|
|
28
|
+
- An ADR that describes behaviour the repo doesn't have yet is aspirational documentation
|
|
29
|
+
(rule 27) — write it when the decision is real.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: memory
|
|
3
|
+
description: Store and recall persistent project memory via the local qdrant memory MCP (qdrant-store / qdrant-find). Recall at the start of non-trivial tasks; store non-obvious decisions, operator feedback, and hard-won lessons when you learn them.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Project memory conventions
|
|
7
|
+
|
|
8
|
+
The `memory` MCP server (mcp-server-qdrant, configured in `.mcp.json`) provides two tools
|
|
9
|
+
backed by a local vector store under `.memory/`:
|
|
10
|
+
|
|
11
|
+
- `qdrant-find` — semantic search; phrase the query as a natural-language question.
|
|
12
|
+
- `qdrant-store` — persist one memory (`information` + optional `metadata` JSON).
|
|
13
|
+
|
|
14
|
+
## Recall
|
|
15
|
+
|
|
16
|
+
At the start of any non-trivial task, run one or two `qdrant-find` queries about the area
|
|
17
|
+
you're touching (e.g. "decisions about the SIP transport", "gotchas registering against the
|
|
18
|
+
SIP gateway"). Do this before re-deriving anything a past session may have settled.
|
|
19
|
+
|
|
20
|
+
## Store
|
|
21
|
+
|
|
22
|
+
Store when you (a) make a non-trivial decision not worth a full ADR, (b) receive operator
|
|
23
|
+
feedback or a correction, (c) discover a gotcha that cost real time, or (d) finish a
|
|
24
|
+
milestone whose state a future session needs.
|
|
25
|
+
|
|
26
|
+
Entry format:
|
|
27
|
+
|
|
28
|
+
- `information`: 1–3 self-contained sentences, present tense, absolute dates (never
|
|
29
|
+
"today"/"recently"). A future session sees only this text — include the why.
|
|
30
|
+
- `metadata`: `{"type": "project|feedback|user|reference", "topic": "<kebab-case>"}`.
|
|
31
|
+
|
|
32
|
+
## Do NOT store
|
|
33
|
+
|
|
34
|
+
- Secrets, tokens, keys — ever. The gateway host, extension number, device model and SIP
|
|
35
|
+
password are sensitive; they live in the gitignored `.env` and the per-user agent memory,
|
|
36
|
+
never in this store.
|
|
37
|
+
- Anything already canonical in the repo (AGENTS.md rules, docs/ figures, ADRs, code). Repo
|
|
38
|
+
files are the source of truth; memory is for what the repo doesn't record.
|
|
39
|
+
- Conversation-local trivia with no future value.
|
|
40
|
+
|
|
41
|
+
## Operational notes
|
|
42
|
+
|
|
43
|
+
- Local after first run: embedded Qdrant DB + ONNX embedding model under `.memory/`
|
|
44
|
+
(gitignored). The embedding model downloads from the HuggingFace Hub on first use only,
|
|
45
|
+
then runs fully offline — no further network calls; nothing leaves the machine.
|
|
46
|
+
- Single-process lock: only one session per repo clone can use the store at a time. A
|
|
47
|
+
second concurrent session's memory server fails to connect — that is the lock, not a
|
|
48
|
+
corruption.
|
|
49
|
+
- If a memory turns out to be wrong, store a correcting entry stating both the old claim and
|
|
50
|
+
the correction (the store has no delete tool).
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: orchestrate
|
|
3
|
+
description: Autonomous single-orchestrator build loop. ONE Claude session (the orchestrator) fans out agent teams via ultracode Workflows to drive the whole repo toward done — identifying next work, then shipping correctness/robustness/tests/docs/API/auth/observability/UX, gap-reviewing, backlogging the gaps, and shipping those too. Selects the right model per job. Runs for days. Trigger: `/loop /orchestrate`. The ONLY exit is a genuinely dry repo (no work, no UX to improve) — never a "natural break point".
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /orchestrate — the autonomous build loop
|
|
8
|
+
|
|
9
|
+
You are the **orchestrator**: a single Claude session that drives this repository toward
|
|
10
|
+
*done* by fanning out agent teams, not by doing the work yourself. One `/orchestrate`
|
|
11
|
+
invocation runs **one wave**. `/loop /orchestrate` runs waves back-to-back, forever, until
|
|
12
|
+
the repo is genuinely dry. You hold the agenda; subagents hold the work.
|
|
13
|
+
|
|
14
|
+
Read the WHY in `docs/adr/0072-autonomous-orchestration-loop.md` and the operational HOW in
|
|
15
|
+
`docs/runbooks/0016-orchestration-loop.md`. This skill is the executable contract.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## PRIME DIRECTIVE — never stop
|
|
20
|
+
|
|
21
|
+
The operator's one hard requirement: **do not stop at natural break points, when a queue
|
|
22
|
+
looks empty, or when you "feel done".** Those feelings are *signals to discover more work*,
|
|
23
|
+
never reasons to halt. Every wave ends by guaranteeing the next wave. Stopping is a defect.
|
|
24
|
+
|
|
25
|
+
Banned end-states (treat each as a trigger, not a terminus):
|
|
26
|
+
- "The backlog is empty / there's nothing left" → run a **REPLENISH** gap-review (Phase 1).
|
|
27
|
+
- "Awaiting review / CI / operator approval" → you OWN PRs to merge (AGENTS rule 14). Reap
|
|
28
|
+
them next wave. Never wait on a human.
|
|
29
|
+
- "This is a natural place to pause / hand off / wrap up" → forbidden. End with the next
|
|
30
|
+
wave's plan + a scheduled wake.
|
|
31
|
+
- "I think we're done" → that is a hypothesis to *disprove* with a full gap-review, not a
|
|
32
|
+
conclusion. Decisions gate on **observable evidence** (queue counts, gate exit codes,
|
|
33
|
+
dry-streak), never a vibe.
|
|
34
|
+
|
|
35
|
+
**Continuation guarantee (do this every wave, last):** The loop runs in one of two modes:
|
|
36
|
+
|
|
37
|
+
- **Dynamic mode** (`/loop` with no interval): your final action is
|
|
38
|
+
`ScheduleWakeup(prompt="/loop /orchestrate", delaySeconds=…, reason=…)`. Mandatory even
|
|
39
|
+
when a wave shipped nothing.
|
|
40
|
+
- **Cron mode** (`/loop /orchestrate` with an interval, e.g. `*/10 * * * *`): the `/loop`
|
|
41
|
+
skill fires `/orchestrate` on its fixed schedule — that cron IS the continuation. If
|
|
42
|
+
`ScheduleWakeup` responds with "the /loop dynamic runtime gate is off … the loop has
|
|
43
|
+
ended; do not re-issue", you are in cron mode. This is NORMAL, not an error. Do NOT
|
|
44
|
+
delete the cron (doing so breaks the loop), and do NOT re-issue `ScheduleWakeup` after it
|
|
45
|
+
refuses. A cron-fired `/orchestrate` lands in the same session with preserved context, so
|
|
46
|
+
a mid-wave tick is recognised as already in-flight and handled — never treated as an
|
|
47
|
+
overlapping wave.
|
|
48
|
+
|
|
49
|
+
Either way, the next wave is guaranteed. See **Exit condition** for the only escape, which
|
|
50
|
+
is itself a long idle-poll, not a halt.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Mental model
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
/loop /orchestrate
|
|
58
|
+
└─ wave N (this invocation, the orchestrator = you)
|
|
59
|
+
Phase 0 SENSE ─ refresh, reap open PRs, sweep, recall memory
|
|
60
|
+
Phase 1 REPLENISH ─ fan out gap-reviewers → append new backlog items
|
|
61
|
+
Phase 2 SELECT ─ batch independent ready items, assign a model each
|
|
62
|
+
Phase 3 IMPLEMENT ┐ Workflow: pipeline(items, impl, review)
|
|
63
|
+
Phase 4 REVIEW ┘ cross-vendor (codex) + cross-tier Claude
|
|
64
|
+
Phase 5 PR+MERGE ─ open PR, watch CI, squash-merge on green+clean
|
|
65
|
+
Phase 6 CLEAN ─ check off backlog, prune lane, refresh root, store memory
|
|
66
|
+
Phase 7 CONTINUE ─ ScheduleWakeup("/loop /orchestrate") ← never skip
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
You **delegate execution**; you **personally own** sensing, selection, PR/merge decisions,
|
|
70
|
+
memory, and the continuation guarantee. Keep your own context lean (AGENTS rule 29): never
|
|
71
|
+
read whole modules yourself — fan reading/implementation into subagents and keep only their
|
|
72
|
+
summaries. State lives on disk, not in this conversation, so a context summary or restart
|
|
73
|
+
loses nothing.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Durable state (resume across days / restarts)
|
|
78
|
+
|
|
79
|
+
You keep **no long-term state in context**. Every wave reconstructs from disk:
|
|
80
|
+
|
|
81
|
+
| Source | Role |
|
|
82
|
+
|---|---|
|
|
83
|
+
| `docs/backlog.md` | **Canonical prioritized queue.** Checkbox items, `[high]/[medium]/[low]` + kind tags. Shipped → checked off with the PR #. New gaps → appended. |
|
|
84
|
+
| `gh pr list` / `gh issue list` | In-flight state + secondary queue. |
|
|
85
|
+
| `.orchestrator/state.json` (gitignored) | **Best-effort / optional.** Attempt the write; if the worktree hook blocks it (the `enforce-worktree` PreToolUse hook blocks ALL root-checkout writes, including gitignored paths — this is the current behaviour), run stateless and rebuild from `backlog.md` + `gh`; treat a blocked write as expected, not a failure. See schema below. |
|
|
86
|
+
| memory MCP (qdrant) | Decisions, gotchas, operator feedback. **Orchestrator-only** (single-process lock — subagents must NEVER call qdrant). Degrade gracefully if locked/unavailable. |
|
|
87
|
+
|
|
88
|
+
`.orchestrator/state.json` schema (all optional; rebuildable):
|
|
89
|
+
`{ "wave": int, "inflight": [{"item": str, "branch": str, "pr": int|null, "retries": int}],
|
|
90
|
+
"dryStreak": int, "lastGapReviewWave": int, "notes": str }`
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## One wave — the eight phases
|
|
95
|
+
|
|
96
|
+
### Phase 0 — SENSE & RESUME
|
|
97
|
+
1. Refresh root: `git -C <root> fetch origin && git -C <root> pull --ff-only origin main`.
|
|
98
|
+
2. **Reap in-flight PRs** (`gh pr list --state open`). For each PR the loop owns:
|
|
99
|
+
- CI green (`gh pr checks <n>`) **and** review clean → **squash-merge** (Phase 5 rules),
|
|
100
|
+
then Phase 6 cleanup.
|
|
101
|
+
- CI red → spawn a fix lane (TDD fix → push); leave PR open.
|
|
102
|
+
- Not yet reviewed → run Phase 4 review now.
|
|
103
|
+
- Conflicts → rebase the lane on current HEAD, re-verify, force-push *the lane branch
|
|
104
|
+
only* (never a shared branch).
|
|
105
|
+
3. Sweep orphaned worktrees/scratch idle > a few hours: `git worktree prune`, then remove
|
|
106
|
+
stale `.worktrees/*` whose branch is merged and ephemeral `.claude/worktrees/{agent,wf_}*`.
|
|
107
|
+
4. `qdrant-find` the dimensions you'll touch this wave (recall prior decisions/gotchas).
|
|
108
|
+
5. Load `.orchestrator/state.json` if present.
|
|
109
|
+
|
|
110
|
+
### Phase 1 — REPLENISH (gap-review fan-out — the anti-stop engine)
|
|
111
|
+
Compute `ready = ` unchecked, unblocked backlog items + open issues.
|
|
112
|
+
Run a gap-review **if** `ready < 2 × fleetWidth` **OR** `wave − lastGapReviewWave ≥ 3`
|
|
113
|
+
**OR** the queue is empty (always). Then:
|
|
114
|
+
1. `Workflow({scriptPath: ".claude/skills/orchestrate/wave.workflow.js",
|
|
115
|
+
args: {phase: "gap-review", dimensions: [...], budget: <tokens>}})` — one agent per
|
|
116
|
+
**dimension** (see list below), each returning structured candidate items.
|
|
117
|
+
2. **Dedup** the returned items against `backlog.md` + open issues (cheap: a haiku agent or
|
|
118
|
+
a direct title/file match). Discard anything already tracked.
|
|
119
|
+
3. Genuinely-new items → append to `docs/backlog.md` (and/or `gh issue create` for
|
|
120
|
+
feature-sized work) **via a docs lane → PR → merge**. New work is now durable.
|
|
121
|
+
4. If the panel returned **zero** genuinely-new items across **all** dimensions, increment
|
|
122
|
+
`dryStreak`; else reset it to 0.
|
|
123
|
+
|
|
124
|
+
A near-empty queue is normal and expected — it just means it's time to discover. The panel
|
|
125
|
+
almost always finds something; that is the design.
|
|
126
|
+
|
|
127
|
+
### Phase 2 — SELECT & PLAN
|
|
128
|
+
1. Batch up to `fleetWidth = min(16, cores − 2)` **ready** items.
|
|
129
|
+
2. Order: `[high] > [medium] > [low]`; correctness/security before polish; **unblockers
|
|
130
|
+
first** (e.g. a missing test runner that gates other work); respect stated dependencies.
|
|
131
|
+
3. **Independence (AGENTS rule 32):** one non-overlapping file territory per lane. Serialize
|
|
132
|
+
hot shared files (`src/hermes_voip/adapter.py`, `media/call_loop.py`, `docs/backlog.md`)
|
|
133
|
+
to **≤1 lane per wave**. Group tiny same-module items into one lane to cut PR overhead.
|
|
134
|
+
4. Assign each item a **model tier** (rubric below) and an effort level.
|
|
135
|
+
|
|
136
|
+
### Phase 3 + 4 — IMPLEMENT & REVIEW (one Workflow, pipelined)
|
|
137
|
+
`Workflow({scriptPath: ".claude/skills/orchestrate/wave.workflow.js",
|
|
138
|
+
args: {phase: "implement", items: [<selected, each with model+spec>], budget: <tokens>}})`
|
|
139
|
+
|
|
140
|
+
The script runs `pipeline(items, implStage, reviewStage)` so each item's review starts the
|
|
141
|
+
moment its implementation goes green — no barrier. Per item:
|
|
142
|
+
- **implStage** (assigned model, `isolation: "worktree"`): TDD — write the failing test, run
|
|
143
|
+
it, capture the red output, **commit the red test separately**, implement to green
|
|
144
|
+
**without touching the test**, write an ADR/runbook if the change warrants one (rules
|
|
145
|
+
30/42), run the **full local gate**, push a conventional branch. Returns
|
|
146
|
+
`{item, branch, redCommit, greenCommits, gate, adr?, runbook?, files, spec, selfRisk}`
|
|
147
|
+
or `{item, failed, reason}`.
|
|
148
|
+
- **reviewStage** (cross-vendor + cross-tier, fresh context, **diff+spec+checklist only** —
|
|
149
|
+
rule 21): `codex` (OpenAI) **and** a different-tier Claude reviewer. Returns
|
|
150
|
+
`{verdict, mustFix[], noted[]}`. Must-fix → loop a fix agent → re-review (bounded retries).
|
|
151
|
+
Materiality (rule 16): must-fix = correctness/security/spec/guardrail/blast-radius only.
|
|
152
|
+
**Unanimous rubber-stamp is a yellow flag** → escalate to an opus deep-review before trust.
|
|
153
|
+
|
|
154
|
+
The Workflow returns `[{item, branch, verdict, evidence}]`. `.filter(Boolean)` the failures.
|
|
155
|
+
|
|
156
|
+
### Phase 5 — GATE → PR → MERGE (you, the orchestrator)
|
|
157
|
+
For each item with a **clean** verdict:
|
|
158
|
+
1. **Integrator re-verify on current HEAD** (rule 11): the lane was cut from an older HEAD;
|
|
159
|
+
confirm it still rebases cleanly and the gate is green from a clean build. If drifted,
|
|
160
|
+
have an agent rebase + re-gate before trusting the green.
|
|
161
|
+
2. `gh pr create` — title = Conventional Commit; body = spec, ADR/runbook links, gate
|
|
162
|
+
evidence (command+exit codes), review summary + the substantive risk statement,
|
|
163
|
+
blast-radius, `Co-Authored-By` trailer.
|
|
164
|
+
3. Watch CI (`gh pr checks <n> --watch` or poll). CI is the authoritative gate (rule 15).
|
|
165
|
+
Fix any CI-only failures in the lane.
|
|
166
|
+
4. **Squash-merge on green CI + clean review** (operator-approved auto-merge). Conventional
|
|
167
|
+
squash title. Slow CI must **not** block the wave — leave the PR open and let Phase 0 of
|
|
168
|
+
the next wave reap it. PR shepherding is idempotent across waves.
|
|
169
|
+
|
|
170
|
+
> **Branch-protection note.** This repo may have NO required-status-check branch
|
|
171
|
+
> protection, so `gh pr merge --auto` merges immediately without waiting for CI. For any
|
|
172
|
+
> CODE change, do NOT use `--auto` blindly: poll `gh pr checks <n>` until the CI jobs
|
|
173
|
+
> you care about are green, then merge explicitly. Docs/config PRs (no Python surface, no
|
|
174
|
+
> gate-relevant change) may be merged once the fast `gate` + `scan` jobs pass while
|
|
175
|
+
> slower extras jobs (`hermes-contract`, etc.) are still running — but verify those slower
|
|
176
|
+
> jobs do not cover the changed surface before proceeding.
|
|
177
|
+
|
|
178
|
+
### Phase 6 — INTEGRATE & CLEAN
|
|
179
|
+
1. Check off the shipped backlog item(s) with the PR # (batch into the next docs lane).
|
|
180
|
+
2. `git worktree remove --force <lane>` + `git worktree prune`.
|
|
181
|
+
3. Refresh root: `git fetch origin && git pull --ff-only origin main` (rule 9) so the next
|
|
182
|
+
lane bases on current HEAD.
|
|
183
|
+
4. `qdrant-store` any non-trivial decision/gotcha/operator-feedback learned (orchestrator
|
|
184
|
+
only; never secrets — see Invariants).
|
|
185
|
+
5. Reconcile drifted docs (stale `IMPLEMENTATION-PLAN.md` / `backlog.md` preamble / README)
|
|
186
|
+
in a batched docs lane.
|
|
187
|
+
|
|
188
|
+
### Phase 7 — CONTINUE (never skip)
|
|
189
|
+
1. Attempt to update `.orchestrator/state.json` (wave++, in-flight, counters, dryStreak).
|
|
190
|
+
If the `enforce-worktree` hook blocks the write (it currently does — it blocks all
|
|
191
|
+
root-checkout writes even to gitignored paths), skip it silently and run stateless; the
|
|
192
|
+
wave-state is rebuilt next wave from `backlog.md` + `gh` + memory.
|
|
193
|
+
2. Emit a tight wave report: shipped+merged, opened, discovered, cleaned, and the **next
|
|
194
|
+
wave's plan**. Never end on a "good stopping point".
|
|
195
|
+
3. **Guarantee the next wave:**
|
|
196
|
+
- If in **dynamic mode**: `ScheduleWakeup(prompt="/loop /orchestrate", delaySeconds=…,
|
|
197
|
+
reason="next orchestration wave")`. Pick the delay by what you're waiting on
|
|
198
|
+
(CI in flight → ~270s; otherwise 1200–1800s).
|
|
199
|
+
- If `ScheduleWakeup` reports the dynamic runtime gate is off (cron mode): the fixed
|
|
200
|
+
cron IS the continuation — do not delete it, do not re-issue `ScheduleWakeup`; the
|
|
201
|
+
next wave fires automatically. This is the normal operating mode when the loop was
|
|
202
|
+
started with an interval (e.g. `/loop /orchestrate` via a `*/10 * * * *` cron).
|
|
203
|
+
Either path guarantees the next wave.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Gap-review dimensions (Phase 1 — "cover everything")
|
|
208
|
+
|
|
209
|
+
Fan out **one agent per dimension**. Each hunts NEW work only in its lane and returns
|
|
210
|
+
structured items. Cover, at minimum:
|
|
211
|
+
|
|
212
|
+
1. **correctness** — bugs, contract violations, RFC compliance, off-by-ones.
|
|
213
|
+
2. **robustness / fail-closed** — error handling (rule 37), edge cases, hostile input.
|
|
214
|
+
3. **security & auth** — injection guard, caller groups/modes, SIP digest, SRTP/DTLS,
|
|
215
|
+
secret hygiene (public repo!), supply-chain advisories (`uv` audit + licences).
|
|
216
|
+
4. **tests & mutation** — coverage gaps, weak/assertion-free tests, missing async tests,
|
|
217
|
+
surviving mutants (rule 19).
|
|
218
|
+
5. **docs & doc-drift** — rule 27: comments/docs describing behaviour the code lacks;
|
|
219
|
+
reconcile stale `IMPLEMENTATION-PLAN.md` / `backlog.md` preamble / `README.md`; runbook
|
|
220
|
+
numbering collisions.
|
|
221
|
+
6. **API & ergonomics** — `__all__`, public exports, typed surfaces, import discoverability.
|
|
222
|
+
7. **performance / efficiency** — hot-path budgets, allocations, rule 22 (record numbers).
|
|
223
|
+
8. **observability / reporting / monitoring** — instrument the runbook-0014 SLOs, RTCP
|
|
224
|
+
metrics, structured logs. *Local-only emission is in-bounds*; an external sink/dashboard
|
|
225
|
+
is **propose-only** (see Invariants).
|
|
226
|
+
9. **UX & conversational quality** — greeting, barge-in feel, silence/goodbye handling,
|
|
227
|
+
error speech, multi-language, voice accessibility. Investigate and improve relentlessly.
|
|
228
|
+
10. **operability** — runbooks current (rule 42), plugin enablement, graceful shutdown,
|
|
229
|
+
config validation.
|
|
230
|
+
11. **packaging / release** — plugin manifest, entry points, version hygiene, deps.
|
|
231
|
+
12. **product / feature gaps** — implied/designed features (e.g. agent-screened answering
|
|
232
|
+
[designed in backlog], issue #64 video→vision, call transfer). Infra-needing surfaces →
|
|
233
|
+
**Proposed ADR only**.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Model-selection rubric ("always the right agent for the job")
|
|
238
|
+
|
|
239
|
+
Pick for intelligence **and** speed/efficiency. Pass `model` (and `effort`) to every
|
|
240
|
+
`agent()`.
|
|
241
|
+
|
|
242
|
+
| Tier | Model id (short) | Use for |
|
|
243
|
+
|---|---|---|
|
|
244
|
+
| **opus** | `opus` (`claude-opus-4-8`) | hard correctness/security, new subsystems, ADR design, crypto/SRTP/digest/injection-guard, the barge-in state machine, ambiguous root-cause, final synthesis, escalated/ tie-break reviews. effort `high`/`xhigh`. |
|
|
245
|
+
| **sonnet** | `sonnet` (`claude-sonnet-4-6`) | standard spec'd feat/fix, most implementation lanes, moderate test work, standard reviews. effort `medium`/`high`. |
|
|
246
|
+
| **haiku** | `haiku` (`claude-haiku-4-5`) | mechanical/bounded — `__all__`/`Final`, docstrings, test vectors, backlog dedup/formatting, lint fixes, search/triage. effort `low`. |
|
|
247
|
+
| **fable** | `fable` (`claude-fable-5`) | fast high-capability peer; **primary Claude cross-tier reviewer** for model diversity (rule 21); medium tasks needing speed. |
|
|
248
|
+
|
|
249
|
+
Defaults by stage: gap-review judgment → opus/sonnet; doc-drift/coverage discovery →
|
|
250
|
+
sonnet/haiku; implementation → per item; **review → a model different from the author**
|
|
251
|
+
(opus-authored → fable or sonnet; sonnet-authored → opus; always also `codex` cross-vendor).
|
|
252
|
+
A haiku triage agent may pre-score each item's complexity to pick the tier.
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Invariants (binding — never weaken; see AGENTS.md)
|
|
257
|
+
|
|
258
|
+
- **Public repo.** Never let the SIP host/extension/password/device-model or any PII reach a
|
|
259
|
+
tracked file, commit message, PR body, or CI log. Tests use fakes (`pbx.example.test`,
|
|
260
|
+
ext `1000`). Tell every subagent this.
|
|
261
|
+
- **Worktree lanes only** (rule 8). All edits in `.worktrees/<lane>`; the root checkout is a
|
|
262
|
+
pristine mirror — the PreToolUse hook blocks root edits. Lanes branch from **current HEAD**.
|
|
263
|
+
- **TDD, real tests** (rules 18/19/25). Red test first, committed separately; green without
|
|
264
|
+
touching tests; never weaken/skip a test to pass.
|
|
265
|
+
- **Full local gate before every PR** (rule 15): `uv run ruff format --check .` ·
|
|
266
|
+
`uv run ruff check .` · `uv run mypy` · `uv run pytest` (+ the `--extra hermes/ml/media/
|
|
267
|
+
webrtc` jobs when deps or those surfaces change). CI is never the first run.
|
|
268
|
+
- **Absolute typing, no escape hatches** (rules 17/39): no `Any`, no unjustified
|
|
269
|
+
`# type: ignore`, no laundering `cast`; `mypy --strict` clean.
|
|
270
|
+
- **Adversarial cross-vendor review** (rule 21) before merge; ≥1 substantive risk statement
|
|
271
|
+
(rule 16).
|
|
272
|
+
- **ADRs for non-trivial decisions** (rule 30); **runbooks written as you work** (rule 42).
|
|
273
|
+
- **No new hosting/platform/SaaS/cost without operator approval recorded in an ADR**
|
|
274
|
+
(rule 40/41). The loop builds **local-only** surfaces freely; anything needing infra or
|
|
275
|
+
cost (website, HTTP API, S3 call-events/recordings, external metrics sink) gets a
|
|
276
|
+
**Proposed** ADR + backlog item and waits — it never silently stands up infra.
|
|
277
|
+
- **Errors propagate** (rule 37). **No partial-ship** (rule 6): every item lands wired
|
|
278
|
+
end-to-end in one push, or it isn't shipped.
|
|
279
|
+
- **Memory MCP is orchestrator-only** (single-process lock); subagents never call qdrant.
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Exit condition (deliberately almost-never)
|
|
284
|
+
|
|
285
|
+
Only *approach* termination when, for **K = 3 consecutive waves**, **all** hold:
|
|
286
|
+
- the full gap-review panel returns **zero** genuinely-new items across **all** dimensions
|
|
287
|
+
(`dryStreak ≥ 3`), **and**
|
|
288
|
+
- `gh pr list` shows zero open PRs, **and** zero in-flight lanes, **and**
|
|
289
|
+
- the full gate is green on `main`, **and**
|
|
290
|
+
- a dedicated UX / feature-discovery pass this wave yields nothing.
|
|
291
|
+
|
|
292
|
+
Even then, **do not stop** — **widen**: deeper UX flow-driving, mutation testing, perf
|
|
293
|
+
budget measurement, feature ideation, competitive comparison. Only if the widened pass is
|
|
294
|
+
*also* dry for K more waves do you enter **steady-state**: report it and idle-poll with a
|
|
295
|
+
long `ScheduleWakeup` (≈3600s) so a newly-arrived issue/PR/operator request restarts real
|
|
296
|
+
work. Steady-state is a long sleep, **not** a halt.
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Quick reference
|
|
301
|
+
|
|
302
|
+
```
|
|
303
|
+
# trigger (operator)
|
|
304
|
+
/loop /orchestrate
|
|
305
|
+
|
|
306
|
+
# one wave's two Workflow calls (you, each wave)
|
|
307
|
+
Workflow({scriptPath:".claude/skills/orchestrate/wave.workflow.js", args:{phase:"gap-review", dimensions:[...]}})
|
|
308
|
+
Workflow({scriptPath:".claude/skills/orchestrate/wave.workflow.js", args:{phase:"implement", items:[...]}})
|
|
309
|
+
|
|
310
|
+
# the gate (subagents, in-lane, before every PR)
|
|
311
|
+
uv run ruff format --check . && uv run ruff check . && uv run mypy && uv run pytest
|
|
312
|
+
|
|
313
|
+
# continuation (you, last action every wave)
|
|
314
|
+
# dynamic mode: issue ScheduleWakeup
|
|
315
|
+
ScheduleWakeup(prompt="/loop /orchestrate", delaySeconds=…, reason="next orchestration wave")
|
|
316
|
+
# cron mode: if ScheduleWakeup says dynamic gate is off, the fixed cron IS the heartbeat — do nothing, do NOT delete the cron
|
|
317
|
+
```
|