agentirc-cli 8.2.0__tar.gz → 8.4.0__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 (441) hide show
  1. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/CHANGELOG.md +36 -0
  2. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/PKG-INFO +1 -1
  3. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/client.py +138 -111
  4. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/config.py +2 -0
  5. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/ircd.py +16 -2
  6. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/server_link.py +224 -44
  7. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/protocol/extensions/tracing.md +7 -1
  8. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/telemetry/__init__.py +7 -0
  9. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/telemetry/context.py +37 -0
  10. agentirc_cli-8.4.0/culture/telemetry/metrics.py +225 -0
  11. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/telemetry/tracing.py +4 -1
  12. agentirc_cli-8.4.0/docs/agentirc/telemetry.md +140 -0
  13. agentirc_cli-8.4.0/docs/superpowers/plans/2026-04-25-otel-federation.md +229 -0
  14. agentirc_cli-8.4.0/docs/superpowers/plans/2026-04-26-otel-metrics.md +303 -0
  15. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/pyproject.toml +1 -1
  16. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/conftest.py +25 -0
  17. agentirc_cli-8.4.0/tests/telemetry/_metrics_helpers.py +66 -0
  18. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/telemetry/test_context.py +42 -0
  19. agentirc_cli-8.4.0/tests/telemetry/test_federation_propagation.py +141 -0
  20. agentirc_cli-8.4.0/tests/telemetry/test_metrics_clients.py +79 -0
  21. agentirc_cli-8.4.0/tests/telemetry/test_metrics_events.py +81 -0
  22. agentirc_cli-8.4.0/tests/telemetry/test_metrics_init.py +58 -0
  23. agentirc_cli-8.4.0/tests/telemetry/test_metrics_irc.py +99 -0
  24. agentirc_cli-8.4.0/tests/telemetry/test_metrics_s2s.py +160 -0
  25. agentirc_cli-8.4.0/tests/telemetry/test_metrics_trace_inbound.py +127 -0
  26. agentirc_cli-8.4.0/tests/telemetry/test_s2s_dispatch_span.py +156 -0
  27. agentirc_cli-8.4.0/tests/telemetry/test_s2s_relay_span.py +114 -0
  28. agentirc_cli-8.4.0/tests/telemetry/test_s2s_session_span.py +64 -0
  29. agentirc_cli-8.4.0/tests/telemetry/test_server_link_inject.py +124 -0
  30. agentirc_cli-8.4.0/tests/telemetry/test_session_span.py +105 -0
  31. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_federation.py +47 -0
  32. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/uv.lock +1 -1
  33. agentirc_cli-8.2.0/docs/agentirc/telemetry.md +0 -81
  34. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/.claude/agents/doc-test-alignment.md +0 -0
  35. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/.claude/skills/pr-review/SKILL.md +0 -0
  36. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/.claude/skills/run-tests/SKILL.md +0 -0
  37. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/.claude/skills/run-tests/scripts/test.sh +0 -0
  38. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/.flake8 +0 -0
  39. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/.github/workflows/docs-check.yml +0 -0
  40. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/.github/workflows/publish.yml +0 -0
  41. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/.github/workflows/security-checks.yml +0 -0
  42. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/.github/workflows/tests.yml +0 -0
  43. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/.gitignore +0 -0
  44. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/.markdownlint-cli2.yaml +0 -0
  45. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/.pr_agent.toml +0 -0
  46. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/.pre-commit-config.yaml +0 -0
  47. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/.pylintrc +0 -0
  48. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/CLAUDE.md +0 -0
  49. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/Gemfile +0 -0
  50. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/Gemfile.lock +0 -0
  51. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/LICENSE +0 -0
  52. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/README.md +0 -0
  53. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/SECURITY.md +0 -0
  54. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/_config.base.yml +0 -0
  55. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/_config.culture.yml +0 -0
  56. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/_data/sites.yml +0 -0
  57. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/_includes/head_custom.html +0 -0
  58. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/_sass/color_schemes/anthropic.scss +0 -0
  59. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/_sass/color_schemes/dark-terminal.scss +0 -0
  60. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/_sass/custom/custom.scss +0 -0
  61. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/assets/images/IMG_3183.png +0 -0
  62. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/assets/images/apple-touch-icon.png +0 -0
  63. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/assets/images/favicon-16x16.png +0 -0
  64. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/assets/images/favicon-32x32.png +0 -0
  65. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/assets/images/favicon.ico +0 -0
  66. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/assets/images/og-agentirc.png +0 -0
  67. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/assets/images/og-culture.png +0 -0
  68. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/__init__.py +0 -0
  69. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/__main__.py +0 -0
  70. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/CLAUDE.md +0 -0
  71. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/__init__.py +0 -0
  72. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/__main__.py +0 -0
  73. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/channel.py +0 -0
  74. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/docs/agentirc-architecture.md +0 -0
  75. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/docs/agentirc-features.md +0 -0
  76. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/docs/agentirc-skill.md +0 -0
  77. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/docs/agentirc.md +0 -0
  78. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/events.py +0 -0
  79. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/history_store.py +0 -0
  80. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/remote_client.py +0 -0
  81. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/room_store.py +0 -0
  82. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/rooms_util.py +0 -0
  83. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/skill.py +0 -0
  84. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/skills/__init__.py +0 -0
  85. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/skills/history.py +0 -0
  86. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/skills/icon.py +0 -0
  87. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/skills/rooms.py +0 -0
  88. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/skills/threads.py +0 -0
  89. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/agentirc/thread_store.py +0 -0
  90. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/aio.py +0 -0
  91. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/bots/__init__.py +0 -0
  92. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/bots/bot.py +0 -0
  93. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/bots/bot_manager.py +0 -0
  94. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/bots/config.py +0 -0
  95. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/bots/filter_dsl.py +0 -0
  96. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/bots/http_listener.py +0 -0
  97. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/bots/system/__init__.py +0 -0
  98. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/bots/system/welcome/__init__.py +0 -0
  99. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/bots/system/welcome/bot.yaml +0 -0
  100. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/bots/system/welcome/handler.py +0 -0
  101. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/bots/template_engine.py +0 -0
  102. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/bots/virtual_client.py +0 -0
  103. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/__init__.py +0 -0
  104. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/_passthrough.py +0 -0
  105. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/afi.py +0 -0
  106. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/agent.py +0 -0
  107. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/bot.py +0 -0
  108. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/channel.py +0 -0
  109. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/devex.py +0 -0
  110. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/introspect.py +0 -0
  111. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/mesh.py +0 -0
  112. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/server.py +0 -0
  113. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/shared/__init__.py +0 -0
  114. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/shared/constants.py +0 -0
  115. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/shared/display.py +0 -0
  116. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/shared/formatting.py +0 -0
  117. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/shared/ipc.py +0 -0
  118. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/shared/mesh.py +0 -0
  119. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/shared/process.py +0 -0
  120. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/cli/skills.py +0 -0
  121. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/__init__.py +0 -0
  122. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/acp/__init__.py +0 -0
  123. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/acp/agent_runner.py +0 -0
  124. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/acp/config.py +0 -0
  125. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/acp/culture.yaml +0 -0
  126. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/acp/daemon.py +0 -0
  127. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/acp/ipc.py +0 -0
  128. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/acp/irc_transport.py +0 -0
  129. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/acp/message_buffer.py +0 -0
  130. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/acp/skill/SKILL.md +0 -0
  131. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/acp/skill/__init__.py +0 -0
  132. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/acp/skill/irc_client.py +0 -0
  133. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/acp/socket_server.py +0 -0
  134. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/acp/supervisor.py +0 -0
  135. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/acp/webhook.py +0 -0
  136. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/__init__.py +0 -0
  137. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/__main__.py +0 -0
  138. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/agent_runner.py +0 -0
  139. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/config.py +0 -0
  140. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/culture.yaml +0 -0
  141. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/daemon.py +0 -0
  142. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/ipc.py +0 -0
  143. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/irc_transport.py +0 -0
  144. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/message_buffer.py +0 -0
  145. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/skill/SKILL.md +0 -0
  146. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/skill/__init__.py +0 -0
  147. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/skill/irc_client.py +0 -0
  148. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/socket_server.py +0 -0
  149. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/supervisor.py +0 -0
  150. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/claude/webhook.py +0 -0
  151. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/codex/__init__.py +0 -0
  152. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/codex/agent_runner.py +0 -0
  153. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/codex/config.py +0 -0
  154. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/codex/culture.yaml +0 -0
  155. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/codex/daemon.py +0 -0
  156. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/codex/ipc.py +0 -0
  157. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/codex/irc_transport.py +0 -0
  158. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/codex/message_buffer.py +0 -0
  159. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/codex/skill/SKILL.md +0 -0
  160. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/codex/skill/__init__.py +0 -0
  161. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/codex/skill/irc_client.py +0 -0
  162. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/codex/socket_server.py +0 -0
  163. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/codex/supervisor.py +0 -0
  164. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/codex/webhook.py +0 -0
  165. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/copilot/__init__.py +0 -0
  166. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/copilot/agent_runner.py +0 -0
  167. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/copilot/config.py +0 -0
  168. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/copilot/culture.yaml +0 -0
  169. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/copilot/daemon.py +0 -0
  170. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/copilot/ipc.py +0 -0
  171. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/copilot/irc_transport.py +0 -0
  172. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/copilot/message_buffer.py +0 -0
  173. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/copilot/skill/SKILL.md +0 -0
  174. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/copilot/skill/__init__.py +0 -0
  175. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/copilot/skill/irc_client.py +0 -0
  176. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/copilot/socket_server.py +0 -0
  177. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/copilot/supervisor.py +0 -0
  178. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/clients/copilot/webhook.py +0 -0
  179. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/config.py +0 -0
  180. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/console/__init__.py +0 -0
  181. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/console/app.py +0 -0
  182. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/console/client.py +0 -0
  183. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/console/commands.py +0 -0
  184. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/console/status.py +0 -0
  185. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/console/widgets/__init__.py +0 -0
  186. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/console/widgets/chat.py +0 -0
  187. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/console/widgets/info_panel.py +0 -0
  188. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/console/widgets/sidebar.py +0 -0
  189. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/constants.py +0 -0
  190. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/credentials.py +0 -0
  191. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/formatting.py +0 -0
  192. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/learn_prompt.py +0 -0
  193. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/mesh_config.py +0 -0
  194. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/observer.py +0 -0
  195. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/overview/__init__.py +0 -0
  196. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/overview/collector.py +0 -0
  197. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/overview/model.py +0 -0
  198. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/overview/renderer_text.py +0 -0
  199. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/overview/renderer_web.py +0 -0
  200. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/overview/web/style.css +0 -0
  201. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/persistence.py +0 -0
  202. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/pidfile.py +0 -0
  203. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/protocol/__init__.py +0 -0
  204. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/protocol/commands.py +0 -0
  205. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/protocol/extensions/events.md +0 -0
  206. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/protocol/extensions/federation.md +0 -0
  207. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/protocol/extensions/history.md +0 -0
  208. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/protocol/extensions/icons.md +0 -0
  209. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/protocol/extensions/rooms.md +0 -0
  210. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/protocol/extensions/tags.md +0 -0
  211. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/protocol/extensions/threads.md +0 -0
  212. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/protocol/message.py +0 -0
  213. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/protocol/protocol-index.md +0 -0
  214. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/protocol/replies.py +0 -0
  215. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/culture/skills/culture/SKILL.md +0 -0
  216. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/README.md +0 -0
  217. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/agentirc/architecture-overview.md +0 -0
  218. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/agentirc/bots.md +0 -0
  219. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/agentirc/events.md +0 -0
  220. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/agentirc/index.md +0 -0
  221. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/agentirc/otelcol-template.yaml +0 -0
  222. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/agentirc/why-agentirc.md +0 -0
  223. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/culture/agent-lifecycle.md +0 -0
  224. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/culture/choose-a-harness.md +0 -0
  225. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/culture/features.md +0 -0
  226. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/culture/index.md +0 -0
  227. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/culture/mental-model.md +0 -0
  228. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/culture/operate.md +0 -0
  229. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/culture/patterns.md +0 -0
  230. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/culture/quickstart.md +0 -0
  231. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/culture/reflective-development.md +0 -0
  232. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/culture/vision-patterns-index.md +0 -0
  233. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/culture/vision.md +0 -0
  234. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/culture/what-is-culture.md +0 -0
  235. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/architecture/agent-harness-spec.md +0 -0
  236. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/architecture/index.md +0 -0
  237. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/architecture/layers.md +0 -0
  238. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/architecture/subsites.md +0 -0
  239. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/architecture/threads.md +0 -0
  240. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/cli/afi.md +0 -0
  241. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/cli/commands.md +0 -0
  242. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/cli/devex.md +0 -0
  243. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/cli/index.md +0 -0
  244. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/console.md +0 -0
  245. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/harnesses/acp.md +0 -0
  246. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/harnesses/claude.md +0 -0
  247. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/harnesses/codex.md +0 -0
  248. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/harnesses/copilot.md +0 -0
  249. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/harnesses/index.md +0 -0
  250. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/index.md +0 -0
  251. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/server/architecture.md +0 -0
  252. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/server/config.md +0 -0
  253. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/server/deployment.md +0 -0
  254. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/server/index.md +0 -0
  255. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/reference/server/security.md +0 -0
  256. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/resources/github-copilot-sdk-instructions.md +0 -0
  257. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/resources/positioning.md +0 -0
  258. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/concepts/federation.md +0 -0
  259. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/concepts/harnesses.md +0 -0
  260. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/concepts/humans-and-agents.md +0 -0
  261. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/concepts/index.md +0 -0
  262. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/concepts/persistence.md +0 -0
  263. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/concepts/rooms.md +0 -0
  264. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/demos/magic-demo.md +0 -0
  265. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/guides/first-session.md +0 -0
  266. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/guides/index.md +0 -0
  267. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/guides/join-as-human.md +0 -0
  268. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/guides/local-setup.md +0 -0
  269. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/guides/multi-machine.md +0 -0
  270. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/use-cases/01-pair-programming.md +0 -0
  271. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/use-cases/02-code-review-ensemble.md +0 -0
  272. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/use-cases/03-cross-server-delegation.md +0 -0
  273. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/use-cases/04-knowledge-propagation.md +0 -0
  274. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/use-cases/05-the-observer.md +0 -0
  275. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/use-cases/06-cross-server-ops.md +0 -0
  276. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/use-cases/07-supervisor-intervention.md +0 -0
  277. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/use-cases/08-apps-as-agents.md +0 -0
  278. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/use-cases/09-research-swarm.md +0 -0
  279. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/use-cases/10-agent-lifecycle.md +0 -0
  280. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/shared/use-cases-index.md +0 -0
  281. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-03-19-layer1-core-irc.md +0 -0
  282. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-03-21-layer5-agent-harness.md +0 -0
  283. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-03-30-overview.md +0 -0
  284. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-03-30-rooms-management.md +0 -0
  285. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-04-02-conversation-threads.md +0 -0
  286. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-04-02-ops-tooling.md +0 -0
  287. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-04-04-culture-rename.md +0 -0
  288. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-04-05-docs-speak-culture.md +0 -0
  289. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-04-06-console-chat.md +0 -0
  290. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-04-09-decentralized-agent-config.md +0 -0
  291. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-04-12-console-enhancements.md +0 -0
  292. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-04-15-mesh-events.md +0 -0
  293. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-04-18-culture-dev-positioning.md +0 -0
  294. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-04-22-agex-integration.md +0 -0
  295. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/plans/2026-04-24-otel-foundation.md +0 -0
  296. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-03-19-agentirc-design.md +0 -0
  297. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-03-21-layer5-agent-harness-design.md +0 -0
  298. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-03-30-overview-design.md +0 -0
  299. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-03-30-rooms-management-design.md +0 -0
  300. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-02-conversation-threads-design.md +0 -0
  301. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-02-ops-tooling-design.md +0 -0
  302. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-03-bots-webhooks-design.md +0 -0
  303. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-04-culture-rename-design.md +0 -0
  304. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-05-docs-speak-culture-design.md +0 -0
  305. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-05-lifecycle-reframe-design.md +0 -0
  306. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-06-cli-reorganization-design.md +0 -0
  307. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-06-console-chat-design.md +0 -0
  308. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-07-entity-archiving-design.md +0 -0
  309. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-07-reflective-development-reframe-design.md +0 -0
  310. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-08-reflective-development-deepening-design.md +0 -0
  311. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-09-decentralized-agent-config-design.md +0 -0
  312. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-12-console-enhancements-design.md +0 -0
  313. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-15-mesh-events-design.md +0 -0
  314. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-17-sites-repositioning-design.md +0 -0
  315. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-18-culture-dev-positioning-design.md +0 -0
  316. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-22-agex-integration-design.md +0 -0
  317. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/docs/superpowers/specs/2026-04-24-otel-observability-design.md +0 -0
  318. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/favicon.ico +0 -0
  319. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/packages/agent-harness/README.md +0 -0
  320. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/packages/agent-harness/config.py +0 -0
  321. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/packages/agent-harness/culture.yaml +0 -0
  322. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/packages/agent-harness/daemon.py +0 -0
  323. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/packages/agent-harness/ipc.py +0 -0
  324. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/packages/agent-harness/irc_transport.py +0 -0
  325. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/packages/agent-harness/message_buffer.py +0 -0
  326. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/packages/agent-harness/skill/SKILL.md +0 -0
  327. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/packages/agent-harness/skill/irc_client.py +0 -0
  328. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/packages/agent-harness/socket_server.py +0 -0
  329. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/packages/agent-harness/webhook.py +0 -0
  330. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
  331. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/plugins/claude-code/skills/culture/SKILL.md +0 -0
  332. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/plugins/claude-code/skills/irc/SKILL.md +0 -0
  333. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/plugins/codex/skills/culture-irc/SKILL.md +0 -0
  334. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/robots.txt +0 -0
  335. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/sitemap-agentirc.html +0 -0
  336. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/sitemap-main.html +0 -0
  337. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/sitemap.html +0 -0
  338. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/sonar-project.properties +0 -0
  339. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/__init__.py +0 -0
  340. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/telemetry/__init__.py +0 -0
  341. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/telemetry/_fakes.py +0 -0
  342. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/telemetry/test_config.py +0 -0
  343. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/telemetry/test_config_load.py +0 -0
  344. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/telemetry/test_dispatch_span.py +0 -0
  345. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/telemetry/test_emit_event_span.py +0 -0
  346. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/telemetry/test_outbound_inject.py +0 -0
  347. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/telemetry/test_parse_error.py +0 -0
  348. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/telemetry/test_privmsg_span.py +0 -0
  349. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/telemetry/test_server_init.py +0 -0
  350. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/telemetry/test_tracing.py +0 -0
  351. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_acp_daemon.py +0 -0
  352. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_agent_runner.py +0 -0
  353. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_archive.py +0 -0
  354. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_bot.py +0 -0
  355. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_bot_config.py +0 -0
  356. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_bot_config_fires_event_toplevel.py +0 -0
  357. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_bot_manager.py +0 -0
  358. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_bots_integration.py +0 -0
  359. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_channel.py +0 -0
  360. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_channel_cli.py +0 -0
  361. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_cli_afi.py +0 -0
  362. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_cli_devex.py +0 -0
  363. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_cli_introspect.py +0 -0
  364. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_cli_passthrough.py +0 -0
  365. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_codex_daemon.py +0 -0
  366. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_connection.py +0 -0
  367. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_console_chat_markdown.py +0 -0
  368. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_console_client.py +0 -0
  369. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_console_commands.py +0 -0
  370. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_console_connection.py +0 -0
  371. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_console_fixes_224_227.py +0 -0
  372. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_console_icons.py +0 -0
  373. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_console_integration.py +0 -0
  374. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_console_status.py +0 -0
  375. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_copilot_daemon.py +0 -0
  376. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_credentials.py +0 -0
  377. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_culture_config.py +0 -0
  378. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_daemon.py +0 -0
  379. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_daemon_config.py +0 -0
  380. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_daemon_ipc.py +0 -0
  381. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_discovery.py +0 -0
  382. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_display.py +0 -0
  383. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_events_basic.py +0 -0
  384. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_events_bot_chain.py +0 -0
  385. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_events_bot_trigger.py +0 -0
  386. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_events_cap_fallback.py +0 -0
  387. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_events_catalog.py +0 -0
  388. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_events_federation.py +0 -0
  389. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_events_history.py +0 -0
  390. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_events_lifecycle.py +0 -0
  391. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_events_reserved_nick.py +0 -0
  392. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_filter_dsl.py +0 -0
  393. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_history.py +0 -0
  394. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_http_listener.py +0 -0
  395. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_integration_layer5.py +0 -0
  396. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_ipc.py +0 -0
  397. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_irc_transport.py +0 -0
  398. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_irc_transport_tags.py +0 -0
  399. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_learn_prompt.py +0 -0
  400. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_link_reconnect.py +0 -0
  401. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_manifest_config.py +0 -0
  402. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_mention_alias.py +0 -0
  403. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_mention_target_cleanup.py +0 -0
  404. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_mention_warning.py +0 -0
  405. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_mentions.py +0 -0
  406. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_mesh_config.py +0 -0
  407. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_mesh_readiness.py +0 -0
  408. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_message.py +0 -0
  409. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_message_buffer.py +0 -0
  410. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_message_tags.py +0 -0
  411. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_messaging.py +0 -0
  412. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_migrate_cli.py +0 -0
  413. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_modes.py +0 -0
  414. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_overview_cli.py +0 -0
  415. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_overview_collector.py +0 -0
  416. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_overview_model.py +0 -0
  417. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_overview_renderer.py +0 -0
  418. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_overview_web.py +0 -0
  419. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_persistence.py +0 -0
  420. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_persistence_timeout.py +0 -0
  421. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_pidfile.py +0 -0
  422. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_poll_loop.py +0 -0
  423. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_register_cli.py +0 -0
  424. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_room_persistence.py +0 -0
  425. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_rooms.py +0 -0
  426. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_rooms_federation.py +0 -0
  427. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_rooms_integration.py +0 -0
  428. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_server_icon_skill.py +0 -0
  429. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_setup_update_cli.py +0 -0
  430. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_skill_client.py +0 -0
  431. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_skill_docs.py +0 -0
  432. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_skills.py +0 -0
  433. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_socket_server.py +0 -0
  434. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_supervisor.py +0 -0
  435. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_template_engine.py +0 -0
  436. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_thread_buffer.py +0 -0
  437. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_threads.py +0 -0
  438. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_virtual_client.py +0 -0
  439. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_wait_for_port.py +0 -0
  440. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_webhook.py +0 -0
  441. {agentirc_cli-8.2.0 → agentirc_cli-8.4.0}/tests/test_welcome_bot.py +0 -0
@@ -4,6 +4,42 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  Format follows [Keep a Changelog](https://keepachangelog.com/).
6
6
 
7
+ ## [8.4.0] - 2026-04-25
8
+
9
+ ### Added
10
+
11
+ - `culture/telemetry/metrics.py`: `init_metrics(config)` + `MetricsRegistry` dataclass for all 15 server-side instruments — mirrors `tracing.py`'s idempotency + no-op pattern.
12
+ - Public `culture.telemetry.MetricsRegistry` and `culture.telemetry.init_metrics`.
13
+ - `TelemetryConfig.metrics_enabled` (default `True`) and `metrics_export_interval_ms` (default 10000).
14
+ - Message-flow metrics: `culture.irc.bytes_sent`, `culture.irc.bytes_received`, `culture.irc.message.size`, `culture.privmsg.delivered`.
15
+ - Events metrics: `culture.events.emitted`, `culture.events.render.duration`.
16
+ - Federation metrics: `culture.s2s.messages` (inbound), `culture.s2s.relay_latency`, `culture.s2s.links_active`, `culture.s2s.link_events`.
17
+ - Client metrics: `culture.clients.connected`, `culture.client.session.duration`, `culture.client.command.duration`.
18
+ - `culture.trace.inbound` counter — closes Plan 2's deferral.
19
+ - `tests/conftest.py` `metrics_reader` fixture parallel to `tracing_exporter`.
20
+ - `tests/telemetry/_metrics_helpers.py` — `get_counter_value`, `get_histogram_count`, `get_up_down_value`.
21
+
22
+ ## [8.3.0] - 2026-04-25
23
+
24
+ ### Added
25
+
26
+ - `irc.s2s.session` span over ServerLink connection lifetime.
27
+ - `irc.s2s.<VERB>` per-verb spans on inbound federation messages with traceparent extraction and the inbound mitigation rules from `culture/protocol/extensions/tracing.md`.
28
+ - `irc.s2s.relay` span on outbound relay enforcing the re-sign-per-hop rule.
29
+ - `irc.client.session` span over Client connection lifetime (#290).
30
+ - `irc.join` and `irc.part` spans (#290).
31
+ - Public `culture.telemetry.context_from_traceparent` and `culture.telemetry.current_traceparent` helpers.
32
+ - Single traceparent injection choke point at `ServerLink.send_raw`.
33
+ - End-to-end propagation tests proving one `trace_id` spans federated client → server → relay → server hops.
34
+
35
+ ### Changed
36
+
37
+ - `Client._dispatch` span name and `irc.command` attribute now uppercase, matching `ServerLink._dispatch` convention.
38
+
39
+ ### Fixed
40
+
41
+ - `_replay_event` uses the hasattr-guarded comparison so string-typed federated `event.type` no longer skips the typed fast path. (#291)
42
+
7
43
  ## [8.2.0] - 2026-04-24
8
44
 
9
45
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentirc-cli
3
- Version: 8.2.0
3
+ Version: 8.4.0
4
4
  Summary: Legacy alias for culture — install culture instead
5
5
  Project-URL: Homepage, https://github.com/OriNachum/culture
6
6
  Author: Ori Nachum
@@ -4,11 +4,12 @@ from __future__ import annotations
4
4
  import asyncio
5
5
  import logging
6
6
  import re
7
+ import time
7
8
  from typing import TYPE_CHECKING
8
9
 
9
10
  from opentelemetry import trace as _otel_trace
10
- from opentelemetry.trace import NonRecordingSpan, SpanContext, TraceFlags
11
- from opentelemetry.trace.propagation import set_span_in_context
11
+ from opentelemetry.context import Context as _OtelContext
12
+ from opentelemetry.trace import Span as _OtelSpan
12
13
 
13
14
  from culture.agentirc.channel import Channel
14
15
  from culture.agentirc.skill import Event, EventType
@@ -18,6 +19,8 @@ from culture.protocol import replies
18
19
  from culture.protocol.message import Message
19
20
  from culture.telemetry.context import TRACEPARENT_TAG as _TP_TAG_NAME
20
21
  from culture.telemetry.context import (
22
+ context_from_traceparent,
23
+ current_traceparent,
21
24
  extract_traceparent_from_tags,
22
25
  )
23
26
  from culture.telemetry.context import inject_traceparent as _inject_traceparent
@@ -25,39 +28,12 @@ from culture.telemetry.context import inject_traceparent as _inject_traceparent
25
28
  # OTEL instrumentation name (must match `_CULTURE_TRACER_NAME` in
26
29
  # culture/telemetry/tracing.py so trace consumers see one consistent value).
27
30
  _TRACER_NAME = "culture.agentirc"
28
- # Span attribute keys for PRIVMSG body capture, defined once so a future
29
- # rename / sanitization layer has one edit point.
31
+ # Span attribute keys, defined once so a future rename / sanitization layer
32
+ # has one edit point.
30
33
  _ATTR_BODY = "irc.message.body"
31
34
  _ATTR_SIZE = "irc.message.size"
32
-
33
-
34
- def _context_from_traceparent(tp: str):
35
- """Build an OTEL context whose current span is a NonRecordingSpan
36
- synthesized from a W3C traceparent string. The `_dispatch` span we
37
- start next will be a child of this context."""
38
- # Format: 00-<trace-id>-<parent-id>-<flags>
39
- _, trace_hex, parent_hex, flags_hex = tp.split("-")
40
- span_ctx = SpanContext(
41
- trace_id=int(trace_hex, 16),
42
- span_id=int(parent_hex, 16),
43
- is_remote=True,
44
- trace_flags=TraceFlags(int(flags_hex, 16)),
45
- )
46
- return set_span_in_context(NonRecordingSpan(span_ctx))
47
-
48
-
49
- def _current_traceparent() -> str | None:
50
- """Return the W3C traceparent for the currently-active span, or None
51
- if no span is recording (no-op tracer / sampler dropped).
52
- """
53
- span = _otel_trace.get_current_span()
54
- ctx = span.get_span_context()
55
- if not ctx.is_valid:
56
- return None
57
- return (
58
- f"00-{format(ctx.trace_id, '032x')}-{format(ctx.span_id, '016x')}"
59
- f"-{format(int(ctx.trace_flags), '02x')}"
60
- )
35
+ _ATTR_NICK = "irc.client.nick"
36
+ _ATTR_CHANNEL = "irc.channel"
61
37
 
62
38
 
63
39
  if TYPE_CHECKING:
@@ -86,6 +62,7 @@ class Client:
86
62
  self.caps: set[str] = set()
87
63
  self.modes: set[str] = set()
88
64
  self.icon: str | None = None
65
+ self._session_span: _OtelSpan | None = None
89
66
 
90
67
  @property
91
68
  def prefix(self) -> str:
@@ -97,12 +74,16 @@ class Client:
97
74
  # block and `send_tagged`'s tag-stripping for non-capable clients
98
75
  # would be undone here.
99
76
  if "message-tags" in self.caps:
100
- tp = _current_traceparent()
77
+ tp = current_traceparent()
101
78
  if tp is not None:
102
79
  _inject_traceparent(message, traceparent=tp, tracestate=None)
103
80
  try:
104
- self.writer.write(message.format().encode("utf-8"))
81
+ wire = message.format().encode("utf-8")
82
+ self.writer.write(wire)
105
83
  await self.writer.drain()
84
+ # Record bytes after a successful drain so we don't count
85
+ # writes that immediately faulted.
86
+ self.server.metrics.irc_bytes_sent.add(len(wire), {"direction": "s2c"})
106
87
  except OSError:
107
88
  pass # Client disconnected; cleanup happens in ircd._handle_connection
108
89
 
@@ -114,14 +95,16 @@ class Client:
114
95
  AND the client negotiated the `message-tags` capability.
115
96
  """
116
97
  if "message-tags" in self.caps:
117
- tp = _current_traceparent()
98
+ tp = current_traceparent()
118
99
  if tp is not None:
119
100
  # send_raw takes a pre-formatted line without an existing tag
120
101
  # block; prefix a fresh @tag.
121
102
  line = f"@{_TP_TAG_NAME}={tp} {line}"
122
103
  try:
123
- self.writer.write(f"{line}\r\n".encode("utf-8"))
104
+ wire = f"{line}\r\n".encode("utf-8")
105
+ self.writer.write(wire)
124
106
  await self.writer.drain()
107
+ self.server.metrics.irc_bytes_sent.add(len(wire), {"direction": "s2c"})
125
108
  except OSError:
126
109
  pass # Client disconnected; cleanup happens in ircd._handle_connection
127
110
 
@@ -166,35 +149,63 @@ class Client:
166
149
  },
167
150
  )
168
151
  continue
152
+ # Record received bytes + message size for every successfully-parsed
153
+ # line. +2 accounts for the \r\n that was stripped during line-split.
154
+ line_bytes = len(line.encode("utf-8")) + 2
155
+ self.server.metrics.irc_bytes_received.add(line_bytes, {"direction": "c2s"})
156
+ self.server.metrics.irc_message_size.record(
157
+ line_bytes, {"verb": msg.command, "direction": "c2s"}
158
+ )
169
159
  if msg.command:
170
160
  await self._dispatch(msg)
171
161
  return buffer
172
162
 
173
163
  async def handle(self, initial_msg: str | None = None) -> None:
174
- buffer = ""
175
- if initial_msg:
176
- buffer = initial_msg.replace("\r\n", "\n").replace("\r", "\n")
177
- buffer = await self._process_buffer(buffer)
178
- while True:
179
- data = await self.reader.read(4096)
180
- if not data:
181
- break
182
- buffer += data.decode("utf-8", errors="replace")
183
- # Cap buffer to prevent unbounded memory growth (512 bytes per RFC 2812)
184
- if len(buffer) > 8192:
185
- buffer = buffer[-4096:]
186
- # Normalize all line endings to \n for simpler parsing
187
- buffer = buffer.replace("\r\n", "\n").replace("\r", "\n")
188
- buffer = await self._process_buffer(buffer)
164
+ peer_info = self.writer.get_extra_info("peername")
165
+ remote_addr = f"{peer_info[0]}:{peer_info[1]}" if peer_info else ""
166
+ kind = "human" # Plan 5/6 will refine to bot/harness
167
+ self.server.metrics.clients_connected.add(1, {"kind": kind})
168
+ session_started = time.perf_counter()
169
+ with _otel_trace.get_tracer(_TRACER_NAME).start_as_current_span(
170
+ "irc.client.session",
171
+ attributes={"irc.client.remote_addr": remote_addr},
172
+ ) as span:
173
+ self._session_span = span
174
+ try:
175
+ buffer = ""
176
+ if initial_msg:
177
+ buffer = initial_msg.replace("\r\n", "\n").replace("\r", "\n")
178
+ buffer = await self._process_buffer(buffer)
179
+ while True:
180
+ data = await self.reader.read(4096)
181
+ if not data:
182
+ break
183
+ buffer += data.decode("utf-8", errors="replace")
184
+ # Cap buffer to prevent unbounded memory growth (512 bytes per RFC 2812)
185
+ if len(buffer) > 8192:
186
+ buffer = buffer[-4096:]
187
+ # Normalize all line endings to \n for simpler parsing
188
+ buffer = buffer.replace("\r\n", "\n").replace("\r", "\n")
189
+ buffer = await self._process_buffer(buffer)
190
+ except (ConnectionError, asyncio.IncompleteReadError):
191
+ pass
192
+ finally:
193
+ self.server.metrics.clients_connected.add(-1, {"kind": kind})
194
+ self.server.metrics.client_session_duration.record(
195
+ time.perf_counter() - session_started, {"kind": kind}
196
+ )
189
197
 
190
198
  async def _dispatch(self, msg: Message) -> None:
191
199
  extract = extract_traceparent_from_tags(msg, peer=None)
192
- parent_ctx = None
200
+ self.server.metrics.trace_inbound.add(1, {"result": extract.status, "peer": ""})
193
201
  if extract.status == "valid":
194
- parent_ctx = _context_from_traceparent(extract.traceparent)
202
+ parent_ctx: _OtelContext | None = context_from_traceparent(extract.traceparent)
203
+ else:
204
+ parent_ctx = _OtelContext() # force root: detach from session span
195
205
 
206
+ verb = msg.command.upper()
196
207
  attrs = {
197
- "irc.command": msg.command,
208
+ "irc.command": verb,
198
209
  "irc.prefix_nick": (msg.prefix.split("!")[0] if msg.prefix else ""),
199
210
  "culture.trace.origin": "local" if extract.status == "missing" else "remote",
200
211
  }
@@ -202,8 +213,9 @@ class Client:
202
213
  attrs["culture.trace.dropped_reason"] = extract.status
203
214
 
204
215
  # Per-call get_tracer: test fixture swaps provider between tests.
216
+ cmd_started = time.perf_counter()
205
217
  with _otel_trace.get_tracer(_TRACER_NAME).start_as_current_span(
206
- f"irc.command.{msg.command}",
218
+ f"irc.command.{verb}",
207
219
  context=parent_ctx,
208
220
  attributes=attrs,
209
221
  ):
@@ -223,6 +235,9 @@ class Client:
223
235
  await self.send_numeric(
224
236
  replies.ERR_UNKNOWNCOMMAND, msg.command, "Unknown command"
225
237
  )
238
+ self.server.metrics.client_command_duration.record(
239
+ (time.perf_counter() - cmd_started) * 1000.0, {"verb": verb}
240
+ )
226
241
 
227
242
  async def _handle_ping(self, msg: Message) -> None:
228
243
  token = msg.params[0] if msg.params else ""
@@ -304,6 +319,8 @@ class Client:
304
319
 
305
320
  self.nick = nick
306
321
  self.server.clients[nick] = self
322
+ if self._session_span is not None:
323
+ self._session_span.set_attribute(_ATTR_NICK, nick)
307
324
  await self._try_register()
308
325
 
309
326
  async def _handle_user(self, msg: Message) -> None:
@@ -355,45 +372,49 @@ class Client:
355
372
  return
356
373
 
357
374
  channel_name = msg.params[0]
358
- if not channel_name.startswith("#"):
359
- return
375
+ with _otel_trace.get_tracer(_TRACER_NAME).start_as_current_span(
376
+ "irc.join",
377
+ attributes={_ATTR_CHANNEL: channel_name, _ATTR_NICK: self.nick or ""},
378
+ ):
379
+ if not channel_name.startswith("#"):
380
+ return
360
381
 
361
- # Block joins to archived rooms
362
- existing = self.server.channels.get(channel_name)
363
- if existing and existing.archived:
364
- await self.send(
365
- Message(
366
- prefix=self.server.config.name,
367
- command="NOTICE",
368
- params=[self.nick, f"{channel_name} is archived and cannot be joined"],
382
+ # Block joins to archived rooms
383
+ existing = self.server.channels.get(channel_name)
384
+ if existing and existing.archived:
385
+ await self.send(
386
+ Message(
387
+ prefix=self.server.config.name,
388
+ command="NOTICE",
389
+ params=[self.nick, f"{channel_name} is archived and cannot be joined"],
390
+ )
369
391
  )
370
- )
371
- return
392
+ return
372
393
 
373
- channel = self.server.get_or_create_channel(channel_name)
374
- if self in channel.members:
375
- return
394
+ channel = self.server.get_or_create_channel(channel_name)
395
+ if self in channel.members:
396
+ return
376
397
 
377
- channel.add(self)
378
- self.channels.add(channel)
398
+ channel.add(self)
399
+ self.channels.add(channel)
379
400
 
380
- # Notify all channel members (including self)
381
- join_msg = Message(prefix=self.prefix, command="JOIN", params=[channel_name])
382
- for member in list(channel.members):
383
- await member.send(join_msg)
401
+ # Notify all channel members (including self)
402
+ join_msg = Message(prefix=self.prefix, command="JOIN", params=[channel_name])
403
+ for member in list(channel.members):
404
+ await member.send(join_msg)
384
405
 
385
- # Send topic if set
386
- if channel.topic:
387
- await self.send_numeric(replies.RPL_TOPIC, channel_name, channel.topic)
406
+ # Send topic if set
407
+ if channel.topic:
408
+ await self.send_numeric(replies.RPL_TOPIC, channel_name, channel.topic)
388
409
 
389
- # Send names list
390
- await self._send_names(channel)
410
+ # Send names list
411
+ await self._send_names(channel)
391
412
 
392
- # Emit event AFTER delivering all join-related numerics (topic, NAMES)
393
- # so that the event PRIVMSG doesn't interleave with 353/366 in client buffers.
394
- await self.server.emit_event(
395
- Event(type=EventType.JOIN, channel=channel_name, nick=self.nick)
396
- )
413
+ # Emit event AFTER delivering all join-related numerics (topic, NAMES)
414
+ # so that the event PRIVMSG doesn't interleave with 353/366 in client buffers.
415
+ await self.server.emit_event(
416
+ Event(type=EventType.JOIN, channel=channel_name, nick=self.nick)
417
+ )
397
418
 
398
419
  async def _handle_part(self, msg: Message) -> None:
399
420
  if not msg.params:
@@ -401,36 +422,40 @@ class Client:
401
422
  return
402
423
 
403
424
  channel_name = msg.params[0]
404
- reason = msg.params[1] if len(msg.params) > 1 else ""
425
+ with _otel_trace.get_tracer(_TRACER_NAME).start_as_current_span(
426
+ "irc.part",
427
+ attributes={_ATTR_CHANNEL: channel_name, _ATTR_NICK: self.nick or ""},
428
+ ):
429
+ reason = msg.params[1] if len(msg.params) > 1 else ""
405
430
 
406
- channel = self.server.channels.get(channel_name)
407
- if not channel or self not in channel.members:
408
- await self.send_numeric(
409
- replies.ERR_NOTONCHANNEL,
410
- channel_name,
411
- replies.MSG_NOTONCHANNEL,
412
- )
413
- return
431
+ channel = self.server.channels.get(channel_name)
432
+ if not channel or self not in channel.members:
433
+ await self.send_numeric(
434
+ replies.ERR_NOTONCHANNEL,
435
+ channel_name,
436
+ replies.MSG_NOTONCHANNEL,
437
+ )
438
+ return
414
439
 
415
- part_params = [channel_name, reason] if reason else [channel_name]
416
- part_msg = Message(prefix=self.prefix, command="PART", params=part_params)
417
- for member in list(channel.members):
418
- await member.send(part_msg)
440
+ part_params = [channel_name, reason] if reason else [channel_name]
441
+ part_msg = Message(prefix=self.prefix, command="PART", params=part_params)
442
+ for member in list(channel.members):
443
+ await member.send(part_msg)
419
444
 
420
- await self.server.emit_event(
421
- Event(
422
- type=EventType.PART,
423
- channel=channel_name,
424
- nick=self.nick,
425
- data={"reason": reason},
445
+ await self.server.emit_event(
446
+ Event(
447
+ type=EventType.PART,
448
+ channel=channel_name,
449
+ nick=self.nick,
450
+ data={"reason": reason},
451
+ )
426
452
  )
427
- )
428
453
 
429
- channel.remove(self)
430
- self.channels.discard(channel)
454
+ channel.remove(self)
455
+ self.channels.discard(channel)
431
456
 
432
- if not channel.members and not channel.persistent:
433
- del self.server.channels[channel_name]
457
+ if not channel.members and not channel.persistent:
458
+ del self.server.channels[channel_name]
434
459
 
435
460
  async def _handle_topic(self, msg: Message) -> None:
436
461
  if not msg.params:
@@ -726,6 +751,7 @@ class Client:
726
751
  for member in list(channel.members):
727
752
  if member is not self:
728
753
  await member.send(relay)
754
+ self.server.metrics.privmsg_delivered.add(1, {"kind": "channel", "channel": target})
729
755
  event_data = {"text": text}
730
756
  if is_notice:
731
757
  event_data["notice"] = True
@@ -760,6 +786,7 @@ class Client:
760
786
  )
761
787
  else:
762
788
  await recipient.send(relay)
789
+ self.server.metrics.privmsg_delivered.add(1, {"kind": "dm"})
763
790
  event_data = {"text": text, "target": target}
764
791
  if is_notice:
765
792
  event_data["notice"] = True
@@ -24,6 +24,8 @@ class TelemetryConfig:
24
24
  otlp_compression: str = "gzip" # gzip | none
25
25
  traces_enabled: bool = True
26
26
  traces_sampler: str = "parentbased_always_on"
27
+ metrics_enabled: bool = True
28
+ metrics_export_interval_ms: int = 10000
27
29
 
28
30
 
29
31
  @dataclass
@@ -5,6 +5,7 @@ import asyncio
5
5
  import base64
6
6
  import json
7
7
  import logging
8
+ import time
8
9
  from collections import deque
9
10
  from typing import TYPE_CHECKING
10
11
 
@@ -25,6 +26,9 @@ from culture.protocol.message import Message
25
26
 
26
27
  logger = logging.getLogger(__name__)
27
28
 
29
+ # Span/metric attribute keys defined once so a future rename has one edit point.
30
+ _ATTR_EVENT_TYPE = "event.type"
31
+
28
32
  if TYPE_CHECKING:
29
33
  from culture.agentirc.client import Client
30
34
  from culture.agentirc.remote_client import RemoteClient
@@ -35,10 +39,11 @@ class IRCd:
35
39
  """The culture IRC server."""
36
40
 
37
41
  def __init__(self, config: ServerConfig):
38
- from culture.telemetry import init_telemetry
42
+ from culture.telemetry import init_metrics, init_telemetry
39
43
 
40
44
  self.config = config
41
45
  self.tracer = init_telemetry(config)
46
+ self.metrics = init_metrics(config)
42
47
  self.clients: dict[str, Client | VirtualClient] = {} # nick -> Client
43
48
  self.channels: dict[str, Channel] = {} # name -> Channel
44
49
  self.skills: list[Skill] = []
@@ -178,7 +183,7 @@ class IRCd:
178
183
  # server_link.py).
179
184
  event_type_str = event.type.value if hasattr(event.type, "value") else str(event.type)
180
185
  attrs: dict[str, str] = {
181
- "event.type": event_type_str,
186
+ _ATTR_EVENT_TYPE: event_type_str,
182
187
  "event.origin": "federated" if origin_tag else "local",
183
188
  }
184
189
  if event.channel:
@@ -212,6 +217,12 @@ class IRCd:
212
217
  async def emit_event(self, event: Event) -> None:
213
218
  origin_tag = event.data.get("_origin")
214
219
  attrs = self._build_event_span_attrs(event, origin_tag)
220
+ event_type_str = attrs[_ATTR_EVENT_TYPE]
221
+ origin_str = "federated" if origin_tag else "local"
222
+
223
+ self.metrics.events_emitted.add(1, {_ATTR_EVENT_TYPE: event_type_str, "origin": origin_str})
224
+ render_started = time.perf_counter()
225
+
215
226
  # Per-call get_tracer: the `tracing_exporter` test fixture swaps the
216
227
  # global provider between tests; a cached Tracer would bind to the
217
228
  # first test's provider and stop delivering to later ones.
@@ -226,6 +237,9 @@ class IRCd:
226
237
  await self._dispatch_to_bots(event)
227
238
  await self._surface_event_privmsg(event)
228
239
 
240
+ render_ms = (time.perf_counter() - render_started) * 1000.0
241
+ self.metrics.events_render_duration.record(render_ms, {_ATTR_EVENT_TYPE: event_type_str})
242
+
229
243
  _NO_SURFACE_TYPES = NO_SURFACE_EVENT_TYPES
230
244
 
231
245
  @staticmethod