ciris-agent 1.7.7__py3-none-any.whl

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 (986) hide show
  1. ciris_adapters/README.md +113 -0
  2. ciris_adapters/__init__.py +30 -0
  3. ciris_adapters/ciris_covenant_metrics/README.md +144 -0
  4. ciris_adapters/ciris_covenant_metrics/__init__.py +36 -0
  5. ciris_adapters/ciris_covenant_metrics/adapter.py +249 -0
  6. ciris_adapters/ciris_covenant_metrics/manifest.json +152 -0
  7. ciris_adapters/ciris_covenant_metrics/services.py +403 -0
  8. ciris_adapters/ciris_hosted_tools/__init__.py +24 -0
  9. ciris_adapters/ciris_hosted_tools/adapter.py +169 -0
  10. ciris_adapters/ciris_hosted_tools/manifest.json +94 -0
  11. ciris_adapters/ciris_hosted_tools/services.py +744 -0
  12. ciris_adapters/external_data_sql/README.md +559 -0
  13. ciris_adapters/external_data_sql/__init__.py +43 -0
  14. ciris_adapters/external_data_sql/adapter.py +144 -0
  15. ciris_adapters/external_data_sql/configurable.py +315 -0
  16. ciris_adapters/external_data_sql/dialects/__init__.py +37 -0
  17. ciris_adapters/external_data_sql/dialects/base.py +133 -0
  18. ciris_adapters/external_data_sql/dialects/mysql.py +63 -0
  19. ciris_adapters/external_data_sql/dialects/postgresql.py +59 -0
  20. ciris_adapters/external_data_sql/dialects/sqlite.py +62 -0
  21. ciris_adapters/external_data_sql/example_config.json +88 -0
  22. ciris_adapters/external_data_sql/example_privacy_schema.yaml +127 -0
  23. ciris_adapters/external_data_sql/manifest.json +195 -0
  24. ciris_adapters/external_data_sql/privacy_schema_loader.py +189 -0
  25. ciris_adapters/external_data_sql/protocol.py +101 -0
  26. ciris_adapters/external_data_sql/schemas.py +146 -0
  27. ciris_adapters/external_data_sql/service.py +1547 -0
  28. ciris_adapters/external_data_sql/service_old.py +492 -0
  29. ciris_adapters/home_assistant/__init__.py +63 -0
  30. ciris_adapters/home_assistant/adapter.py +201 -0
  31. ciris_adapters/home_assistant/communication_service.py +347 -0
  32. ciris_adapters/home_assistant/configurable.py +667 -0
  33. ciris_adapters/home_assistant/manifest.json +203 -0
  34. ciris_adapters/home_assistant/schemas.py +129 -0
  35. ciris_adapters/home_assistant/service.py +751 -0
  36. ciris_adapters/home_assistant/tool_service.py +441 -0
  37. ciris_adapters/mcp_client/__init__.py +82 -0
  38. ciris_adapters/mcp_client/adapter.py +847 -0
  39. ciris_adapters/mcp_client/config.py +280 -0
  40. ciris_adapters/mcp_client/configurable.py +422 -0
  41. ciris_adapters/mcp_client/manifest.json +185 -0
  42. ciris_adapters/mcp_client/mcp_communication_service.py +393 -0
  43. ciris_adapters/mcp_client/mcp_tool_service.py +463 -0
  44. ciris_adapters/mcp_client/mcp_wise_service.py +394 -0
  45. ciris_adapters/mcp_client/schemas.py +149 -0
  46. ciris_adapters/mcp_client/security.py +592 -0
  47. ciris_adapters/mcp_common/__init__.py +44 -0
  48. ciris_adapters/mcp_common/manifest.json +25 -0
  49. ciris_adapters/mcp_common/protocol.py +315 -0
  50. ciris_adapters/mcp_common/schemas.py +225 -0
  51. ciris_adapters/mcp_server/__init__.py +47 -0
  52. ciris_adapters/mcp_server/adapter.py +581 -0
  53. ciris_adapters/mcp_server/config.py +260 -0
  54. ciris_adapters/mcp_server/configurable.py +393 -0
  55. ciris_adapters/mcp_server/handlers.py +663 -0
  56. ciris_adapters/mcp_server/manifest.json +211 -0
  57. ciris_adapters/mcp_server/security.py +500 -0
  58. ciris_adapters/mock_llm/README.md +117 -0
  59. ciris_adapters/mock_llm/__init__.py +21 -0
  60. ciris_adapters/mock_llm/adapter.py +131 -0
  61. ciris_adapters/mock_llm/configurable.py +237 -0
  62. ciris_adapters/mock_llm/manifest.json +106 -0
  63. ciris_adapters/mock_llm/protocol.py +37 -0
  64. ciris_adapters/mock_llm/responses.py +520 -0
  65. ciris_adapters/mock_llm/responses_action_selection.py +1041 -0
  66. ciris_adapters/mock_llm/responses_epistemic.py +17 -0
  67. ciris_adapters/mock_llm/responses_feedback.py +27 -0
  68. ciris_adapters/mock_llm/schemas.py +35 -0
  69. ciris_adapters/mock_llm/service.py +294 -0
  70. ciris_adapters/navigation/__init__.py +21 -0
  71. ciris_adapters/navigation/adapter.py +129 -0
  72. ciris_adapters/navigation/configurable.py +239 -0
  73. ciris_adapters/navigation/manifest.json +104 -0
  74. ciris_adapters/navigation/service.py +487 -0
  75. ciris_adapters/reddit/README.md +132 -0
  76. ciris_adapters/reddit/REDDIT_ADAPTER_ANALYSIS.md +715 -0
  77. ciris_adapters/reddit/REDDIT_ADAPTER_SUMMARY.txt +278 -0
  78. ciris_adapters/reddit/REDDIT_ANALYSIS_INDEX.md +307 -0
  79. ciris_adapters/reddit/REDDIT_PRODUCTION_READINESS_PLAN.md +518 -0
  80. ciris_adapters/reddit/__init__.py +15 -0
  81. ciris_adapters/reddit/adapter.py +189 -0
  82. ciris_adapters/reddit/configurable.py +274 -0
  83. ciris_adapters/reddit/error_handler.py +307 -0
  84. ciris_adapters/reddit/manifest.json +218 -0
  85. ciris_adapters/reddit/observer.py +532 -0
  86. ciris_adapters/reddit/protocol.py +34 -0
  87. ciris_adapters/reddit/schemas.py +433 -0
  88. ciris_adapters/reddit/service.py +1471 -0
  89. ciris_adapters/sample_adapter/README.md +474 -0
  90. ciris_adapters/sample_adapter/__init__.py +45 -0
  91. ciris_adapters/sample_adapter/adapter.py +208 -0
  92. ciris_adapters/sample_adapter/configurable.py +469 -0
  93. ciris_adapters/sample_adapter/manifest.json +247 -0
  94. ciris_adapters/sample_adapter/services.py +486 -0
  95. ciris_adapters/weather/__init__.py +16 -0
  96. ciris_adapters/weather/adapter.py +130 -0
  97. ciris_adapters/weather/configurable.py +240 -0
  98. ciris_adapters/weather/manifest.json +156 -0
  99. ciris_adapters/weather/service.py +600 -0
  100. ciris_agent-1.7.7.dist-info/METADATA +284 -0
  101. ciris_agent-1.7.7.dist-info/RECORD +986 -0
  102. ciris_agent-1.7.7.dist-info/WHEEL +5 -0
  103. ciris_agent-1.7.7.dist-info/entry_points.txt +15 -0
  104. ciris_agent-1.7.7.dist-info/licenses/LICENSE +205 -0
  105. ciris_agent-1.7.7.dist-info/licenses/NOTICE +82 -0
  106. ciris_agent-1.7.7.dist-info/top_level.txt +4 -0
  107. ciris_engine/__init__.py +15 -0
  108. ciris_engine/ciris_templates/ally.yaml +632 -0
  109. ciris_engine/ciris_templates/default.yaml +411 -0
  110. ciris_engine/ciris_templates/echo-core.yaml +629 -0
  111. ciris_engine/ciris_templates/echo-speculative.yaml +764 -0
  112. ciris_engine/ciris_templates/echo.yaml +647 -0
  113. ciris_engine/ciris_templates/sage.yaml +332 -0
  114. ciris_engine/ciris_templates/scout.yaml +338 -0
  115. ciris_engine/ciris_templates/test.yaml +168 -0
  116. ciris_engine/cli.py +42 -0
  117. ciris_engine/config/CIRIS_SERVICES.json +19 -0
  118. ciris_engine/config/MODEL_CAPABILITIES.json +419 -0
  119. ciris_engine/config/PRICING_DATA.json +179 -0
  120. ciris_engine/config/__init__.py +50 -0
  121. ciris_engine/config/ciris_services.py +113 -0
  122. ciris_engine/config/model_capabilities.py +388 -0
  123. ciris_engine/config/pricing_models.py +276 -0
  124. ciris_engine/constants.py +35 -0
  125. ciris_engine/data/__init__.py +1 -0
  126. ciris_engine/data/covenant_1.0b.txt +978 -0
  127. ciris_engine/gui_static/11steps.svg +107 -0
  128. ciris_engine/gui_static/2x-schematics.png +0 -0
  129. ciris_engine/gui_static/404/index.html +1 -0
  130. ciris_engine/gui_static/404.html +1 -0
  131. ciris_engine/gui_static/_next/static/0edhkwDxd5UccTsCmtaBi/_buildManifest.js +1 -0
  132. ciris_engine/gui_static/_next/static/0edhkwDxd5UccTsCmtaBi/_ssgManifest.js +1 -0
  133. ciris_engine/gui_static/_next/static/U-3xTQao7hc2wnAi-Uekm/_buildManifest.js +1 -0
  134. ciris_engine/gui_static/_next/static/U-3xTQao7hc2wnAi-Uekm/_ssgManifest.js +1 -0
  135. ciris_engine/gui_static/_next/static/chunks/3297-60e86ba0f8a7b040.js +1 -0
  136. ciris_engine/gui_static/_next/static/chunks/3835-2aad4b7f5f8e4643.js +1 -0
  137. ciris_engine/gui_static/_next/static/chunks/4499-99a0bc47de0b8975.js +1 -0
  138. ciris_engine/gui_static/_next/static/chunks/4534-af88cd4ba6e99bff.js +1 -0
  139. ciris_engine/gui_static/_next/static/chunks/4541-84b455f9e0dc4cfe.js +1 -0
  140. ciris_engine/gui_static/_next/static/chunks/4789-61412711484754bb.js +1 -0
  141. ciris_engine/gui_static/_next/static/chunks/6539-c6398bc9d7018430.js +1 -0
  142. ciris_engine/gui_static/_next/static/chunks/704-8e827b26cc8c2d32.js +1 -0
  143. ciris_engine/gui_static/_next/static/chunks/704-fb45d630f3192c6f.js +1 -0
  144. ciris_engine/gui_static/_next/static/chunks/8072-de4952a2e6d2b33f.js +1 -0
  145. ciris_engine/gui_static/_next/static/chunks/8315-b91d03a3949db0af.js +1 -0
  146. ciris_engine/gui_static/_next/static/chunks/8386-f93a83ccbd789bd9.js +1 -0
  147. ciris_engine/gui_static/_next/static/chunks/87c73c54-781a7f35148d5433.js +1 -0
  148. ciris_engine/gui_static/_next/static/chunks/8903-fefea3339a02d41b.js +1 -0
  149. ciris_engine/gui_static/_next/static/chunks/9090-e66485adf8d9d990.js +1 -0
  150. ciris_engine/gui_static/_next/static/chunks/app/_not-found/page-a67d9808462c23b1.js +1 -0
  151. ciris_engine/gui_static/_next/static/chunks/app/account/api-keys/page-2d7ee1583bbbd02e.js +1 -0
  152. ciris_engine/gui_static/_next/static/chunks/app/account/api-keys/page-6a3c2bae6fe92b7b.js +1 -0
  153. ciris_engine/gui_static/_next/static/chunks/app/account/consent/page-2ed3a035136bc4e8.js +1 -0
  154. ciris_engine/gui_static/_next/static/chunks/app/account/consent/page-b2f5c91844a32422.js +1 -0
  155. ciris_engine/gui_static/_next/static/chunks/app/account/page-25b90f89af3ea58c.js +1 -0
  156. ciris_engine/gui_static/_next/static/chunks/app/account/page-b65d16c94ecaf69c.js +1 -0
  157. ciris_engine/gui_static/_next/static/chunks/app/account/privacy/page-675b6d05c8f9184f.js +1 -0
  158. ciris_engine/gui_static/_next/static/chunks/app/account/privacy/page-cbee2e1c8ab52145.js +1 -0
  159. ciris_engine/gui_static/_next/static/chunks/app/account/settings/page-0f44da06697cf9f0.js +1 -0
  160. ciris_engine/gui_static/_next/static/chunks/app/account/settings/page-563420253577edbf.js +1 -0
  161. ciris_engine/gui_static/_next/static/chunks/app/adapters/page-1854631018bc32be.js +1 -0
  162. ciris_engine/gui_static/_next/static/chunks/app/agents/page-8353752c176a7c70.js +1 -0
  163. ciris_engine/gui_static/_next/static/chunks/app/agents/page-f61a529f110a6040.js +1 -0
  164. ciris_engine/gui_static/_next/static/chunks/app/api-demo/page-7f19b9d20d39be28.js +1 -0
  165. ciris_engine/gui_static/_next/static/chunks/app/api-demo/page-d1063938f249b8bd.js +1 -0
  166. ciris_engine/gui_static/_next/static/chunks/app/audit/page-321b6728b8fff0bb.js +1 -0
  167. ciris_engine/gui_static/_next/static/chunks/app/audit/page-ebac35ca961a1277.js +1 -0
  168. ciris_engine/gui_static/_next/static/chunks/app/billing/page-6f3dc3bd02924f8e.js +1 -0
  169. ciris_engine/gui_static/_next/static/chunks/app/billing/page-fa4a469f814c821a.js +1 -0
  170. ciris_engine/gui_static/_next/static/chunks/app/comms/page-0d4f734269addd8f.js +1 -0
  171. ciris_engine/gui_static/_next/static/chunks/app/comms/page-79227d426050089c.js +1 -0
  172. ciris_engine/gui_static/_next/static/chunks/app/config/page-018d21d683b6e5bc.js +1 -0
  173. ciris_engine/gui_static/_next/static/chunks/app/config/page-2aa5a5363ca2a371.js +1 -0
  174. ciris_engine/gui_static/_next/static/chunks/app/consent/page-198373205fd316e2.js +1 -0
  175. ciris_engine/gui_static/_next/static/chunks/app/consent/page-f2ca39e7713b13f8.js +1 -0
  176. ciris_engine/gui_static/_next/static/chunks/app/dashboard/page-1dd5a196f643c60d.js +1 -0
  177. ciris_engine/gui_static/_next/static/chunks/app/dashboard/page-530a04d3abbb8cda.js +1 -0
  178. ciris_engine/gui_static/_next/static/chunks/app/docs/page-3193b06d094ab654.js +1 -0
  179. ciris_engine/gui_static/_next/static/chunks/app/docs/page-330e996dedb87aba.js +1 -0
  180. ciris_engine/gui_static/_next/static/chunks/app/layout-0a70f5fc460298b1.js +1 -0
  181. ciris_engine/gui_static/_next/static/chunks/app/layout-21f2f99dd5b336e9.js +1 -0
  182. ciris_engine/gui_static/_next/static/chunks/app/login/page-33240e6c6034a49d.js +1 -0
  183. ciris_engine/gui_static/_next/static/chunks/app/login/page-68ffab6d54a7fdcd.js +1 -0
  184. ciris_engine/gui_static/_next/static/chunks/app/logs/page-8a6167aecc4a475c.js +1 -0
  185. ciris_engine/gui_static/_next/static/chunks/app/memory/page-9ca8c5d0056de3ff.js +1 -0
  186. ciris_engine/gui_static/_next/static/chunks/app/memory/page-e961226941c18f81.js +1 -0
  187. ciris_engine/gui_static/_next/static/chunks/app/page-6fdb065a787a4974.js +1 -0
  188. ciris_engine/gui_static/_next/static/chunks/app/page-89f87d431be6064a.js +1 -0
  189. ciris_engine/gui_static/_next/static/chunks/app/runtime/page-2e728b9c43aa164d.js +1 -0
  190. ciris_engine/gui_static/_next/static/chunks/app/runtime/page-c7dd033dc40a72f0.js +1 -0
  191. ciris_engine/gui_static/_next/static/chunks/app/services/page-ae9f0bdf11d01a95.js +1 -0
  192. ciris_engine/gui_static/_next/static/chunks/app/services/page-b10feb79ca5d75e5.js +1 -0
  193. ciris_engine/gui_static/_next/static/chunks/app/sessions/page-13ebe7ef1c16ae11.js +1 -0
  194. ciris_engine/gui_static/_next/static/chunks/app/sessions/page-e6c82b16d617f785.js +1 -0
  195. ciris_engine/gui_static/_next/static/chunks/app/setup/page-0beb5f5b5a5c20fc.js +1 -0
  196. ciris_engine/gui_static/_next/static/chunks/app/setup/page-2595e729eae30c0e.js +1 -0
  197. ciris_engine/gui_static/_next/static/chunks/app/status-dashboard/page-1037c987aecc3653.js +1 -0
  198. ciris_engine/gui_static/_next/static/chunks/app/status-dashboard/page-2ffd147f6d3162ff.js +1 -0
  199. ciris_engine/gui_static/_next/static/chunks/app/system/page-2c5798d58cafcd91.js +1 -0
  200. ciris_engine/gui_static/_next/static/chunks/app/system/page-505b1ba4eceb01c3.js +1 -0
  201. ciris_engine/gui_static/_next/static/chunks/app/test-auth/page-b0cad31d5cb1b2fa.js +1 -0
  202. ciris_engine/gui_static/_next/static/chunks/app/test-auth/page-f3ecd7a8012df230.js +1 -0
  203. ciris_engine/gui_static/_next/static/chunks/app/test-login/page-f35117fdc4105801.js +1 -0
  204. ciris_engine/gui_static/_next/static/chunks/app/test-login/page-fb583a7924114906.js +1 -0
  205. ciris_engine/gui_static/_next/static/chunks/app/test-sdk/page-50f116fd76935563.js +1 -0
  206. ciris_engine/gui_static/_next/static/chunks/app/test-sdk/page-c37d8aa5ba623a44.js +1 -0
  207. ciris_engine/gui_static/_next/static/chunks/app/tools/page-429aec7a707777ef.js +1 -0
  208. ciris_engine/gui_static/_next/static/chunks/app/tools/page-5f705aad60e0c04e.js +1 -0
  209. ciris_engine/gui_static/_next/static/chunks/app/users/page-13476b8b0f3808cc.js +1 -0
  210. ciris_engine/gui_static/_next/static/chunks/app/users/page-7e500d154ed5bba4.js +1 -0
  211. ciris_engine/gui_static/_next/static/chunks/app/wa/page-cc4a9d8a5cb44d08.js +1 -0
  212. ciris_engine/gui_static/_next/static/chunks/app/wa/page-ec3e429efbc79230.js +1 -0
  213. ciris_engine/gui_static/_next/static/chunks/framework-9d29490f5ba089ba.js +1 -0
  214. ciris_engine/gui_static/_next/static/chunks/main-1f554952e47a82c4.js +1 -0
  215. ciris_engine/gui_static/_next/static/chunks/main-app-26fa8aed029082e5.js +1 -0
  216. ciris_engine/gui_static/_next/static/chunks/main-app-97b0486ef6bcef25.js +1 -0
  217. ciris_engine/gui_static/_next/static/chunks/pages/_app-6ce685456e616eb2.js +1 -0
  218. ciris_engine/gui_static/_next/static/chunks/pages/_error-d4bce98d93fe21e7.js +1 -0
  219. ciris_engine/gui_static/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  220. ciris_engine/gui_static/_next/static/chunks/webpack-fcebd240b7f8477d.js +1 -0
  221. ciris_engine/gui_static/_next/static/css/16b94b1fe0cc6e37.css +3 -0
  222. ciris_engine/gui_static/_next/static/css/77a24ceaae86deff.css +3 -0
  223. ciris_engine/gui_static/_next/static/media/4cf2300e9c8272f7-s.p.woff2 +0 -0
  224. ciris_engine/gui_static/_next/static/media/747892c23ea88013-s.woff2 +0 -0
  225. ciris_engine/gui_static/_next/static/media/8d697b304b401681-s.woff2 +0 -0
  226. ciris_engine/gui_static/_next/static/media/93f479601ee12b01-s.p.woff2 +0 -0
  227. ciris_engine/gui_static/_next/static/media/9610d9e46709d722-s.woff2 +0 -0
  228. ciris_engine/gui_static/_next/static/media/ba015fad6dcf6784-s.woff2 +0 -0
  229. ciris_engine/gui_static/_next/static/media/d8298875641ec7d4-s.p.woff2 +0 -0
  230. ciris_engine/gui_static/account/api-keys/index.html +1 -0
  231. ciris_engine/gui_static/account/api-keys/index.txt +27 -0
  232. ciris_engine/gui_static/account/consent/index.html +1 -0
  233. ciris_engine/gui_static/account/consent/index.txt +27 -0
  234. ciris_engine/gui_static/account/index.html +1 -0
  235. ciris_engine/gui_static/account/index.txt +27 -0
  236. ciris_engine/gui_static/account/privacy/index.html +1 -0
  237. ciris_engine/gui_static/account/privacy/index.txt +27 -0
  238. ciris_engine/gui_static/account/settings/index.html +1 -0
  239. ciris_engine/gui_static/account/settings/index.txt +27 -0
  240. ciris_engine/gui_static/adapters/index.html +1 -0
  241. ciris_engine/gui_static/adapters/index.txt +27 -0
  242. ciris_engine/gui_static/agents/index.html +1 -0
  243. ciris_engine/gui_static/agents/index.txt +27 -0
  244. ciris_engine/gui_static/andrew-roberts-euBRXcx57T4-unsplash.jpg +0 -0
  245. ciris_engine/gui_static/api-demo/index.html +1 -0
  246. ciris_engine/gui_static/api-demo/index.txt +27 -0
  247. ciris_engine/gui_static/audit/index.html +1 -0
  248. ciris_engine/gui_static/audit/index.txt +27 -0
  249. ciris_engine/gui_static/billing/index.html +1 -0
  250. ciris_engine/gui_static/billing/index.txt +27 -0
  251. ciris_engine/gui_static/blurryinfo.png +0 -0
  252. ciris_engine/gui_static/chip-vincent-PkQDwfl9Flc-unsplash.jpg +0 -0
  253. ciris_engine/gui_static/ciris-architecture.svg +338 -0
  254. ciris_engine/gui_static/comms/index.html +1 -0
  255. ciris_engine/gui_static/comms/index.txt +27 -0
  256. ciris_engine/gui_static/config/index.html +1 -0
  257. ciris_engine/gui_static/config/index.txt +27 -0
  258. ciris_engine/gui_static/consent/index.html +1 -0
  259. ciris_engine/gui_static/consent/index.txt +27 -0
  260. ciris_engine/gui_static/dashboard/index.html +1 -0
  261. ciris_engine/gui_static/dashboard/index.txt +27 -0
  262. ciris_engine/gui_static/docs/index.html +1 -0
  263. ciris_engine/gui_static/docs/index.txt +27 -0
  264. ciris_engine/gui_static/eric.png +0 -0
  265. ciris_engine/gui_static/file.svg +1 -0
  266. ciris_engine/gui_static/globe.svg +1 -0
  267. ciris_engine/gui_static/index.html +1 -0
  268. ciris_engine/gui_static/index.txt +27 -0
  269. ciris_engine/gui_static/infogfx-1@2x.png +0 -0
  270. ciris_engine/gui_static/infogfx-2.png +0 -0
  271. ciris_engine/gui_static/infogfx-dark-1.png +0 -0
  272. ciris_engine/gui_static/kelly-vohs-soSTXmIxTDU-unsplash.jpg +0 -0
  273. ciris_engine/gui_static/login/index.html +1 -0
  274. ciris_engine/gui_static/login/index.txt +27 -0
  275. ciris_engine/gui_static/logs/index.html +1 -0
  276. ciris_engine/gui_static/logs/index.txt +27 -0
  277. ciris_engine/gui_static/memory/index.html +1 -0
  278. ciris_engine/gui_static/memory/index.txt +27 -0
  279. ciris_engine/gui_static/nathan-farrish-ArcTfEoBgzs-unsplash.jpg +0 -0
  280. ciris_engine/gui_static/next.svg +1 -0
  281. ciris_engine/gui_static/overview.svg +512 -0
  282. ciris_engine/gui_static/overview1.svg +407 -0
  283. ciris_engine/gui_static/overview2.svg +370 -0
  284. ciris_engine/gui_static/pipeline-visualization.svg +278 -0
  285. ciris_engine/gui_static/privacy-policy.html +160 -0
  286. ciris_engine/gui_static/runtime/index.html +8 -0
  287. ciris_engine/gui_static/runtime/index.txt +27 -0
  288. ciris_engine/gui_static/services/index.html +1 -0
  289. ciris_engine/gui_static/services/index.txt +27 -0
  290. ciris_engine/gui_static/sessions/index.html +1 -0
  291. ciris_engine/gui_static/sessions/index.txt +27 -0
  292. ciris_engine/gui_static/setup/index.html +1 -0
  293. ciris_engine/gui_static/setup/index.txt +27 -0
  294. ciris_engine/gui_static/status-dashboard/index.html +1 -0
  295. ciris_engine/gui_static/status-dashboard/index.txt +27 -0
  296. ciris_engine/gui_static/system/index.html +1 -0
  297. ciris_engine/gui_static/system/index.txt +27 -0
  298. ciris_engine/gui_static/terms-of-service.html +174 -0
  299. ciris_engine/gui_static/test-auth/index.html +1 -0
  300. ciris_engine/gui_static/test-auth/index.txt +27 -0
  301. ciris_engine/gui_static/test-login/index.html +1 -0
  302. ciris_engine/gui_static/test-login/index.txt +27 -0
  303. ciris_engine/gui_static/test-sdk/index.html +1 -0
  304. ciris_engine/gui_static/test-sdk/index.txt +27 -0
  305. ciris_engine/gui_static/tools/index.html +1 -0
  306. ciris_engine/gui_static/tools/index.txt +27 -0
  307. ciris_engine/gui_static/users/index.html +1 -0
  308. ciris_engine/gui_static/users/index.txt +27 -0
  309. ciris_engine/gui_static/vercel.svg +1 -0
  310. ciris_engine/gui_static/videos/video1.mp4 +0 -0
  311. ciris_engine/gui_static/videos/video3.mp4 +0 -0
  312. ciris_engine/gui_static/wa/index.html +1 -0
  313. ciris_engine/gui_static/wa/index.txt +27 -0
  314. ciris_engine/gui_static/window.svg +1 -0
  315. ciris_engine/logic/__init__.py +8 -0
  316. ciris_engine/logic/adapters/__init__.py +74 -0
  317. ciris_engine/logic/adapters/api/__init__.py +5 -0
  318. ciris_engine/logic/adapters/api/adapter.py +1037 -0
  319. ciris_engine/logic/adapters/api/api_communication.py +370 -0
  320. ciris_engine/logic/adapters/api/api_document.py +330 -0
  321. ciris_engine/logic/adapters/api/api_observer.py +24 -0
  322. ciris_engine/logic/adapters/api/api_runtime_control.py +388 -0
  323. ciris_engine/logic/adapters/api/api_tools.py +299 -0
  324. ciris_engine/logic/adapters/api/api_vision.py +215 -0
  325. ciris_engine/logic/adapters/api/app.py +272 -0
  326. ciris_engine/logic/adapters/api/auth.py +159 -0
  327. ciris_engine/logic/adapters/api/config.py +101 -0
  328. ciris_engine/logic/adapters/api/constants.py +55 -0
  329. ciris_engine/logic/adapters/api/dependencies/__init__.py +1 -0
  330. ciris_engine/logic/adapters/api/dependencies/auth.py +260 -0
  331. ciris_engine/logic/adapters/api/endpoints/__init__.py +1 -0
  332. ciris_engine/logic/adapters/api/endpoints/emergency.py +86 -0
  333. ciris_engine/logic/adapters/api/middleware/__init__.py +1 -0
  334. ciris_engine/logic/adapters/api/middleware/rate_limiter.py +302 -0
  335. ciris_engine/logic/adapters/api/models.py +29 -0
  336. ciris_engine/logic/adapters/api/routes/__init__.py +52 -0
  337. ciris_engine/logic/adapters/api/routes/agent.py +1762 -0
  338. ciris_engine/logic/adapters/api/routes/audit.py +707 -0
  339. ciris_engine/logic/adapters/api/routes/auth.py +1745 -0
  340. ciris_engine/logic/adapters/api/routes/billing.py +895 -0
  341. ciris_engine/logic/adapters/api/routes/config.py +329 -0
  342. ciris_engine/logic/adapters/api/routes/connectors.py +534 -0
  343. ciris_engine/logic/adapters/api/routes/consent.py +637 -0
  344. ciris_engine/logic/adapters/api/routes/dsar.py +637 -0
  345. ciris_engine/logic/adapters/api/routes/dsar_multi_source.py +484 -0
  346. ciris_engine/logic/adapters/api/routes/emergency.py +302 -0
  347. ciris_engine/logic/adapters/api/routes/memory.py +733 -0
  348. ciris_engine/logic/adapters/api/routes/memory_filters.py +230 -0
  349. ciris_engine/logic/adapters/api/routes/memory_models.py +112 -0
  350. ciris_engine/logic/adapters/api/routes/memory_queries.py +236 -0
  351. ciris_engine/logic/adapters/api/routes/memory_query_helpers.py +394 -0
  352. ciris_engine/logic/adapters/api/routes/memory_visualization.py +359 -0
  353. ciris_engine/logic/adapters/api/routes/memory_visualization_helpers.py +110 -0
  354. ciris_engine/logic/adapters/api/routes/partnership.py +541 -0
  355. ciris_engine/logic/adapters/api/routes/setup.py +1374 -0
  356. ciris_engine/logic/adapters/api/routes/system.py +3049 -0
  357. ciris_engine/logic/adapters/api/routes/system_extensions.py +952 -0
  358. ciris_engine/logic/adapters/api/routes/telemetry.py +1987 -0
  359. ciris_engine/logic/adapters/api/routes/telemetry_converters.py +141 -0
  360. ciris_engine/logic/adapters/api/routes/telemetry_helpers.py +111 -0
  361. ciris_engine/logic/adapters/api/routes/telemetry_logs_reader.py +280 -0
  362. ciris_engine/logic/adapters/api/routes/telemetry_metrics.py +131 -0
  363. ciris_engine/logic/adapters/api/routes/telemetry_models.py +190 -0
  364. ciris_engine/logic/adapters/api/routes/telemetry_otlp.py +878 -0
  365. ciris_engine/logic/adapters/api/routes/telemetry_resource_helpers.py +191 -0
  366. ciris_engine/logic/adapters/api/routes/tickets.py +541 -0
  367. ciris_engine/logic/adapters/api/routes/tools.py +556 -0
  368. ciris_engine/logic/adapters/api/routes/transparency.py +281 -0
  369. ciris_engine/logic/adapters/api/routes/users.py +981 -0
  370. ciris_engine/logic/adapters/api/routes/verification.py +373 -0
  371. ciris_engine/logic/adapters/api/routes/wa.py +369 -0
  372. ciris_engine/logic/adapters/api/service_configuration.py +177 -0
  373. ciris_engine/logic/adapters/api/services/__init__.py +1 -0
  374. ciris_engine/logic/adapters/api/services/auth_service.py +1417 -0
  375. ciris_engine/logic/adapters/api/services/oauth_security.py +68 -0
  376. ciris_engine/logic/adapters/base.py +141 -0
  377. ciris_engine/logic/adapters/base_adapter.py +73 -0
  378. ciris_engine/logic/adapters/base_observer.py +1141 -0
  379. ciris_engine/logic/adapters/base_vision.py +312 -0
  380. ciris_engine/logic/adapters/cirisnode_client.py +307 -0
  381. ciris_engine/logic/adapters/cli/__init__.py +3 -0
  382. ciris_engine/logic/adapters/cli/adapter.py +207 -0
  383. ciris_engine/logic/adapters/cli/cli_adapter.py +902 -0
  384. ciris_engine/logic/adapters/cli/cli_observer.py +268 -0
  385. ciris_engine/logic/adapters/cli/cli_tools.py +427 -0
  386. ciris_engine/logic/adapters/cli/cli_wa_service.py +134 -0
  387. ciris_engine/logic/adapters/cli/config.py +73 -0
  388. ciris_engine/logic/adapters/discord/__init__.py +3 -0
  389. ciris_engine/logic/adapters/discord/adapter.py +783 -0
  390. ciris_engine/logic/adapters/discord/ciris_discord_client.py +159 -0
  391. ciris_engine/logic/adapters/discord/config.py +177 -0
  392. ciris_engine/logic/adapters/discord/constants.py +185 -0
  393. ciris_engine/logic/adapters/discord/discord-stubs.pyi +50 -0
  394. ciris_engine/logic/adapters/discord/discord_adapter.py +1584 -0
  395. ciris_engine/logic/adapters/discord/discord_audit.py +150 -0
  396. ciris_engine/logic/adapters/discord/discord_channel_manager.py +351 -0
  397. ciris_engine/logic/adapters/discord/discord_connection_manager.py +313 -0
  398. ciris_engine/logic/adapters/discord/discord_embed_formatter.py +369 -0
  399. ciris_engine/logic/adapters/discord/discord_error_classifier.py +302 -0
  400. ciris_engine/logic/adapters/discord/discord_error_handler.py +316 -0
  401. ciris_engine/logic/adapters/discord/discord_guidance_handler.py +460 -0
  402. ciris_engine/logic/adapters/discord/discord_message_handler.py +207 -0
  403. ciris_engine/logic/adapters/discord/discord_observer.py +670 -0
  404. ciris_engine/logic/adapters/discord/discord_rate_limiter.py +249 -0
  405. ciris_engine/logic/adapters/discord/discord_reaction_handler.py +278 -0
  406. ciris_engine/logic/adapters/discord/discord_tool_handler.py +465 -0
  407. ciris_engine/logic/adapters/discord/discord_tool_service.py +790 -0
  408. ciris_engine/logic/adapters/discord/discord_tools.py +90 -0
  409. ciris_engine/logic/adapters/discord/discord_vision_helper.py +148 -0
  410. ciris_engine/logic/adapters/discord/py.typed +0 -0
  411. ciris_engine/logic/adapters/document_parser.py +320 -0
  412. ciris_engine/logic/audit/__init__.py +10 -0
  413. ciris_engine/logic/audit/hash_chain.py +313 -0
  414. ciris_engine/logic/audit/signature_manager.py +352 -0
  415. ciris_engine/logic/audit/verifier.py +408 -0
  416. ciris_engine/logic/buses/__init__.py +21 -0
  417. ciris_engine/logic/buses/base_bus.py +178 -0
  418. ciris_engine/logic/buses/bus_manager.py +121 -0
  419. ciris_engine/logic/buses/communication_bus.py +387 -0
  420. ciris_engine/logic/buses/llm_bus.py +722 -0
  421. ciris_engine/logic/buses/memory_bus.py +577 -0
  422. ciris_engine/logic/buses/prohibitions.py +502 -0
  423. ciris_engine/logic/buses/runtime_control_bus.py +539 -0
  424. ciris_engine/logic/buses/tool_bus.py +482 -0
  425. ciris_engine/logic/buses/wise_bus.py +684 -0
  426. ciris_engine/logic/config/__init__.py +25 -0
  427. ciris_engine/logic/config/bootstrap.py +255 -0
  428. ciris_engine/logic/config/config_accessor.py +202 -0
  429. ciris_engine/logic/config/db_paths.py +194 -0
  430. ciris_engine/logic/config/env_utils.py +39 -0
  431. ciris_engine/logic/conscience/__init__.py +16 -0
  432. ciris_engine/logic/conscience/build_deferral_package.py +0 -0
  433. ciris_engine/logic/conscience/core.py +688 -0
  434. ciris_engine/logic/conscience/interface.py +33 -0
  435. ciris_engine/logic/conscience/registry.py +76 -0
  436. ciris_engine/logic/conscience/thought_depth_guardrail.py +231 -0
  437. ciris_engine/logic/conscience/updated_status_conscience.py +156 -0
  438. ciris_engine/logic/context/__init__.py +10 -0
  439. ciris_engine/logic/context/batch_context.py +550 -0
  440. ciris_engine/logic/context/builder.py +149 -0
  441. ciris_engine/logic/context/channel_resolution.py +136 -0
  442. ciris_engine/logic/context/secrets_snapshot.py +52 -0
  443. ciris_engine/logic/context/system_snapshot.py +116 -0
  444. ciris_engine/logic/context/system_snapshot_helpers.py +1651 -0
  445. ciris_engine/logic/covenant/__init__.py +33 -0
  446. ciris_engine/logic/covenant/executor.py +303 -0
  447. ciris_engine/logic/covenant/extractor.py +382 -0
  448. ciris_engine/logic/covenant/handler.py +241 -0
  449. ciris_engine/logic/covenant/verifier.py +383 -0
  450. ciris_engine/logic/dma/__init__.py +15 -0
  451. ciris_engine/logic/dma/action_selection/__init__.py +11 -0
  452. ciris_engine/logic/dma/action_selection/action_instruction_generator.py +444 -0
  453. ciris_engine/logic/dma/action_selection/context_builder.py +508 -0
  454. ciris_engine/logic/dma/action_selection/faculty_integration.py +193 -0
  455. ciris_engine/logic/dma/action_selection/special_cases.py +132 -0
  456. ciris_engine/logic/dma/action_selection_pdma.py +365 -0
  457. ciris_engine/logic/dma/base_dma.py +335 -0
  458. ciris_engine/logic/dma/csdma.py +239 -0
  459. ciris_engine/logic/dma/dma_executor.py +575 -0
  460. ciris_engine/logic/dma/dsdma_base.py +410 -0
  461. ciris_engine/logic/dma/exceptions.py +4 -0
  462. ciris_engine/logic/dma/factory.py +150 -0
  463. ciris_engine/logic/dma/pdma.py +120 -0
  464. ciris_engine/logic/dma/prompt_loader.py +189 -0
  465. ciris_engine/logic/dma/prompts/action_selection_pdma.yml +58 -0
  466. ciris_engine/logic/dma/prompts/csdma_common_sense.yml +28 -0
  467. ciris_engine/logic/dma/prompts/dsdma_base.yml +17 -0
  468. ciris_engine/logic/dma/prompts/pdma_ethical.yml +42 -0
  469. ciris_engine/logic/formatters/__init__.py +26 -0
  470. ciris_engine/logic/formatters/crisis_resources.py +80 -0
  471. ciris_engine/logic/formatters/escalation.py +21 -0
  472. ciris_engine/logic/formatters/identity.py +224 -0
  473. ciris_engine/logic/formatters/prompt_blocks.py +64 -0
  474. ciris_engine/logic/formatters/system_snapshot.py +193 -0
  475. ciris_engine/logic/formatters/user_profiles.py +108 -0
  476. ciris_engine/logic/handlers/__init__.py +1 -0
  477. ciris_engine/logic/handlers/control/__init__.py +1 -0
  478. ciris_engine/logic/handlers/control/defer_handler.py +195 -0
  479. ciris_engine/logic/handlers/control/ponder_handler.py +154 -0
  480. ciris_engine/logic/handlers/control/reject_handler.py +81 -0
  481. ciris_engine/logic/handlers/external/__init__.py +1 -0
  482. ciris_engine/logic/handlers/external/observe_handler.py +154 -0
  483. ciris_engine/logic/handlers/external/speak_handler.py +250 -0
  484. ciris_engine/logic/handlers/external/tool_handler.py +148 -0
  485. ciris_engine/logic/handlers/memory/__init__.py +1 -0
  486. ciris_engine/logic/handlers/memory/forget_handler.py +107 -0
  487. ciris_engine/logic/handlers/memory/memorize_handler.py +391 -0
  488. ciris_engine/logic/handlers/memory/recall_handler.py +213 -0
  489. ciris_engine/logic/handlers/terminal/__init__.py +1 -0
  490. ciris_engine/logic/handlers/terminal/task_complete_handler.py +299 -0
  491. ciris_engine/logic/infrastructure/__init__.py +1 -0
  492. ciris_engine/logic/infrastructure/handlers/__init__.py +8 -0
  493. ciris_engine/logic/infrastructure/handlers/action_dispatcher.py +382 -0
  494. ciris_engine/logic/infrastructure/handlers/base_handler.py +450 -0
  495. ciris_engine/logic/infrastructure/handlers/exceptions.py +2 -0
  496. ciris_engine/logic/infrastructure/handlers/handler_registry.py +59 -0
  497. ciris_engine/logic/infrastructure/handlers/helpers.py +55 -0
  498. ciris_engine/logic/infrastructure/step_streaming.py +149 -0
  499. ciris_engine/logic/infrastructure/sub_services/__init__.py +1 -0
  500. ciris_engine/logic/infrastructure/sub_services/identity_variance_monitor.py +1035 -0
  501. ciris_engine/logic/infrastructure/sub_services/pattern_analysis_loop.py +758 -0
  502. ciris_engine/logic/infrastructure/sub_services/wa_cli_bootstrap.py +229 -0
  503. ciris_engine/logic/infrastructure/sub_services/wa_cli_display.py +176 -0
  504. ciris_engine/logic/infrastructure/sub_services/wa_cli_oauth.py +404 -0
  505. ciris_engine/logic/infrastructure/sub_services/wa_cli_wizard.py +181 -0
  506. ciris_engine/logic/persistence/__init__.py +130 -0
  507. ciris_engine/logic/persistence/analytics.py +97 -0
  508. ciris_engine/logic/persistence/db/__init__.py +28 -0
  509. ciris_engine/logic/persistence/db/core.py +520 -0
  510. ciris_engine/logic/persistence/db/dialect.py +380 -0
  511. ciris_engine/logic/persistence/db/execution_helpers.py +216 -0
  512. ciris_engine/logic/persistence/db/migration_runner.py +191 -0
  513. ciris_engine/logic/persistence/db/operations.py +313 -0
  514. ciris_engine/logic/persistence/db/query_builder.py +232 -0
  515. ciris_engine/logic/persistence/db/retry.py +154 -0
  516. ciris_engine/logic/persistence/db/setup.py +18 -0
  517. ciris_engine/logic/persistence/migrations/postgres/001_initial_schema.sql +4 -0
  518. ciris_engine/logic/persistence/migrations/postgres/002_add_retry_status.sql +3 -0
  519. ciris_engine/logic/persistence/migrations/postgres/003_add_task_update_tracking.sql +8 -0
  520. ciris_engine/logic/persistence/migrations/postgres/004_add_occurrence_id.sql +54 -0
  521. ciris_engine/logic/persistence/migrations/postgres/005_add_consolidation_locks.sql +22 -0
  522. ciris_engine/logic/persistence/migrations/postgres/006_add_correlation_id_unique_index.sql +16 -0
  523. ciris_engine/logic/persistence/migrations/postgres/007_add_dsar_tickets.sql +39 -0
  524. ciris_engine/logic/persistence/migrations/postgres/008_rename_to_tickets_add_sop.sql +123 -0
  525. ciris_engine/logic/persistence/migrations/postgres/009_add_ticket_status_columns.sql +39 -0
  526. ciris_engine/logic/persistence/migrations/postgres/010_add_images_to_tasks.sql +5 -0
  527. ciris_engine/logic/persistence/migrations/sqlite/001_initial_schema.sql +357 -0
  528. ciris_engine/logic/persistence/migrations/sqlite/002_add_retry_status.sql +3 -0
  529. ciris_engine/logic/persistence/migrations/sqlite/003_add_task_update_tracking.sql +8 -0
  530. ciris_engine/logic/persistence/migrations/sqlite/004_add_occurrence_id.sql +45 -0
  531. ciris_engine/logic/persistence/migrations/sqlite/005_add_consolidation_locks.sql +22 -0
  532. ciris_engine/logic/persistence/migrations/sqlite/006_add_correlation_id_unique_index.sql +16 -0
  533. ciris_engine/logic/persistence/migrations/sqlite/007_add_dsar_tickets.sql +39 -0
  534. ciris_engine/logic/persistence/migrations/sqlite/008_rename_to_tickets_add_sop.sql +120 -0
  535. ciris_engine/logic/persistence/migrations/sqlite/009_add_ticket_status_columns.sql +129 -0
  536. ciris_engine/logic/persistence/migrations/sqlite/010_add_images_to_tasks.sql +17 -0
  537. ciris_engine/logic/persistence/models/__init__.py +141 -0
  538. ciris_engine/logic/persistence/models/correlations.py +881 -0
  539. ciris_engine/logic/persistence/models/deferral.py +68 -0
  540. ciris_engine/logic/persistence/models/dsar.py +286 -0
  541. ciris_engine/logic/persistence/models/graph.py +362 -0
  542. ciris_engine/logic/persistence/models/identity.py +264 -0
  543. ciris_engine/logic/persistence/models/queue_status.py +139 -0
  544. ciris_engine/logic/persistence/models/tasks.py +1043 -0
  545. ciris_engine/logic/persistence/models/thoughts.py +400 -0
  546. ciris_engine/logic/persistence/models/tickets.py +518 -0
  547. ciris_engine/logic/persistence/stores/__init__.py +13 -0
  548. ciris_engine/logic/persistence/stores/auth_helpers.py +117 -0
  549. ciris_engine/logic/persistence/stores/authentication_store.py +414 -0
  550. ciris_engine/logic/persistence/utils.py +212 -0
  551. ciris_engine/logic/processors/__init__.py +30 -0
  552. ciris_engine/logic/processors/core/__init__.py +1 -0
  553. ciris_engine/logic/processors/core/base_processor.py +280 -0
  554. ciris_engine/logic/processors/core/main_processor.py +1777 -0
  555. ciris_engine/logic/processors/core/step_decorators.py +1583 -0
  556. ciris_engine/logic/processors/core/thought_processor/__init__.py +20 -0
  557. ciris_engine/logic/processors/core/thought_processor/action_execution.py +49 -0
  558. ciris_engine/logic/processors/core/thought_processor/conscience_execution.py +382 -0
  559. ciris_engine/logic/processors/core/thought_processor/finalize_action.py +66 -0
  560. ciris_engine/logic/processors/core/thought_processor/gather_context.py +120 -0
  561. ciris_engine/logic/processors/core/thought_processor/main.py +920 -0
  562. ciris_engine/logic/processors/core/thought_processor/perform_aspdma.py +86 -0
  563. ciris_engine/logic/processors/core/thought_processor/perform_dmas.py +106 -0
  564. ciris_engine/logic/processors/core/thought_processor/recursive_processing.py +237 -0
  565. ciris_engine/logic/processors/core/thought_processor/round_complete.py +52 -0
  566. ciris_engine/logic/processors/core/thought_processor/start_round.py +64 -0
  567. ciris_engine/logic/processors/exceptions.py +59 -0
  568. ciris_engine/logic/processors/states/__init__.py +1 -0
  569. ciris_engine/logic/processors/states/dream_processor.py +1381 -0
  570. ciris_engine/logic/processors/states/play_processor.py +141 -0
  571. ciris_engine/logic/processors/states/shutdown_processor.py +623 -0
  572. ciris_engine/logic/processors/states/solitude_processor.py +305 -0
  573. ciris_engine/logic/processors/states/wakeup_processor.py +802 -0
  574. ciris_engine/logic/processors/states/work_processor.py +742 -0
  575. ciris_engine/logic/processors/support/__init__.py +1 -0
  576. ciris_engine/logic/processors/support/dma_orchestrator.py +336 -0
  577. ciris_engine/logic/processors/support/processing_queue.py +133 -0
  578. ciris_engine/logic/processors/support/shutdown_condition_evaluator.py +294 -0
  579. ciris_engine/logic/processors/support/state_manager.py +358 -0
  580. ciris_engine/logic/processors/support/task_manager.py +303 -0
  581. ciris_engine/logic/processors/support/thought_escalation.py +116 -0
  582. ciris_engine/logic/processors/support/thought_manager.py +328 -0
  583. ciris_engine/logic/processors/support/thought_manager_enhanced.py +105 -0
  584. ciris_engine/logic/registries/__init__.py +34 -0
  585. ciris_engine/logic/registries/base.py +653 -0
  586. ciris_engine/logic/registries/circuit_breaker.py +275 -0
  587. ciris_engine/logic/registries/typed_registries.py +184 -0
  588. ciris_engine/logic/runtime/__init__.py +7 -0
  589. ciris_engine/logic/runtime/adapter_loader.py +261 -0
  590. ciris_engine/logic/runtime/adapter_manager.py +1053 -0
  591. ciris_engine/logic/runtime/ciris_runtime.py +2342 -0
  592. ciris_engine/logic/runtime/ciris_runtime_helpers.py +923 -0
  593. ciris_engine/logic/runtime/component_builder.py +361 -0
  594. ciris_engine/logic/runtime/identity_manager.py +219 -0
  595. ciris_engine/logic/runtime/module_loader.py +207 -0
  596. ciris_engine/logic/runtime/prevent_sideeffects.py +30 -0
  597. ciris_engine/logic/runtime/runtime_interface.py +23 -0
  598. ciris_engine/logic/runtime/service_initializer.py +1623 -0
  599. ciris_engine/logic/secrets/__init__.py +30 -0
  600. ciris_engine/logic/secrets/encryption.py +175 -0
  601. ciris_engine/logic/secrets/filter.py +295 -0
  602. ciris_engine/logic/secrets/service.py +652 -0
  603. ciris_engine/logic/secrets/store.py +669 -0
  604. ciris_engine/logic/services/__init__.py +1 -0
  605. ciris_engine/logic/services/adaptation/__init__.py +3 -0
  606. ciris_engine/logic/services/base_graph_service.py +142 -0
  607. ciris_engine/logic/services/base_infrastructure_service.py +69 -0
  608. ciris_engine/logic/services/base_scheduled_service.py +136 -0
  609. ciris_engine/logic/services/base_service.py +247 -0
  610. ciris_engine/logic/services/governance/__init__.py +3 -0
  611. ciris_engine/logic/services/governance/adaptive_filter/__init__.py +14 -0
  612. ciris_engine/logic/services/governance/adaptive_filter/service.py +818 -0
  613. ciris_engine/logic/services/governance/consent/__init__.py +53 -0
  614. ciris_engine/logic/services/governance/consent/air.py +403 -0
  615. ciris_engine/logic/services/governance/consent/decay.py +324 -0
  616. ciris_engine/logic/services/governance/consent/dsar_automation.py +589 -0
  617. ciris_engine/logic/services/governance/consent/exceptions.py +106 -0
  618. ciris_engine/logic/services/governance/consent/metrics.py +270 -0
  619. ciris_engine/logic/services/governance/consent/partnership.py +533 -0
  620. ciris_engine/logic/services/governance/consent/service.py +1256 -0
  621. ciris_engine/logic/services/governance/dsar/__init__.py +29 -0
  622. ciris_engine/logic/services/governance/dsar/orchestrator.py +977 -0
  623. ciris_engine/logic/services/governance/dsar/schemas.py +141 -0
  624. ciris_engine/logic/services/governance/dsar/signature_service.py +283 -0
  625. ciris_engine/logic/services/governance/self_observation/__init__.py +20 -0
  626. ciris_engine/logic/services/governance/self_observation/service.py +1153 -0
  627. ciris_engine/logic/services/governance/visibility/__init__.py +17 -0
  628. ciris_engine/logic/services/governance/visibility/service.py +512 -0
  629. ciris_engine/logic/services/governance/wise_authority/__init__.py +15 -0
  630. ciris_engine/logic/services/governance/wise_authority/service.py +827 -0
  631. ciris_engine/logic/services/graph/__init__.py +5 -0
  632. ciris_engine/logic/services/graph/audit_service/__init__.py +5 -0
  633. ciris_engine/logic/services/graph/audit_service/service.py +1675 -0
  634. ciris_engine/logic/services/graph/base.py +208 -0
  635. ciris_engine/logic/services/graph/config_service/__init__.py +5 -0
  636. ciris_engine/logic/services/graph/config_service/service.py +372 -0
  637. ciris_engine/logic/services/graph/incident_service/__init__.py +5 -0
  638. ciris_engine/logic/services/graph/incident_service/service.py +803 -0
  639. ciris_engine/logic/services/graph/memory_service.py +1120 -0
  640. ciris_engine/logic/services/graph/telemetry_service/__init__.py +5 -0
  641. ciris_engine/logic/services/graph/telemetry_service/exceptions.py +104 -0
  642. ciris_engine/logic/services/graph/telemetry_service/helpers.py +1337 -0
  643. ciris_engine/logic/services/graph/telemetry_service/service.py +2429 -0
  644. ciris_engine/logic/services/graph/tsdb_consolidation/__init__.py +17 -0
  645. ciris_engine/logic/services/graph/tsdb_consolidation/aggregation_helpers.py +355 -0
  646. ciris_engine/logic/services/graph/tsdb_consolidation/cleanup_helpers.py +438 -0
  647. ciris_engine/logic/services/graph/tsdb_consolidation/compressor.py +260 -0
  648. ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/__init__.py +27 -0
  649. ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/audit.py +326 -0
  650. ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/conversation.py +291 -0
  651. ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/memory.py +197 -0
  652. ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/metrics.py +251 -0
  653. ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/task.py +257 -0
  654. ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/trace.py +363 -0
  655. ciris_engine/logic/services/graph/tsdb_consolidation/data_converter.py +545 -0
  656. ciris_engine/logic/services/graph/tsdb_consolidation/date_calculation_helpers.py +193 -0
  657. ciris_engine/logic/services/graph/tsdb_consolidation/db_query_helpers.py +296 -0
  658. ciris_engine/logic/services/graph/tsdb_consolidation/edge_helpers.py +92 -0
  659. ciris_engine/logic/services/graph/tsdb_consolidation/edge_manager.py +896 -0
  660. ciris_engine/logic/services/graph/tsdb_consolidation/extensive_helpers.py +322 -0
  661. ciris_engine/logic/services/graph/tsdb_consolidation/period_manager.py +152 -0
  662. ciris_engine/logic/services/graph/tsdb_consolidation/profound_helpers.py +277 -0
  663. ciris_engine/logic/services/graph/tsdb_consolidation/query_manager.py +812 -0
  664. ciris_engine/logic/services/graph/tsdb_consolidation/service.py +1692 -0
  665. ciris_engine/logic/services/graph/tsdb_consolidation/sql_builders.py +363 -0
  666. ciris_engine/logic/services/infrastructure/__init__.py +1 -0
  667. ciris_engine/logic/services/infrastructure/authentication/__init__.py +5 -0
  668. ciris_engine/logic/services/infrastructure/authentication/service.py +1634 -0
  669. ciris_engine/logic/services/infrastructure/database_maintenance/__init__.py +15 -0
  670. ciris_engine/logic/services/infrastructure/database_maintenance/service.py +764 -0
  671. ciris_engine/logic/services/infrastructure/resource_monitor/__init__.py +7 -0
  672. ciris_engine/logic/services/infrastructure/resource_monitor/ciris_billing_provider.py +755 -0
  673. ciris_engine/logic/services/infrastructure/resource_monitor/service.py +409 -0
  674. ciris_engine/logic/services/infrastructure/resource_monitor/simple_credit_provider.py +129 -0
  675. ciris_engine/logic/services/lifecycle/__init__.py +3 -0
  676. ciris_engine/logic/services/lifecycle/initialization/__init__.py +10 -0
  677. ciris_engine/logic/services/lifecycle/initialization/service.py +312 -0
  678. ciris_engine/logic/services/lifecycle/scheduler/__init__.py +5 -0
  679. ciris_engine/logic/services/lifecycle/scheduler/service.py +607 -0
  680. ciris_engine/logic/services/lifecycle/shutdown/__init__.py +9 -0
  681. ciris_engine/logic/services/lifecycle/shutdown/service.py +378 -0
  682. ciris_engine/logic/services/lifecycle/time/__init__.py +15 -0
  683. ciris_engine/logic/services/lifecycle/time/service.py +259 -0
  684. ciris_engine/logic/services/memory_service/__init__.py +8 -0
  685. ciris_engine/logic/services/mixins/__init__.py +13 -0
  686. ciris_engine/logic/services/mixins/example_usage.py +200 -0
  687. ciris_engine/logic/services/mixins/request_metrics.py +179 -0
  688. ciris_engine/logic/services/runtime/__init__.py +3 -0
  689. ciris_engine/logic/services/runtime/adapter_configuration/__init__.py +16 -0
  690. ciris_engine/logic/services/runtime/adapter_configuration/service.py +674 -0
  691. ciris_engine/logic/services/runtime/adapter_configuration/session.py +67 -0
  692. ciris_engine/logic/services/runtime/control_service/__init__.py +5 -0
  693. ciris_engine/logic/services/runtime/control_service/service.py +2269 -0
  694. ciris_engine/logic/services/runtime/llm_service/__init__.py +14 -0
  695. ciris_engine/logic/services/runtime/llm_service/pricing_calculator.py +279 -0
  696. ciris_engine/logic/services/runtime/llm_service/service.py +930 -0
  697. ciris_engine/logic/services/tools/__init__.py +5 -0
  698. ciris_engine/logic/services/tools/core_tool_service/__init__.py +8 -0
  699. ciris_engine/logic/services/tools/core_tool_service/service.py +852 -0
  700. ciris_engine/logic/setup/__init__.py +1 -0
  701. ciris_engine/logic/setup/first_run.py +250 -0
  702. ciris_engine/logic/setup/wizard.py +327 -0
  703. ciris_engine/logic/telemetry/__init__.py +46 -0
  704. ciris_engine/logic/telemetry/core.py +239 -0
  705. ciris_engine/logic/telemetry/hot_cold_config.py +133 -0
  706. ciris_engine/logic/telemetry/log_collector.py +190 -0
  707. ciris_engine/logic/telemetry/resource_monitor.py +7 -0
  708. ciris_engine/logic/telemetry/security.py +79 -0
  709. ciris_engine/logic/utils/__init__.py +18 -0
  710. ciris_engine/logic/utils/channel_utils.py +75 -0
  711. ciris_engine/logic/utils/consent/__init__.py +1 -0
  712. ciris_engine/logic/utils/consent/partnership_utils.py +172 -0
  713. ciris_engine/logic/utils/constants.py +92 -0
  714. ciris_engine/logic/utils/context_utils.py +145 -0
  715. ciris_engine/logic/utils/directory_setup.py +533 -0
  716. ciris_engine/logic/utils/graphql_context_provider.py +152 -0
  717. ciris_engine/logic/utils/identity_resolution.py +843 -0
  718. ciris_engine/logic/utils/incident_capture_handler.py +303 -0
  719. ciris_engine/logic/utils/initialization_manager.py +74 -0
  720. ciris_engine/logic/utils/jsondict_helpers.py +290 -0
  721. ciris_engine/logic/utils/log_sanitizer.py +97 -0
  722. ciris_engine/logic/utils/logging_config.py +151 -0
  723. ciris_engine/logic/utils/observability_decorators.py +544 -0
  724. ciris_engine/logic/utils/occurrence_utils.py +155 -0
  725. ciris_engine/logic/utils/path_resolution.py +281 -0
  726. ciris_engine/logic/utils/platform_detection.py +286 -0
  727. ciris_engine/logic/utils/privacy.py +266 -0
  728. ciris_engine/logic/utils/profile_loader.py +124 -0
  729. ciris_engine/logic/utils/profile_manager.py +16 -0
  730. ciris_engine/logic/utils/runtime_utils.py +69 -0
  731. ciris_engine/logic/utils/shutdown_manager.py +107 -0
  732. ciris_engine/logic/utils/task_formatters.py +60 -0
  733. ciris_engine/logic/utils/task_thought_factory.py +404 -0
  734. ciris_engine/logic/utils/thought_utils.py +54 -0
  735. ciris_engine/logic/utils/user_utils.py +70 -0
  736. ciris_engine/protocols/__init__.py +0 -0
  737. ciris_engine/protocols/adapters/__init__.py +35 -0
  738. ciris_engine/protocols/adapters/base.py +149 -0
  739. ciris_engine/protocols/adapters/configurable.py +265 -0
  740. ciris_engine/protocols/adapters/message.py +90 -0
  741. ciris_engine/protocols/audit/__init__.py +1 -0
  742. ciris_engine/protocols/buses/__init__.py +1 -0
  743. ciris_engine/protocols/config/__init__.py +1 -0
  744. ciris_engine/protocols/conscience/__init__.py +1 -0
  745. ciris_engine/protocols/consent.py +88 -0
  746. ciris_engine/protocols/context/__init__.py +1 -0
  747. ciris_engine/protocols/data/__init__.py +1 -0
  748. ciris_engine/protocols/dma/__init__.py +1 -0
  749. ciris_engine/protocols/dma/base.py +107 -0
  750. ciris_engine/protocols/faculties.py +34 -0
  751. ciris_engine/protocols/formatters/__init__.py +1 -0
  752. ciris_engine/protocols/handlers/__init__.py +1 -0
  753. ciris_engine/protocols/infrastructure/__init__.py +25 -0
  754. ciris_engine/protocols/infrastructure/base.py +377 -0
  755. ciris_engine/protocols/persistence/__init__.py +1 -0
  756. ciris_engine/protocols/pipeline_control.py +609 -0
  757. ciris_engine/protocols/processors/__init__.py +19 -0
  758. ciris_engine/protocols/processors/agent.py +299 -0
  759. ciris_engine/protocols/processors/base.py +130 -0
  760. ciris_engine/protocols/processors/orchestration.py +62 -0
  761. ciris_engine/protocols/registries/__init__.py +1 -0
  762. ciris_engine/protocols/runtime/__init__.py +1 -0
  763. ciris_engine/protocols/runtime/base.py +163 -0
  764. ciris_engine/protocols/secrets/__init__.py +1 -0
  765. ciris_engine/protocols/services/__init__.py +80 -0
  766. ciris_engine/protocols/services/adaptation/__init__.py +7 -0
  767. ciris_engine/protocols/services/adaptation/self_observation.py +265 -0
  768. ciris_engine/protocols/services/governance/__init__.py +20 -0
  769. ciris_engine/protocols/services/governance/communication.py +58 -0
  770. ciris_engine/protocols/services/governance/filter.py +56 -0
  771. ciris_engine/protocols/services/governance/visibility.py +32 -0
  772. ciris_engine/protocols/services/governance/wa_auth.py +192 -0
  773. ciris_engine/protocols/services/governance/wise_authority.py +75 -0
  774. ciris_engine/protocols/services/graph/__init__.py +19 -0
  775. ciris_engine/protocols/services/graph/audit.py +92 -0
  776. ciris_engine/protocols/services/graph/config.py +54 -0
  777. ciris_engine/protocols/services/graph/incident_management.py +103 -0
  778. ciris_engine/protocols/services/graph/memory.py +110 -0
  779. ciris_engine/protocols/services/graph/telemetry.py +51 -0
  780. ciris_engine/protocols/services/graph/tsdb_consolidation.py +87 -0
  781. ciris_engine/protocols/services/infrastructure/__init__.py +11 -0
  782. ciris_engine/protocols/services/infrastructure/authentication.py +159 -0
  783. ciris_engine/protocols/services/infrastructure/credit_gate.py +46 -0
  784. ciris_engine/protocols/services/infrastructure/database_maintenance.py +25 -0
  785. ciris_engine/protocols/services/infrastructure/resource_monitor.py +83 -0
  786. ciris_engine/protocols/services/lifecycle/__init__.py +13 -0
  787. ciris_engine/protocols/services/lifecycle/initialization.py +41 -0
  788. ciris_engine/protocols/services/lifecycle/scheduler.py +42 -0
  789. ciris_engine/protocols/services/lifecycle/shutdown.py +50 -0
  790. ciris_engine/protocols/services/lifecycle/time.py +31 -0
  791. ciris_engine/protocols/services/runtime/__init__.py +13 -0
  792. ciris_engine/protocols/services/runtime/llm.py +50 -0
  793. ciris_engine/protocols/services/runtime/runtime_control.py +193 -0
  794. ciris_engine/protocols/services/runtime/secrets.py +100 -0
  795. ciris_engine/protocols/services/runtime/tool.py +123 -0
  796. ciris_engine/protocols/telemetry/__init__.py +1 -0
  797. ciris_engine/protocols/utils/__init__.py +1 -0
  798. ciris_engine/schemas/__init__.py +112 -0
  799. ciris_engine/schemas/actions/__init__.py +37 -0
  800. ciris_engine/schemas/actions/parameters.py +137 -0
  801. ciris_engine/schemas/adapters/__init__.py +13 -0
  802. ciris_engine/schemas/adapters/cirisnode.py +135 -0
  803. ciris_engine/schemas/adapters/cli.py +97 -0
  804. ciris_engine/schemas/adapters/cli_tools.py +98 -0
  805. ciris_engine/schemas/adapters/discord.py +125 -0
  806. ciris_engine/schemas/adapters/graphql_core.py +144 -0
  807. ciris_engine/schemas/adapters/registration.py +47 -0
  808. ciris_engine/schemas/adapters/runtime_context.py +48 -0
  809. ciris_engine/schemas/adapters/tool_execution.py +45 -0
  810. ciris_engine/schemas/adapters/tools.py +96 -0
  811. ciris_engine/schemas/api/__init__.py +1 -0
  812. ciris_engine/schemas/api/agent.py +50 -0
  813. ciris_engine/schemas/api/audit.py +38 -0
  814. ciris_engine/schemas/api/auth.py +351 -0
  815. ciris_engine/schemas/api/config_security.py +242 -0
  816. ciris_engine/schemas/api/emergency.py +111 -0
  817. ciris_engine/schemas/api/responses.py +72 -0
  818. ciris_engine/schemas/api/runtime.py +26 -0
  819. ciris_engine/schemas/api/telemetry.py +109 -0
  820. ciris_engine/schemas/api/wa.py +90 -0
  821. ciris_engine/schemas/audit/__init__.py +13 -0
  822. ciris_engine/schemas/audit/core.py +139 -0
  823. ciris_engine/schemas/audit/hash_chain.py +58 -0
  824. ciris_engine/schemas/audit/verification.py +131 -0
  825. ciris_engine/schemas/buses/__init__.py +1 -0
  826. ciris_engine/schemas/config/__init__.py +41 -0
  827. ciris_engine/schemas/config/agent.py +279 -0
  828. ciris_engine/schemas/config/cognitive_state_behaviors.py +194 -0
  829. ciris_engine/schemas/config/default_dsar_sops.py +178 -0
  830. ciris_engine/schemas/config/essential.py +195 -0
  831. ciris_engine/schemas/config/tickets.py +86 -0
  832. ciris_engine/schemas/conscience/__init__.py +25 -0
  833. ciris_engine/schemas/conscience/context.py +34 -0
  834. ciris_engine/schemas/conscience/core.py +145 -0
  835. ciris_engine/schemas/conscience/results.py +24 -0
  836. ciris_engine/schemas/consent/__init__.py +5 -0
  837. ciris_engine/schemas/consent/core.py +404 -0
  838. ciris_engine/schemas/context/__init__.py +1 -0
  839. ciris_engine/schemas/covenant.py +382 -0
  840. ciris_engine/schemas/data/__init__.py +1 -0
  841. ciris_engine/schemas/dma/__init__.py +16 -0
  842. ciris_engine/schemas/dma/core.py +199 -0
  843. ciris_engine/schemas/dma/faculty.py +192 -0
  844. ciris_engine/schemas/dma/prompts.py +172 -0
  845. ciris_engine/schemas/dma/results.py +103 -0
  846. ciris_engine/schemas/formatters/__init__.py +1 -0
  847. ciris_engine/schemas/handlers/__init__.py +10 -0
  848. ciris_engine/schemas/handlers/context.py +119 -0
  849. ciris_engine/schemas/handlers/contexts.py +100 -0
  850. ciris_engine/schemas/handlers/core.py +167 -0
  851. ciris_engine/schemas/handlers/memory_schemas.py +67 -0
  852. ciris_engine/schemas/handlers/schemas.py +95 -0
  853. ciris_engine/schemas/identity.py +149 -0
  854. ciris_engine/schemas/infrastructure/__init__.py +1 -0
  855. ciris_engine/schemas/infrastructure/base.py +256 -0
  856. ciris_engine/schemas/infrastructure/behavioral_patterns.py +129 -0
  857. ciris_engine/schemas/infrastructure/feedback_loop.py +57 -0
  858. ciris_engine/schemas/infrastructure/identity_variance.py +141 -0
  859. ciris_engine/schemas/infrastructure/oauth.py +175 -0
  860. ciris_engine/schemas/infrastructure/wa_cli_wizard.py +54 -0
  861. ciris_engine/schemas/persistence/__init__.py +34 -0
  862. ciris_engine/schemas/persistence/core.py +140 -0
  863. ciris_engine/schemas/persistence/correlations.py +73 -0
  864. ciris_engine/schemas/persistence/postgres/__init__.py +1 -0
  865. ciris_engine/schemas/persistence/postgres/tables.py +280 -0
  866. ciris_engine/schemas/persistence/sqlite/__init__.py +1 -0
  867. ciris_engine/schemas/persistence/sqlite/tables.py +281 -0
  868. ciris_engine/schemas/platform.py +149 -0
  869. ciris_engine/schemas/processors/__init__.py +26 -0
  870. ciris_engine/schemas/processors/base.py +130 -0
  871. ciris_engine/schemas/processors/cognitive.py +77 -0
  872. ciris_engine/schemas/processors/context.py +35 -0
  873. ciris_engine/schemas/processors/core.py +152 -0
  874. ciris_engine/schemas/processors/dma.py +105 -0
  875. ciris_engine/schemas/processors/error.py +122 -0
  876. ciris_engine/schemas/processors/main.py +109 -0
  877. ciris_engine/schemas/processors/phase_results.py +21 -0
  878. ciris_engine/schemas/processors/results.py +99 -0
  879. ciris_engine/schemas/processors/solitude.py +79 -0
  880. ciris_engine/schemas/processors/state.py +202 -0
  881. ciris_engine/schemas/processors/state_example.py +177 -0
  882. ciris_engine/schemas/processors/states.py +21 -0
  883. ciris_engine/schemas/processors/status.py +34 -0
  884. ciris_engine/schemas/registries/__init__.py +1 -0
  885. ciris_engine/schemas/registries/base.py +66 -0
  886. ciris_engine/schemas/resources/__init__.py +15 -0
  887. ciris_engine/schemas/resources/crisis.py +315 -0
  888. ciris_engine/schemas/runtime/__init__.py +42 -0
  889. ciris_engine/schemas/runtime/adapter_management.py +186 -0
  890. ciris_engine/schemas/runtime/api.py +58 -0
  891. ciris_engine/schemas/runtime/audit.py +50 -0
  892. ciris_engine/schemas/runtime/bootstrap.py +33 -0
  893. ciris_engine/schemas/runtime/contexts.py +61 -0
  894. ciris_engine/schemas/runtime/core.py +161 -0
  895. ciris_engine/schemas/runtime/enums.py +167 -0
  896. ciris_engine/schemas/runtime/extended.py +232 -0
  897. ciris_engine/schemas/runtime/manifest.py +311 -0
  898. ciris_engine/schemas/runtime/memory.py +60 -0
  899. ciris_engine/schemas/runtime/messages.py +108 -0
  900. ciris_engine/schemas/runtime/models.py +156 -0
  901. ciris_engine/schemas/runtime/processing_context.py +43 -0
  902. ciris_engine/schemas/runtime/protocols_core.py +96 -0
  903. ciris_engine/schemas/runtime/resources.py +33 -0
  904. ciris_engine/schemas/runtime/system_context.py +417 -0
  905. ciris_engine/schemas/secrets/__init__.py +1 -0
  906. ciris_engine/schemas/secrets/core.py +267 -0
  907. ciris_engine/schemas/secrets/service.py +95 -0
  908. ciris_engine/schemas/services/__init__.py +33 -0
  909. ciris_engine/schemas/services/audit_summary_node.py +172 -0
  910. ciris_engine/schemas/services/authority/__init__.py +39 -0
  911. ciris_engine/schemas/services/authority/jwt.py +158 -0
  912. ciris_engine/schemas/services/authority/wa_updates.py +138 -0
  913. ciris_engine/schemas/services/authority/wise_authority.py +163 -0
  914. ciris_engine/schemas/services/authority_core.py +370 -0
  915. ciris_engine/schemas/services/capabilities.py +72 -0
  916. ciris_engine/schemas/services/community_core.py +95 -0
  917. ciris_engine/schemas/services/context.py +111 -0
  918. ciris_engine/schemas/services/conversation_summary_node.py +189 -0
  919. ciris_engine/schemas/services/core/__init__.py +153 -0
  920. ciris_engine/schemas/services/core/runtime.py +262 -0
  921. ciris_engine/schemas/services/core/runtime_config.py +117 -0
  922. ciris_engine/schemas/services/core/secrets.py +65 -0
  923. ciris_engine/schemas/services/correlation_node.py +179 -0
  924. ciris_engine/schemas/services/credit_gate.py +92 -0
  925. ciris_engine/schemas/services/discord_nodes.py +299 -0
  926. ciris_engine/schemas/services/feedback_core.py +131 -0
  927. ciris_engine/schemas/services/filters_core.py +270 -0
  928. ciris_engine/schemas/services/governance.py +26 -0
  929. ciris_engine/schemas/services/graph/__init__.py +26 -0
  930. ciris_engine/schemas/services/graph/attributes.py +254 -0
  931. ciris_engine/schemas/services/graph/audit.py +98 -0
  932. ciris_engine/schemas/services/graph/consolidation.py +338 -0
  933. ciris_engine/schemas/services/graph/edge_types.py +43 -0
  934. ciris_engine/schemas/services/graph/edges.py +88 -0
  935. ciris_engine/schemas/services/graph/incident.py +312 -0
  936. ciris_engine/schemas/services/graph/memory.py +84 -0
  937. ciris_engine/schemas/services/graph/node_data.py +174 -0
  938. ciris_engine/schemas/services/graph/query_results.py +82 -0
  939. ciris_engine/schemas/services/graph/telemetry.py +250 -0
  940. ciris_engine/schemas/services/graph/tsdb_consolidation.py +27 -0
  941. ciris_engine/schemas/services/graph/tsdb_models.py +107 -0
  942. ciris_engine/schemas/services/graph_core.py +196 -0
  943. ciris_engine/schemas/services/graph_typed_nodes.py +194 -0
  944. ciris_engine/schemas/services/infrastructure/__init__.py +1 -0
  945. ciris_engine/schemas/services/infrastructure/resource_monitor.py +20 -0
  946. ciris_engine/schemas/services/lifecycle/__init__.py +9 -0
  947. ciris_engine/schemas/services/lifecycle/initialization.py +33 -0
  948. ciris_engine/schemas/services/lifecycle/time.py +50 -0
  949. ciris_engine/schemas/services/llm.py +187 -0
  950. ciris_engine/schemas/services/metadata.py +43 -0
  951. ciris_engine/schemas/services/nodes.py +704 -0
  952. ciris_engine/schemas/services/operations.py +126 -0
  953. ciris_engine/schemas/services/requests.py +128 -0
  954. ciris_engine/schemas/services/resources_core.py +182 -0
  955. ciris_engine/schemas/services/runtime_control.py +1010 -0
  956. ciris_engine/schemas/services/shutdown.py +88 -0
  957. ciris_engine/schemas/services/special/__init__.py +0 -0
  958. ciris_engine/schemas/services/special/self_observation.py +396 -0
  959. ciris_engine/schemas/services/trace_summary_node.py +199 -0
  960. ciris_engine/schemas/services/visibility.py +98 -0
  961. ciris_engine/schemas/streaming/__init__.py +10 -0
  962. ciris_engine/schemas/streaming/reasoning_stream.py +95 -0
  963. ciris_engine/schemas/telemetry/__init__.py +0 -0
  964. ciris_engine/schemas/telemetry/collector.py +67 -0
  965. ciris_engine/schemas/telemetry/core.py +252 -0
  966. ciris_engine/schemas/telemetry/unified.py +59 -0
  967. ciris_engine/schemas/tools.py +72 -0
  968. ciris_engine/schemas/types.py +47 -0
  969. ciris_engine/schemas/utils/__init__.py +1 -0
  970. ciris_engine/schemas/utils/config_validator.py +54 -0
  971. ciris_engine/utils/__init__.py +1 -0
  972. ciris_engine/utils/serialization.py +35 -0
  973. ciris_sdk/__init__.py +124 -0
  974. ciris_sdk/auth_store.py +261 -0
  975. ciris_sdk/client.py +261 -0
  976. ciris_sdk/exceptions.py +73 -0
  977. ciris_sdk/model_types.py +258 -0
  978. ciris_sdk/models.py +354 -0
  979. ciris_sdk/pagination.py +214 -0
  980. ciris_sdk/rate_limiter.py +188 -0
  981. ciris_sdk/setup.py +17 -0
  982. ciris_sdk/telemetry_models.py +257 -0
  983. ciris_sdk/telemetry_responses.py +199 -0
  984. ciris_sdk/transport.py +177 -0
  985. ciris_sdk/websocket.py +400 -0
  986. main.py +766 -0
@@ -0,0 +1,1651 @@
1
+ """
2
+ Helper functions for system_snapshot.py to reduce complexity.
3
+
4
+ Organized by logical function groups:
5
+ 1. Thought Processing
6
+ 2. Channel Resolution
7
+ 3. Identity Management
8
+ 4. Task Processing
9
+ 5. System Context
10
+ 6. Service Health
11
+ 7. System Data
12
+ 8. User Management
13
+ """
14
+
15
+ import json
16
+ import logging
17
+ import re
18
+ from datetime import datetime, timezone
19
+ from typing import Any, Dict, List, Optional, Set, Tuple
20
+
21
+ from pydantic import BaseModel
22
+
23
+ from ciris_engine.logic import persistence
24
+ from ciris_engine.logic.secrets.service import SecretsService
25
+ from ciris_engine.logic.services.memory_service import LocalGraphMemoryService
26
+ from ciris_engine.logic.utils.jsondict_helpers import get_list
27
+ from ciris_engine.protocols.services.lifecycle.time import TimeServiceProtocol
28
+ from ciris_engine.schemas.adapters.tools import ToolInfo
29
+ from ciris_engine.schemas.infrastructure.identity_variance import IdentityData, IdentitySummary
30
+ from ciris_engine.schemas.runtime.models import Task
31
+ from ciris_engine.schemas.runtime.system_context import ChannelContext, TaskSummary, ThoughtSummary, UserProfile
32
+ from ciris_engine.schemas.services.graph_core import ConnectedNodeInfo, GraphNode, GraphScope, NodeType, SecretsData
33
+ from ciris_engine.schemas.services.lifecycle.time import LocalizedTimeData
34
+ from ciris_engine.schemas.services.operations import MemoryQuery
35
+ from ciris_engine.schemas.types import JSONDict
36
+
37
+ from .secrets_snapshot import ERROR_KEY, build_secrets_snapshot
38
+
39
+ logger = logging.getLogger(__name__)
40
+
41
+
42
+ # =============================================================================
43
+ # 1. THOUGHT PROCESSING
44
+ # =============================================================================
45
+
46
+
47
+ def _extract_thought_summary(thought: Any) -> Optional[ThoughtSummary]:
48
+ """Extract thought summary from thought object."""
49
+ if not thought:
50
+ return None
51
+
52
+ status_val = getattr(thought, "status", None)
53
+ if status_val is not None and hasattr(status_val, "value"):
54
+ status_val = status_val.value
55
+ elif status_val is not None:
56
+ status_val = str(status_val)
57
+
58
+ thought_type_val = getattr(thought, "thought_type", None)
59
+ thought_id_val = getattr(thought, "thought_id", None)
60
+ if thought_id_val is None:
61
+ thought_id_val = "unknown" # Provide a default value for required field
62
+
63
+ return ThoughtSummary(
64
+ thought_id=thought_id_val,
65
+ content=getattr(thought, "content", None),
66
+ status=status_val,
67
+ source_task_id=getattr(thought, "source_task_id", None),
68
+ thought_type=thought_type_val,
69
+ thought_depth=getattr(thought, "thought_depth", None),
70
+ )
71
+
72
+
73
+ # =============================================================================
74
+ # 1. STANDARDIZED NODE ATTRIBUTE EXTRACTION
75
+ # =============================================================================
76
+
77
+
78
+ def extract_node_attributes(node: Any) -> JSONDict:
79
+ """Extract attributes dictionary from any GraphNode - standardized and reusable.
80
+
81
+ This function handles all the different ways GraphNode attributes can be stored
82
+ and provides a consistent interface for accessing them.
83
+
84
+ Always returns a JSON-compatible dict (never None), returning empty dict for invalid nodes.
85
+
86
+ Returns:
87
+ JSONDict: Graph node attributes as JSON-serializable dictionary
88
+ """
89
+ if not node or not hasattr(node, "attributes"):
90
+ return {}
91
+
92
+ if node.attributes is None:
93
+ return {}
94
+ elif isinstance(node.attributes, dict):
95
+ return node.attributes
96
+ elif hasattr(node.attributes, "model_dump"):
97
+ # Cast to dict to satisfy type checker
98
+ return dict(node.attributes.model_dump())
99
+ else:
100
+ logger.warning(f"Unexpected node attributes type: {type(node.attributes)}")
101
+ return {}
102
+
103
+
104
+ def collect_memorized_attributes(attrs: JSONDict, known_fields: Set[str]) -> Dict[str, str]:
105
+ """Collect arbitrary attributes as memorized_attributes - standardized and reusable.
106
+
107
+ This function extracts all attributes that aren't in the known_fields set
108
+ and converts them to string values for type safety.
109
+
110
+ Args:
111
+ attrs: Node attributes from graph (JSON-compatible dict)
112
+ known_fields: Set of known field names to exclude
113
+
114
+ Returns:
115
+ Dict mapping attribute names to string values
116
+ """
117
+ import json
118
+
119
+ memorized_attributes = {}
120
+ for key, value in attrs.items():
121
+ if key not in known_fields:
122
+ # Convert value to string for type safety
123
+ if value is None:
124
+ memorized_attributes[key] = ""
125
+ elif isinstance(value, (dict, list)):
126
+ # Use JSON serialization with datetime handler for complex objects
127
+ memorized_attributes[key] = json.dumps(value, default=_json_serial_for_users)
128
+ else:
129
+ # Use string conversion for simple types (handles datetime via str())
130
+ memorized_attributes[key] = str(value)
131
+ return memorized_attributes
132
+
133
+
134
+ def get_channel_id_from_node(node: Any, attrs: JSONDict) -> str:
135
+ """Extract channel_id from node, with fallback to node.id.
136
+
137
+ Args:
138
+ node: GraphNode object
139
+ attrs: Node attributes (JSON-compatible dict)
140
+
141
+ Returns:
142
+ Channel ID string
143
+ """
144
+ return str(attrs.get("channel_id", node.id.split("/")[-1] if "/" in node.id else node.id))
145
+
146
+
147
+ # =============================================================================
148
+ # 2. CHANNEL RESOLUTION
149
+ # =============================================================================
150
+
151
+
152
+ def _extract_from_system_snapshot_channel_context(
153
+ context: Any, source_name: str
154
+ ) -> Tuple[Optional[str], Optional[Any]]:
155
+ """Extract channel info from system_snapshot.channel_context."""
156
+ if hasattr(context, "system_snapshot") and hasattr(context.system_snapshot, "channel_context"):
157
+ extracted_context = context.system_snapshot.channel_context
158
+ if extracted_context and hasattr(extracted_context, "channel_id"):
159
+ extracted_id = str(extracted_context.channel_id)
160
+ logger.debug(f"Found channel_context in {source_name}.system_snapshot.channel_context")
161
+ return extracted_id, extracted_context
162
+ return None, None
163
+
164
+
165
+ def _extract_from_system_snapshot_channel_id(context: Any, source_name: str) -> Tuple[Optional[str], Optional[Any]]:
166
+ """Extract channel info from system_snapshot.channel_id."""
167
+ if hasattr(context, "system_snapshot") and hasattr(context.system_snapshot, "channel_id"):
168
+ cid = context.system_snapshot.channel_id
169
+ if cid is not None:
170
+ logger.debug(f"Found channel_id '{cid}' in {source_name}.system_snapshot.channel_id")
171
+ return str(cid), None
172
+ return None, None
173
+
174
+
175
+ def _extract_from_direct_channel_id(context: Any) -> Tuple[Optional[str], Optional[Any]]:
176
+ """Extract channel info from direct channel_id attribute."""
177
+ if isinstance(context, dict):
178
+ cid = context.get("channel_id")
179
+ return str(cid) if cid is not None else None, None
180
+ elif hasattr(context, "channel_id"):
181
+ cid = getattr(context, "channel_id", None)
182
+ return str(cid) if cid is not None else None, None
183
+ return None, None
184
+
185
+
186
+ def _safe_extract_channel_info(context: Any, source_name: str) -> Tuple[Optional[str], Optional[Any]]:
187
+ """Extract both channel_id and channel_context from context."""
188
+ if not context:
189
+ return None, None
190
+ try:
191
+ # First check if context has system_snapshot.channel_context
192
+ channel_id, channel_context = _extract_from_system_snapshot_channel_context(context, source_name)
193
+ if channel_id:
194
+ return channel_id, channel_context
195
+
196
+ # Then check if context has system_snapshot.channel_id
197
+ channel_id, channel_context = _extract_from_system_snapshot_channel_id(context, source_name)
198
+ if channel_id:
199
+ return channel_id, channel_context
200
+
201
+ # Then check direct channel_id attribute
202
+ return _extract_from_direct_channel_id(context)
203
+
204
+ except Exception as e: # pragma: no cover - defensive
205
+ logger.error(f"Error extracting channel info from {source_name}: {e}")
206
+ raise # FAIL FAST AND LOUD - configuration/programming error
207
+
208
+
209
+ def _get_initial_channel_info(task: Optional[Task], thought: Any) -> Tuple[Optional[str], Optional[Any]]:
210
+ """Extract initial channel ID and context from task/thought."""
211
+ channel_id = None
212
+ channel_context = None
213
+
214
+ if task and task.context:
215
+ channel_id, channel_context = _safe_extract_channel_info(task.context, "task.context")
216
+ if not channel_id and thought and thought.context:
217
+ channel_id, channel_context = _safe_extract_channel_info(thought.context, "thought.context")
218
+
219
+ return channel_id, channel_context
220
+
221
+
222
+ async def _perform_direct_channel_lookup(memory_service: Any, channel_id: str) -> List[Any]:
223
+ """Perform direct memory lookup for channel by node_id."""
224
+ query = MemoryQuery(
225
+ node_id=f"channel/{channel_id}",
226
+ scope=GraphScope.LOCAL,
227
+ type=NodeType.CHANNEL,
228
+ include_edges=False,
229
+ depth=1,
230
+ )
231
+ logger.debug(f"[DEBUG DB TIMING] About to query memory service for channel/{channel_id}")
232
+ channel_nodes = await memory_service.recall(query)
233
+ logger.debug(f"[DEBUG DB TIMING] Completed memory service query for channel/{channel_id}")
234
+ return list(channel_nodes) if channel_nodes else []
235
+
236
+
237
+ async def _perform_channel_search(memory_service: Any, channel_id: str) -> List[Any]:
238
+ """Perform search-based channel lookup."""
239
+ from ciris_engine.schemas.services.graph.memory import MemorySearchFilter
240
+
241
+ search_filter = MemorySearchFilter(node_type=NodeType.CHANNEL.value, scope=GraphScope.LOCAL.value, limit=10)
242
+ logger.debug(f"[DEBUG DB TIMING] About to search memory service for channel {channel_id}")
243
+ search_results = await memory_service.search(query=channel_id, filters=search_filter)
244
+ logger.debug(f"[DEBUG DB TIMING] Completed memory service search for channel {channel_id}")
245
+ return list(search_results) if search_results else []
246
+
247
+
248
+ def _extract_channel_from_search_results(search_results: List[Any], channel_id: str) -> Optional[Any]:
249
+ """Extract matching channel from search results."""
250
+ for node in search_results:
251
+ if node.attributes:
252
+ attrs = node.attributes if isinstance(node.attributes, dict) else node.attributes.model_dump()
253
+ if attrs.get("channel_id") == channel_id or node.id == f"channel/{channel_id}":
254
+ return node
255
+ return None
256
+
257
+
258
+ # Legacy function - now uses standardized extract_node_attributes
259
+ def _extract_channel_node_attributes(
260
+ node: Any,
261
+ ) -> Optional[JSONDict]:
262
+ """Extract attributes dictionary from channel GraphNode."""
263
+ return extract_node_attributes(node)
264
+
265
+
266
+ def _get_known_channel_fields() -> Set[str]:
267
+ """Get set of known ChannelContext fields."""
268
+ return {
269
+ "channel_id",
270
+ "channel_type",
271
+ "created_at",
272
+ "channel_name",
273
+ "is_private",
274
+ "participants",
275
+ "is_active",
276
+ "last_activity",
277
+ "message_count",
278
+ "allowed_actions",
279
+ "moderation_level",
280
+ }
281
+
282
+
283
+ def _build_required_channel_fields(attrs: JSONDict, node: Any) -> JSONDict:
284
+ """Build required ChannelContext fields with defaults."""
285
+ # Get created_at, or generate new timestamp if missing
286
+ created_at_val = attrs.get("created_at")
287
+ if created_at_val is None:
288
+ created_at_val = datetime.now(timezone.utc).isoformat()
289
+
290
+ return {
291
+ "channel_id": get_channel_id_from_node(node, attrs),
292
+ "channel_type": attrs.get("channel_type", "unknown"),
293
+ "created_at": created_at_val,
294
+ }
295
+
296
+
297
+ def _build_optional_channel_fields(
298
+ attrs: JSONDict,
299
+ ) -> JSONDict:
300
+ """Build optional ChannelContext fields with defaults."""
301
+ return {
302
+ "channel_name": attrs.get("channel_name", None),
303
+ "is_private": attrs.get("is_private", False),
304
+ "participants": attrs.get("participants", []),
305
+ "is_active": attrs.get("is_active", True),
306
+ "last_activity": attrs.get("last_activity", None),
307
+ "message_count": attrs.get("message_count", 0),
308
+ "allowed_actions": attrs.get("allowed_actions", []),
309
+ "moderation_level": attrs.get("moderation_level", "standard"),
310
+ }
311
+
312
+
313
+ # Legacy function - now uses standardized collect_memorized_attributes
314
+ def _collect_memorized_attributes(attrs: JSONDict, known_fields: Set[str]) -> Dict[str, str]:
315
+ """Collect arbitrary attributes the agent memorized about this channel."""
316
+ return collect_memorized_attributes(attrs, known_fields)
317
+
318
+
319
+ def _convert_graph_node_to_channel_context(node: Any) -> Optional[ChannelContext]:
320
+ """Convert a GraphNode containing channel data to a ChannelContext object."""
321
+ if not node or not node.attributes:
322
+ return None
323
+
324
+ try:
325
+ # Extract attributes from GraphNode
326
+ attrs = _extract_channel_node_attributes(node)
327
+ if attrs is None:
328
+ return None
329
+
330
+ # Get known field definitions
331
+ known_fields = _get_known_channel_fields()
332
+
333
+ # Build context data
334
+ context_data = _build_required_channel_fields(attrs, node)
335
+ context_data.update(_build_optional_channel_fields(attrs))
336
+ context_data["memorized_attributes"] = _collect_memorized_attributes(attrs, known_fields)
337
+
338
+ return ChannelContext(**context_data)
339
+
340
+ except Exception as e:
341
+ logger.warning(f"Failed to convert GraphNode to ChannelContext: {e}")
342
+ return None
343
+
344
+
345
+ async def _resolve_channel_context(
346
+ task: Optional[Task], thought: Any, memory_service: Optional[LocalGraphMemoryService]
347
+ ) -> Tuple[Optional[str], Optional[ChannelContext]]:
348
+ """Resolve channel ID and context from task/thought with memory lookup."""
349
+ # Get initial channel info from task/thought
350
+ channel_id, initial_context = _get_initial_channel_info(task, thought)
351
+
352
+ # If we already have a ChannelContext, use it
353
+ if isinstance(initial_context, ChannelContext):
354
+ return channel_id, initial_context
355
+
356
+ # Start with the initial context (may be None or some other object)
357
+ channel_context = initial_context
358
+
359
+ # Attempt memory lookup if we have both channel_id and memory_service
360
+ if channel_id and memory_service:
361
+ try:
362
+ # First try direct lookup for performance
363
+ channel_nodes = await _perform_direct_channel_lookup(memory_service, channel_id)
364
+
365
+ if channel_nodes:
366
+ # Convert the first found channel node to ChannelContext
367
+ channel_context = _convert_graph_node_to_channel_context(channel_nodes[0])
368
+ else:
369
+ # If not found, try search
370
+ search_results = await _perform_channel_search(memory_service, channel_id)
371
+ found_channel = _extract_channel_from_search_results(search_results, channel_id)
372
+ if found_channel:
373
+ # Convert the found channel node to ChannelContext
374
+ channel_context = _convert_graph_node_to_channel_context(found_channel)
375
+
376
+ except Exception as e:
377
+ logger.debug(f"Failed to retrieve channel context for {channel_id}: {e}")
378
+
379
+ return channel_id, channel_context
380
+
381
+
382
+ # =============================================================================
383
+ # 3. IDENTITY MANAGEMENT
384
+ # =============================================================================
385
+
386
+
387
+ async def _extract_agent_identity(
388
+ memory_service: Optional[LocalGraphMemoryService],
389
+ ) -> Tuple[IdentityData, IdentitySummary]:
390
+ """Extract agent identity data from graph memory."""
391
+ # Default values for when no memory service or identity is available
392
+ default_identity_data = IdentityData(
393
+ agent_id="unknown",
394
+ description="No identity data available",
395
+ role="Unknown",
396
+ trust_level=0.5,
397
+ )
398
+ default_identity_summary = IdentitySummary()
399
+
400
+ if not memory_service:
401
+ return default_identity_data, default_identity_summary
402
+
403
+ try:
404
+ # Query for the agent's identity node from the graph
405
+ identity_query = MemoryQuery(
406
+ node_id="agent/identity", scope=GraphScope.IDENTITY, type=NodeType.AGENT, include_edges=False, depth=1
407
+ )
408
+ logger.debug("[DEBUG DB TIMING] About to query memory service for agent/identity")
409
+ identity_nodes = await memory_service.recall(identity_query)
410
+ logger.debug("[DEBUG DB TIMING] Completed memory service query for agent/identity")
411
+ identity_result = identity_nodes[0] if identity_nodes else None
412
+
413
+ if not identity_result or not identity_result.attributes:
414
+ return default_identity_data, default_identity_summary
415
+
416
+ # Extract attributes using the standardized helper
417
+ attrs_dict = extract_node_attributes(identity_result)
418
+ if not attrs_dict:
419
+ return default_identity_data, default_identity_summary
420
+
421
+ # Create typed identity data
422
+ identity_data = IdentityData(
423
+ agent_id=attrs_dict.get("agent_id", "unknown"),
424
+ description=attrs_dict.get("description", "No description available"),
425
+ role=attrs_dict.get("role_description", "Unknown"),
426
+ trust_level=attrs_dict.get("trust_level", 0.5),
427
+ stewardship=attrs_dict.get("stewardship"),
428
+ )
429
+
430
+ # Create typed identity summary
431
+ identity_summary = IdentitySummary(
432
+ identity_purpose=attrs_dict.get("role_description"),
433
+ identity_capabilities=attrs_dict.get("permitted_actions", []),
434
+ identity_restrictions=attrs_dict.get("restricted_capabilities", []),
435
+ )
436
+
437
+ return identity_data, identity_summary
438
+
439
+ except Exception as e:
440
+ logger.warning(f"Failed to retrieve agent identity from graph: {e}")
441
+ return default_identity_data, default_identity_summary
442
+
443
+
444
+ # =============================================================================
445
+ # 4. TASK PROCESSING
446
+ # =============================================================================
447
+
448
+
449
+ def _get_recent_tasks(limit: int = 10) -> List[TaskSummary]:
450
+ """Get recent completed tasks as TaskSummary objects."""
451
+ recent_tasks_list: List[TaskSummary] = []
452
+ logger.debug("[DEBUG DB TIMING] About to get recent completed tasks")
453
+ db_recent_tasks = persistence.get_recent_completed_tasks("default", limit)
454
+ logger.debug(f"[DEBUG DB TIMING] Completed get recent completed tasks: {len(db_recent_tasks)} tasks")
455
+
456
+ for t_obj in db_recent_tasks:
457
+ # db_recent_tasks returns List[Task], convert to TaskSummary
458
+ if isinstance(t_obj, BaseModel):
459
+ recent_tasks_list.append(
460
+ TaskSummary(
461
+ task_id=t_obj.task_id,
462
+ channel_id=getattr(t_obj, "channel_id", "system"),
463
+ created_at=t_obj.created_at,
464
+ status=t_obj.status.value if hasattr(t_obj.status, "value") else str(t_obj.status),
465
+ priority=getattr(t_obj, "priority", 0),
466
+ retry_count=getattr(t_obj, "retry_count", 0),
467
+ parent_task_id=getattr(t_obj, "parent_task_id", None),
468
+ )
469
+ )
470
+ return recent_tasks_list
471
+
472
+
473
+ def _get_top_tasks(limit: int = 10) -> List[TaskSummary]:
474
+ """Get top pending tasks as TaskSummary objects."""
475
+ top_tasks_list: List[TaskSummary] = []
476
+ logger.debug("[DEBUG DB TIMING] About to get top tasks")
477
+ db_top_tasks = persistence.get_top_tasks("default", limit)
478
+ logger.debug(f"[DEBUG DB TIMING] Completed get top tasks: {len(db_top_tasks)} tasks")
479
+
480
+ for t_obj in db_top_tasks:
481
+ # db_top_tasks returns List[Task], convert to TaskSummary
482
+ if isinstance(t_obj, BaseModel):
483
+ top_tasks_list.append(
484
+ TaskSummary(
485
+ task_id=t_obj.task_id,
486
+ channel_id=getattr(t_obj, "channel_id", "system"),
487
+ created_at=t_obj.created_at,
488
+ status=t_obj.status.value if hasattr(t_obj.status, "value") else str(t_obj.status),
489
+ priority=getattr(t_obj, "priority", 0),
490
+ retry_count=getattr(t_obj, "retry_count", 0),
491
+ parent_task_id=getattr(t_obj, "parent_task_id", None),
492
+ )
493
+ )
494
+ return top_tasks_list
495
+
496
+
497
+ def _build_current_task_summary(task: Optional[Task]) -> Optional[TaskSummary]:
498
+ """Convert Task to TaskSummary."""
499
+ if not task:
500
+ return None
501
+
502
+ # Convert Task to TaskSummary
503
+ if isinstance(task, BaseModel):
504
+ return TaskSummary(
505
+ task_id=task.task_id,
506
+ channel_id=getattr(task, "channel_id", "system"),
507
+ created_at=task.created_at,
508
+ status=task.status.value if hasattr(task.status, "value") else str(task.status),
509
+ priority=getattr(task, "priority", 0),
510
+ retry_count=getattr(task, "retry_count", 0),
511
+ parent_task_id=getattr(task, "parent_task_id", None),
512
+ )
513
+ return None
514
+
515
+
516
+ # =============================================================================
517
+ # 5. SYSTEM CONTEXT
518
+ # =============================================================================
519
+
520
+
521
+ async def _get_secrets_data(secrets_service: Optional[SecretsService]) -> SecretsData:
522
+ """Get secrets snapshot data."""
523
+ if secrets_service:
524
+ # Get the raw snapshot data
525
+ snapshot_data = await build_secrets_snapshot(secrets_service)
526
+
527
+ # Check if there was an error
528
+ error_message = snapshot_data.get(ERROR_KEY)
529
+ filter_status = "active" if not error_message else "error"
530
+
531
+ if error_message:
532
+ logger.warning(
533
+ "Secrets snapshot reported an internal error in secrets service. See monitoring for details."
534
+ )
535
+
536
+ # Convert to typed schema
537
+ return SecretsData(
538
+ secrets_count=snapshot_data.get("total_secrets_stored", 0),
539
+ filter_status=filter_status,
540
+ last_updated=None, # Not provided by build_secrets_snapshot
541
+ detected_secrets=snapshot_data.get("detected_secrets", []),
542
+ secrets_filter_version=snapshot_data.get("secrets_filter_version", 0),
543
+ additional_data=snapshot_data, # Store full data for backwards compatibility
544
+ )
545
+
546
+ return SecretsData()
547
+
548
+
549
+ def _get_shutdown_context(runtime: Optional[Any]) -> Optional[Any]:
550
+ """Extract shutdown context from runtime."""
551
+ if runtime and hasattr(runtime, "current_shutdown_context"):
552
+ return runtime.current_shutdown_context
553
+ return None
554
+
555
+
556
+ def _format_critical_alert(alert: str) -> str:
557
+ """Format a critical resource alert."""
558
+ return f"🚨 CRITICAL! RESOURCE LIMIT BREACHED! {alert} - REJECT OR DEFER ALL TASKS!"
559
+
560
+
561
+ def _get_system_unhealthy_alert() -> str:
562
+ """Get system unhealthy alert message."""
563
+ return "🚨 CRITICAL! SYSTEM UNHEALTHY! RESOURCE LIMITS EXCEEDED - IMMEDIATE ACTION REQUIRED!"
564
+
565
+
566
+ def _get_resource_check_failed_alert(error: str) -> str:
567
+ """Get resource check failed alert message."""
568
+ return f"🚨 CRITICAL! FAILED TO CHECK RESOURCES: {error}"
569
+
570
+
571
+ def _process_critical_alerts(snapshot: Any, resource_alerts: List[str]) -> None:
572
+ """Process critical resource alerts from snapshot."""
573
+ if snapshot.critical:
574
+ for alert in snapshot.critical:
575
+ resource_alerts.append(_format_critical_alert(alert))
576
+
577
+
578
+ def _check_system_health(snapshot: Any, resource_alerts: List[str]) -> None:
579
+ """Check system health and add alerts if unhealthy."""
580
+ if not snapshot.healthy:
581
+ resource_alerts.append(_get_system_unhealthy_alert())
582
+
583
+
584
+ def _collect_resource_alerts(resource_monitor: Any) -> List[str]:
585
+ """Collect critical resource alerts."""
586
+ resource_alerts: List[str] = []
587
+ try:
588
+ if resource_monitor is not None:
589
+ snapshot = resource_monitor.snapshot
590
+ _process_critical_alerts(snapshot, resource_alerts)
591
+ _check_system_health(snapshot, resource_alerts)
592
+ else:
593
+ logger.warning("Resource monitor not available - cannot check resource constraints")
594
+ except Exception as e:
595
+ logger.error(f"Failed to get resource alerts: {e}")
596
+ resource_alerts.append(_get_resource_check_failed_alert(str(e)))
597
+ return resource_alerts
598
+
599
+
600
+ # =============================================================================
601
+ # 6. SERVICE HEALTH
602
+ # =============================================================================
603
+
604
+
605
+ async def _safe_get_health_status(service: Any) -> tuple[bool, bool]:
606
+ """Safely get health status from a service.
607
+
608
+ Returns:
609
+ (has_health_method, is_healthy): Tuple indicating if service has health methods and its health status
610
+ """
611
+ try:
612
+ # Check for ServiceProtocol standard method first
613
+ if hasattr(service, "is_healthy"):
614
+ health_status = await service.is_healthy()
615
+ return True, bool(health_status)
616
+ # Fallback to legacy method
617
+ elif hasattr(service, "get_health_status"):
618
+ health_status = await service.get_health_status()
619
+ return True, getattr(health_status, "is_healthy", False)
620
+ except Exception as e:
621
+ logger.warning(f"Failed to get health status from service: {e}")
622
+ return True, False # Has method but failed
623
+ return False, False # No health method
624
+
625
+
626
+ async def _safe_get_circuit_breaker_status(service: Any) -> tuple[bool, str]:
627
+ """Safely get circuit breaker status from a service.
628
+
629
+ Returns:
630
+ (has_circuit_breaker, status): Tuple indicating if service has circuit breaker and its status
631
+ """
632
+ try:
633
+ if hasattr(service, "get_circuit_breaker_status"):
634
+ cb_status = await service.get_circuit_breaker_status()
635
+ return True, str(cb_status) if cb_status else "UNKNOWN"
636
+ except Exception as e:
637
+ logger.warning(f"Failed to get circuit breaker status from service: {e}")
638
+ return True, "UNKNOWN" # Has method but failed
639
+ return False, "UNKNOWN" # No circuit breaker method
640
+
641
+
642
+ async def _process_single_service(
643
+ service: Any, service_name: str, service_health: Dict[str, bool], circuit_breaker_status: Dict[str, str]
644
+ ) -> None:
645
+ """Process health and circuit breaker status for a single service."""
646
+ # Get health status - only include if service has health methods
647
+ has_health_method, health_status = await _safe_get_health_status(service)
648
+ if has_health_method:
649
+ service_health[service_name] = health_status
650
+
651
+ # Get circuit breaker status - only include if service has circuit breaker methods
652
+ has_circuit_breaker, cb_status = await _safe_get_circuit_breaker_status(service)
653
+ if has_circuit_breaker:
654
+ circuit_breaker_status[service_name] = cb_status
655
+
656
+
657
+ async def _process_services_group(
658
+ services_group: JSONDict,
659
+ prefix: str,
660
+ service_health: Dict[str, bool],
661
+ circuit_breaker_status: Dict[str, str],
662
+ ) -> None:
663
+ """Process a group of services (handlers or global services)."""
664
+ for service_type, services_raw in services_group.items():
665
+ # Type narrow services to list before iteration
666
+ services_list = get_list(services_group, service_type, [])
667
+ for service in services_list:
668
+ service_name = f"{prefix}.{service_type}"
669
+ await _process_single_service(service, service_name, service_health, circuit_breaker_status)
670
+
671
+
672
+ async def _collect_service_health(
673
+ service_registry: Optional[Any], runtime: Optional[Any] = None
674
+ ) -> Tuple[Dict[str, bool], Dict[str, str]]:
675
+ """Collect service health and circuit breaker status."""
676
+ service_health: Dict[str, bool] = {}
677
+ circuit_breaker_status: Dict[str, str] = {}
678
+
679
+ # First, collect core services from runtime (21 core services)
680
+ if runtime:
681
+ core_services = [
682
+ # Graph Services (6)
683
+ "memory_service",
684
+ "config_service",
685
+ "telemetry_service",
686
+ "audit_service",
687
+ "incident_management_service",
688
+ "tsdb_consolidation_service",
689
+ # Infrastructure Services (7)
690
+ "time_service",
691
+ "shutdown_service",
692
+ "initialization_service",
693
+ "authentication_service",
694
+ "resource_monitor",
695
+ "maintenance_service", # database maintenance
696
+ "secrets_service",
697
+ # Governance Services (4)
698
+ "wa_auth_system", # wise authority
699
+ "adaptive_filter_service",
700
+ "visibility_service",
701
+ "self_observation_service",
702
+ # Runtime Services (3)
703
+ "llm_service",
704
+ "runtime_control_service",
705
+ "task_scheduler",
706
+ # Tool Services (1)
707
+ "secrets_tool_service", # secrets tool
708
+ ]
709
+
710
+ for service_name in core_services:
711
+ service = getattr(runtime, service_name, None)
712
+ if service:
713
+ await _process_single_service(service, service_name, service_health, circuit_breaker_status)
714
+
715
+ # Then, collect handler-specific services from service_registry
716
+ if service_registry:
717
+ try:
718
+ registry_info = service_registry.get_provider_info()
719
+
720
+ # Process handler-specific services
721
+ for handler, service_types in registry_info.get("handlers", {}).items():
722
+ await _process_services_group(service_types, handler, service_health, circuit_breaker_status)
723
+
724
+ # Process global services
725
+ global_services = registry_info.get("global_services", {})
726
+ await _process_services_group(global_services, "global", service_health, circuit_breaker_status)
727
+
728
+ except Exception as e:
729
+ logger.warning(f"Failed to collect service health status: {e}")
730
+
731
+ return service_health, circuit_breaker_status
732
+
733
+
734
+ # =============================================================================
735
+ # 7. SYSTEM DATA
736
+ # =============================================================================
737
+
738
+
739
+ async def _get_telemetry_summary(telemetry_service: Optional[Any]) -> Optional[Any]:
740
+ """Get telemetry summary for resource usage."""
741
+ if telemetry_service:
742
+ try:
743
+ telemetry_summary = await telemetry_service.get_telemetry_summary()
744
+ logger.debug("Successfully retrieved telemetry summary")
745
+ return telemetry_summary
746
+ except Exception as e:
747
+ logger.warning(f"Failed to get telemetry summary: {e}")
748
+ return None
749
+
750
+
751
+ async def _get_continuity_summary(telemetry_service: Optional[Any]) -> Optional[Any]:
752
+ """Get continuity awareness summary from lifecycle events."""
753
+ if telemetry_service and hasattr(telemetry_service, "get_continuity_summary"):
754
+ try:
755
+ continuity_summary = await telemetry_service.get_continuity_summary()
756
+ logger.debug("Successfully retrieved continuity summary")
757
+ return continuity_summary
758
+ except Exception as e:
759
+ logger.warning(f"Failed to get continuity summary: {e}")
760
+ return None
761
+
762
+
763
+ def _validate_channel_list(channels: List[Any], adapter_name: str) -> None:
764
+ """Validate that channel list contains ChannelContext objects."""
765
+ if channels and not isinstance(channels[0], ChannelContext):
766
+ raise TypeError(
767
+ f"Adapter {adapter_name} returned invalid channel list type: {type(channels[0])}, expected ChannelContext"
768
+ )
769
+
770
+
771
+ def _process_adapter_channels(
772
+ adapter_name: str, adapter: Any, adapter_channels: Dict[str, List[ChannelContext]]
773
+ ) -> None:
774
+ """Process channels from a single adapter."""
775
+ if hasattr(adapter, "get_channel_list"):
776
+ channels = adapter.get_channel_list()
777
+ if channels:
778
+ _validate_channel_list(channels, adapter_name)
779
+ # Use channel_type from first channel
780
+ adapter_type = channels[0].channel_type
781
+ adapter_channels[adapter_type] = channels
782
+ logger.debug(f"Found {len(channels)} channels for {adapter_type} adapter")
783
+
784
+
785
+ def _has_valid_adapter_manager(runtime: Optional[Any]) -> bool:
786
+ """Check if runtime has valid adapter manager."""
787
+ return runtime is not None and hasattr(runtime, "adapter_manager") and runtime.adapter_manager is not None
788
+
789
+
790
+ async def _collect_adapter_channels(runtime: Optional[Any]) -> Dict[str, List[ChannelContext]]:
791
+ """Collect available channels from all adapters."""
792
+ adapter_channels: Dict[str, List[ChannelContext]] = {}
793
+
794
+ if _has_valid_adapter_manager(runtime):
795
+ try:
796
+ # Assert runtime is not None since we checked it
797
+ assert runtime is not None
798
+ adapter_manager = runtime.adapter_manager
799
+ # Get all active adapters
800
+ for adapter_name, adapter in adapter_manager._adapters.items():
801
+ _process_adapter_channels(adapter_name, adapter, adapter_channels)
802
+ except Exception as e:
803
+ logger.error(f"Failed to get adapter channels: {e}")
804
+ raise # FAIL FAST AND LOUD
805
+
806
+ return adapter_channels
807
+
808
+
809
+ def _validate_runtime_capabilities(runtime: Optional[Any]) -> bool:
810
+ """Check if runtime has required attributes for tool collection."""
811
+ if runtime is None:
812
+ return False
813
+ if not hasattr(runtime, "bus_manager"):
814
+ return False
815
+ if not hasattr(runtime, "service_registry"):
816
+ return False
817
+ return True
818
+
819
+
820
+ def _get_tool_services(service_registry: Any) -> List[Any]:
821
+ """Get and validate tool services from registry."""
822
+ tool_services = service_registry.get_services_by_type("tool")
823
+
824
+ # Validate tool_services is iterable but not a string
825
+ try:
826
+ # Check if it's truly iterable and not a mock
827
+ if not hasattr(tool_services, "__iter__") or isinstance(tool_services, str):
828
+ logger.error(f"get_services_by_type('tool') returned non-iterable: {type(tool_services)}")
829
+ return []
830
+
831
+ # Try to convert to list to ensure it's really iterable
832
+ return list(tool_services)
833
+ except (TypeError, AttributeError):
834
+ logger.error(f"get_services_by_type('tool') returned non-iterable: {type(tool_services)}")
835
+ return []
836
+
837
+
838
+ async def _call_async_or_sync_method(obj: Any, method_name: str, *args: Any) -> Any:
839
+ """Call a method that might be async or sync."""
840
+ import inspect
841
+
842
+ if not hasattr(obj, method_name):
843
+ return None
844
+
845
+ method = getattr(obj, method_name)
846
+
847
+ # Handle Mock objects that don't have real methods
848
+ if hasattr(method, "_mock_name"):
849
+ # This is a mock, call it and check if result is a coroutine
850
+ result = method(*args)
851
+ if inspect.iscoroutine(result):
852
+ return await result
853
+ return result
854
+
855
+ if inspect.iscoroutinefunction(method):
856
+ return await method(*args)
857
+ else:
858
+ return method(*args)
859
+
860
+
861
+ async def _get_tool_info_safely(tool_service: Any, tool_name: str, adapter_id: str) -> Optional[ToolInfo]:
862
+ """Get tool info with error handling and type validation."""
863
+ if not hasattr(tool_service, "get_tool_info"):
864
+ return None
865
+
866
+ try:
867
+ tool_info = await _call_async_or_sync_method(tool_service, "get_tool_info", tool_name)
868
+
869
+ if tool_info:
870
+ if not isinstance(tool_info, ToolInfo):
871
+ raise TypeError(
872
+ f"Tool service {adapter_id} returned invalid type for {tool_name}: {type(tool_info)}, expected ToolInfo"
873
+ )
874
+ return tool_info
875
+ except Exception as e:
876
+ logger.error(f"Failed to get info for tool {tool_name}: {e}")
877
+ raise
878
+
879
+ return None
880
+
881
+
882
+ def _extract_adapter_type(adapter_id: str) -> str:
883
+ """Extract adapter type from adapter_id."""
884
+ return adapter_id.split("_")[0] if "_" in adapter_id else adapter_id
885
+
886
+
887
+ def _validate_tool_infos(tool_infos: List[ToolInfo]) -> None:
888
+ """Validate all tools are ToolInfo instances - FAIL FAST."""
889
+ for ti in tool_infos:
890
+ if not isinstance(ti, ToolInfo):
891
+ raise TypeError(f"Non-ToolInfo object in tool_infos: {type(ti)}, this violates type safety!")
892
+
893
+
894
+ async def _collect_available_tools(runtime: Optional[Any]) -> Dict[str, List[ToolInfo]]:
895
+ """Collect available tools from all adapters via tool bus."""
896
+ available_tools: Dict[str, List[ToolInfo]] = {}
897
+
898
+ if not _validate_runtime_capabilities(runtime):
899
+ return available_tools
900
+
901
+ try:
902
+ # Assert runtime is not None since we validated it
903
+ assert runtime is not None
904
+ service_registry = runtime.service_registry
905
+ tool_services = _get_tool_services(service_registry)
906
+
907
+ for tool_service in tool_services:
908
+ adapter_id = getattr(tool_service, "adapter_id", "unknown")
909
+
910
+ # Get available tools from this service
911
+ tool_names = await _call_async_or_sync_method(tool_service, "get_available_tools")
912
+ if not tool_names:
913
+ continue
914
+
915
+ # Get detailed info for each tool
916
+ tool_infos: List[ToolInfo] = []
917
+ for tool_name in tool_names:
918
+ tool_info = await _get_tool_info_safely(tool_service, tool_name, adapter_id)
919
+ if tool_info:
920
+ tool_infos.append(tool_info)
921
+
922
+ if tool_infos:
923
+ _validate_tool_infos(tool_infos)
924
+
925
+ # Group by adapter type
926
+ adapter_type = _extract_adapter_type(adapter_id)
927
+ if adapter_type not in available_tools:
928
+ available_tools[adapter_type] = []
929
+ available_tools[adapter_type].extend(tool_infos)
930
+ logger.debug(f"Found {len(tool_infos)} tools for {adapter_type} adapter")
931
+
932
+ except Exception as e:
933
+ logger.error(f"Failed to get available tools: {e}")
934
+ raise # FAIL FAST AND LOUD
935
+
936
+ return available_tools
937
+
938
+
939
+ def _collect_enrichment_tools(available_tools: Dict[str, List[ToolInfo]]) -> List[tuple[str, ToolInfo]]:
940
+ """Collect all tools marked for context enrichment."""
941
+ enrichment_tools: List[tuple[str, ToolInfo]] = []
942
+ for adapter_type, tools in available_tools.items():
943
+ for tool in tools:
944
+ if tool.context_enrichment:
945
+ enrichment_tools.append((adapter_type, tool))
946
+ logger.info(f"[CONTEXT_ENRICHMENT] Found enrichment tool: {adapter_type}:{tool.name}")
947
+ return enrichment_tools
948
+
949
+
950
+ def _log_no_enrichment_tools(available_tools: Dict[str, List[ToolInfo]]) -> None:
951
+ """Log debug info when no enrichment tools are found."""
952
+ logger.info("[CONTEXT_ENRICHMENT] No context enrichment tools found in available_tools")
953
+ logger.info(f"[CONTEXT_ENRICHMENT] available_tools keys: {list(available_tools.keys())}")
954
+ for adapter_type, tools in available_tools.items():
955
+ logger.info(f"[CONTEXT_ENRICHMENT] {adapter_type} has {len(tools)} tools: {[t.name for t in tools]}")
956
+
957
+
958
+ async def _find_tool_service(tool_services: List[Any], adapter_type: str, tool_name: str) -> Optional[Any]:
959
+ """Find the tool service that provides the specified tool."""
960
+ for ts in tool_services:
961
+ adapter_id = getattr(ts, "adapter_id", "")
962
+ ts_adapter_type = _extract_adapter_type(adapter_id)
963
+ if ts_adapter_type == adapter_type:
964
+ available = await _call_async_or_sync_method(ts, "get_available_tools")
965
+ if available and tool_name in available:
966
+ return ts
967
+ return None
968
+
969
+
970
+ def _process_tool_result(result: Any, tool_key: str) -> Any:
971
+ """Process and return the appropriate result from a tool execution."""
972
+ if hasattr(result, "data") and result.data is not None:
973
+ logger.info(f"[CONTEXT_ENRICHMENT] {tool_key} returned data with {len(result.data)} keys")
974
+ return result.data
975
+ elif hasattr(result, "success") and result.success:
976
+ return {"success": True, "message": "Tool executed successfully"}
977
+ else:
978
+ logger.info(f"[CONTEXT_ENRICHMENT] {tool_key} returned raw result")
979
+ return result
980
+
981
+
982
+ async def _execute_enrichment_tool(tool_services: List[Any], adapter_type: str, tool: ToolInfo) -> tuple[str, Any]:
983
+ """Execute a single enrichment tool and return (tool_key, result)."""
984
+ tool_key = f"{adapter_type}:{tool.name}"
985
+
986
+ tool_service = await _find_tool_service(tool_services, adapter_type, tool.name)
987
+ if not tool_service:
988
+ logger.warning(f"[CONTEXT_ENRICHMENT] No tool service found for {tool_key}")
989
+ return tool_key, None
990
+
991
+ params = tool.context_enrichment_params or {}
992
+ logger.info(f"[CONTEXT_ENRICHMENT] Executing {tool_key} with params: {params}")
993
+
994
+ result = await _call_async_or_sync_method(tool_service, "execute_tool", tool.name, params)
995
+ if result:
996
+ return tool_key, _process_tool_result(result, tool_key)
997
+ return tool_key, None
998
+
999
+
1000
+ async def _run_context_enrichment_tools(
1001
+ runtime: Optional[Any], available_tools: Dict[str, List[ToolInfo]]
1002
+ ) -> Dict[str, Any]:
1003
+ """Run context enrichment tools and return their results.
1004
+
1005
+ Context enrichment tools are marked with context_enrichment=True in their ToolInfo.
1006
+ They are automatically executed during context gathering to provide additional
1007
+ information for action selection (e.g., listing available Home Assistant entities).
1008
+
1009
+ Args:
1010
+ runtime: The runtime object with service_registry and bus_manager
1011
+ available_tools: Already collected available tools by adapter type
1012
+
1013
+ Returns:
1014
+ Dict mapping "adapter_type:tool_name" to tool execution results
1015
+ """
1016
+ enrichment_results: Dict[str, Any] = {}
1017
+
1018
+ if not _validate_runtime_capabilities(runtime):
1019
+ return enrichment_results
1020
+
1021
+ assert runtime is not None
1022
+
1023
+ enrichment_tools = _collect_enrichment_tools(available_tools)
1024
+ if not enrichment_tools:
1025
+ _log_no_enrichment_tools(available_tools)
1026
+ return enrichment_results
1027
+
1028
+ tool_services = _get_tool_services(runtime.service_registry)
1029
+
1030
+ for adapter_type, tool in enrichment_tools:
1031
+ try:
1032
+ tool_key, result = await _execute_enrichment_tool(tool_services, adapter_type, tool)
1033
+ if result is not None:
1034
+ enrichment_results[tool_key] = result
1035
+ except Exception as e:
1036
+ tool_key = f"{adapter_type}:{tool.name}"
1037
+ logger.error(f"[CONTEXT_ENRICHMENT] Failed to execute {tool_key}: {e}")
1038
+ enrichment_results[tool_key] = {"error": str(e)}
1039
+
1040
+ logger.info(f"[CONTEXT_ENRICHMENT] Collected {len(enrichment_results)} enrichment results")
1041
+ return enrichment_results
1042
+
1043
+
1044
+ # =============================================================================
1045
+ # 8. USER MANAGEMENT
1046
+ # =============================================================================
1047
+
1048
+
1049
+ def _extract_user_from_task_context(task: Optional[Task], user_ids: Set[str]) -> None:
1050
+ """Extract user ID from task context."""
1051
+ if task and task.context:
1052
+ logger.debug(f"[USER EXTRACTION] Task context exists, user_id value: {repr(task.context.user_id)}")
1053
+ if task.context.user_id:
1054
+ user_ids.add(str(task.context.user_id))
1055
+ logger.debug(f"[USER EXTRACTION] Found user {task.context.user_id} from task context")
1056
+ else:
1057
+ logger.debug("[USER EXTRACTION] Task context.user_id is None or empty")
1058
+ else:
1059
+ logger.debug(f"[USER EXTRACTION] Task or task.context is None (task={task is not None})")
1060
+
1061
+
1062
+ def _extract_users_from_thought_content(thought: Any, user_ids: Set[str]) -> None:
1063
+ """Extract user IDs from thought content patterns."""
1064
+ if not thought:
1065
+ return
1066
+
1067
+ thought_content = getattr(thought, "content", "") or ""
1068
+
1069
+ # Discord user ID pattern
1070
+ discord_mentions = re.findall(r"<@(\d+)>", thought_content)
1071
+ if discord_mentions:
1072
+ user_ids.update(discord_mentions)
1073
+ logger.debug(f"[USER EXTRACTION] Found {len(discord_mentions)} users from Discord mentions: {discord_mentions}")
1074
+
1075
+ # Also look for "ID: <number>" pattern
1076
+ id_mentions = re.findall(r"ID:\s*(\d+)", thought_content)
1077
+ if id_mentions:
1078
+ user_ids.update(id_mentions)
1079
+ logger.debug(f"[USER EXTRACTION] Found {len(id_mentions)} users from ID patterns: {id_mentions}")
1080
+
1081
+
1082
+ def _extract_user_from_thought_context(thought: Any, user_ids: Set[str]) -> None:
1083
+ """Extract user ID from thought context."""
1084
+ if hasattr(thought, "context") and thought.context:
1085
+ if hasattr(thought.context, "user_id") and thought.context.user_id:
1086
+ user_ids.add(str(thought.context.user_id))
1087
+ logger.debug(f"[USER EXTRACTION] Found user {thought.context.user_id} from thought context")
1088
+
1089
+
1090
+ def _extract_users_from_correlation_history(task: Optional[Task], user_ids: Set[str]) -> None:
1091
+ """Extract user IDs from correlation history database."""
1092
+ if not (task and task.context and task.context.correlation_id):
1093
+ return
1094
+
1095
+ try:
1096
+ from ciris_engine.logic.persistence.db.dialect import get_adapter
1097
+
1098
+ adapter = get_adapter()
1099
+
1100
+ with persistence.get_db_connection() as conn:
1101
+ cursor = conn.cursor()
1102
+
1103
+ # Build dialect-specific SQL
1104
+ if adapter.is_postgresql():
1105
+ sql = """
1106
+ SELECT DISTINCT tags->>'user_id' as user_id
1107
+ FROM service_correlations
1108
+ WHERE correlation_id = %s
1109
+ AND tags->>'user_id' IS NOT NULL
1110
+ """
1111
+ else:
1112
+ sql = """
1113
+ SELECT DISTINCT json_extract(tags, '$.user_id') as user_id
1114
+ FROM service_correlations
1115
+ WHERE correlation_id = ?
1116
+ AND json_extract(tags, '$.user_id') IS NOT NULL
1117
+ """
1118
+
1119
+ cursor.execute(sql, (task.context.correlation_id,))
1120
+ correlation_users = cursor.fetchall()
1121
+ for row in correlation_users:
1122
+ if row["user_id"]:
1123
+ user_ids.add(str(row["user_id"]))
1124
+ logger.debug(f"[USER EXTRACTION] Found user {row['user_id']} from correlation history")
1125
+ except Exception as e:
1126
+ logger.warning(f"[USER EXTRACTION] Failed to extract users from correlation history: {e}")
1127
+
1128
+
1129
+ def _extract_user_ids_from_context(task: Optional[Task], thought: Any) -> Set[str]:
1130
+ """Extract user IDs from task, thought, and correlation history."""
1131
+ user_ids_to_enrich: Set[str] = set()
1132
+
1133
+ # Extract from all sources using helper functions
1134
+ _extract_user_from_task_context(task, user_ids_to_enrich)
1135
+ _extract_users_from_thought_content(thought, user_ids_to_enrich)
1136
+ _extract_user_from_thought_context(thought, user_ids_to_enrich)
1137
+ _extract_users_from_correlation_history(task, user_ids_to_enrich)
1138
+
1139
+ logger.info(f"[USER EXTRACTION] Total users to enrich: {len(user_ids_to_enrich)} users: {user_ids_to_enrich}")
1140
+ return user_ids_to_enrich
1141
+
1142
+
1143
+ def _json_serial_for_users(obj: Any) -> Any:
1144
+ """JSON serializer for user profile data."""
1145
+ if hasattr(obj, "isoformat"):
1146
+ return obj.isoformat()
1147
+ if hasattr(obj, "model_dump"):
1148
+ return obj.model_dump()
1149
+ return str(obj)
1150
+
1151
+
1152
+ def _should_skip_user_enrichment(user_id: str, existing_user_ids: Set[str]) -> bool:
1153
+ """Check if user should be skipped during enrichment."""
1154
+ if user_id in existing_user_ids:
1155
+ logger.debug(f"[USER EXTRACTION] User {user_id} already exists, skipping")
1156
+ return True
1157
+ return False
1158
+
1159
+
1160
+ def _create_user_memory_query(user_id: str) -> MemoryQuery:
1161
+ """Create memory query for user enrichment."""
1162
+ return MemoryQuery(
1163
+ node_id=f"user/{user_id}",
1164
+ scope=GraphScope.LOCAL,
1165
+ type=NodeType.USER,
1166
+ include_edges=True,
1167
+ depth=2,
1168
+ )
1169
+
1170
+
1171
+ def _determine_if_admin_user(user_id: str) -> bool:
1172
+ """Determine if a user_id represents an admin or system user."""
1173
+ # Check for admin patterns
1174
+ user_id_lower = user_id.lower()
1175
+ return user_id_lower.startswith("wa-") or user_id_lower == "admin" or user_id_lower.startswith("admin_")
1176
+
1177
+
1178
+ async def _create_default_user_node(
1179
+ user_id: str, memory_service: LocalGraphMemoryService, channel_id: Optional[str]
1180
+ ) -> Optional[GraphNode]:
1181
+ """Create a new user node with appropriate defaults.
1182
+
1183
+ Creates minimal user profile for new users:
1184
+ - Admin/WA users: minimal profile with system defaults
1185
+ - Regular users: basic profile ready for user settings
1186
+ """
1187
+ try:
1188
+ # Determine user type
1189
+ is_admin = _determine_if_admin_user(user_id)
1190
+
1191
+ # Create timestamp for first_seen
1192
+ current_time = datetime.now(timezone.utc)
1193
+
1194
+ # Build base attributes as JSON-compatible dict
1195
+ base_attributes: JSONDict = {
1196
+ "user_id": user_id,
1197
+ "display_name": "Admin" if is_admin else f"User_{user_id}",
1198
+ "first_seen": current_time.isoformat(),
1199
+ "created_by": "UserEnrichment",
1200
+ }
1201
+
1202
+ # Add channel if provided
1203
+ if channel_id:
1204
+ base_attributes["channels"] = [channel_id]
1205
+
1206
+ # Add role-specific defaults
1207
+ if is_admin:
1208
+ # Admin users get minimal profile
1209
+ base_attributes["trust_level"] = 1.0
1210
+ base_attributes["is_wa"] = user_id.lower().startswith("wa-")
1211
+ else:
1212
+ # Regular users get basic profile
1213
+ base_attributes["trust_level"] = 0.5
1214
+ base_attributes["communication_style"] = "formal"
1215
+ base_attributes["preferred_language"] = "en"
1216
+ base_attributes["timezone"] = "UTC"
1217
+
1218
+ # Create GraphNode
1219
+ new_node = GraphNode(
1220
+ id=f"user/{user_id}",
1221
+ type=NodeType.USER,
1222
+ scope=GraphScope.LOCAL,
1223
+ attributes=base_attributes,
1224
+ )
1225
+
1226
+ # Save to memory graph
1227
+ logger.info(
1228
+ f"[USER EXTRACTION] Creating new user node for {user_id} (admin={is_admin}) with attributes: {list(base_attributes.keys())}"
1229
+ )
1230
+ await memory_service.memorize(new_node)
1231
+
1232
+ return new_node
1233
+
1234
+ except Exception as e:
1235
+ logger.error(f"Failed to create default user node for {user_id}: {e}")
1236
+ return None
1237
+
1238
+
1239
+ async def _process_user_node_for_profile(
1240
+ user_node: Any, user_id: str, memory_service: LocalGraphMemoryService, channel_id: Optional[str]
1241
+ ) -> UserProfile:
1242
+ """Process user node to create enriched user profile."""
1243
+ # Extract node attributes using helper
1244
+ attrs = _extract_node_attributes(user_node)
1245
+
1246
+ # Collect connected nodes using helper
1247
+ connected_nodes_info = await _collect_connected_nodes(memory_service, user_id)
1248
+
1249
+ # Parse datetime fields safely
1250
+ last_interaction = _parse_datetime_safely(attrs.get("last_seen"), "last_seen", user_id)
1251
+ created_at = _parse_datetime_safely(attrs.get("first_seen") or attrs.get("created_at"), "created_at", user_id)
1252
+
1253
+ # Create user profile using helper
1254
+ user_profile = _create_user_profile_from_node(user_id, attrs, connected_nodes_info, last_interaction, created_at)
1255
+
1256
+ # Collect cross-channel messages and add to profile notes
1257
+ if channel_id:
1258
+ recent_messages = await _collect_cross_channel_messages(user_id, channel_id)
1259
+ if recent_messages:
1260
+ # Ensure notes is not None before concatenating
1261
+ current_notes = user_profile.notes or ""
1262
+ user_profile.notes = current_notes + (
1263
+ f"\nRecent messages from other channels: {json.dumps(recent_messages, default=_json_serial_for_users)}"
1264
+ )
1265
+
1266
+ logger.info(
1267
+ f"Added user profile for {user_id} with attributes: {list(attrs.keys())} and {len(connected_nodes_info)} connected nodes"
1268
+ )
1269
+ return user_profile
1270
+
1271
+
1272
+ async def _enrich_single_user_profile(
1273
+ user_id: str, memory_service: LocalGraphMemoryService, channel_id: Optional[str]
1274
+ ) -> Optional[UserProfile]:
1275
+ """Enrich a single user profile from memory graph.
1276
+
1277
+ If no user node exists, creates one automatically with appropriate defaults.
1278
+ This ensures user_profiles is never empty in system snapshots.
1279
+ """
1280
+ try:
1281
+ # Query user node with ALL attributes
1282
+ user_query = _create_user_memory_query(user_id)
1283
+ logger.debug(f"[DEBUG] Querying memory for user/{user_id}")
1284
+ user_results = await memory_service.recall(user_query)
1285
+ logger.debug(
1286
+ f"[USER EXTRACTION] Query returned {len(user_results) if user_results else 0} results for user {user_id}"
1287
+ )
1288
+
1289
+ if user_results:
1290
+ user_node = user_results[0]
1291
+ return await _process_user_node_for_profile(user_node, user_id, memory_service, channel_id)
1292
+
1293
+ # No user node exists - create one automatically
1294
+ logger.info(f"[USER EXTRACTION] No node found for user/{user_id}, creating new user node with defaults")
1295
+ new_user_node = await _create_default_user_node(user_id, memory_service, channel_id)
1296
+
1297
+ if new_user_node:
1298
+ return await _process_user_node_for_profile(new_user_node, user_id, memory_service, channel_id)
1299
+
1300
+ except Exception as e:
1301
+ logger.warning(f"Failed to enrich user {user_id}: {e}")
1302
+
1303
+ return None
1304
+
1305
+
1306
+ async def _enrich_user_profiles(
1307
+ memory_service: LocalGraphMemoryService,
1308
+ user_ids: Set[str],
1309
+ channel_id: Optional[str],
1310
+ existing_profiles: List[UserProfile],
1311
+ ) -> List[UserProfile]:
1312
+ """Enrich user profiles from memory graph with comprehensive data."""
1313
+ existing_user_ids = {p.user_id for p in existing_profiles}
1314
+
1315
+ for user_id in user_ids:
1316
+ logger.debug(f"[USER EXTRACTION] Processing user {user_id}")
1317
+
1318
+ if _should_skip_user_enrichment(user_id, existing_user_ids):
1319
+ continue
1320
+
1321
+ user_profile = await _enrich_single_user_profile(user_id, memory_service, channel_id)
1322
+ if user_profile:
1323
+ existing_profiles.append(user_profile)
1324
+
1325
+ return existing_profiles
1326
+
1327
+
1328
+ # =============================================================================
1329
+ # TIME LOCALIZATION HELPERS
1330
+ # =============================================================================
1331
+
1332
+
1333
+ def _get_localized_times(time_service: TimeServiceProtocol) -> LocalizedTimeData:
1334
+ """Get current time localized to LONDON, CHICAGO, and TOKYO timezones.
1335
+
1336
+ FAILS FAST AND LOUD if time_service is None.
1337
+
1338
+ Cross-platform compatible: Falls back to UTC offsets on Windows where
1339
+ IANA timezone database may not be available.
1340
+ """
1341
+ if time_service is None:
1342
+ raise RuntimeError(
1343
+ "CRITICAL: time_service is None! Cannot get localized times. "
1344
+ "The system must be properly initialized with a time service."
1345
+ )
1346
+
1347
+ from datetime import datetime, timedelta
1348
+ from datetime import timezone as dt_timezone
1349
+ from datetime import tzinfo
1350
+ from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
1351
+
1352
+ # Get current UTC time from time service
1353
+ utc_time = time_service.now()
1354
+ if not isinstance(utc_time, datetime):
1355
+ raise RuntimeError(
1356
+ f"CRITICAL: time_service.now() returned {type(utc_time)}, expected datetime. "
1357
+ f"Time service is not properly configured."
1358
+ )
1359
+
1360
+ # Define timezone objects using zoneinfo with Windows fallback
1361
+ # Windows may not have IANA timezone database, use fixed UTC offsets as fallback
1362
+ london_tz: tzinfo
1363
+ try:
1364
+ london_tz = ZoneInfo("Europe/London")
1365
+ except ZoneInfoNotFoundError:
1366
+ # GMT/BST: UTC+0/+1 - use UTC+0 as conservative fallback
1367
+ london_tz = dt_timezone(timedelta(hours=0))
1368
+ logger.warning("Europe/London timezone not found, using UTC+0 fallback")
1369
+
1370
+ chicago_tz: tzinfo
1371
+ try:
1372
+ chicago_tz = ZoneInfo("America/Chicago")
1373
+ except ZoneInfoNotFoundError:
1374
+ # CST/CDT: UTC-6/-5 - use UTC-6 as conservative fallback
1375
+ chicago_tz = dt_timezone(timedelta(hours=-6))
1376
+ logger.warning("America/Chicago timezone not found, using UTC-6 fallback")
1377
+
1378
+ tokyo_tz: tzinfo
1379
+ try:
1380
+ tokyo_tz = ZoneInfo("Asia/Tokyo")
1381
+ except ZoneInfoNotFoundError:
1382
+ # JST: UTC+9 (no DST)
1383
+ tokyo_tz = dt_timezone(timedelta(hours=9))
1384
+ logger.warning("Asia/Tokyo timezone not found, using UTC+9 fallback")
1385
+
1386
+ # Convert to localized times
1387
+ utc_iso = utc_time.isoformat()
1388
+ london_time = utc_time.astimezone(london_tz).isoformat()
1389
+ chicago_time = utc_time.astimezone(chicago_tz).isoformat()
1390
+ tokyo_time = utc_time.astimezone(tokyo_tz).isoformat()
1391
+
1392
+ return LocalizedTimeData(
1393
+ utc=utc_iso,
1394
+ london=london_time,
1395
+ chicago=chicago_time,
1396
+ tokyo=tokyo_time,
1397
+ )
1398
+
1399
+
1400
+ # =============================================================================
1401
+ # USER PROFILE ENRICHMENT HELPERS (Reusable for other adapters)
1402
+ # =============================================================================
1403
+
1404
+
1405
+ def get_known_user_fields() -> Set[str]:
1406
+ """Get set of known UserProfile fields."""
1407
+ return {
1408
+ "user_id",
1409
+ "display_name",
1410
+ "username", # Alternative for display_name
1411
+ "created_at",
1412
+ "first_seen", # Alternative for created_at
1413
+ "preferred_language",
1414
+ "language", # Alternative for preferred_language
1415
+ "timezone",
1416
+ "communication_style",
1417
+ # User-configurable preferences (protected from agent, visible in snapshot)
1418
+ "user_preferred_name",
1419
+ "location",
1420
+ "interaction_preferences",
1421
+ "oauth_name",
1422
+ # User interaction tracking
1423
+ "total_interactions",
1424
+ "last_interaction",
1425
+ "last_seen", # Alternative for last_interaction
1426
+ "trust_level",
1427
+ "is_wa",
1428
+ "permissions",
1429
+ "restrictions",
1430
+ "consent_stream",
1431
+ "consent_expires_at",
1432
+ "partnership_requested_at",
1433
+ "partnership_approved",
1434
+ }
1435
+
1436
+
1437
+ def build_user_profile_from_node(
1438
+ user_id: str,
1439
+ attrs: JSONDict,
1440
+ connected_nodes_info: List[ConnectedNodeInfo],
1441
+ last_interaction: Optional[datetime],
1442
+ created_at: Optional[datetime],
1443
+ ) -> UserProfile:
1444
+ """Create a UserProfile from node attributes using standardized approach."""
1445
+ # Get known field definitions
1446
+ known_fields = get_known_user_fields()
1447
+
1448
+ # Collect arbitrary attributes in memorized_attributes
1449
+ memorized_attributes = collect_memorized_attributes(attrs, known_fields)
1450
+
1451
+ # Create connected nodes summary for notes
1452
+ notes_content = ""
1453
+ if connected_nodes_info:
1454
+ # Convert typed objects to dict for JSON serialization
1455
+ connected_data = [node.model_dump() for node in connected_nodes_info]
1456
+ notes_content = f"Connected nodes: {json.dumps(connected_data, default=_json_serial_for_users)}"
1457
+
1458
+ # Parse consent information from node attributes
1459
+ consent_expires_at = _parse_datetime_safely(attrs.get("consent_expires_at"), "consent_expires_at", user_id)
1460
+ partnership_requested_at = _parse_datetime_safely(
1461
+ attrs.get("partnership_requested_at"), "partnership_requested_at", user_id
1462
+ )
1463
+
1464
+ return UserProfile(
1465
+ user_id=user_id,
1466
+ display_name=attrs.get("username", attrs.get("display_name", f"User_{user_id}")),
1467
+ created_at=created_at or datetime.now(),
1468
+ preferred_language=attrs.get("language", attrs.get("preferred_language", "en")),
1469
+ timezone=attrs.get("timezone", "UTC"),
1470
+ communication_style=attrs.get("communication_style", "formal"),
1471
+ # User-configurable preferences (protected from agent modification in MANAGED_USER_ATTRIBUTES)
1472
+ user_preferred_name=attrs.get("user_preferred_name"),
1473
+ location=attrs.get("location"),
1474
+ interaction_preferences=attrs.get("interaction_preferences"),
1475
+ oauth_name=attrs.get("oauth_name"),
1476
+ total_interactions=attrs.get("total_interactions", 0),
1477
+ last_interaction=last_interaction,
1478
+ trust_level=attrs.get("trust_level", 0.5),
1479
+ is_wa=attrs.get("is_wa", False),
1480
+ permissions=attrs.get("permissions", []),
1481
+ restrictions=attrs.get("restrictions", []),
1482
+ # Consent relationship state
1483
+ consent_stream=attrs.get("consent_stream", "TEMPORARY"),
1484
+ consent_expires_at=consent_expires_at,
1485
+ partnership_requested_at=partnership_requested_at,
1486
+ partnership_approved=attrs.get("partnership_approved", False),
1487
+ # Store arbitrary attributes the agent memorized
1488
+ memorized_attributes=memorized_attributes,
1489
+ # Store connected nodes info in notes
1490
+ notes=notes_content,
1491
+ )
1492
+
1493
+
1494
+ # Legacy function - now uses standardized extract_node_attributes
1495
+ def _extract_node_attributes(
1496
+ node: GraphNode,
1497
+ ) -> JSONDict:
1498
+ """Extract attributes from a graph node, handling both dict and Pydantic models."""
1499
+ return extract_node_attributes(node)
1500
+
1501
+
1502
+ def _parse_datetime_safely(raw_value: Any, field_name: str, user_id: str) -> Optional[datetime]:
1503
+ """Parse datetime value safely, returning None for any invalid data."""
1504
+ if raw_value is None:
1505
+ return None
1506
+
1507
+ if isinstance(raw_value, datetime):
1508
+ return raw_value
1509
+ elif isinstance(raw_value, str):
1510
+ try:
1511
+ # Try to parse as ISO datetime
1512
+ return datetime.fromisoformat(raw_value.replace("Z", "+00:00"))
1513
+ except (ValueError, AttributeError):
1514
+ logger.warning(
1515
+ f"FIELD_FAILED_VALIDATION: User {user_id} has invalid {field_name}: '{raw_value}', using None"
1516
+ )
1517
+ return None
1518
+ else:
1519
+ logger.warning(
1520
+ f"FIELD_FAILED_VALIDATION: User {user_id} has non-datetime {field_name}: {type(raw_value)}, using None"
1521
+ )
1522
+ return None
1523
+
1524
+
1525
+ async def _collect_connected_nodes(memory_service: LocalGraphMemoryService, user_id: str) -> List[ConnectedNodeInfo]:
1526
+ """Collect connected nodes for a user node from the memory graph."""
1527
+ connected_nodes_info = []
1528
+ try:
1529
+ # Get edges for this user node
1530
+ from ciris_engine.logic.persistence.models.graph import get_edges_for_node
1531
+
1532
+ edges = get_edges_for_node(f"user/{user_id}", GraphScope.LOCAL)
1533
+
1534
+ for edge in edges:
1535
+ # Get the connected node
1536
+ connected_node_id = edge.target if edge.source == f"user/{user_id}" else edge.source
1537
+ connected_query = MemoryQuery(
1538
+ node_id=connected_node_id, scope=GraphScope.LOCAL, include_edges=False, depth=1
1539
+ )
1540
+ connected_results = await memory_service.recall(connected_query)
1541
+ if connected_results:
1542
+ connected_node = connected_results[0]
1543
+ # Extract attributes using our standardized helper
1544
+ connected_attrs = extract_node_attributes(connected_node)
1545
+
1546
+ # Create typed connected node info
1547
+ connected_info = ConnectedNodeInfo(
1548
+ node_id=connected_node.id,
1549
+ node_type=connected_node.type,
1550
+ relationship=edge.relationship,
1551
+ attributes=connected_attrs or {},
1552
+ )
1553
+ connected_nodes_info.append(connected_info)
1554
+ except Exception as e:
1555
+ logger.warning(f"Failed to get connected nodes for user {user_id}: {e}")
1556
+
1557
+ return connected_nodes_info
1558
+
1559
+
1560
+ # Legacy function - now uses standardized build_user_profile_from_node
1561
+ def _create_user_profile_from_node(
1562
+ user_id: str,
1563
+ attrs: JSONDict,
1564
+ connected_nodes_info: List[ConnectedNodeInfo],
1565
+ last_interaction: Optional[datetime],
1566
+ created_at: Optional[datetime],
1567
+ ) -> UserProfile:
1568
+ """Create a UserProfile from node attributes and connected nodes."""
1569
+ return build_user_profile_from_node(user_id, attrs, connected_nodes_info, last_interaction, created_at)
1570
+
1571
+
1572
+ async def _collect_cross_channel_messages(user_id: str, channel_id: str) -> List[JSONDict]:
1573
+ """Collect recent messages from this user in other channels."""
1574
+ recent_messages = []
1575
+ try:
1576
+ from ciris_engine.logic.persistence.db.dialect import get_adapter
1577
+
1578
+ adapter = get_adapter()
1579
+
1580
+ # Query service correlations for user's recent messages
1581
+ with persistence.get_db_connection() as conn:
1582
+ cursor = conn.cursor()
1583
+
1584
+ # Build dialect-specific SQL
1585
+ if adapter.is_postgresql():
1586
+ # PostgreSQL: Cast JSONB to text for LIKE operator
1587
+ sql = """
1588
+ SELECT
1589
+ c.correlation_id,
1590
+ c.handler_name,
1591
+ c.request_data,
1592
+ c.created_at,
1593
+ c.tags
1594
+ FROM service_correlations c
1595
+ WHERE
1596
+ c.tags::text LIKE %s
1597
+ AND c.tags::text NOT LIKE %s
1598
+ AND c.handler_name IN ('ObserveHandler', 'SpeakHandler')
1599
+ ORDER BY c.created_at DESC
1600
+ LIMIT 3
1601
+ """
1602
+ else:
1603
+ # SQLite: tags is TEXT, LIKE works directly
1604
+ sql = """
1605
+ SELECT
1606
+ c.correlation_id,
1607
+ c.handler_name,
1608
+ c.request_data,
1609
+ c.created_at,
1610
+ c.tags
1611
+ FROM service_correlations c
1612
+ WHERE
1613
+ c.tags LIKE ?
1614
+ AND c.tags NOT LIKE ?
1615
+ AND c.handler_name IN ('ObserveHandler', 'SpeakHandler')
1616
+ ORDER BY c.created_at DESC
1617
+ LIMIT 3
1618
+ """
1619
+
1620
+ cursor.execute(sql, (f'%"user_id":"{user_id}"%', f'%"channel_id":"{channel_id}"%'))
1621
+
1622
+ for row in cursor.fetchall():
1623
+ try:
1624
+ tags = json.loads(row["tags"]) if row["tags"] else {}
1625
+ msg_channel = tags.get("channel_id", "unknown")
1626
+ msg_content = "Message in " + msg_channel
1627
+
1628
+ # Try to extract content from request_data
1629
+ if row["request_data"]:
1630
+ req_data = json.loads(row["request_data"])
1631
+ if isinstance(req_data, dict):
1632
+ msg_content = req_data.get("content", req_data.get("message", msg_content))
1633
+
1634
+ recent_messages.append(
1635
+ {
1636
+ "channel": msg_channel,
1637
+ "content": msg_content,
1638
+ "timestamp": (
1639
+ row["created_at"].isoformat()
1640
+ if hasattr(row["created_at"], "isoformat")
1641
+ else str(row["created_at"])
1642
+ ),
1643
+ }
1644
+ )
1645
+ except (json.JSONDecodeError, TypeError, AttributeError, KeyError):
1646
+ # Skip malformed entries
1647
+ pass
1648
+ except Exception as e:
1649
+ logger.warning(f"Failed to collect cross-channel messages for user {user_id}: {e}")
1650
+
1651
+ return recent_messages