agentirc-cli 8.1.0__tar.gz → 8.2.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 (425) hide show
  1. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/CHANGELOG.md +11 -0
  2. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/PKG-INFO +5 -1
  3. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/_config.culture.yml +3 -0
  4. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/_data/sites.yml +1 -0
  5. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/_includes/head_custom.html +1 -0
  6. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/agentirc/client.py +200 -74
  7. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/agentirc/config.py +15 -0
  8. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/agentirc/ircd.py +49 -20
  9. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/config.py +10 -0
  10. agentirc_cli-8.2.0/culture/protocol/extensions/tracing.md +31 -0
  11. agentirc_cli-8.2.0/culture/telemetry/__init__.py +22 -0
  12. agentirc_cli-8.2.0/culture/telemetry/context.py +84 -0
  13. agentirc_cli-8.2.0/culture/telemetry/tracing.py +114 -0
  14. agentirc_cli-8.2.0/docs/agentirc/otelcol-template.yaml +25 -0
  15. agentirc_cli-8.2.0/docs/agentirc/telemetry.md +81 -0
  16. agentirc_cli-8.2.0/docs/reference/architecture/index.md +27 -0
  17. agentirc_cli-8.2.0/docs/reference/architecture/subsites.md +111 -0
  18. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/reference/server/config.md +26 -0
  19. agentirc_cli-8.2.0/docs/superpowers/plans/2026-04-24-otel-foundation.md +2159 -0
  20. agentirc_cli-8.2.0/docs/superpowers/specs/2026-04-24-otel-observability-design.md +334 -0
  21. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/pyproject.toml +5 -1
  22. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/sitemap.html +1 -0
  23. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/conftest.py +26 -0
  24. agentirc_cli-8.2.0/tests/telemetry/__init__.py +0 -0
  25. agentirc_cli-8.2.0/tests/telemetry/_fakes.py +26 -0
  26. agentirc_cli-8.2.0/tests/telemetry/test_config.py +26 -0
  27. agentirc_cli-8.2.0/tests/telemetry/test_config_load.py +36 -0
  28. agentirc_cli-8.2.0/tests/telemetry/test_context.py +105 -0
  29. agentirc_cli-8.2.0/tests/telemetry/test_dispatch_span.py +45 -0
  30. agentirc_cli-8.2.0/tests/telemetry/test_emit_event_span.py +42 -0
  31. agentirc_cli-8.2.0/tests/telemetry/test_outbound_inject.py +70 -0
  32. agentirc_cli-8.2.0/tests/telemetry/test_parse_error.py +33 -0
  33. agentirc_cli-8.2.0/tests/telemetry/test_privmsg_span.py +45 -0
  34. agentirc_cli-8.2.0/tests/telemetry/test_server_init.py +29 -0
  35. agentirc_cli-8.2.0/tests/telemetry/test_tracing.py +49 -0
  36. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/uv.lock +185 -1
  37. agentirc_cli-8.1.0/docs/reference/architecture/index.md +0 -13
  38. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/.claude/agents/doc-test-alignment.md +0 -0
  39. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/.claude/skills/pr-review/SKILL.md +0 -0
  40. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/.claude/skills/run-tests/SKILL.md +0 -0
  41. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/.claude/skills/run-tests/scripts/test.sh +0 -0
  42. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/.flake8 +0 -0
  43. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/.github/workflows/docs-check.yml +0 -0
  44. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/.github/workflows/publish.yml +0 -0
  45. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/.github/workflows/security-checks.yml +0 -0
  46. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/.github/workflows/tests.yml +0 -0
  47. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/.gitignore +0 -0
  48. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/.markdownlint-cli2.yaml +0 -0
  49. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/.pr_agent.toml +0 -0
  50. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/.pre-commit-config.yaml +0 -0
  51. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/.pylintrc +0 -0
  52. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/CLAUDE.md +0 -0
  53. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/Gemfile +0 -0
  54. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/Gemfile.lock +0 -0
  55. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/LICENSE +0 -0
  56. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/README.md +0 -0
  57. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/SECURITY.md +0 -0
  58. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/_config.base.yml +0 -0
  59. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/_sass/color_schemes/anthropic.scss +0 -0
  60. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/_sass/color_schemes/dark-terminal.scss +0 -0
  61. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/_sass/custom/custom.scss +0 -0
  62. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/assets/images/IMG_3183.png +0 -0
  63. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/assets/images/apple-touch-icon.png +0 -0
  64. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/assets/images/favicon-16x16.png +0 -0
  65. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/assets/images/favicon-32x32.png +0 -0
  66. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/assets/images/favicon.ico +0 -0
  67. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/assets/images/og-agentirc.png +0 -0
  68. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/assets/images/og-culture.png +0 -0
  69. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/__init__.py +0 -0
  70. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/__main__.py +0 -0
  71. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/agentirc/CLAUDE.md +0 -0
  72. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/agentirc/__init__.py +0 -0
  73. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/agentirc/__main__.py +0 -0
  74. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/agentirc/channel.py +0 -0
  75. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/agentirc/docs/agentirc-architecture.md +0 -0
  76. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/agentirc/docs/agentirc-features.md +0 -0
  77. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/agentirc/docs/agentirc-skill.md +0 -0
  78. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/agentirc/docs/agentirc.md +0 -0
  79. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/agentirc/events.py +0 -0
  80. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/agentirc/history_store.py +0 -0
  81. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/agentirc/remote_client.py +0 -0
  82. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/agentirc/room_store.py +0 -0
  83. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/agentirc/rooms_util.py +0 -0
  84. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/agentirc/server_link.py +0 -0
  85. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/agentirc/skill.py +0 -0
  86. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/agentirc/skills/__init__.py +0 -0
  87. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/agentirc/skills/history.py +0 -0
  88. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/agentirc/skills/icon.py +0 -0
  89. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/agentirc/skills/rooms.py +0 -0
  90. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/agentirc/skills/threads.py +0 -0
  91. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/agentirc/thread_store.py +0 -0
  92. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/aio.py +0 -0
  93. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/bots/__init__.py +0 -0
  94. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/bots/bot.py +0 -0
  95. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/bots/bot_manager.py +0 -0
  96. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/bots/config.py +0 -0
  97. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/bots/filter_dsl.py +0 -0
  98. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/bots/http_listener.py +0 -0
  99. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/bots/system/__init__.py +0 -0
  100. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/bots/system/welcome/__init__.py +0 -0
  101. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/bots/system/welcome/bot.yaml +0 -0
  102. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/bots/system/welcome/handler.py +0 -0
  103. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/bots/template_engine.py +0 -0
  104. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/bots/virtual_client.py +0 -0
  105. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/cli/__init__.py +0 -0
  106. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/cli/_passthrough.py +0 -0
  107. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/cli/afi.py +0 -0
  108. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/cli/agent.py +0 -0
  109. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/cli/bot.py +0 -0
  110. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/cli/channel.py +0 -0
  111. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/cli/devex.py +0 -0
  112. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/cli/introspect.py +0 -0
  113. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/cli/mesh.py +0 -0
  114. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/cli/server.py +0 -0
  115. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/cli/shared/__init__.py +0 -0
  116. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/cli/shared/constants.py +0 -0
  117. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/cli/shared/display.py +0 -0
  118. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/cli/shared/formatting.py +0 -0
  119. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/cli/shared/ipc.py +0 -0
  120. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/cli/shared/mesh.py +0 -0
  121. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/cli/shared/process.py +0 -0
  122. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/cli/skills.py +0 -0
  123. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/__init__.py +0 -0
  124. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/acp/__init__.py +0 -0
  125. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/acp/agent_runner.py +0 -0
  126. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/acp/config.py +0 -0
  127. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/acp/culture.yaml +0 -0
  128. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/acp/daemon.py +0 -0
  129. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/acp/ipc.py +0 -0
  130. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/acp/irc_transport.py +0 -0
  131. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/acp/message_buffer.py +0 -0
  132. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/acp/skill/SKILL.md +0 -0
  133. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/acp/skill/__init__.py +0 -0
  134. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/acp/skill/irc_client.py +0 -0
  135. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/acp/socket_server.py +0 -0
  136. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/acp/supervisor.py +0 -0
  137. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/acp/webhook.py +0 -0
  138. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/claude/__init__.py +0 -0
  139. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/claude/__main__.py +0 -0
  140. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/claude/agent_runner.py +0 -0
  141. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/claude/config.py +0 -0
  142. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/claude/culture.yaml +0 -0
  143. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/claude/daemon.py +0 -0
  144. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/claude/ipc.py +0 -0
  145. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/claude/irc_transport.py +0 -0
  146. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/claude/message_buffer.py +0 -0
  147. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/claude/skill/SKILL.md +0 -0
  148. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/claude/skill/__init__.py +0 -0
  149. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/claude/skill/irc_client.py +0 -0
  150. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/claude/socket_server.py +0 -0
  151. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/claude/supervisor.py +0 -0
  152. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/claude/webhook.py +0 -0
  153. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/codex/__init__.py +0 -0
  154. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/codex/agent_runner.py +0 -0
  155. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/codex/config.py +0 -0
  156. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/codex/culture.yaml +0 -0
  157. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/codex/daemon.py +0 -0
  158. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/codex/ipc.py +0 -0
  159. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/codex/irc_transport.py +0 -0
  160. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/codex/message_buffer.py +0 -0
  161. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/codex/skill/SKILL.md +0 -0
  162. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/codex/skill/__init__.py +0 -0
  163. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/codex/skill/irc_client.py +0 -0
  164. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/codex/socket_server.py +0 -0
  165. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/codex/supervisor.py +0 -0
  166. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/codex/webhook.py +0 -0
  167. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/copilot/__init__.py +0 -0
  168. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/copilot/agent_runner.py +0 -0
  169. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/copilot/config.py +0 -0
  170. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/copilot/culture.yaml +0 -0
  171. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/copilot/daemon.py +0 -0
  172. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/copilot/ipc.py +0 -0
  173. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/copilot/irc_transport.py +0 -0
  174. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/copilot/message_buffer.py +0 -0
  175. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/copilot/skill/SKILL.md +0 -0
  176. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/copilot/skill/__init__.py +0 -0
  177. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/copilot/skill/irc_client.py +0 -0
  178. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/copilot/socket_server.py +0 -0
  179. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/copilot/supervisor.py +0 -0
  180. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/clients/copilot/webhook.py +0 -0
  181. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/console/__init__.py +0 -0
  182. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/console/app.py +0 -0
  183. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/console/client.py +0 -0
  184. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/console/commands.py +0 -0
  185. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/console/status.py +0 -0
  186. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/console/widgets/__init__.py +0 -0
  187. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/console/widgets/chat.py +0 -0
  188. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/console/widgets/info_panel.py +0 -0
  189. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/console/widgets/sidebar.py +0 -0
  190. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/constants.py +0 -0
  191. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/credentials.py +0 -0
  192. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/formatting.py +0 -0
  193. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/learn_prompt.py +0 -0
  194. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/mesh_config.py +0 -0
  195. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/observer.py +0 -0
  196. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/overview/__init__.py +0 -0
  197. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/overview/collector.py +0 -0
  198. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/overview/model.py +0 -0
  199. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/overview/renderer_text.py +0 -0
  200. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/overview/renderer_web.py +0 -0
  201. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/overview/web/style.css +0 -0
  202. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/persistence.py +0 -0
  203. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/pidfile.py +0 -0
  204. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/protocol/__init__.py +0 -0
  205. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/protocol/commands.py +0 -0
  206. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/protocol/extensions/events.md +0 -0
  207. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/protocol/extensions/federation.md +0 -0
  208. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/protocol/extensions/history.md +0 -0
  209. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/protocol/extensions/icons.md +0 -0
  210. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/protocol/extensions/rooms.md +0 -0
  211. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/protocol/extensions/tags.md +0 -0
  212. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/protocol/extensions/threads.md +0 -0
  213. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/protocol/message.py +0 -0
  214. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/protocol/protocol-index.md +0 -0
  215. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/protocol/replies.py +0 -0
  216. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/culture/skills/culture/SKILL.md +0 -0
  217. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/README.md +0 -0
  218. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/agentirc/architecture-overview.md +0 -0
  219. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/agentirc/bots.md +0 -0
  220. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/agentirc/events.md +0 -0
  221. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/agentirc/index.md +0 -0
  222. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/agentirc/why-agentirc.md +0 -0
  223. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/culture/agent-lifecycle.md +0 -0
  224. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/culture/choose-a-harness.md +0 -0
  225. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/culture/features.md +0 -0
  226. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/culture/index.md +0 -0
  227. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/culture/mental-model.md +0 -0
  228. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/culture/operate.md +0 -0
  229. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/culture/patterns.md +0 -0
  230. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/culture/quickstart.md +0 -0
  231. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/culture/reflective-development.md +0 -0
  232. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/culture/vision-patterns-index.md +0 -0
  233. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/culture/vision.md +0 -0
  234. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/culture/what-is-culture.md +0 -0
  235. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/reference/architecture/agent-harness-spec.md +0 -0
  236. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/reference/architecture/layers.md +0 -0
  237. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/reference/architecture/threads.md +0 -0
  238. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/reference/cli/afi.md +0 -0
  239. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/reference/cli/commands.md +0 -0
  240. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/reference/cli/devex.md +0 -0
  241. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/reference/cli/index.md +0 -0
  242. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/reference/console.md +0 -0
  243. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/reference/harnesses/acp.md +0 -0
  244. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/reference/harnesses/claude.md +0 -0
  245. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/reference/harnesses/codex.md +0 -0
  246. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/reference/harnesses/copilot.md +0 -0
  247. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/reference/harnesses/index.md +0 -0
  248. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/reference/index.md +0 -0
  249. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/reference/server/architecture.md +0 -0
  250. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/reference/server/deployment.md +0 -0
  251. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/reference/server/index.md +0 -0
  252. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/reference/server/security.md +0 -0
  253. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/resources/github-copilot-sdk-instructions.md +0 -0
  254. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/resources/positioning.md +0 -0
  255. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/shared/concepts/federation.md +0 -0
  256. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/shared/concepts/harnesses.md +0 -0
  257. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/shared/concepts/humans-and-agents.md +0 -0
  258. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/shared/concepts/index.md +0 -0
  259. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/shared/concepts/persistence.md +0 -0
  260. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/shared/concepts/rooms.md +0 -0
  261. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/shared/demos/magic-demo.md +0 -0
  262. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/shared/guides/first-session.md +0 -0
  263. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/shared/guides/index.md +0 -0
  264. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/shared/guides/join-as-human.md +0 -0
  265. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/shared/guides/local-setup.md +0 -0
  266. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/shared/guides/multi-machine.md +0 -0
  267. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/shared/use-cases/01-pair-programming.md +0 -0
  268. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/shared/use-cases/02-code-review-ensemble.md +0 -0
  269. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/shared/use-cases/03-cross-server-delegation.md +0 -0
  270. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/shared/use-cases/04-knowledge-propagation.md +0 -0
  271. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/shared/use-cases/05-the-observer.md +0 -0
  272. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/shared/use-cases/06-cross-server-ops.md +0 -0
  273. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/shared/use-cases/07-supervisor-intervention.md +0 -0
  274. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/shared/use-cases/08-apps-as-agents.md +0 -0
  275. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/shared/use-cases/09-research-swarm.md +0 -0
  276. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/shared/use-cases/10-agent-lifecycle.md +0 -0
  277. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/shared/use-cases-index.md +0 -0
  278. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/plans/2026-03-19-layer1-core-irc.md +0 -0
  279. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/plans/2026-03-21-layer5-agent-harness.md +0 -0
  280. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/plans/2026-03-30-overview.md +0 -0
  281. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/plans/2026-03-30-rooms-management.md +0 -0
  282. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/plans/2026-04-02-conversation-threads.md +0 -0
  283. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/plans/2026-04-02-ops-tooling.md +0 -0
  284. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/plans/2026-04-04-culture-rename.md +0 -0
  285. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/plans/2026-04-05-docs-speak-culture.md +0 -0
  286. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/plans/2026-04-06-console-chat.md +0 -0
  287. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/plans/2026-04-09-decentralized-agent-config.md +0 -0
  288. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/plans/2026-04-12-console-enhancements.md +0 -0
  289. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/plans/2026-04-15-mesh-events.md +0 -0
  290. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/plans/2026-04-18-culture-dev-positioning.md +0 -0
  291. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/plans/2026-04-22-agex-integration.md +0 -0
  292. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/specs/2026-03-19-agentirc-design.md +0 -0
  293. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/specs/2026-03-21-layer5-agent-harness-design.md +0 -0
  294. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/specs/2026-03-30-overview-design.md +0 -0
  295. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/specs/2026-03-30-rooms-management-design.md +0 -0
  296. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/specs/2026-04-02-conversation-threads-design.md +0 -0
  297. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/specs/2026-04-02-ops-tooling-design.md +0 -0
  298. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/specs/2026-04-03-bots-webhooks-design.md +0 -0
  299. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/specs/2026-04-04-culture-rename-design.md +0 -0
  300. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/specs/2026-04-05-docs-speak-culture-design.md +0 -0
  301. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/specs/2026-04-05-lifecycle-reframe-design.md +0 -0
  302. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/specs/2026-04-06-cli-reorganization-design.md +0 -0
  303. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/specs/2026-04-06-console-chat-design.md +0 -0
  304. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/specs/2026-04-07-entity-archiving-design.md +0 -0
  305. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/specs/2026-04-07-reflective-development-reframe-design.md +0 -0
  306. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/specs/2026-04-08-reflective-development-deepening-design.md +0 -0
  307. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/specs/2026-04-09-decentralized-agent-config-design.md +0 -0
  308. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/specs/2026-04-12-console-enhancements-design.md +0 -0
  309. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/specs/2026-04-15-mesh-events-design.md +0 -0
  310. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/specs/2026-04-17-sites-repositioning-design.md +0 -0
  311. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/specs/2026-04-18-culture-dev-positioning-design.md +0 -0
  312. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/docs/superpowers/specs/2026-04-22-agex-integration-design.md +0 -0
  313. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/favicon.ico +0 -0
  314. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/packages/agent-harness/README.md +0 -0
  315. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/packages/agent-harness/config.py +0 -0
  316. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/packages/agent-harness/culture.yaml +0 -0
  317. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/packages/agent-harness/daemon.py +0 -0
  318. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/packages/agent-harness/ipc.py +0 -0
  319. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/packages/agent-harness/irc_transport.py +0 -0
  320. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/packages/agent-harness/message_buffer.py +0 -0
  321. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/packages/agent-harness/skill/SKILL.md +0 -0
  322. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/packages/agent-harness/skill/irc_client.py +0 -0
  323. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/packages/agent-harness/socket_server.py +0 -0
  324. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/packages/agent-harness/webhook.py +0 -0
  325. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/plugins/claude-code/.claude-plugin/plugin.json +0 -0
  326. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/plugins/claude-code/skills/culture/SKILL.md +0 -0
  327. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/plugins/claude-code/skills/irc/SKILL.md +0 -0
  328. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/plugins/codex/skills/culture-irc/SKILL.md +0 -0
  329. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/robots.txt +0 -0
  330. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/sitemap-agentirc.html +0 -0
  331. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/sitemap-main.html +0 -0
  332. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/sonar-project.properties +0 -0
  333. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/__init__.py +0 -0
  334. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_acp_daemon.py +0 -0
  335. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_agent_runner.py +0 -0
  336. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_archive.py +0 -0
  337. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_bot.py +0 -0
  338. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_bot_config.py +0 -0
  339. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_bot_config_fires_event_toplevel.py +0 -0
  340. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_bot_manager.py +0 -0
  341. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_bots_integration.py +0 -0
  342. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_channel.py +0 -0
  343. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_channel_cli.py +0 -0
  344. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_cli_afi.py +0 -0
  345. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_cli_devex.py +0 -0
  346. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_cli_introspect.py +0 -0
  347. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_cli_passthrough.py +0 -0
  348. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_codex_daemon.py +0 -0
  349. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_connection.py +0 -0
  350. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_console_chat_markdown.py +0 -0
  351. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_console_client.py +0 -0
  352. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_console_commands.py +0 -0
  353. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_console_connection.py +0 -0
  354. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_console_fixes_224_227.py +0 -0
  355. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_console_icons.py +0 -0
  356. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_console_integration.py +0 -0
  357. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_console_status.py +0 -0
  358. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_copilot_daemon.py +0 -0
  359. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_credentials.py +0 -0
  360. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_culture_config.py +0 -0
  361. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_daemon.py +0 -0
  362. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_daemon_config.py +0 -0
  363. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_daemon_ipc.py +0 -0
  364. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_discovery.py +0 -0
  365. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_display.py +0 -0
  366. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_events_basic.py +0 -0
  367. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_events_bot_chain.py +0 -0
  368. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_events_bot_trigger.py +0 -0
  369. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_events_cap_fallback.py +0 -0
  370. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_events_catalog.py +0 -0
  371. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_events_federation.py +0 -0
  372. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_events_history.py +0 -0
  373. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_events_lifecycle.py +0 -0
  374. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_events_reserved_nick.py +0 -0
  375. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_federation.py +0 -0
  376. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_filter_dsl.py +0 -0
  377. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_history.py +0 -0
  378. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_http_listener.py +0 -0
  379. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_integration_layer5.py +0 -0
  380. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_ipc.py +0 -0
  381. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_irc_transport.py +0 -0
  382. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_irc_transport_tags.py +0 -0
  383. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_learn_prompt.py +0 -0
  384. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_link_reconnect.py +0 -0
  385. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_manifest_config.py +0 -0
  386. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_mention_alias.py +0 -0
  387. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_mention_target_cleanup.py +0 -0
  388. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_mention_warning.py +0 -0
  389. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_mentions.py +0 -0
  390. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_mesh_config.py +0 -0
  391. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_mesh_readiness.py +0 -0
  392. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_message.py +0 -0
  393. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_message_buffer.py +0 -0
  394. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_message_tags.py +0 -0
  395. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_messaging.py +0 -0
  396. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_migrate_cli.py +0 -0
  397. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_modes.py +0 -0
  398. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_overview_cli.py +0 -0
  399. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_overview_collector.py +0 -0
  400. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_overview_model.py +0 -0
  401. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_overview_renderer.py +0 -0
  402. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_overview_web.py +0 -0
  403. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_persistence.py +0 -0
  404. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_persistence_timeout.py +0 -0
  405. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_pidfile.py +0 -0
  406. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_poll_loop.py +0 -0
  407. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_register_cli.py +0 -0
  408. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_room_persistence.py +0 -0
  409. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_rooms.py +0 -0
  410. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_rooms_federation.py +0 -0
  411. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_rooms_integration.py +0 -0
  412. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_server_icon_skill.py +0 -0
  413. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_setup_update_cli.py +0 -0
  414. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_skill_client.py +0 -0
  415. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_skill_docs.py +0 -0
  416. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_skills.py +0 -0
  417. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_socket_server.py +0 -0
  418. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_supervisor.py +0 -0
  419. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_template_engine.py +0 -0
  420. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_thread_buffer.py +0 -0
  421. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_threads.py +0 -0
  422. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_virtual_client.py +0 -0
  423. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_wait_for_port.py +0 -0
  424. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_webhook.py +0 -0
  425. {agentirc_cli-8.1.0 → agentirc_cli-8.2.0}/tests/test_welcome_bot.py +0 -0
@@ -4,6 +4,17 @@ 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.2.0] - 2026-04-24
8
+
9
+ ### Added
10
+
11
+ - OpenTelemetry foundation: `culture/telemetry/` package with TracerProvider bootstrap, W3C trace context extract/inject helpers for IRCv3 tags, and `TelemetryConfig` block in `server.yaml`.
12
+ - Protocol extension: `culture.dev/traceparent` and `culture.dev/tracestate` IRCv3 tags (`culture/protocol/extensions/tracing.md`).
13
+ - Server-side tracing: `IRCd.emit_event`, `Client._dispatch`, `Client._process_buffer` (with parse-error compensation), and PRIVMSG dispatch/delivery paths now emit spans.
14
+ - Outbound traceparent injection on `Client.send` and `Client.send_raw` when a span is active.
15
+ - Operator docs at `docs/agentirc/telemetry.md` and starter collector config at `docs/agentirc/otelcol-template.yaml`; `docs/reference/server/config.md` documents the new `telemetry` block.
16
+ - Dependencies: `opentelemetry-api`, `opentelemetry-sdk`, `opentelemetry-exporter-otlp-proto-grpc`, `opentelemetry-semantic-conventions`.
17
+
7
18
  ## [8.1.0] - 2026-04-23
8
19
 
9
20
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentirc-cli
3
- Version: 8.1.0
3
+ Version: 8.2.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
@@ -18,6 +18,10 @@ Requires-Dist: aiohttp>=3.9
18
18
  Requires-Dist: anthropic>=0.40
19
19
  Requires-Dist: claude-agent-sdk>=0.1
20
20
  Requires-Dist: mistune>=3.0
21
+ Requires-Dist: opentelemetry-api>=1.25
22
+ Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.25
23
+ Requires-Dist: opentelemetry-sdk>=1.25
24
+ Requires-Dist: opentelemetry-semantic-conventions>=0.46b0
21
25
  Requires-Dist: pyyaml>=6.0
22
26
  Requires-Dist: textual>=1.0
23
27
  Provides-Extra: copilot
@@ -36,6 +36,8 @@ aux_links:
36
36
  - "/agentirc/"
37
37
  "Agent Experience":
38
38
  - "https://culture.dev/agex/"
39
+ "Agent First Interop":
40
+ - "https://culture.dev/afi/"
39
41
  "Citation CLI":
40
42
  - "/citation-cli/"
41
43
  "GitHub":
@@ -49,5 +51,6 @@ footer_content: >-
49
51
  Inspectable CLI via <a href="/reference/cli/devex/">culture devex</a>
50
52
  (explain / overview / learn at every level).
51
53
  Agent Experience via <a href="https://culture.dev/agex/">agex-cli</a>.
54
+ Agent First Interop via <a href="https://culture.dev/afi/">afi-cli</a>.
52
55
  Code distribution via <a href="/citation-cli/">Citation CLI</a>.
53
56
  Source on <a href="https://github.com/agentculture/culture">GitHub</a>.
@@ -1,3 +1,4 @@
1
+ afi: https://culture.dev/afi/
1
2
  agentirc: https://culture.dev/agentirc/
2
3
  agex: https://culture.dev/agex/
3
4
  citation_cli: https://culture.dev/citation-cli/
@@ -4,4 +4,5 @@
4
4
  <link rel="icon" type="image/png" sizes="16x16" href="{{ '/assets/images/favicon-16x16.png' | relative_url }}">
5
5
  <link rel="apple-touch-icon" sizes="180x180" href="{{ '/assets/images/apple-touch-icon.png' | relative_url }}">
6
6
  <link rel="related" href="{{ site.data.sites.agex }}" title="Agent Experience">
7
+ <link rel="related" href="{{ site.data.sites.afi }}" title="Agent First Interop">
7
8
  <link rel="related" href="{{ site.data.sites.citation_cli }}" title="Citation CLI">
@@ -6,12 +6,59 @@ import logging
6
6
  import re
7
7
  from typing import TYPE_CHECKING
8
8
 
9
+ 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
12
+
9
13
  from culture.agentirc.channel import Channel
10
14
  from culture.agentirc.skill import Event, EventType
11
15
  from culture.aio import maybe_await
12
16
  from culture.constants import SYSTEM_USER_PREFIX
13
17
  from culture.protocol import replies
14
18
  from culture.protocol.message import Message
19
+ from culture.telemetry.context import TRACEPARENT_TAG as _TP_TAG_NAME
20
+ from culture.telemetry.context import (
21
+ extract_traceparent_from_tags,
22
+ )
23
+ from culture.telemetry.context import inject_traceparent as _inject_traceparent
24
+
25
+ # OTEL instrumentation name (must match `_CULTURE_TRACER_NAME` in
26
+ # culture/telemetry/tracing.py so trace consumers see one consistent value).
27
+ _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.
30
+ _ATTR_BODY = "irc.message.body"
31
+ _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
+ )
61
+
15
62
 
16
63
  if TYPE_CHECKING:
17
64
  from culture.agentirc.ircd import IRCd
@@ -45,6 +92,14 @@ class Client:
45
92
  return f"{self.nick}!{self.user}@{self.host}"
46
93
 
47
94
  async def send(self, message: Message) -> None:
95
+ # Only inject trace context for clients that negotiated IRCv3
96
+ # message-tags; otherwise older clients would see an unexpected @-tag
97
+ # block and `send_tagged`'s tag-stripping for non-capable clients
98
+ # would be undone here.
99
+ if "message-tags" in self.caps:
100
+ tp = _current_traceparent()
101
+ if tp is not None:
102
+ _inject_traceparent(message, traceparent=tp, tracestate=None)
48
103
  try:
49
104
  self.writer.write(message.format().encode("utf-8"))
50
105
  await self.writer.drain()
@@ -55,7 +110,15 @@ class Client:
55
110
  """Write a pre-formatted IRC line to the client socket.
56
111
 
57
112
  Appends CRLF internally, matching ServerLink.send_raw convention.
113
+ Injects `culture.dev/traceparent` as an IRCv3 tag when a span is active
114
+ AND the client negotiated the `message-tags` capability.
58
115
  """
116
+ if "message-tags" in self.caps:
117
+ tp = _current_traceparent()
118
+ if tp is not None:
119
+ # send_raw takes a pre-formatted line without an existing tag
120
+ # block; prefix a fresh @tag.
121
+ line = f"@{_TP_TAG_NAME}={tp} {line}"
59
122
  try:
60
123
  self.writer.write(f"{line}\r\n".encode("utf-8"))
61
124
  await self.writer.drain()
@@ -84,13 +147,28 @@ class Client:
84
147
 
85
148
  async def _process_buffer(self, buffer: str) -> str:
86
149
  """Parse and dispatch all complete lines from buffer, return remainder."""
87
- while "\n" in buffer:
88
- line, buffer = buffer.split("\n", 1)
89
- if line.strip():
90
- msg = Message.parse(line)
150
+ # Per-call get_tracer: test fixture swaps provider between tests.
151
+ with _otel_trace.get_tracer(_TRACER_NAME).start_as_current_span(
152
+ "irc.client.process_buffer"
153
+ ) as span:
154
+ while "\n" in buffer:
155
+ line, buffer = buffer.split("\n", 1)
156
+ if not line.strip():
157
+ continue
158
+ try:
159
+ msg = Message.parse(line)
160
+ except Exception as exc: # noqa: BLE001 -- widen for any parser failure
161
+ span.add_event(
162
+ "irc.parse_error",
163
+ attributes={
164
+ "line_preview": line[:64],
165
+ "error": type(exc).__name__,
166
+ },
167
+ )
168
+ continue
91
169
  if msg.command:
92
170
  await self._dispatch(msg)
93
- return buffer
171
+ return buffer
94
172
 
95
173
  async def handle(self, initial_msg: str | None = None) -> None:
96
174
  buffer = ""
@@ -110,20 +188,41 @@ class Client:
110
188
  buffer = await self._process_buffer(buffer)
111
189
 
112
190
  async def _dispatch(self, msg: Message) -> None:
113
- handler = getattr(self, f"_handle_{msg.command.lower()}", None)
114
- if handler:
115
- await maybe_await(handler(msg))
116
- else:
117
- skill = self.server.get_skill_for_command(msg.command)
118
- if skill and self._registered:
119
- try:
120
- await skill.on_command(self, msg)
121
- except Exception:
122
- logging.getLogger(__name__).exception(
123
- "Skill %s failed on command %s", skill.name, msg.command
124
- )
191
+ extract = extract_traceparent_from_tags(msg, peer=None)
192
+ parent_ctx = None
193
+ if extract.status == "valid":
194
+ parent_ctx = _context_from_traceparent(extract.traceparent)
195
+
196
+ attrs = {
197
+ "irc.command": msg.command,
198
+ "irc.prefix_nick": (msg.prefix.split("!")[0] if msg.prefix else ""),
199
+ "culture.trace.origin": "local" if extract.status == "missing" else "remote",
200
+ }
201
+ if extract.status in ("malformed", "too_long"):
202
+ attrs["culture.trace.dropped_reason"] = extract.status
203
+
204
+ # Per-call get_tracer: test fixture swaps provider between tests.
205
+ with _otel_trace.get_tracer(_TRACER_NAME).start_as_current_span(
206
+ f"irc.command.{msg.command}",
207
+ context=parent_ctx,
208
+ attributes=attrs,
209
+ ):
210
+ handler = getattr(self, f"_handle_{msg.command.lower()}", None)
211
+ if handler:
212
+ await maybe_await(handler(msg))
125
213
  else:
126
- await self.send_numeric(replies.ERR_UNKNOWNCOMMAND, msg.command, "Unknown command")
214
+ skill = self.server.get_skill_for_command(msg.command)
215
+ if skill and self._registered:
216
+ try:
217
+ await skill.on_command(self, msg)
218
+ except Exception:
219
+ logging.getLogger(__name__).exception(
220
+ "Skill %s failed on command %s", skill.name, msg.command
221
+ )
222
+ else:
223
+ await self.send_numeric(
224
+ replies.ERR_UNKNOWNCOMMAND, msg.command, "Unknown command"
225
+ )
127
226
 
128
227
  async def _handle_ping(self, msg: Message) -> None:
129
228
  token = msg.params[0] if msg.params else ""
@@ -615,46 +714,64 @@ class Client:
615
714
  await self.send_numeric(replies.RPL_UMODEIS, mode_str)
616
715
 
617
716
  async def _send_to_channel(self, channel, target, relay, text, is_notice):
618
- for member in list(channel.members):
619
- if member is not self:
620
- await member.send(relay)
621
- event_data = {"text": text}
622
- if is_notice:
623
- event_data["notice"] = True
624
- await self.server.emit_event(
625
- Event(
626
- type=EventType.MESSAGE,
627
- channel=target,
628
- nick=self.nick,
629
- data=event_data,
717
+ with _otel_trace.get_tracer(_TRACER_NAME).start_as_current_span(
718
+ "irc.privmsg.deliver.channel",
719
+ attributes={
720
+ "irc.channel": target,
721
+ _ATTR_BODY: text,
722
+ _ATTR_SIZE: len(text),
723
+ "irc.notice": is_notice,
724
+ },
725
+ ):
726
+ for member in list(channel.members):
727
+ if member is not self:
728
+ await member.send(relay)
729
+ event_data = {"text": text}
730
+ if is_notice:
731
+ event_data["notice"] = True
732
+ await self.server.emit_event(
733
+ Event(
734
+ type=EventType.MESSAGE,
735
+ channel=target,
736
+ nick=self.nick,
737
+ data=event_data,
738
+ )
630
739
  )
631
- )
632
740
 
633
741
  async def _send_to_client(self, target, relay, text, is_notice):
634
742
  from culture.agentirc.remote_client import RemoteClient
635
743
 
636
- recipient = self.server.get_client(target)
637
- if not recipient:
638
- return False
639
- if isinstance(recipient, RemoteClient):
640
- s2s_cmd = "SNOTICE" if is_notice else "SMSG"
641
- await recipient.link.send_raw(
642
- f":{self.server.config.name} {s2s_cmd} {target} {self.nick} :{text}"
643
- )
644
- else:
645
- await recipient.send(relay)
646
- event_data = {"text": text, "target": target}
647
- if is_notice:
648
- event_data["notice"] = True
649
- await self.server.emit_event(
650
- Event(
651
- type=EventType.MESSAGE,
652
- channel=None,
653
- nick=self.nick,
654
- data=event_data,
744
+ with _otel_trace.get_tracer(_TRACER_NAME).start_as_current_span(
745
+ "irc.privmsg.deliver.dm",
746
+ attributes={
747
+ "irc.target.nick": target,
748
+ _ATTR_BODY: text,
749
+ _ATTR_SIZE: len(text),
750
+ "irc.notice": is_notice,
751
+ },
752
+ ):
753
+ recipient = self.server.get_client(target)
754
+ if not recipient:
755
+ return False
756
+ if isinstance(recipient, RemoteClient):
757
+ s2s_cmd = "SNOTICE" if is_notice else "SMSG"
758
+ await recipient.link.send_raw(
759
+ f":{self.server.config.name} {s2s_cmd} {target} {self.nick} :{text}"
760
+ )
761
+ else:
762
+ await recipient.send(relay)
763
+ event_data = {"text": text, "target": target}
764
+ if is_notice:
765
+ event_data["notice"] = True
766
+ await self.server.emit_event(
767
+ Event(
768
+ type=EventType.MESSAGE,
769
+ channel=None,
770
+ nick=self.nick,
771
+ data=event_data,
772
+ )
655
773
  )
656
- )
657
- return True
774
+ return True
658
775
 
659
776
  async def _handle_privmsg(self, msg: Message) -> None:
660
777
  if len(msg.params) < 2:
@@ -665,28 +782,37 @@ class Client:
665
782
 
666
783
  target = msg.params[0]
667
784
  text = msg.params[1]
668
- relay = Message(prefix=self.prefix, command="PRIVMSG", params=[target, text])
669
-
670
- if target.startswith("#"):
671
- channel = self.server.channels.get(target)
672
- if not channel:
673
- await self.send_numeric(
674
- replies.ERR_NOSUCHCHANNEL, target, replies.MSG_NOSUCHCHANNEL
675
- )
676
- return
677
- if self not in channel.members:
678
- await self.send_numeric(
679
- replies.ERR_CANNOTSENDTOCHAN, target, "Cannot send to channel"
680
- )
681
- return
682
- await self._send_to_channel(channel, target, relay, text, False)
683
- await self._notify_mentions(target, text)
684
- else:
685
- found = await self._send_to_client(target, relay, text, False)
686
- if not found:
687
- await self.send_numeric(replies.ERR_NOSUCHNICK, target, replies.MSG_NOSUCHNICK)
688
- return
689
- await self._notify_mentions(None, text)
785
+ # Per-call get_tracer: test fixture swaps provider between tests.
786
+ with _otel_trace.get_tracer(_TRACER_NAME).start_as_current_span(
787
+ "irc.privmsg.dispatch",
788
+ attributes={
789
+ "irc.target": target,
790
+ _ATTR_BODY: text,
791
+ _ATTR_SIZE: len(text),
792
+ },
793
+ ):
794
+ relay = Message(prefix=self.prefix, command="PRIVMSG", params=[target, text])
795
+
796
+ if target.startswith("#"):
797
+ channel = self.server.channels.get(target)
798
+ if not channel:
799
+ await self.send_numeric(
800
+ replies.ERR_NOSUCHCHANNEL, target, replies.MSG_NOSUCHCHANNEL
801
+ )
802
+ return
803
+ if self not in channel.members:
804
+ await self.send_numeric(
805
+ replies.ERR_CANNOTSENDTOCHAN, target, "Cannot send to channel"
806
+ )
807
+ return
808
+ await self._send_to_channel(channel, target, relay, text, False)
809
+ await self._notify_mentions(target, text)
810
+ else:
811
+ found = await self._send_to_client(target, relay, text, False)
812
+ if not found:
813
+ await self.send_numeric(replies.ERR_NOSUCHNICK, target, replies.MSG_NOSUCHNICK)
814
+ return
815
+ await self._notify_mentions(None, text)
690
816
 
691
817
  async def _notify_mentions(self, channel_name: str | None, text: str) -> None:
692
818
  from culture.agentirc.remote_client import RemoteClient
@@ -12,6 +12,20 @@ class LinkConfig:
12
12
  trust: str = "full" # "full" or "restricted"
13
13
 
14
14
 
15
+ @dataclass
16
+ class TelemetryConfig:
17
+ """OpenTelemetry settings. Mirrors server.yaml `telemetry:` block."""
18
+
19
+ enabled: bool = False
20
+ service_name: str = "culture.agentirc"
21
+ otlp_endpoint: str = "http://localhost:4317"
22
+ otlp_protocol: str = "grpc" # grpc | http/protobuf (only grpc supported initially)
23
+ otlp_timeout_ms: int = 5000
24
+ otlp_compression: str = "gzip" # gzip | none
25
+ traces_enabled: bool = True
26
+ traces_sampler: str = "parentbased_always_on"
27
+
28
+
15
29
  @dataclass
16
30
  class ServerConfig:
17
31
  """Configuration for a culture server instance."""
@@ -23,3 +37,4 @@ class ServerConfig:
23
37
  data_dir: str = ""
24
38
  links: list[LinkConfig] = field(default_factory=list)
25
39
  system_bots: dict = field(default_factory=dict)
40
+ telemetry: TelemetryConfig = field(default_factory=TelemetryConfig)
@@ -8,6 +8,8 @@ import logging
8
8
  from collections import deque
9
9
  from typing import TYPE_CHECKING
10
10
 
11
+ from opentelemetry import trace as _otel_trace
12
+
11
13
  from culture.agentirc.channel import Channel
12
14
  from culture.agentirc.config import ServerConfig
13
15
  from culture.agentirc.events import NO_SURFACE_EVENT_TYPES, render_event
@@ -33,7 +35,10 @@ class IRCd:
33
35
  """The culture IRC server."""
34
36
 
35
37
  def __init__(self, config: ServerConfig):
38
+ from culture.telemetry import init_telemetry
39
+
36
40
  self.config = config
41
+ self.tracer = init_telemetry(config)
37
42
  self.clients: dict[str, Client | VirtualClient] = {} # nick -> Client
38
43
  self.channels: dict[str, Channel] = {} # name -> Channel
39
44
  self.skills: list[Skill] = []
@@ -166,36 +171,60 @@ class IRCd:
166
171
  self._seq += 1
167
172
  return self._seq
168
173
 
169
- async def emit_event(self, event: Event) -> None:
170
- # 1) Sequence + log.
171
- seq = self.next_seq()
172
- self._event_log.append((seq, event))
174
+ @staticmethod
175
+ def _build_event_span_attrs(event: Event, origin_tag: str | None) -> dict[str, str]:
176
+ # event.type may be an EventType enum OR a plain string — federated
177
+ # events forward unknown types verbatim (see _parse_event_type in
178
+ # server_link.py).
179
+ event_type_str = event.type.value if hasattr(event.type, "value") else str(event.type)
180
+ attrs: dict[str, str] = {
181
+ "event.type": event_type_str,
182
+ "event.origin": "federated" if origin_tag else "local",
183
+ }
184
+ if event.channel:
185
+ attrs["event.channel"] = event.channel
186
+ if origin_tag:
187
+ attrs["culture.federation.peer"] = origin_tag
188
+ return attrs
173
189
 
174
- # 2) Run skill hooks.
190
+ async def _run_skill_hooks(self, event: Event) -> None:
175
191
  for skill in self.skills:
176
192
  try:
177
193
  await skill.on_event(event)
178
194
  except Exception:
179
195
  logger.exception("Skill %s failed on event %s", skill.name, event.type)
180
196
 
181
- # 3) Relay to linked peers — only relay locally-originated events
182
- # (no mesh routing; scope is direct peers only)
183
- if not event.data.get("_origin"):
184
- for peer_name, link in list(self.links.items()):
185
- try:
186
- await link.relay_event(event)
187
- except Exception:
188
- logger.exception("Failed to relay event to %s", peer_name)
189
-
190
- # 4) Dispatch to event-triggered bots.
191
- if self.bot_manager is not None:
197
+ async def _relay_to_peers(self, event: Event) -> None:
198
+ for peer_name, link in list(self.links.items()):
192
199
  try:
193
- await self.bot_manager.on_event(event)
200
+ await link.relay_event(event)
194
201
  except Exception:
195
- logger.exception("bot_manager.on_event failed")
202
+ logger.exception("Failed to relay event to %s", peer_name)
203
+
204
+ async def _dispatch_to_bots(self, event: Event) -> None:
205
+ if self.bot_manager is None:
206
+ return
207
+ try:
208
+ await self.bot_manager.on_event(event)
209
+ except Exception:
210
+ logger.exception("bot_manager.on_event failed")
196
211
 
197
- # 5) Surface as tagged PRIVMSG from system-<server>.
198
- await self._surface_event_privmsg(event)
212
+ async def emit_event(self, event: Event) -> None:
213
+ origin_tag = event.data.get("_origin")
214
+ attrs = self._build_event_span_attrs(event, origin_tag)
215
+ # Per-call get_tracer: the `tracing_exporter` test fixture swaps the
216
+ # global provider between tests; a cached Tracer would bind to the
217
+ # first test's provider and stop delivering to later ones.
218
+ with _otel_trace.get_tracer("culture.agentirc").start_as_current_span(
219
+ "irc.event.emit", attributes=attrs
220
+ ):
221
+ seq = self.next_seq()
222
+ self._event_log.append((seq, event))
223
+ await self._run_skill_hooks(event)
224
+ if not origin_tag:
225
+ await self._relay_to_peers(event)
226
+ await self._dispatch_to_bots(event)
227
+ await self._surface_event_privmsg(event)
199
228
 
200
229
  _NO_SURFACE_TYPES = NO_SURFACE_EVENT_TYPES
201
230
 
@@ -15,6 +15,8 @@ from pathlib import Path
15
15
 
16
16
  import yaml
17
17
 
18
+ from culture.agentirc.config import TelemetryConfig
19
+
18
20
  logger = logging.getLogger("culture")
19
21
 
20
22
 
@@ -98,6 +100,7 @@ class ServerConfig:
98
100
  server: ServerConnConfig = field(default_factory=ServerConnConfig)
99
101
  supervisor: SupervisorConfig = field(default_factory=SupervisorConfig)
100
102
  webhooks: WebhookConfig = field(default_factory=WebhookConfig)
103
+ telemetry: TelemetryConfig = field(default_factory=TelemetryConfig)
101
104
  buffer_size: int = 500
102
105
  poll_interval: int = 60
103
106
  sleep_start: str = "23:00"
@@ -202,6 +205,7 @@ def load_server_config(path: str | Path) -> ServerConfig:
202
205
  server = ServerConnConfig(**raw.get("server", {}))
203
206
  supervisor = SupervisorConfig(**raw.get("supervisor", {}))
204
207
  webhooks = WebhookConfig(**raw.get("webhooks", {}))
208
+ telemetry = TelemetryConfig(**raw.get("telemetry", {}))
205
209
 
206
210
  manifest = raw.get("agents") or {}
207
211
  if not isinstance(manifest, dict):
@@ -211,6 +215,7 @@ def load_server_config(path: str | Path) -> ServerConfig:
211
215
  server=server,
212
216
  supervisor=supervisor,
213
217
  webhooks=webhooks,
218
+ telemetry=telemetry,
214
219
  buffer_size=raw.get("buffer_size", 500),
215
220
  poll_interval=raw.get("poll_interval", 60),
216
221
  sleep_start=raw.get("sleep_start", "23:00"),
@@ -258,6 +263,7 @@ def _load_legacy_config(path: str | Path) -> ServerConfig:
258
263
  server = ServerConnConfig(**raw.get("server", {}))
259
264
  supervisor = SupervisorConfig(**raw.get("supervisor", {}))
260
265
  webhooks = WebhookConfig(**raw.get("webhooks", {}))
266
+ telemetry = TelemetryConfig(**raw.get("telemetry", {}))
261
267
 
262
268
  agents = []
263
269
  known = _KNOWN_AGENT_FIELDS | {"nick", "directory"}
@@ -278,6 +284,7 @@ def _load_legacy_config(path: str | Path) -> ServerConfig:
278
284
  server=server,
279
285
  supervisor=supervisor,
280
286
  webhooks=webhooks,
287
+ telemetry=telemetry,
281
288
  buffer_size=raw.get("buffer_size", 500),
282
289
  poll_interval=raw.get("poll_interval", 60),
283
290
  sleep_start=raw.get("sleep_start", "23:00"),
@@ -354,10 +361,12 @@ def migrate_legacy_to_manifest(path: str | Path) -> ServerConfig:
354
361
  server = ServerConnConfig(**raw.get("server", {}))
355
362
  supervisor = SupervisorConfig(**raw.get("supervisor", {}))
356
363
  webhooks = WebhookConfig(**raw.get("webhooks", {}))
364
+ telemetry = TelemetryConfig(**raw.get("telemetry", {}))
357
365
  config = ServerConfig(
358
366
  server=server,
359
367
  supervisor=supervisor,
360
368
  webhooks=webhooks,
369
+ telemetry=telemetry,
361
370
  buffer_size=raw.get("buffer_size", 500),
362
371
  poll_interval=raw.get("poll_interval", 60),
363
372
  sleep_start=raw.get("sleep_start", "23:00"),
@@ -516,6 +525,7 @@ def save_server_config(path: str | Path, config: ServerConfig) -> None:
516
525
  "server": asdict(config.server),
517
526
  "supervisor": asdict(config.supervisor),
518
527
  "webhooks": asdict(config.webhooks),
528
+ "telemetry": asdict(config.telemetry),
519
529
  "buffer_size": config.buffer_size,
520
530
  "poll_interval": config.poll_interval,
521
531
  "sleep_start": config.sleep_start,
@@ -0,0 +1,31 @@
1
+ # Extension: W3C Trace Context over IRC
2
+
3
+ **Tags:**
4
+
5
+ - `culture.dev/traceparent` — the W3C `traceparent` header value, verbatim (55 chars, format `00-<32-hex-trace-id>-<16-hex-parent-id>-<2-hex-flags>`). MUST match the W3C regex. If malformed, servers MUST silently drop the tag (treat as absent).
6
+ - `culture.dev/tracestate` — the W3C `tracestate` header value (comma-separated `key=value` list, vendor-specific). Optional. MUST be ≤ 512 bytes after IRCv3 unescape. If longer, servers MUST drop the tag.
7
+
8
+ **Scope:** outbound on every client-originated message when a span context is active (PRIVMSG, NOTICE, JOIN, PART, KICK, MODE, SEVENT, SMSG, SNOTICE). On federation relay, the tags are re-injected from the *current* server's span context — they are not copied verbatim from the received message. This produces a parent-per-hop span tree instead of collapsing all hops into one.
9
+
10
+ **Inbound handling.** On every ingress (local client `_dispatch` and S2S `_dispatch`):
11
+
12
+ 1. Tag absent → start a new root span. Attribute: `culture.trace.origin=local`.
13
+ 2. Tag present and valid → start a child span linked to the extracted context. Attributes: `culture.trace.origin=remote`, `culture.federation.peer=<peer>`.
14
+ 3. Tag present but malformed or over the length cap → drop the tag, start a new root span. Attributes: `culture.trace.origin=remote`, `culture.trace.dropped_reason=malformed|too_long`, `culture.federation.peer=<peer>`. Log a rate-limited warning. Increment `culture.trace.inbound{result=malformed|too_long, peer=<peer>}`.
15
+
16
+ **Length caps** (hard-coded, not configurable):
17
+
18
+ - `traceparent`: exactly 55 characters.
19
+ - `tracestate`: ≤ 512 bytes post-unescape.
20
+
21
+ **Non-goal.** Trace context is not authenticated. Federation peer trust is the authorization mechanism; tracing is observability only. An operator who cares about forged trace IDs from upstream peers should rely on peer trust, not on tag validation.
22
+
23
+ **Compat.** Additive IRCv3 tag. Peers that don't recognize the tag pass it through on relay (standard IRCv3 tag behavior). No wire version bump. Project version bumps as a minor per `CLAUDE.md`.
24
+
25
+ **Example.**
26
+
27
+ User on `spark` sends:
28
+
29
+ @culture.dev/traceparent=00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01 PRIVMSG #general :hi
30
+
31
+ Server `spark` starts span `irc.command.PRIVMSG` as a child of the extracted context, relays the event via SEVENT to `thor`, re-injecting its own span's traceparent. Server `thor` sees trace ID `4bf92f35…4736` and starts another child span — the collector stitches both spans into one trace.
@@ -0,0 +1,22 @@
1
+ """OpenTelemetry integration for Culture.
2
+
3
+ Public surface re-exported here; call sites import from `culture.telemetry`.
4
+ """
5
+
6
+ from culture.telemetry.context import (
7
+ TRACEPARENT_TAG,
8
+ TRACESTATE_TAG,
9
+ ExtractResult,
10
+ extract_traceparent_from_tags,
11
+ inject_traceparent,
12
+ )
13
+ from culture.telemetry.tracing import init_telemetry
14
+
15
+ __all__ = [
16
+ "ExtractResult",
17
+ "TRACEPARENT_TAG",
18
+ "TRACESTATE_TAG",
19
+ "extract_traceparent_from_tags",
20
+ "init_telemetry",
21
+ "inject_traceparent",
22
+ ]