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.
Files changed (390) hide show
  1. hermes_voip-0.1.1/.claude/hooks/enforce-worktree.mjs +65 -0
  2. hermes_voip-0.1.1/.claude/settings.json +42 -0
  3. hermes_voip-0.1.1/.claude/skills/adr/SKILL.md +29 -0
  4. hermes_voip-0.1.1/.claude/skills/memory/SKILL.md +50 -0
  5. hermes_voip-0.1.1/.claude/skills/orchestrate/SKILL.md +317 -0
  6. hermes_voip-0.1.1/.claude/skills/orchestrate/wave.workflow.js +416 -0
  7. hermes_voip-0.1.1/.claude/skills/worktree-lane/SKILL.md +74 -0
  8. hermes_voip-0.1.1/.devcontainer/.gitignore +1 -0
  9. hermes_voip-0.1.1/.devcontainer/Dockerfile +135 -0
  10. hermes_voip-0.1.1/.devcontainer/devcontainer.json +46 -0
  11. hermes_voip-0.1.1/.devcontainer/docker-compose.yml +41 -0
  12. hermes_voip-0.1.1/.devcontainer/post-create.sh +142 -0
  13. hermes_voip-0.1.1/.devcontainer/post-start.sh +13 -0
  14. hermes_voip-0.1.1/.env.example +501 -0
  15. hermes_voip-0.1.1/.github/workflows/gate.yml +205 -0
  16. hermes_voip-0.1.1/.github/workflows/gitleaks.yml +23 -0
  17. hermes_voip-0.1.1/.github/workflows/publish.yml +274 -0
  18. hermes_voip-0.1.1/.github/workflows/supply-chain.yml +71 -0
  19. hermes_voip-0.1.1/.gitignore +65 -0
  20. hermes_voip-0.1.1/.gitleaks.toml +33 -0
  21. hermes_voip-0.1.1/.mcp.json +16 -0
  22. hermes_voip-0.1.1/.pre-commit-config.yaml +43 -0
  23. hermes_voip-0.1.1/.python-version +1 -0
  24. hermes_voip-0.1.1/AGENTS.md +199 -0
  25. hermes_voip-0.1.1/CHANGELOG.md +176 -0
  26. hermes_voip-0.1.1/CLAUDE.md +48 -0
  27. hermes_voip-0.1.1/LICENSE +194 -0
  28. hermes_voip-0.1.1/NOTICE +5 -0
  29. hermes_voip-0.1.1/PKG-INFO +712 -0
  30. hermes_voip-0.1.1/README.md +678 -0
  31. hermes_voip-0.1.1/THIRD_PARTY_NOTICES.md +142 -0
  32. hermes_voip-0.1.1/conftest.py +23 -0
  33. hermes_voip-0.1.1/docs/CLAUDE.md +7 -0
  34. hermes_voip-0.1.1/docs/adr/0000-template.md +26 -0
  35. hermes_voip-0.1.1/docs/adr/0001-python-uv-toolchain.md +50 -0
  36. hermes_voip-0.1.1/docs/adr/0002-voip-plugin-architecture.md +123 -0
  37. hermes_voip-0.1.1/docs/adr/0003-cascaded-media-architecture.md +177 -0
  38. hermes_voip-0.1.1/docs/adr/0004-provider-interface-abstraction.md +398 -0
  39. hermes_voip-0.1.1/docs/adr/0005-telephony-media-and-transport.md +239 -0
  40. hermes_voip-0.1.1/docs/adr/0006-streaming-stt-provider.md +190 -0
  41. hermes_voip-0.1.1/docs/adr/0007-streaming-tts-provider.md +345 -0
  42. hermes_voip-0.1.1/docs/adr/0008-vad-endpointing-barge-in.md +203 -0
  43. hermes_voip-0.1.1/docs/adr/0009-prompt-injection-guard.md +234 -0
  44. hermes_voip-0.1.1/docs/adr/0010-dtmf-handling.md +272 -0
  45. hermes_voip-0.1.1/docs/adr/0011-multi-registration-and-call-control.md +124 -0
  46. hermes_voip-0.1.1/docs/adr/0012-default-model-pins.md +90 -0
  47. hermes_voip-0.1.1/docs/adr/0013-sdes-srtp-media-encryption.md +181 -0
  48. hermes_voip-0.1.1/docs/adr/0014-adapter-subclasses-real-base-via-lazy-split.md +93 -0
  49. hermes_voip-0.1.1/docs/adr/0015-symmetric-rtp-comedia-latching.md +84 -0
  50. hermes_voip-0.1.1/docs/adr/0016-webrtc-transport.md +391 -0
  51. hermes_voip-0.1.1/docs/adr/0017-outbound-tts-resample-in-send-path.md +158 -0
  52. hermes_voip-0.1.1/docs/adr/0018-video-call-support.md +606 -0
  53. hermes_voip-0.1.1/docs/adr/0019-outbound-calling-uac-originate.md +445 -0
  54. hermes_voip-0.1.1/docs/adr/0020-voip-caller-modes.md +547 -0
  55. hermes_voip-0.1.1/docs/adr/0021-voip-caller-groups.md +478 -0
  56. hermes_voip-0.1.1/docs/adr/0022-g722-wideband-codec.md +143 -0
  57. hermes_voip-0.1.1/docs/adr/0023-echo-robust-barge-in.md +164 -0
  58. hermes_voip-0.1.1/docs/adr/0024-supply-chain-audit-all-extras.md +90 -0
  59. hermes_voip-0.1.1/docs/adr/0025-tts-failover-and-elevenlabs-model-id-guard.md +161 -0
  60. hermes_voip-0.1.1/docs/adr/0026-call-termination-hermes-session-signal.md +195 -0
  61. hermes_voip-0.1.1/docs/adr/0027-elevenlabs-v3-audio-tags-model-conditional.md +114 -0
  62. hermes_voip-0.1.1/docs/adr/0028-barge-in-clean-stop-fade.md +132 -0
  63. hermes_voip-0.1.1/docs/adr/0029-agent-triggered-outbound-calls.md +194 -0
  64. hermes_voip-0.1.1/docs/adr/0030-dead-air-comfort-filler.md +169 -0
  65. hermes_voip-0.1.1/docs/adr/0031-intercom-caller-mode-and-in-call-dtmf-actuation.md +200 -0
  66. hermes_voip-0.1.1/docs/adr/0032-webrtc-media-wiring-and-opus.md +171 -0
  67. hermes_voip-0.1.1/docs/adr/0033-in-process-aec-aggressive-barge-in.md +206 -0
  68. hermes_voip-0.1.1/docs/adr/0034-ice-turn-trickle-consent.md +253 -0
  69. hermes_voip-0.1.1/docs/adr/0035-caller-group-channel-routing.md +138 -0
  70. hermes_voip-0.1.1/docs/adr/0036-dtmf-sip-info-and-in-band.md +163 -0
  71. hermes_voip-0.1.1/docs/adr/0037-hermes-plugin-manifest-and-install-models.md +181 -0
  72. hermes_voip-0.1.1/docs/adr/0038-wss-signalling-wiring.md +202 -0
  73. hermes_voip-0.1.1/docs/adr/0042-webrtc-inbound-real-gateway-fixes.md +118 -0
  74. hermes_voip-0.1.1/docs/adr/0043-ipv6-first-ice-and-default-stun.md +87 -0
  75. hermes_voip-0.1.1/docs/adr/0044-webrtc-video.md +215 -0
  76. hermes_voip-0.1.1/docs/adr/0045-multi-intercom-openings.md +130 -0
  77. hermes_voip-0.1.1/docs/adr/0046-best-practices-alignment.md +133 -0
  78. hermes_voip-0.1.1/docs/adr/0047-bundled-call-skills.md +99 -0
  79. hermes_voip-0.1.1/docs/adr/0048-transfer-attended.md +123 -0
  80. hermes_voip-0.1.1/docs/adr/0049-outbound-webrtc-opus.md +130 -0
  81. hermes_voip-0.1.1/docs/adr/0050-webrtc-dtls-active-answerer.md +79 -0
  82. hermes_voip-0.1.1/docs/adr/0052-rich-inbound-call-context.md +123 -0
  83. hermes_voip-0.1.1/docs/adr/0053-sip-dtls-srtp-media.md +255 -0
  84. hermes_voip-0.1.1/docs/adr/0054-comfort-filler-random-periodic-multilanguage.md +138 -0
  85. hermes_voip-0.1.1/docs/adr/0055-sip-signalling-robustness-refresh-recovery-and-cancel.md +147 -0
  86. hermes_voip-0.1.1/docs/adr/0056-media-quality-loss-resilience.md +170 -0
  87. hermes_voip-0.1.1/docs/adr/0057-conversational-ux-silence-goodbye-streaming.md +240 -0
  88. hermes_voip-0.1.1/docs/adr/0058-sip-digest-sha256-md5sess.md +73 -0
  89. hermes_voip-0.1.1/docs/adr/0059-adapter-production-safety-lifecycle.md +163 -0
  90. hermes_voip-0.1.1/docs/adr/0060-s3-tables-iceberg-call-events-recordings.md +356 -0
  91. hermes_voip-0.1.1/docs/adr/0061-rtcp-sr-rr-rtcp-mux.md +216 -0
  92. hermes_voip-0.1.1/docs/adr/0062-pyjwt-cve-override.md +228 -0
  93. hermes_voip-0.1.1/docs/adr/0063-adapter-media-activation-and-error-sanitization.md +142 -0
  94. hermes_voip-0.1.1/docs/adr/0064-call-progress-detection-fax-amd.md +251 -0
  95. hermes_voip-0.1.1/docs/adr/0065-answer-time-dialog-registration-ack-aware-abort.md +132 -0
  96. hermes_voip-0.1.1/docs/adr/0066-srtcp-transform.md +140 -0
  97. hermes_voip-0.1.1/docs/adr/0067-outbound-sdes-srtp-offering.md +183 -0
  98. hermes_voip-0.1.1/docs/adr/0068-v3-audio-tag-prompt-encouragement.md +137 -0
  99. hermes_voip-0.1.1/docs/adr/0069-outbound-sip-cancel.md +126 -0
  100. hermes_voip-0.1.1/docs/adr/0070-secure-media-mandate.md +116 -0
  101. hermes_voip-0.1.1/docs/adr/0071-rfc4028-session-timers.md +171 -0
  102. hermes_voip-0.1.1/docs/adr/0072-autonomous-orchestration-loop.md +163 -0
  103. hermes_voip-0.1.1/docs/adr/0073-sdes-answer-selects-strongest-suite.md +52 -0
  104. hermes_voip-0.1.1/docs/adr/0074-proactive-place-call-operator-origin.md +86 -0
  105. hermes_voip-0.1.1/docs/adr/0075-structured-lifecycle-rtcp-log-events.md +72 -0
  106. hermes_voip-0.1.1/docs/adr/0076-refuse-spoken-safe-decline-line.md +98 -0
  107. hermes_voip-0.1.1/docs/adr/0077-dtmf-feed-result-type.md +125 -0
  108. hermes_voip-0.1.1/docs/adr/0078-negotiate-audio-answerer-preference.md +111 -0
  109. hermes_voip-0.1.1/docs/adr/0079-reinvite-nonaudio-sdp-rejection.md +48 -0
  110. hermes_voip-0.1.1/docs/adr/0080-registration-sips-on-secure-transport-and-digest-reactive-contracts.md +135 -0
  111. hermes_voip-0.1.1/docs/adr/0081-malformed-message-skip-vs-framing-failure.md +161 -0
  112. hermes_voip-0.1.1/docs/adr/CLAUDE.md +7 -0
  113. hermes_voip-0.1.1/docs/backlog.md +1090 -0
  114. hermes_voip-0.1.1/docs/plan/IMPLEMENTATION-PLAN.md +249 -0
  115. hermes_voip-0.1.1/docs/plan/MULTIREG-CALLCONTROL-PLAN.md +49 -0
  116. hermes_voip-0.1.1/docs/reference/elevenlabs-v3-audio-tags.md +113 -0
  117. hermes_voip-0.1.1/docs/runbooks/0001-sip-extension-credentials.md +76 -0
  118. hermes_voip-0.1.1/docs/runbooks/0002-voip-live-validation.md +787 -0
  119. hermes_voip-0.1.1/docs/runbooks/0003-supply-chain-audit.md +143 -0
  120. hermes_voip-0.1.1/docs/runbooks/0004-voip-tts-voice.md +230 -0
  121. hermes_voip-0.1.1/docs/runbooks/0005-voip-rtp-inactivity-timeout.md +88 -0
  122. hermes_voip-0.1.1/docs/runbooks/0006-voip-comfort-filler.md +170 -0
  123. hermes_voip-0.1.1/docs/runbooks/0007-voip-outbound-calling.md +262 -0
  124. hermes_voip-0.1.1/docs/runbooks/0008-voip-intercom-and-dtmf.md +308 -0
  125. hermes_voip-0.1.1/docs/runbooks/0009-voip-webrtc-media.md +493 -0
  126. hermes_voip-0.1.1/docs/runbooks/0010-voip-caller-modes.md +285 -0
  127. hermes_voip-0.1.1/docs/runbooks/0011-voip-enable-plugin.md +189 -0
  128. hermes_voip-0.1.1/docs/runbooks/0012-voip-inbound-call-context.md +106 -0
  129. hermes_voip-0.1.1/docs/runbooks/0013-voip-incident-oncall.md +615 -0
  130. hermes_voip-0.1.1/docs/runbooks/0014-voip-slo-metrics.md +467 -0
  131. hermes_voip-0.1.1/docs/runbooks/0015-voip-silence-reprompt-and-goodbye.md +134 -0
  132. hermes_voip-0.1.1/docs/runbooks/0016-orchestration-loop.md +196 -0
  133. hermes_voip-0.1.1/docs/runbooks/0017-devcontainer-resources.md +84 -0
  134. hermes_voip-0.1.1/docs/runbooks/0018-voip-acoustic-echo-cancellation.md +149 -0
  135. hermes_voip-0.1.1/docs/runbooks/0019-release-process.md +298 -0
  136. hermes_voip-0.1.1/docs/runbooks/CLAUDE.md +12 -0
  137. hermes_voip-0.1.1/docs/stack.md +26 -0
  138. hermes_voip-0.1.1/packaging/hermes-plugins/hermes-voip/__init__.py +26 -0
  139. hermes_voip-0.1.1/packaging/hermes-plugins/hermes-voip/plugin.yaml +147 -0
  140. hermes_voip-0.1.1/pyproject.toml +323 -0
  141. hermes_voip-0.1.1/src/hermes_voip/__init__.py +75 -0
  142. hermes_voip-0.1.1/src/hermes_voip/_header_list.py +53 -0
  143. hermes_voip-0.1.1/src/hermes_voip/_lazy_singleton.py +239 -0
  144. hermes_voip-0.1.1/src/hermes_voip/adapter.py +7683 -0
  145. hermes_voip-0.1.1/src/hermes_voip/aio.py +138 -0
  146. hermes_voip-0.1.1/src/hermes_voip/call.py +792 -0
  147. hermes_voip-0.1.1/src/hermes_voip/call_context.py +584 -0
  148. hermes_voip-0.1.1/src/hermes_voip/call_end.py +150 -0
  149. hermes_voip-0.1.1/src/hermes_voip/caller_modes.py +1476 -0
  150. hermes_voip-0.1.1/src/hermes_voip/config.py +2324 -0
  151. hermes_voip-0.1.1/src/hermes_voip/dialog.py +364 -0
  152. hermes_voip-0.1.1/src/hermes_voip/digest.py +367 -0
  153. hermes_voip-0.1.1/src/hermes_voip/dtmf.py +548 -0
  154. hermes_voip-0.1.1/src/hermes_voip/dtmf_config.py +163 -0
  155. hermes_voip-0.1.1/src/hermes_voip/dtmf_confirm.py +215 -0
  156. hermes_voip-0.1.1/src/hermes_voip/dtmf_sipinfo.py +111 -0
  157. hermes_voip-0.1.1/src/hermes_voip/guard/__init__.py +32 -0
  158. hermes_voip-0.1.1/src/hermes_voip/guard/_onnx_runtime.py +171 -0
  159. hermes_voip-0.1.1/src/hermes_voip/guard/normalize.py +388 -0
  160. hermes_voip-0.1.1/src/hermes_voip/guard/onnx.py +296 -0
  161. hermes_voip-0.1.1/src/hermes_voip/hermes_surface.py +127 -0
  162. hermes_voip-0.1.1/src/hermes_voip/incall.py +317 -0
  163. hermes_voip-0.1.1/src/hermes_voip/intercom.py +362 -0
  164. hermes_voip-0.1.1/src/hermes_voip/keepalive.py +108 -0
  165. hermes_voip-0.1.1/src/hermes_voip/manager.py +574 -0
  166. hermes_voip-0.1.1/src/hermes_voip/manifest.py +329 -0
  167. hermes_voip-0.1.1/src/hermes_voip/media/__init__.py +6 -0
  168. hermes_voip-0.1.1/src/hermes_voip/media/aec.py +369 -0
  169. hermes_voip-0.1.1/src/hermes_voip/media/audio.py +398 -0
  170. hermes_voip-0.1.1/src/hermes_voip/media/call_loop.py +2092 -0
  171. hermes_voip-0.1.1/src/hermes_voip/media/call_progress.py +634 -0
  172. hermes_voip-0.1.1/src/hermes_voip/media/dtls.py +908 -0
  173. hermes_voip-0.1.1/src/hermes_voip/media/endpoint.py +148 -0
  174. hermes_voip-0.1.1/src/hermes_voip/media/engine.py +3716 -0
  175. hermes_voip-0.1.1/src/hermes_voip/media/g722.py +456 -0
  176. hermes_voip-0.1.1/src/hermes_voip/media/ice.py +760 -0
  177. hermes_voip-0.1.1/src/hermes_voip/media/opus.py +436 -0
  178. hermes_voip-0.1.1/src/hermes_voip/media/sip_dtls_session.py +541 -0
  179. hermes_voip-0.1.1/src/hermes_voip/media/srtcp.py +599 -0
  180. hermes_voip-0.1.1/src/hermes_voip/media/srtp.py +921 -0
  181. hermes_voip-0.1.1/src/hermes_voip/media/vad.py +525 -0
  182. hermes_voip-0.1.1/src/hermes_voip/media/video_rtp.py +527 -0
  183. hermes_voip-0.1.1/src/hermes_voip/media/webrtc_session.py +560 -0
  184. hermes_voip-0.1.1/src/hermes_voip/message.py +351 -0
  185. hermes_voip-0.1.1/src/hermes_voip/multi_intercom.py +606 -0
  186. hermes_voip-0.1.1/src/hermes_voip/notice_filter.py +154 -0
  187. hermes_voip-0.1.1/src/hermes_voip/originate.py +190 -0
  188. hermes_voip-0.1.1/src/hermes_voip/outbound_allow.py +74 -0
  189. hermes_voip-0.1.1/src/hermes_voip/plugin.py +388 -0
  190. hermes_voip-0.1.1/src/hermes_voip/plugin.yaml +147 -0
  191. hermes_voip-0.1.1/src/hermes_voip/provider_error.py +137 -0
  192. hermes_voip-0.1.1/src/hermes_voip/providers/__init__.py +44 -0
  193. hermes_voip-0.1.1/src/hermes_voip/providers/asr.py +57 -0
  194. hermes_voip-0.1.1/src/hermes_voip/providers/audio.py +50 -0
  195. hermes_voip-0.1.1/src/hermes_voip/providers/build.py +376 -0
  196. hermes_voip-0.1.1/src/hermes_voip/providers/guard.py +71 -0
  197. hermes_voip-0.1.1/src/hermes_voip/providers/onnx_compat.py +71 -0
  198. hermes_voip-0.1.1/src/hermes_voip/providers/policy.py +212 -0
  199. hermes_voip-0.1.1/src/hermes_voip/providers/registry.py +49 -0
  200. hermes_voip-0.1.1/src/hermes_voip/providers/transport.py +51 -0
  201. hermes_voip-0.1.1/src/hermes_voip/providers/tts.py +74 -0
  202. hermes_voip-0.1.1/src/hermes_voip/py.typed +0 -0
  203. hermes_voip-0.1.1/src/hermes_voip/refer.py +535 -0
  204. hermes_voip-0.1.1/src/hermes_voip/registration.py +419 -0
  205. hermes_voip-0.1.1/src/hermes_voip/rtcp.py +1091 -0
  206. hermes_voip-0.1.1/src/hermes_voip/rtp.py +422 -0
  207. hermes_voip-0.1.1/src/hermes_voip/sdp.py +2101 -0
  208. hermes_voip-0.1.1/src/hermes_voip/session_timer.py +344 -0
  209. hermes_voip-0.1.1/src/hermes_voip/sip.py +30 -0
  210. hermes_voip-0.1.1/src/hermes_voip/skills/enquire-price-availability/SKILL.md +37 -0
  211. hermes_voip-0.1.1/src/hermes_voip/skills/intercom-open-for-delivery/SKILL.md +42 -0
  212. hermes_voip-0.1.1/src/hermes_voip/skills/make-reservation/SKILL.md +38 -0
  213. hermes_voip-0.1.1/src/hermes_voip/skills/reception/SKILL.md +36 -0
  214. hermes_voip-0.1.1/src/hermes_voip/skills/take-message/SKILL.md +37 -0
  215. hermes_voip-0.1.1/src/hermes_voip/skills.py +205 -0
  216. hermes_voip-0.1.1/src/hermes_voip/spoken_text.py +281 -0
  217. hermes_voip-0.1.1/src/hermes_voip/stt/__init__.py +31 -0
  218. hermes_voip-0.1.1/src/hermes_voip/stt/deepgram.py +414 -0
  219. hermes_voip-0.1.1/src/hermes_voip/stt/resample.py +172 -0
  220. hermes_voip-0.1.1/src/hermes_voip/stt/sherpa_onnx.py +501 -0
  221. hermes_voip-0.1.1/src/hermes_voip/tools.py +326 -0
  222. hermes_voip-0.1.1/src/hermes_voip/transport/__init__.py +31 -0
  223. hermes_voip-0.1.1/src/hermes_voip/transport/connection.py +978 -0
  224. hermes_voip-0.1.1/src/hermes_voip/transport/framing.py +137 -0
  225. hermes_voip-0.1.1/src/hermes_voip/transport/transaction.py +201 -0
  226. hermes_voip-0.1.1/src/hermes_voip/transport/ws_connection.py +523 -0
  227. hermes_voip-0.1.1/src/hermes_voip/tts/__init__.py +44 -0
  228. hermes_voip-0.1.1/src/hermes_voip/tts/_stream.py +250 -0
  229. hermes_voip-0.1.1/src/hermes_voip/tts/elevenlabs.py +674 -0
  230. hermes_voip-0.1.1/src/hermes_voip/tts/failover.py +392 -0
  231. hermes_voip-0.1.1/src/hermes_voip/tts/segment.py +305 -0
  232. hermes_voip-0.1.1/src/hermes_voip/tts/sherpa_kokoro.py +345 -0
  233. hermes_voip-0.1.1/src/hermes_voip/voip_tools.py +1588 -0
  234. hermes_voip-0.1.1/tests/__init__.py +8 -0
  235. hermes_voip-0.1.1/tests/conftest.py +17 -0
  236. hermes_voip-0.1.1/tests/e2e/__init__.py +10 -0
  237. hermes_voip-0.1.1/tests/e2e/_fake_gateway.py +568 -0
  238. hermes_voip-0.1.1/tests/e2e/_fake_webrtc_gateway.py +847 -0
  239. hermes_voip-0.1.1/tests/e2e/test_concurrent_inbound_calls.py +579 -0
  240. hermes_voip-0.1.1/tests/e2e/test_inbound_call.py +803 -0
  241. hermes_voip-0.1.1/tests/e2e/test_inbound_webrtc_call.py +693 -0
  242. hermes_voip-0.1.1/tests/e2e/test_outbound_call.py +2041 -0
  243. hermes_voip-0.1.1/tests/fixtures/regen_g722_kat.py +47 -0
  244. hermes_voip-0.1.1/tests/g722_kat_vectors.py +378 -0
  245. hermes_voip-0.1.1/tests/providers/__init__.py +1 -0
  246. hermes_voip-0.1.1/tests/providers/test_build.py +667 -0
  247. hermes_voip-0.1.1/tests/stt/test_deepgram.py +422 -0
  248. hermes_voip-0.1.1/tests/stt/test_resample.py +154 -0
  249. hermes_voip-0.1.1/tests/stt/test_sherpa_onnx.py +596 -0
  250. hermes_voip-0.1.1/tests/test_adapter.py +3371 -0
  251. hermes_voip-0.1.1/tests/test_adapter_attended_consult.py +121 -0
  252. hermes_voip-0.1.1/tests/test_adapter_call_progress.py +406 -0
  253. hermes_voip-0.1.1/tests/test_adapter_caller_modes.py +2278 -0
  254. hermes_voip-0.1.1/tests/test_adapter_observability.py +476 -0
  255. hermes_voip-0.1.1/tests/test_adapter_production_safety.py +641 -0
  256. hermes_voip-0.1.1/tests/test_adapter_reconnect.py +420 -0
  257. hermes_voip-0.1.1/tests/test_adapter_rtcp.py +912 -0
  258. hermes_voip-0.1.1/tests/test_adapter_secure_media.py +501 -0
  259. hermes_voip-0.1.1/tests/test_adapter_session_timers.py +1230 -0
  260. hermes_voip-0.1.1/tests/test_adapter_sip_dtls.py +1566 -0
  261. hermes_voip-0.1.1/tests/test_adapter_webrtc.py +1229 -0
  262. hermes_voip-0.1.1/tests/test_adapter_wss_signalling.py +728 -0
  263. hermes_voip-0.1.1/tests/test_adr_unique_numbers.py +43 -0
  264. hermes_voip-0.1.1/tests/test_aec_barge_in_integration.py +191 -0
  265. hermes_voip-0.1.1/tests/test_aio.py +149 -0
  266. hermes_voip-0.1.1/tests/test_audio_content.py +580 -0
  267. hermes_voip-0.1.1/tests/test_barge_in_gate.py +475 -0
  268. hermes_voip-0.1.1/tests/test_best_practices_alignment.py +397 -0
  269. hermes_voip-0.1.1/tests/test_call.py +860 -0
  270. hermes_voip-0.1.1/tests/test_call_context.py +522 -0
  271. hermes_voip-0.1.1/tests/test_call_dtmf_sipinfo.py +269 -0
  272. hermes_voip-0.1.1/tests/test_call_end.py +106 -0
  273. hermes_voip-0.1.1/tests/test_call_loop.py +4715 -0
  274. hermes_voip-0.1.1/tests/test_call_loop_call_progress.py +499 -0
  275. hermes_voip-0.1.1/tests/test_call_loop_dtmf.py +192 -0
  276. hermes_voip-0.1.1/tests/test_caller_groups.py +1355 -0
  277. hermes_voip-0.1.1/tests/test_caller_modes.py +495 -0
  278. hermes_voip-0.1.1/tests/test_caller_privilege.py +183 -0
  279. hermes_voip-0.1.1/tests/test_codec_capability.py +120 -0
  280. hermes_voip-0.1.1/tests/test_config.py +2336 -0
  281. hermes_voip-0.1.1/tests/test_config_aec.py +114 -0
  282. hermes_voip-0.1.1/tests/test_config_call_progress.py +50 -0
  283. hermes_voip-0.1.1/tests/test_convpath_review.py +562 -0
  284. hermes_voip-0.1.1/tests/test_database_exposure_guard.py +419 -0
  285. hermes_voip-0.1.1/tests/test_dialog.py +473 -0
  286. hermes_voip-0.1.1/tests/test_digest.py +754 -0
  287. hermes_voip-0.1.1/tests/test_dtmf.py +373 -0
  288. hermes_voip-0.1.1/tests/test_dtmf_config.py +125 -0
  289. hermes_voip-0.1.1/tests/test_dtmf_confirm.py +254 -0
  290. hermes_voip-0.1.1/tests/test_dtmf_inband.py +184 -0
  291. hermes_voip-0.1.1/tests/test_dtmf_mode_resolution.py +160 -0
  292. hermes_voip-0.1.1/tests/test_dtmf_receive.py +434 -0
  293. hermes_voip-0.1.1/tests/test_dtmf_sipinfo.py +91 -0
  294. hermes_voip-0.1.1/tests/test_g722_codec.py +139 -0
  295. hermes_voip-0.1.1/tests/test_guard_classifier_miss.py +98 -0
  296. hermes_voip-0.1.1/tests/test_guard_model_manifest.py +63 -0
  297. hermes_voip-0.1.1/tests/test_guard_normalize.py +185 -0
  298. hermes_voip-0.1.1/tests/test_guard_onnx.py +289 -0
  299. hermes_voip-0.1.1/tests/test_guard_onnx_model.py +59 -0
  300. hermes_voip-0.1.1/tests/test_header_list.py +43 -0
  301. hermes_voip-0.1.1/tests/test_hermes_contract.py +224 -0
  302. hermes_voip-0.1.1/tests/test_inbound_stt_chain.py +360 -0
  303. hermes_voip-0.1.1/tests/test_incall.py +423 -0
  304. hermes_voip-0.1.1/tests/test_intercom.py +361 -0
  305. hermes_voip-0.1.1/tests/test_keepalive.py +178 -0
  306. hermes_voip-0.1.1/tests/test_lazy_singleton.py +277 -0
  307. hermes_voip-0.1.1/tests/test_manager.py +741 -0
  308. hermes_voip-0.1.1/tests/test_media_aec.py +382 -0
  309. hermes_voip-0.1.1/tests/test_media_audio.py +507 -0
  310. hermes_voip-0.1.1/tests/test_media_call_progress.py +464 -0
  311. hermes_voip-0.1.1/tests/test_media_concurrency.py +1105 -0
  312. hermes_voip-0.1.1/tests/test_media_concurrency_hermes.py +711 -0
  313. hermes_voip-0.1.1/tests/test_media_dtls.py +741 -0
  314. hermes_voip-0.1.1/tests/test_media_endpoint.py +151 -0
  315. hermes_voip-0.1.1/tests/test_media_engine.py +2240 -0
  316. hermes_voip-0.1.1/tests/test_media_engine_aec.py +260 -0
  317. hermes_voip-0.1.1/tests/test_media_engine_bargein.py +360 -0
  318. hermes_voip-0.1.1/tests/test_media_engine_g722.py +452 -0
  319. hermes_voip-0.1.1/tests/test_media_engine_ice.py +217 -0
  320. hermes_voip-0.1.1/tests/test_media_engine_inband_dtmf.py +304 -0
  321. hermes_voip-0.1.1/tests/test_media_engine_opus.py +213 -0
  322. hermes_voip-0.1.1/tests/test_media_engine_pacing.py +352 -0
  323. hermes_voip-0.1.1/tests/test_media_engine_plc.py +318 -0
  324. hermes_voip-0.1.1/tests/test_media_engine_rtcp.py +1080 -0
  325. hermes_voip-0.1.1/tests/test_media_engine_srtcp.py +403 -0
  326. hermes_voip-0.1.1/tests/test_media_ice.py +418 -0
  327. hermes_voip-0.1.1/tests/test_media_opus.py +212 -0
  328. hermes_voip-0.1.1/tests/test_media_singletons_threadsafe.py +249 -0
  329. hermes_voip-0.1.1/tests/test_media_sip_dtls_session.py +525 -0
  330. hermes_voip-0.1.1/tests/test_media_srtcp.py +726 -0
  331. hermes_voip-0.1.1/tests/test_media_srtp.py +889 -0
  332. hermes_voip-0.1.1/tests/test_media_vad.py +493 -0
  333. hermes_voip-0.1.1/tests/test_media_watchdog.py +276 -0
  334. hermes_voip-0.1.1/tests/test_media_webrtc_session.py +642 -0
  335. hermes_voip-0.1.1/tests/test_message.py +380 -0
  336. hermes_voip-0.1.1/tests/test_model_licence_gate.py +246 -0
  337. hermes_voip-0.1.1/tests/test_multi_intercom.py +612 -0
  338. hermes_voip-0.1.1/tests/test_multi_intercom_webhook.py +179 -0
  339. hermes_voip-0.1.1/tests/test_notice_filter.py +294 -0
  340. hermes_voip-0.1.1/tests/test_onnx_compat.py +70 -0
  341. hermes_voip-0.1.1/tests/test_originate.py +219 -0
  342. hermes_voip-0.1.1/tests/test_outbound_allow.py +62 -0
  343. hermes_voip-0.1.1/tests/test_outbound_uri_injection.py +142 -0
  344. hermes_voip-0.1.1/tests/test_plugin_manifest.py +642 -0
  345. hermes_voip-0.1.1/tests/test_provider_error.py +106 -0
  346. hermes_voip-0.1.1/tests/test_providers.py +159 -0
  347. hermes_voip-0.1.1/tests/test_providers_async.py +186 -0
  348. hermes_voip-0.1.1/tests/test_providers_audio.py +22 -0
  349. hermes_voip-0.1.1/tests/test_providers_conformance.py +114 -0
  350. hermes_voip-0.1.1/tests/test_providers_exports.py +82 -0
  351. hermes_voip-0.1.1/tests/test_providers_policy.py +144 -0
  352. hermes_voip-0.1.1/tests/test_providers_registry.py +41 -0
  353. hermes_voip-0.1.1/tests/test_refer.py +610 -0
  354. hermes_voip-0.1.1/tests/test_register.py +767 -0
  355. hermes_voip-0.1.1/tests/test_register_skills.py +152 -0
  356. hermes_voip-0.1.1/tests/test_registration.py +651 -0
  357. hermes_voip-0.1.1/tests/test_rtcp.py +772 -0
  358. hermes_voip-0.1.1/tests/test_rtp.py +465 -0
  359. hermes_voip-0.1.1/tests/test_runbook_0013_drift.py +108 -0
  360. hermes_voip-0.1.1/tests/test_sdp.py +2834 -0
  361. hermes_voip-0.1.1/tests/test_sdp_video.py +462 -0
  362. hermes_voip-0.1.1/tests/test_session_timer.py +394 -0
  363. hermes_voip-0.1.1/tests/test_sip.py +29 -0
  364. hermes_voip-0.1.1/tests/test_sip_request.py +78 -0
  365. hermes_voip-0.1.1/tests/test_spoken_text.py +392 -0
  366. hermes_voip-0.1.1/tests/test_stt_tts_model_manifest.py +110 -0
  367. hermes_voip-0.1.1/tests/test_supply_chain_schedule.py +204 -0
  368. hermes_voip-0.1.1/tests/test_tools.py +444 -0
  369. hermes_voip-0.1.1/tests/test_tts_elevenlabs.py +903 -0
  370. hermes_voip-0.1.1/tests/test_tts_failover.py +674 -0
  371. hermes_voip-0.1.1/tests/test_tts_pcm16.py +63 -0
  372. hermes_voip-0.1.1/tests/test_tts_segment.py +254 -0
  373. hermes_voip-0.1.1/tests/test_tts_sherpa_kokoro.py +351 -0
  374. hermes_voip-0.1.1/tests/test_tts_sherpa_kokoro_real.py +56 -0
  375. hermes_voip-0.1.1/tests/test_video_rtp.py +468 -0
  376. hermes_voip-0.1.1/tests/test_voip_tools.py +1020 -0
  377. hermes_voip-0.1.1/tests/test_voip_tools_place_call.py +483 -0
  378. hermes_voip-0.1.1/tests/test_voip_tools_transfer_attended.py +435 -0
  379. hermes_voip-0.1.1/tests/test_wheel_packaging.py +211 -0
  380. hermes_voip-0.1.1/tests/transport/__init__.py +1 -0
  381. hermes_voip-0.1.1/tests/transport/_loopback.py +188 -0
  382. hermes_voip-0.1.1/tests/transport/test_connection.py +1114 -0
  383. hermes_voip-0.1.1/tests/transport/test_framing.py +227 -0
  384. hermes_voip-0.1.1/tests/transport/test_keepalive.py +326 -0
  385. hermes_voip-0.1.1/tests/transport/test_keepalive_ping.py +155 -0
  386. hermes_voip-0.1.1/tests/transport/test_transaction.py +214 -0
  387. hermes_voip-0.1.1/tests/transport/test_ws_connection.py +944 -0
  388. hermes_voip-0.1.1/tools/__init__.py +1 -0
  389. hermes_voip-0.1.1/tools/check_database_exposure.py +397 -0
  390. 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
+ ```