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,1692 @@
1
+ """
2
+ TSDB Consolidation Service - Main service class.
3
+
4
+ This service runs every 6 hours to consolidate telemetry and memory data
5
+ into permanent summary records with proper edge connections.
6
+ """
7
+
8
+ import asyncio
9
+ import logging
10
+ import random
11
+ from datetime import datetime, timedelta, timezone
12
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
13
+ from uuid import uuid4
14
+
15
+ if TYPE_CHECKING:
16
+ from ciris_engine.logic.registries.base import ServiceRegistry
17
+
18
+ from ciris_engine.constants import UTC_TIMEZONE_SUFFIX
19
+ from ciris_engine.logic.buses.memory_bus import MemoryBus
20
+ from ciris_engine.logic.services.base_graph_service import BaseGraphService
21
+ from ciris_engine.protocols.infrastructure.base import RegistryAwareServiceProtocol, ServiceRegistryProtocol
22
+ from ciris_engine.protocols.services.lifecycle.time import TimeServiceProtocol
23
+ from ciris_engine.schemas.consent.core import ConsentStream
24
+ from ciris_engine.schemas.runtime.enums import ServiceType
25
+ from ciris_engine.schemas.services.core import ServiceCapabilities, ServiceStatus
26
+ from ciris_engine.schemas.services.graph.consolidation import (
27
+ MetricCorrelationData,
28
+ ServiceInteractionData,
29
+ TaskCorrelationData,
30
+ TraceSpanData,
31
+ TSDBPeriodSummary,
32
+ )
33
+ from ciris_engine.schemas.services.graph.query_results import ServiceCorrelationQueryResult, TSDBNodeQueryResult
34
+ from ciris_engine.schemas.services.graph.tsdb_models import SummaryAttributes
35
+ from ciris_engine.schemas.services.graph_core import GraphNode, NodeType
36
+ from ciris_engine.schemas.services.operations import MemoryOpStatus
37
+
38
+ from .consolidators import (
39
+ AuditConsolidator,
40
+ ConversationConsolidator,
41
+ MemoryConsolidator,
42
+ MetricsConsolidator,
43
+ TaskConsolidator,
44
+ TraceConsolidator,
45
+ )
46
+ from .edge_manager import EdgeManager
47
+ from .period_manager import PeriodManager
48
+ from .query_manager import QueryManager
49
+
50
+ logger = logging.getLogger(__name__)
51
+
52
+
53
+ class TSDBConsolidationService(BaseGraphService, RegistryAwareServiceProtocol):
54
+ """
55
+ Refactored TSDB Consolidation Service.
56
+
57
+ Key improvements:
58
+ 1. Consolidates BOTH graph nodes AND service correlations
59
+ 2. Creates proper edges in graph_edges table
60
+ 3. Links summaries to ALL nodes in the period
61
+ 4. Includes task summaries with outcomes
62
+ """
63
+
64
+ def __init__(
65
+ self,
66
+ memory_bus: Optional[MemoryBus] = None,
67
+ time_service: Optional[TimeServiceProtocol] = None,
68
+ consolidation_interval_hours: int = 6,
69
+ raw_retention_hours: int = 24,
70
+ db_path: Optional[str] = None,
71
+ ) -> None:
72
+ """
73
+ Initialize the consolidation service.
74
+
75
+ Args:
76
+ memory_bus: Bus for memory operations
77
+ time_service: Time service for consistent timestamps
78
+ consolidation_interval_hours: How often to run (default: 6)
79
+ raw_retention_hours: How long to keep raw data (default: 24)
80
+ db_path: Database path to use (if not provided, uses default)
81
+ """
82
+ super().__init__(memory_bus=memory_bus, time_service=time_service)
83
+ self.service_name = "TSDBConsolidationService"
84
+ self.db_path = db_path
85
+
86
+ # Initialize components
87
+ self._period_manager = PeriodManager(consolidation_interval_hours)
88
+ self._query_manager = QueryManager(memory_bus, db_path=db_path)
89
+ self._edge_manager = EdgeManager(db_path=db_path)
90
+
91
+ # Initialize all consolidators
92
+ self._metrics_consolidator = MetricsConsolidator(memory_bus)
93
+ self._memory_consolidator = MemoryConsolidator(memory_bus)
94
+ self._task_consolidator = TaskConsolidator(memory_bus)
95
+ self._conversation_consolidator = ConversationConsolidator(memory_bus, time_service)
96
+ self._trace_consolidator = TraceConsolidator(memory_bus)
97
+ self._audit_consolidator = AuditConsolidator(memory_bus, time_service)
98
+
99
+ self._consolidation_interval = timedelta(hours=consolidation_interval_hours)
100
+ self._raw_retention = timedelta(hours=raw_retention_hours)
101
+
102
+ # Load consolidation intervals from config
103
+ self._load_consolidation_config()
104
+
105
+ # Retention periods for different levels
106
+ self._basic_retention = timedelta(days=7) # Keep basic summaries for 7 days
107
+ self._extensive_retention = timedelta(days=30) # Keep daily summaries for 30 days
108
+
109
+ # Task management
110
+ self._consolidation_task: Optional[asyncio.Task[None]] = None
111
+ self._running = False
112
+
113
+ # Track last successful consolidation
114
+ self._last_consolidation: Optional[datetime] = None
115
+ self._last_extensive_consolidation: Optional[datetime] = None
116
+ self._last_profound_consolidation: Optional[datetime] = None
117
+ self._start_time: Optional[datetime] = None
118
+
119
+ # Telemetry tracking variables
120
+ self._basic_consolidations = 0
121
+ self._extensive_consolidations = 0
122
+ self._profound_consolidations = 0
123
+ self._records_consolidated = 0
124
+ self._records_deleted = 0
125
+ self._compression_ratio = 1.0
126
+ self._last_consolidation_duration = 0.0
127
+ self._consolidation_errors = 0
128
+ self._start_time = None # Will be set when service starts
129
+
130
+ def _load_consolidation_config(self) -> None:
131
+ """Load consolidation configuration from essential config."""
132
+ # Fixed intervals for calendar alignment
133
+ self._basic_interval = timedelta(hours=6) # 00:00, 06:00, 12:00, 18:00 UTC
134
+ self._extensive_interval = timedelta(days=7) # Weekly on Mondays
135
+ self._profound_interval = timedelta(days=30) # Monthly on 1st
136
+
137
+ # Load configurable values
138
+ # Set default configurable values
139
+ self._profound_target_mb_per_day = 20.0 # Default 20MB/day
140
+ logger.info(f"TSDB profound consolidation target: {self._profound_target_mb_per_day} MB/day")
141
+
142
+ async def attach_registry(self, registry: "ServiceRegistryProtocol") -> None:
143
+ """
144
+ Attach service registry for service discovery.
145
+
146
+ Implements RegistryAwareServiceProtocol to enable proper initialization
147
+ of time service dependency.
148
+
149
+ Args:
150
+ registry: Service registry providing access to services
151
+ """
152
+ self._service_registry = registry
153
+
154
+ # Only get time service from registry if not provided
155
+ if not self._time_service and registry:
156
+ from ciris_engine.schemas.runtime.enums import ServiceType
157
+
158
+ time_services = registry.get_services_by_type(ServiceType.TIME)
159
+ if time_services:
160
+ self._time_service = time_services[0]
161
+
162
+ def _now(self) -> datetime:
163
+ """Get current time from time service."""
164
+ return self._time_service.now() if self._time_service else datetime.now(timezone.utc)
165
+
166
+ async def start(self) -> None:
167
+ """Start the consolidation service."""
168
+ if self._running:
169
+ logger.warning("TSDBConsolidationService already running")
170
+ return
171
+
172
+ await super().start()
173
+ self._running = True
174
+ self._start_time = self._now()
175
+
176
+ # Start single consolidation loop that handles basic → extensive → profound sequentially
177
+ # The loop will consolidate missed windows first before entering the regular schedule
178
+ self._consolidation_task = asyncio.create_task(self._consolidation_loop())
179
+ logger.info(
180
+ f"TSDBConsolidationService started - Basic: {self._basic_interval}, Extensive: {self._extensive_interval}, Profound: {self._profound_interval}"
181
+ )
182
+
183
+ async def stop(self) -> None:
184
+ """Stop the consolidation service gracefully."""
185
+ self._running = False
186
+
187
+ # Cancel any ongoing consolidation task
188
+ if self._consolidation_task and not self._consolidation_task.done():
189
+ logger.info("Cancelling ongoing consolidation task...")
190
+ self._consolidation_task.cancel()
191
+ try:
192
+ await self._consolidation_task
193
+ except asyncio.CancelledError:
194
+ logger.debug("Consolidation task cancelled successfully")
195
+ # Only re-raise if we're being cancelled ourselves
196
+ current = asyncio.current_task()
197
+ if current and current.cancelled():
198
+ raise
199
+ # Otherwise, this is a normal stop - don't propagate the cancellation
200
+ except Exception as e:
201
+ logger.error(f"Error cancelling consolidation task: {e}")
202
+
203
+ # Note: Final consolidation should be run explicitly by runtime BEFORE
204
+ # stopping services, not during stop() to avoid dependency issues
205
+
206
+ await super().stop()
207
+ logger.info("TSDBConsolidationService stopped")
208
+
209
+ async def _consolidation_loop(self) -> None:
210
+ """
211
+ Main consolidation loop that runs every 6 hours.
212
+
213
+ The occurrence that wins the lock becomes "the consolidator" and runs:
214
+ 1. Basic consolidation (always)
215
+ 2. Extensive consolidation (if it's Monday)
216
+ 3. Profound consolidation (if it's the 1st of the month)
217
+
218
+ This ensures only ONE occurrence handles all consolidation types sequentially,
219
+ preventing race conditions between consolidation levels.
220
+ """
221
+ # First, consolidate any missed windows in the background
222
+ # This runs asynchronously and doesn't block the main init sequence
223
+ try:
224
+ await self._consolidate_missed_windows()
225
+ except Exception as e:
226
+ logger.error(f"Error consolidating missed windows: {e}", exc_info=True)
227
+ # Continue anyway - don't let missed window errors block regular operation
228
+
229
+ while self._running:
230
+ try:
231
+ # Calculate next run time
232
+ next_run = self._period_manager.get_next_period_start(self._now())
233
+ wait_seconds = (next_run - self._now()).total_seconds()
234
+
235
+ if wait_seconds > 0:
236
+ logger.info(f"Next consolidation at {next_run} ({wait_seconds:.0f}s)")
237
+ await asyncio.sleep(wait_seconds)
238
+
239
+ if self._running:
240
+ # Add random delay (30-600s) to prevent thundering herd
241
+ # when multiple instances start simultaneously
242
+ jitter_seconds = random.randint(30, 600)
243
+ logger.info(f"Adding random jitter delay of {jitter_seconds}s to prevent race conditions")
244
+ await asyncio.sleep(jitter_seconds)
245
+
246
+ if self._running:
247
+ # Run basic consolidation
248
+ # The occurrence that wins the lock becomes "the consolidator" for this run
249
+ await self._run_consolidation()
250
+
251
+ # If we're the consolidator, check if we should run extensive/profound
252
+ now = self._now()
253
+
254
+ # Check if it's Monday (0 = Monday) for extensive consolidation
255
+ if now.weekday() == 0:
256
+ logger.info("It's Monday - running extensive consolidation")
257
+ await self._run_extensive_consolidation()
258
+
259
+ # Check if it's the 1st of the month for profound consolidation
260
+ if now.day == 1:
261
+ logger.info("It's the 1st of the month - running profound consolidation")
262
+ self._run_profound_consolidation()
263
+
264
+ except asyncio.CancelledError:
265
+ logger.debug("Consolidation loop cancelled")
266
+ raise # Re-raise to properly exit the task
267
+ except Exception as e:
268
+ logger.error(f"Consolidation loop error: {e}", exc_info=True)
269
+ await asyncio.sleep(300) # 5 minutes
270
+
271
+ async def _run_consolidation(self) -> None:
272
+ """Run a single consolidation cycle."""
273
+ consolidation_start = self._now()
274
+ total_records_processed = 0
275
+ total_summaries_created = 0
276
+ cleanup_stats = {"nodes_deleted": 0, "edges_deleted": 0}
277
+
278
+ try:
279
+ logger.info("=" * 60)
280
+ logger.info("Starting TSDB consolidation cycle")
281
+ logger.info(f"Started at: {consolidation_start.isoformat()}")
282
+
283
+ # Find periods that need consolidation
284
+ now = self._now()
285
+ cutoff_time = now - timedelta(hours=24)
286
+
287
+ # Get oldest unconsolidated data
288
+ oldest_data = self._find_oldest_unconsolidated_period()
289
+ if not oldest_data:
290
+ logger.info("No unconsolidated data found - nothing to consolidate")
291
+ return
292
+
293
+ logger.info(f"Oldest unconsolidated data from: {oldest_data.isoformat()}")
294
+ logger.info(f"Will consolidate up to: {cutoff_time.isoformat()}")
295
+
296
+ # Process periods
297
+ current_start, _ = self._period_manager.get_period_boundaries(oldest_data)
298
+ periods_consolidated = 0
299
+ max_periods = 30 # Limit per run
300
+
301
+ while current_start < cutoff_time and periods_consolidated < max_periods:
302
+ current_end = current_start + self._consolidation_interval
303
+
304
+ # Try to acquire lock for this period to prevent duplicate consolidation
305
+ lock_acquired = self._query_manager.acquire_period_lock(current_start)
306
+
307
+ if not lock_acquired:
308
+ logger.info(f"Period {current_start.isoformat()} is locked by another instance, skipping")
309
+ current_start = current_end
310
+ continue
311
+
312
+ try:
313
+ # Check if already consolidated (double-check after acquiring lock)
314
+ if not self._query_manager.check_period_consolidated(current_start):
315
+ period_start_time = self._now()
316
+ logger.info(f"Consolidating period: {current_start.isoformat()} to {current_end.isoformat()}")
317
+
318
+ # Count records in this period before consolidation
319
+ period_records = len(self._query_manager.query_all_nodes_in_period(current_start, current_end))
320
+ total_records_processed += period_records
321
+
322
+ summaries = await self._consolidate_period(current_start, current_end)
323
+ if summaries:
324
+ total_summaries_created += len(summaries)
325
+ period_duration = (self._now() - period_start_time).total_seconds()
326
+ logger.info(
327
+ f" ✓ Created {len(summaries)} summaries from {period_records} records in {period_duration:.2f}s"
328
+ )
329
+ periods_consolidated += 1
330
+ else:
331
+ logger.info(" - No summaries created for period (no data)")
332
+ else:
333
+ logger.info(f"Period {current_start.isoformat()} already consolidated by another instance")
334
+
335
+ finally:
336
+ # Always release the lock
337
+ self._query_manager.release_period_lock(current_start)
338
+
339
+ current_start = current_end
340
+
341
+ if periods_consolidated > 0:
342
+ logger.info(f"Consolidation complete: {periods_consolidated} periods processed")
343
+ logger.info(f" - Total records processed: {total_records_processed}")
344
+ logger.info(f" - Total summaries created: {total_summaries_created}")
345
+ if total_records_processed > 0:
346
+ compression_ratio = total_records_processed / max(total_summaries_created, 1)
347
+ logger.info(f" - Compression ratio: {compression_ratio:.1f}:1")
348
+
349
+ # Cleanup old data (run in thread to avoid blocking event loop)
350
+ cleanup_start = self._now()
351
+ logger.info("Starting cleanup of old consolidated data...")
352
+ # Count nodes before cleanup (logged later)
353
+ len(self._query_manager.query_all_nodes_in_period(now - timedelta(days=30), now))
354
+
355
+ # Run cleanup in thread executor to prevent Discord heartbeat blocking
356
+ loop = asyncio.get_event_loop()
357
+ nodes_deleted = await loop.run_in_executor(None, self._cleanup_old_data)
358
+ cleanup_stats["nodes_deleted"] = nodes_deleted
359
+
360
+ # Cleanup orphaned edges
361
+ edges_deleted = self._edge_manager.cleanup_orphaned_edges()
362
+ cleanup_stats["edges_deleted"] = edges_deleted
363
+
364
+ cleanup_duration = (self._now() - cleanup_start).total_seconds()
365
+ if nodes_deleted > 0 or edges_deleted > 0:
366
+ logger.info(f"Cleanup complete in {cleanup_duration:.2f}s:")
367
+ logger.info(f" - Nodes deleted: {nodes_deleted}")
368
+ logger.info(f" - Edges deleted: {edges_deleted}")
369
+
370
+ self._last_consolidation = now
371
+
372
+ # Final summary
373
+ total_duration = (self._now() - consolidation_start).total_seconds()
374
+ logger.info(f"TSDB consolidation cycle completed in {total_duration:.2f}s")
375
+ logger.info("=" * 60)
376
+
377
+ except Exception as e:
378
+ duration = (self._now() - consolidation_start).total_seconds()
379
+ logger.error(f"Consolidation failed after {duration:.2f}s: {e}", exc_info=True)
380
+ logger.error(f"Partial progress - Records: {total_records_processed}, Summaries: {total_summaries_created}")
381
+
382
+ async def _consolidate_missed_windows(self) -> None:
383
+ """
384
+ Consolidate any missed windows since the last consolidation.
385
+ Called at startup to catch up on any periods missed while shutdown.
386
+ """
387
+ try:
388
+ logger.info("Checking for missed consolidation windows...")
389
+
390
+ # Find the last consolidated period
391
+ last_consolidated = await self._query_manager.get_last_consolidated_period()
392
+
393
+ now = self._now()
394
+ cutoff_time = now - timedelta(hours=24) # Don't go back more than 24 hours
395
+
396
+ if last_consolidated:
397
+ # Start from the period after the last consolidated one
398
+ start_from = last_consolidated + self._consolidation_interval
399
+ logger.info(f"Last consolidated period: {last_consolidated}, starting from: {start_from}")
400
+ else:
401
+ # No previous consolidation found, check for oldest data
402
+ oldest_data = self._find_oldest_unconsolidated_period()
403
+ if not oldest_data:
404
+ logger.info("No unconsolidated data found")
405
+ return
406
+
407
+ # Start from the period containing the oldest data
408
+ start_from, _ = self._period_manager.get_period_boundaries(oldest_data)
409
+ logger.info(f"No previous consolidation found, starting from oldest data: {start_from}")
410
+
411
+ # Don't go back too far
412
+ if start_from < cutoff_time:
413
+ start_from = self._period_manager.get_period_start(cutoff_time)
414
+ logger.info(f"Limiting lookback to 24 hours, adjusted start: {start_from}")
415
+
416
+ # Process all missed periods up to the most recent completed period
417
+ current_period_start = self._period_manager.get_period_start(now)
418
+ periods_consolidated = 0
419
+
420
+ period_start = start_from
421
+ while period_start < current_period_start:
422
+ period_end = period_start + self._consolidation_interval
423
+
424
+ # Check if this period needs consolidation
425
+ if not self._query_manager.check_period_consolidated(period_start):
426
+ # Try to acquire lock for this period - only one occurrence should consolidate
427
+ lock_key = f"missed:{period_start.isoformat()}"
428
+ if self._query_manager._try_acquire_lock(lock_key, "missed", period_start.isoformat()):
429
+ logger.info(f"Acquired lock, consolidating missed period: {period_start} to {period_end}")
430
+
431
+ summaries = await self._consolidate_period(period_start, period_end)
432
+ if summaries:
433
+ logger.info(f"Created {len(summaries)} summaries for missed period {period_start}")
434
+ periods_consolidated += 1
435
+ else:
436
+ logger.debug(f"No data found for period {period_start}")
437
+ else:
438
+ logger.info(f"Another occurrence is consolidating period {period_start}, skipping")
439
+ else:
440
+ logger.debug(f"Period {period_start} already consolidated, checking edges...")
441
+ # Ensure edges exist for this already-consolidated period
442
+ await self._ensure_summary_edges(period_start, period_end)
443
+
444
+ # Move to next period
445
+ period_start = period_end
446
+
447
+ # Safety limit to prevent excessive processing
448
+ if periods_consolidated >= 10:
449
+ logger.warning("Reached limit of 10 periods in missed window consolidation")
450
+ break
451
+
452
+ if periods_consolidated > 0:
453
+ logger.info(f"Successfully consolidated {periods_consolidated} missed periods")
454
+ self._last_consolidation = now
455
+ else:
456
+ logger.info("No missed periods needed consolidation")
457
+
458
+ except Exception as e:
459
+ logger.error(f"Failed to consolidate missed windows: {e}", exc_info=True)
460
+
461
+ def _query_period_data(self, period_start: datetime, period_end: datetime) -> Tuple[
462
+ Dict[str, TSDBNodeQueryResult],
463
+ ServiceCorrelationQueryResult,
464
+ List[TaskCorrelationData],
465
+ ]:
466
+ """Query all data for a consolidation period."""
467
+ nodes_by_type = self._query_manager.query_all_nodes_in_period(period_start, period_end)
468
+ correlations = self._query_manager.query_service_correlations(period_start, period_end)
469
+ tasks = self._query_manager.query_tasks_in_period(period_start, period_end)
470
+ return nodes_by_type, correlations, tasks
471
+
472
+ async def _create_metric_summary(
473
+ self,
474
+ nodes_by_type: Dict[str, TSDBNodeQueryResult],
475
+ correlations: ServiceCorrelationQueryResult,
476
+ period_start: datetime,
477
+ period_end: datetime,
478
+ period_label: str,
479
+ converted_correlations: Dict[str, List[Union[MetricCorrelationData, ServiceInteractionData, TraceSpanData]]],
480
+ ) -> Optional[GraphNode]:
481
+ """Create metrics summary from TSDB nodes and correlations."""
482
+ tsdb_nodes = nodes_by_type.get(
483
+ "tsdb_data", TSDBNodeQueryResult(nodes=[], period_start=period_start, period_end=period_end)
484
+ ).nodes
485
+ metric_correlations = correlations.metric_correlations
486
+
487
+ converted_correlations["metric_datapoint"] = list(metric_correlations)
488
+
489
+ if tsdb_nodes or metric_correlations:
490
+ return await self._metrics_consolidator.consolidate(
491
+ period_start, period_end, period_label, tsdb_nodes, metric_correlations
492
+ )
493
+ return None
494
+
495
+ async def _create_task_summary(
496
+ self,
497
+ tasks: List[TaskCorrelationData],
498
+ period_start: datetime,
499
+ period_end: datetime,
500
+ period_label: str,
501
+ ) -> Optional[GraphNode]:
502
+ """Create task summary from task correlations."""
503
+ if tasks:
504
+ return await self._task_consolidator.consolidate(period_start, period_end, period_label, tasks)
505
+ return None
506
+
507
+ async def _create_conversation_summary(
508
+ self,
509
+ correlations: ServiceCorrelationQueryResult,
510
+ period_start: datetime,
511
+ period_end: datetime,
512
+ period_label: str,
513
+ converted_correlations: Dict[str, List[Union[MetricCorrelationData, ServiceInteractionData, TraceSpanData]]],
514
+ ) -> Optional[GraphNode]:
515
+ """Create conversation summary with user participation edges."""
516
+ service_interactions = correlations.service_interactions
517
+ if not service_interactions:
518
+ return None
519
+
520
+ converted_correlations["service_interaction"] = list(service_interactions)
521
+
522
+ conversation_summary = await self._conversation_consolidator.consolidate(
523
+ period_start, period_end, period_label, service_interactions
524
+ )
525
+ if not conversation_summary:
526
+ return None
527
+
528
+ # Create user participation edges
529
+ participant_data = self._conversation_consolidator.get_participant_data(service_interactions)
530
+ if participant_data:
531
+ user_edges = self._edge_manager.create_user_participation_edges(
532
+ conversation_summary, participant_data, period_label
533
+ )
534
+ logger.info(f"Created {user_edges} user participation edges")
535
+
536
+ return conversation_summary
537
+
538
+ async def _create_trace_summary(
539
+ self,
540
+ correlations: ServiceCorrelationQueryResult,
541
+ period_start: datetime,
542
+ period_end: datetime,
543
+ period_label: str,
544
+ converted_correlations: Dict[str, List[Union[MetricCorrelationData, ServiceInteractionData, TraceSpanData]]],
545
+ ) -> Optional[GraphNode]:
546
+ """Create trace summary from trace spans."""
547
+ trace_spans = correlations.trace_spans
548
+ if not trace_spans:
549
+ return None
550
+
551
+ converted_correlations["trace_span"] = list(trace_spans)
552
+ return await self._trace_consolidator.consolidate(period_start, period_end, period_label, trace_spans)
553
+
554
+ async def _create_audit_summary(
555
+ self,
556
+ nodes_by_type: Dict[str, TSDBNodeQueryResult],
557
+ period_start: datetime,
558
+ period_end: datetime,
559
+ period_label: str,
560
+ ) -> Optional[GraphNode]:
561
+ """Create audit summary from audit nodes."""
562
+ audit_nodes = nodes_by_type.get(
563
+ "audit_entry", TSDBNodeQueryResult(nodes=[], period_start=period_start, period_end=period_end)
564
+ ).nodes
565
+ if audit_nodes:
566
+ return await self._audit_consolidator.consolidate(period_start, period_end, period_label, audit_nodes)
567
+ return None
568
+
569
+ async def _consolidate_period(self, period_start: datetime, period_end: datetime) -> List[GraphNode]:
570
+ """
571
+ Consolidate all data for a specific period.
572
+
573
+ This is the main consolidation logic that:
574
+ 1. Queries all nodes and correlations
575
+ 2. Creates summary nodes
576
+ 3. Creates proper edges
577
+
578
+ Args:
579
+ period_start: Start of period
580
+ period_end: End of period
581
+
582
+ Returns:
583
+ List of created summary nodes
584
+ """
585
+ period_label = self._period_manager.get_period_label(period_start)
586
+ summaries_created: List[GraphNode] = []
587
+
588
+ # 1. Query ALL data for the period
589
+ logger.info(f"Querying all data for period {period_label}")
590
+ nodes_by_type, correlations, tasks = self._query_period_data(period_start, period_end)
591
+
592
+ # 1.5. Handle consent expiry - anonymize expired TEMPORARY nodes
593
+ await self._handle_consent_expiry(nodes_by_type, period_end)
594
+
595
+ # 2. Create summaries
596
+ converted_correlations: Dict[str, List[Union[MetricCorrelationData, ServiceInteractionData, TraceSpanData]]] = (
597
+ {}
598
+ )
599
+
600
+ # Create each summary type using helper methods
601
+ metric_summary = await self._create_metric_summary(
602
+ nodes_by_type, correlations, period_start, period_end, period_label, converted_correlations
603
+ )
604
+ if metric_summary:
605
+ summaries_created.append(metric_summary)
606
+
607
+ task_summary = await self._create_task_summary(tasks, period_start, period_end, period_label)
608
+ if task_summary:
609
+ summaries_created.append(task_summary)
610
+
611
+ conversation_summary = await self._create_conversation_summary(
612
+ correlations, period_start, period_end, period_label, converted_correlations
613
+ )
614
+ if conversation_summary:
615
+ summaries_created.append(conversation_summary)
616
+
617
+ trace_summary = await self._create_trace_summary(
618
+ correlations, period_start, period_end, period_label, converted_correlations
619
+ )
620
+ if trace_summary:
621
+ summaries_created.append(trace_summary)
622
+
623
+ audit_summary = await self._create_audit_summary(nodes_by_type, period_start, period_end, period_label)
624
+ if audit_summary:
625
+ summaries_created.append(audit_summary)
626
+
627
+ # 3. Create edges
628
+ if summaries_created:
629
+ await self._create_all_edges(
630
+ summaries_created,
631
+ nodes_by_type,
632
+ converted_correlations,
633
+ tasks, # Use tasks directly (already TaskCorrelationData objects)
634
+ period_start,
635
+ period_label,
636
+ )
637
+
638
+ return summaries_created
639
+
640
+ def _get_consolidator_edges_for_summary(
641
+ self,
642
+ summary: GraphNode,
643
+ nodes_by_type: Dict[str, TSDBNodeQueryResult],
644
+ correlations: Dict[str, List[Union[MetricCorrelationData, ServiceInteractionData, TraceSpanData]]],
645
+ tasks: List[TaskCorrelationData],
646
+ period_start: datetime,
647
+ ) -> List[Any]:
648
+ """
649
+ Get edges from the appropriate consolidator based on summary type.
650
+
651
+ Args:
652
+ summary: Summary node
653
+ nodes_by_type: All nodes in the period by type
654
+ correlations: All correlations in the period by type
655
+ tasks: All tasks in the period
656
+ period_start: Start of the period
657
+
658
+ Returns:
659
+ List of edges from the consolidator
660
+ """
661
+ if summary.type == NodeType.TSDB_SUMMARY:
662
+ tsdb_nodes = nodes_by_type.get(
663
+ "tsdb_data", TSDBNodeQueryResult(nodes=[], period_start=period_start, period_end=period_start)
664
+ ).nodes
665
+ metric_correlations_raw = correlations.get("metric_datapoint", [])
666
+ metric_correlations = [c for c in metric_correlations_raw if isinstance(c, MetricCorrelationData)]
667
+ return self._metrics_consolidator.get_edges(summary, tsdb_nodes, metric_correlations)
668
+
669
+ elif summary.type == NodeType.CONVERSATION_SUMMARY:
670
+ service_interactions_raw = correlations.get("service_interaction", [])
671
+ service_interactions = [c for c in service_interactions_raw if isinstance(c, ServiceInteractionData)]
672
+ return self._conversation_consolidator.get_edges(summary, service_interactions)
673
+
674
+ elif summary.type == NodeType.TRACE_SUMMARY:
675
+ trace_spans_raw = correlations.get("trace_span", [])
676
+ trace_spans = [c for c in trace_spans_raw if isinstance(c, TraceSpanData)]
677
+ return self._trace_consolidator.get_edges(summary, trace_spans)
678
+
679
+ elif summary.type == NodeType.AUDIT_SUMMARY:
680
+ audit_nodes = nodes_by_type.get(
681
+ "audit_entry", TSDBNodeQueryResult(nodes=[], period_start=period_start, period_end=period_start)
682
+ ).nodes
683
+ return self._audit_consolidator.get_edges(summary, audit_nodes)
684
+
685
+ elif summary.type == NodeType.TASK_SUMMARY:
686
+ return self._task_consolidator.get_edges(summary, tasks)
687
+
688
+ return []
689
+
690
+ async def _create_daily_summary_edges(
691
+ self,
692
+ summaries: List[GraphNode],
693
+ day: datetime,
694
+ ) -> None:
695
+ """
696
+ Create edges between daily summaries for the same day.
697
+
698
+ This method creates:
699
+ 1. Cross-type edges (e.g., TSDB->Audit, Task->Trace) within the same day
700
+ 2. Temporal edges to previous day's summaries
701
+
702
+ Args:
703
+ summaries: List of daily summary nodes
704
+ day: The date for these summaries
705
+ """
706
+ if not summaries:
707
+ return
708
+
709
+ # Create cross-summary edges for same day
710
+ if len(summaries) > 1:
711
+ edges_created = self._edge_manager.create_cross_summary_edges(summaries, day)
712
+ logger.info(f"Created {edges_created} same-day edges for {day.date()}")
713
+
714
+ # Create temporal edges to previous day for each summary
715
+ for summary in summaries:
716
+ # Extract summary type from ID (e.g., "tsdb_summary_daily_20250715" -> "tsdb_summary")
717
+ parts = summary.id.split("_")
718
+ if len(parts) >= 3 and parts[2] == "daily":
719
+ summary_type = f"{parts[0]}_{parts[1]}"
720
+ # Previous day
721
+ previous_day = day - timedelta(days=1)
722
+ previous_id = f"{summary_type}_daily_{previous_day.strftime('%Y%m%d')}"
723
+
724
+ # Create temporal edges
725
+ created = self._edge_manager.create_temporal_edges(summary, previous_id)
726
+ if created:
727
+ logger.debug(f"Created {created} temporal edges from {summary.id} to {previous_id}")
728
+
729
+ def _create_consolidator_edges(
730
+ self,
731
+ summaries: List[GraphNode],
732
+ nodes_by_type: Dict[str, TSDBNodeQueryResult],
733
+ correlations: Dict[str, List[Union[MetricCorrelationData, ServiceInteractionData, TraceSpanData]]],
734
+ tasks: List[TaskCorrelationData],
735
+ period_start: datetime,
736
+ period_label: str,
737
+ ) -> int:
738
+ """Create type-specific edges from consolidators and memory edges."""
739
+ all_edges = []
740
+
741
+ # Collect edges from each consolidator based on summary type
742
+ for summary in summaries:
743
+ edges = self._get_consolidator_edges_for_summary(summary, nodes_by_type, correlations, tasks, period_start)
744
+ all_edges.extend(edges)
745
+
746
+ # Get memory edges (links from summaries to memory nodes)
747
+ nodes_by_type_dict = {node_type: result.nodes for node_type, result in nodes_by_type.items()}
748
+ memory_edges = self._memory_consolidator.consolidate(
749
+ period_start, period_start + self._consolidation_interval, period_label, nodes_by_type_dict, summaries
750
+ )
751
+ all_edges.extend(memory_edges)
752
+
753
+ # Create all edges in batch
754
+ if all_edges:
755
+ edges_created = self._edge_manager.create_edges(all_edges)
756
+ logger.info(f"Created {edges_created} edges for period {period_label}")
757
+ return edges_created
758
+ return 0
759
+
760
+ def _create_summarizes_edges(
761
+ self, summaries: List[GraphNode], nodes_by_type: Dict[str, TSDBNodeQueryResult], period_label: str
762
+ ) -> int:
763
+ """Create SUMMARIZES edges from primary summary to all nodes in period.
764
+
765
+ NOTE: Excludes tsdb_data nodes as they are raw telemetry that gets aggregated
766
+ into tsdb_summary. Creating edges from other summaries to tsdb_data would violate
767
+ the data hierarchy - tsdb_data is a different scope/layer than summaries.
768
+
769
+ Correct architecture:
770
+ - tsdb_data → aggregated into → tsdb_summary (via source_node_count)
771
+ - tsdb_summary ← TEMPORAL edges → other summaries (task_summary, etc.)
772
+ """
773
+ # Collect all nodes in period (EXCLUDING tsdb_data - wrong scope/layer)
774
+ all_nodes_in_period = []
775
+ logger.debug(f"Collecting nodes for SUMMARIZES edges. nodes_by_type keys: {list(nodes_by_type.keys())}")
776
+
777
+ for node_type, result in nodes_by_type.items():
778
+ if node_type != "tsdb_data": # tsdb_data is different scope - only referenced by tsdb_summary
779
+ node_count = len(result.nodes) if hasattr(result, "nodes") else 0
780
+ logger.debug(f" {node_type}: {node_count} nodes")
781
+ if hasattr(result, "nodes"):
782
+ all_nodes_in_period.extend(result.nodes)
783
+ else:
784
+ logger.warning(f" {node_type} result has no 'nodes' attribute: {type(result)}")
785
+
786
+ logger.info(f"Total nodes collected for SUMMARIZES edges: {len(all_nodes_in_period)}")
787
+
788
+ if not all_nodes_in_period:
789
+ return 0
790
+
791
+ # Create a primary summary (TSDB or first available) to link all nodes
792
+ primary_summary = next(
793
+ (s for s in summaries if s.type == NodeType.TSDB_SUMMARY), summaries[0] if summaries else None
794
+ )
795
+
796
+ if not primary_summary:
797
+ return 0
798
+
799
+ logger.info(f"Creating edges from {primary_summary.id} to {len(all_nodes_in_period)} nodes in period")
800
+ edges_created = self._edge_manager.create_summary_to_nodes_edges(
801
+ primary_summary, all_nodes_in_period, "SUMMARIZES", f"Node active during {period_label}"
802
+ )
803
+ logger.info(f"Created {edges_created} SUMMARIZES edges for period {period_label}")
804
+ return edges_created
805
+
806
+ def _create_cross_summary_edges(self, summaries: List[GraphNode], period_start: datetime) -> int:
807
+ """Create edges between summaries in the same period."""
808
+ if len(summaries) <= 1:
809
+ return 0
810
+
811
+ cross_edges = self._edge_manager.create_cross_summary_edges(summaries, period_start)
812
+ logger.info(f"Created {cross_edges} cross-summary edges")
813
+ return cross_edges
814
+
815
+ def _create_temporal_edges(self, summaries: List[GraphNode], period_start: datetime) -> int:
816
+ """Create temporal edges to previous and next period summaries."""
817
+ total_created = 0
818
+
819
+ # Link to previous period summaries
820
+ for summary in summaries:
821
+ # Extract summary type prefix (e.g., "tsdb_summary" or "tsdb_summary_daily")
822
+ summary_type = summary.id.split("_")[0] + "_" + summary.id.split("_")[1]
823
+
824
+ # Find the most recent previous summary (handles gaps in timeline)
825
+ previous_id = self._edge_manager.get_previous_summary_id(summary_type, summary.id)
826
+
827
+ if previous_id:
828
+ created = self._edge_manager.create_temporal_edges(summary, previous_id)
829
+ if created:
830
+ logger.debug(f"Created {created} temporal edges for {summary.id}")
831
+ total_created += created
832
+ else:
833
+ logger.debug(f"No previous summary found for {summary.id} - this may be the first summary")
834
+
835
+ # Link to next period summaries
836
+ edges_to_next = self._edge_manager.update_next_period_edges(period_start, summaries)
837
+ if edges_to_next > 0:
838
+ logger.info(f"Created {edges_to_next} edges to next period summaries")
839
+ total_created += edges_to_next
840
+
841
+ return total_created
842
+
843
+ async def _create_all_edges(
844
+ self,
845
+ summaries: List[GraphNode],
846
+ nodes_by_type: Dict[str, TSDBNodeQueryResult],
847
+ correlations: Dict[str, List[Union[MetricCorrelationData, ServiceInteractionData, TraceSpanData]]],
848
+ tasks: List[TaskCorrelationData],
849
+ period_start: datetime,
850
+ period_label: str,
851
+ ) -> None:
852
+ """
853
+ Create all necessary edges for the summaries.
854
+
855
+ This orchestrates creation of:
856
+ 1. Type-specific edges from consolidators
857
+ 2. SUMMARIZES edges to all nodes in period
858
+ 3. Cross-summary edges within same period
859
+ 4. Temporal edges to previous/next periods
860
+
861
+ Args:
862
+ summaries: List of summary nodes created
863
+ nodes_by_type: All nodes in the period by type
864
+ correlations: All correlations in the period by type
865
+ tasks: All tasks in the period
866
+ period_start: Start of the period
867
+ period_label: Human-readable period label
868
+ """
869
+ # Create type-specific and memory edges
870
+ self._create_consolidator_edges(summaries, nodes_by_type, correlations, tasks, period_start, period_label)
871
+
872
+ # Create SUMMARIZES edges to all nodes
873
+ self._create_summarizes_edges(summaries, nodes_by_type, period_label)
874
+
875
+ # Create cross-summary edges
876
+ self._create_cross_summary_edges(summaries, period_start)
877
+
878
+ # Create temporal edges
879
+ self._create_temporal_edges(summaries, period_start)
880
+
881
+ def _find_oldest_unconsolidated_period(self) -> Optional[datetime]:
882
+ """Find the oldest data that needs consolidation."""
883
+ from ciris_engine.logic.persistence.db.core import get_db_connection
884
+ from ciris_engine.logic.services.graph.tsdb_consolidation.sql_builders import parse_datetime_field
885
+
886
+ try:
887
+ with get_db_connection(db_path=self.db_path) as conn:
888
+ cursor = conn.cursor()
889
+
890
+ # Check for oldest TSDB data
891
+ cursor.execute(
892
+ """
893
+ SELECT MIN(created_at) as oldest
894
+ FROM graph_nodes
895
+ WHERE node_type = 'tsdb_data'
896
+ """
897
+ )
898
+ row = cursor.fetchone()
899
+
900
+ if row and row["oldest"]:
901
+ oldest_tsdb = parse_datetime_field(row["oldest"])
902
+ if oldest_tsdb:
903
+ return oldest_tsdb
904
+
905
+ # Check for oldest correlation
906
+ cursor.execute(
907
+ """
908
+ SELECT MIN(timestamp) as oldest
909
+ FROM service_correlations
910
+ """
911
+ )
912
+ row = cursor.fetchone()
913
+
914
+ if row and row["oldest"]:
915
+ oldest_correlation = parse_datetime_field(row["oldest"])
916
+ if oldest_correlation:
917
+ return oldest_correlation
918
+
919
+ except Exception as e:
920
+ logger.error(f"Failed to find oldest data: {e}")
921
+
922
+ return None
923
+
924
+ def _cleanup_old_data(self) -> int:
925
+ """
926
+ Clean up old consolidated data that has been successfully summarized.
927
+
928
+ IMPORTANT: This method NEVER touches the audit_log table.
929
+ Audit entries are preserved forever for absolute reputability.
930
+ Only graph node representations are cleaned up.
931
+ """
932
+ try:
933
+ from ciris_engine.logic.persistence.db.core import get_db_connection
934
+ from ciris_engine.logic.services.graph.tsdb_consolidation.cleanup_helpers import (
935
+ cleanup_audit_summary,
936
+ cleanup_trace_summary,
937
+ cleanup_tsdb_summary,
938
+ )
939
+ from ciris_engine.logic.services.graph.tsdb_consolidation.date_calculation_helpers import (
940
+ get_retention_cutoff_date,
941
+ )
942
+ from ciris_engine.logic.services.graph.tsdb_consolidation.db_query_helpers import query_expired_summaries
943
+
944
+ logger.info("Starting cleanup of consolidated graph data (audit_log untouched)")
945
+
946
+ # Connect to database using get_db_connection (supports both SQLite and PostgreSQL)
947
+ conn = get_db_connection(db_path=self.db_path)
948
+ cursor = conn.cursor()
949
+
950
+ # Find all summaries older than retention period
951
+ retention_cutoff = get_retention_cutoff_date(self._now(), int(self._raw_retention.total_seconds() / 3600))
952
+ summaries = query_expired_summaries(cursor, retention_cutoff)
953
+
954
+ total_deleted = 0
955
+
956
+ # Process each expired summary
957
+ for node_id, node_type, attrs_json in summaries:
958
+ deleted = 0
959
+
960
+ if node_type == "tsdb_summary":
961
+ deleted = cleanup_tsdb_summary(cursor, node_id, attrs_json)
962
+ elif node_type == "audit_summary":
963
+ deleted = cleanup_audit_summary(cursor, node_id, attrs_json)
964
+ elif node_type == "trace_summary":
965
+ deleted = cleanup_trace_summary(cursor, node_id, attrs_json)
966
+
967
+ total_deleted += deleted
968
+
969
+ # Commit changes
970
+ if total_deleted > 0:
971
+ conn.commit()
972
+ logger.info(f"Cleanup complete: deleted {total_deleted} total records")
973
+ else:
974
+ logger.info("No data to cleanup")
975
+
976
+ conn.close()
977
+ return total_deleted
978
+
979
+ except Exception as e:
980
+ logger.error(f"Error during cleanup: {e}", exc_info=True)
981
+ return 0
982
+
983
+ async def is_healthy(self) -> bool:
984
+ """Check if the service is healthy.
985
+
986
+ The service is healthy if:
987
+ - It's running
988
+ - Memory bus is available
989
+
990
+ Note: We don't check consolidation_task state because the task may
991
+ complete between consolidation windows and that's normal behavior.
992
+ """
993
+ return self._running and self._memory_bus is not None
994
+
995
+ def get_capabilities(self) -> ServiceCapabilities:
996
+ """Get service capabilities."""
997
+ return ServiceCapabilities(
998
+ service_name="TSDBConsolidationService",
999
+ actions=[
1000
+ "consolidate_tsdb_nodes",
1001
+ "consolidate_all_data",
1002
+ "create_proper_edges",
1003
+ "track_memory_events",
1004
+ "summarize_tasks",
1005
+ "create_6hour_summaries",
1006
+ ],
1007
+ version="2.0.0",
1008
+ dependencies=["MemoryService", "TimeService"],
1009
+ metadata=None,
1010
+ )
1011
+
1012
+ def get_status(self) -> ServiceStatus:
1013
+ """Get service status."""
1014
+ current_time = self._now()
1015
+ uptime_seconds = 0.0
1016
+ if self._start_time:
1017
+ uptime_seconds = (current_time - self._start_time).total_seconds()
1018
+
1019
+ return ServiceStatus(
1020
+ service_name="TSDBConsolidationService",
1021
+ service_type="graph_service",
1022
+ is_healthy=self._running and self._memory_bus is not None,
1023
+ uptime_seconds=uptime_seconds,
1024
+ metrics={
1025
+ "last_consolidation_timestamp": (
1026
+ self._last_consolidation.timestamp() if self._last_consolidation else 0.0
1027
+ ),
1028
+ "task_running": 1.0 if (self._consolidation_task and not self._consolidation_task.done()) else 0.0,
1029
+ "last_basic_consolidation": self._last_consolidation.timestamp() if self._last_consolidation else 0.0,
1030
+ "last_extensive_consolidation": (
1031
+ self._last_extensive_consolidation.timestamp() if self._last_extensive_consolidation else 0.0
1032
+ ),
1033
+ "last_profound_consolidation": (
1034
+ self._last_profound_consolidation.timestamp() if self._last_profound_consolidation else 0.0
1035
+ ),
1036
+ "consolidation_task_running": (
1037
+ 1.0 if (self._consolidation_task and not self._consolidation_task.done()) else 0.0
1038
+ ),
1039
+ },
1040
+ last_error=None,
1041
+ last_health_check=current_time,
1042
+ custom_metrics={
1043
+ "basic_interval_hours": self._basic_interval.total_seconds() / 3600,
1044
+ "extensive_interval_days": self._extensive_interval.total_seconds() / 86400,
1045
+ "profound_interval_days": self._profound_interval.total_seconds() / 86400,
1046
+ "profound_target_mb_per_day": self._profound_target_mb_per_day,
1047
+ },
1048
+ )
1049
+
1050
+ def get_node_type(self) -> NodeType:
1051
+ """Get the node type this service manages."""
1052
+ return NodeType.TSDB_SUMMARY
1053
+
1054
+ def _is_period_consolidated(self, period_start: datetime, period_end: datetime) -> bool:
1055
+ """Check if a period has already been consolidated."""
1056
+ try:
1057
+ # Query for existing TSDB summary for this exact period
1058
+ # Use direct DB query since MemoryQuery doesn't support field conditions
1059
+ from ciris_engine.logic.persistence.db.core import get_db_connection
1060
+ from ciris_engine.logic.persistence.db.dialect import get_adapter
1061
+
1062
+ adapter = get_adapter()
1063
+ conn = get_db_connection(db_path=self.db_path)
1064
+ cursor = conn.cursor()
1065
+
1066
+ # Query for TSDB summaries with matching period (PostgreSQL: JSONB operators, SQLite: json_extract)
1067
+ if adapter.is_postgresql():
1068
+ sql = """
1069
+ SELECT COUNT(*) FROM graph_nodes
1070
+ WHERE node_type = ?
1071
+ AND attributes_json->>'period_start' = ?
1072
+ AND attributes_json->>'period_end' = ?
1073
+ """
1074
+ else:
1075
+ sql = """
1076
+ SELECT COUNT(*) FROM graph_nodes
1077
+ WHERE node_type = ?
1078
+ AND json_extract(attributes_json, '$.period_start') = ?
1079
+ AND json_extract(attributes_json, '$.period_end') = ?
1080
+ """
1081
+
1082
+ cursor.execute(
1083
+ sql,
1084
+ (NodeType.TSDB_SUMMARY.value, period_start.isoformat(), period_end.isoformat()),
1085
+ )
1086
+
1087
+ result = cursor.fetchone()
1088
+ count = int(result[0]) if result else 0
1089
+ conn.close()
1090
+
1091
+ return count > 0
1092
+ except Exception as e:
1093
+ logger.error(f"Error checking if period consolidated: {e}")
1094
+ return False
1095
+
1096
+ async def _ensure_summary_edges(self, period_start: datetime, period_end: datetime) -> None:
1097
+ """
1098
+ Ensure edges exist for an already-consolidated period.
1099
+ This fixes the issue where summaries exist but have no SUMMARIZES edges.
1100
+
1101
+ Args:
1102
+ period_start: Start of the period
1103
+ period_end: End of the period
1104
+ """
1105
+ try:
1106
+ period_label = self._period_manager.get_period_label(period_start)
1107
+ logger.info(f"Ensuring edges exist for consolidated period {period_label}")
1108
+
1109
+ # Find the summary node for this period
1110
+ period_id = period_start.strftime("%Y%m%d_%H")
1111
+ summary_id = f"tsdb_summary_{period_id}"
1112
+
1113
+ # Check if SUMMARIZES edges already exist
1114
+ from ciris_engine.logic.persistence.db.core import get_db_connection
1115
+
1116
+ with get_db_connection(db_path=self.db_path) as conn:
1117
+ cursor = conn.cursor()
1118
+ cursor.execute(
1119
+ """
1120
+ SELECT COUNT(*) as count
1121
+ FROM graph_edges
1122
+ WHERE source_node_id = ?
1123
+ AND relationship = 'SUMMARIZES'
1124
+ """,
1125
+ (summary_id,),
1126
+ )
1127
+
1128
+ edge_count = cursor.fetchone()["count"]
1129
+
1130
+ if edge_count > 0:
1131
+ logger.debug(f"Period {period_label} already has {edge_count} SUMMARIZES edges")
1132
+ return
1133
+
1134
+ # No SUMMARIZES edges exist - we need to create them
1135
+ logger.warning(f"Period {period_label} has NO SUMMARIZES edges! Creating them now...")
1136
+
1137
+ # Query all nodes in the period
1138
+ nodes_by_type = self._query_manager.query_all_nodes_in_period(period_start, period_end)
1139
+
1140
+ # Get the summary node
1141
+ from ciris_engine.schemas.services.graph_core import GraphNode, GraphScope, NodeType
1142
+
1143
+ summary_node = GraphNode(
1144
+ id=summary_id,
1145
+ type=NodeType.TSDB_SUMMARY,
1146
+ scope=GraphScope.LOCAL,
1147
+ attributes={},
1148
+ updated_by="tsdb_consolidation",
1149
+ updated_at=period_end,
1150
+ )
1151
+
1152
+ # Collect all nodes (except tsdb_data)
1153
+ all_nodes_in_period = []
1154
+ for node_type, result in nodes_by_type.items():
1155
+ if node_type != "tsdb_data" and hasattr(result, "nodes"):
1156
+ all_nodes_in_period.extend(result.nodes)
1157
+
1158
+ if all_nodes_in_period:
1159
+ logger.info(f"Creating SUMMARIZES edges from {summary_id} to {len(all_nodes_in_period)} nodes")
1160
+ edges_created = self._edge_manager.create_summary_to_nodes_edges(
1161
+ summary_node, all_nodes_in_period, "SUMMARIZES", f"Node active during {period_label}"
1162
+ )
1163
+ logger.info(f"Created {edges_created} SUMMARIZES edges for period {period_label}")
1164
+ else:
1165
+ logger.warning(f"No nodes found in period {period_label} to create edges to")
1166
+
1167
+ except Exception as e:
1168
+ logger.error(f"Error ensuring summary edges: {e}", exc_info=True)
1169
+
1170
+ def _calculate_next_run_time(self) -> datetime:
1171
+ """Calculate when the next consolidation should run."""
1172
+ # Run at the start of the next 6-hour period
1173
+ current_time = self._now()
1174
+ hours_since_epoch = current_time.timestamp() / 3600
1175
+ periods_since_epoch = int(hours_since_epoch / 6)
1176
+ next_period = periods_since_epoch + 1
1177
+ next_run_timestamp = next_period * 6 * 3600
1178
+ return datetime.fromtimestamp(next_run_timestamp, tz=timezone.utc)
1179
+
1180
+ def _calculate_next_period_start(self, interval: timedelta) -> datetime:
1181
+ """Calculate the next period start for a given interval."""
1182
+ current_time = self._now()
1183
+ seconds_since_epoch = current_time.timestamp()
1184
+ interval_seconds = interval.total_seconds()
1185
+ periods_since_epoch = int(seconds_since_epoch / interval_seconds)
1186
+ next_period = periods_since_epoch + 1
1187
+ next_run_timestamp = next_period * interval_seconds
1188
+ return datetime.fromtimestamp(next_run_timestamp, tz=timezone.utc)
1189
+
1190
+ def _get_next_weekly_monday(self) -> datetime:
1191
+ """Get next Monday at 00:00 UTC for weekly consolidation."""
1192
+ now = self._now()
1193
+ days_until_monday = (7 - now.weekday()) % 7
1194
+
1195
+ # If it's Monday but past midnight, schedule for next Monday
1196
+ if days_until_monday == 0 and now.hour > 0:
1197
+ days_until_monday = 7
1198
+
1199
+ next_monday = now.date() + timedelta(days=days_until_monday)
1200
+ return datetime.combine(next_monday, datetime.min.time(), tzinfo=timezone.utc)
1201
+
1202
+ def _get_next_month_start(self) -> datetime:
1203
+ """Get first day of next month at 00:00 UTC for monthly consolidation."""
1204
+ now = self._now()
1205
+
1206
+ # If it's the 1st at exactly 00:00, run now
1207
+ if now.day == 1 and now.hour == 0 and now.minute == 0:
1208
+ return now.replace(second=0, microsecond=0)
1209
+
1210
+ # Otherwise, calculate first day of next month
1211
+ if now.month == 12:
1212
+ next_month_date = now.replace(year=now.year + 1, month=1, day=1)
1213
+ else:
1214
+ next_month_date = now.replace(month=now.month + 1, day=1)
1215
+
1216
+ return next_month_date.replace(hour=0, minute=0, second=0, microsecond=0)
1217
+
1218
+ def _cleanup_old_nodes(self) -> int:
1219
+ """Legacy method name - calls _cleanup_old_data."""
1220
+ result = self._cleanup_old_data()
1221
+ return result if result is not None else 0
1222
+
1223
+ def get_summary_for_period(self, period_start: datetime, period_end: datetime) -> Optional[TSDBPeriodSummary]:
1224
+ """Get the summary for a specific period."""
1225
+ try:
1226
+ # Use direct DB query since MemoryQuery doesn't support field conditions
1227
+ from ciris_engine.logic.persistence.db.core import get_db_connection
1228
+ from ciris_engine.logic.persistence.db.dialect import get_adapter
1229
+
1230
+ adapter = get_adapter()
1231
+ conn = get_db_connection(db_path=self.db_path)
1232
+ cursor = conn.cursor()
1233
+
1234
+ # Query for TSDB summaries with matching period (PostgreSQL: JSONB operators, SQLite: json_extract)
1235
+ if adapter.is_postgresql():
1236
+ sql = """
1237
+ SELECT attributes_json FROM graph_nodes
1238
+ WHERE node_type = ?
1239
+ AND attributes_json->>'period_start' = ?
1240
+ AND attributes_json->>'period_end' = ?
1241
+ LIMIT 1
1242
+ """
1243
+ else:
1244
+ sql = """
1245
+ SELECT attributes_json FROM graph_nodes
1246
+ WHERE node_type = ?
1247
+ AND json_extract(attributes_json, '$.period_start') = ?
1248
+ AND json_extract(attributes_json, '$.period_end') = ?
1249
+ LIMIT 1
1250
+ """
1251
+
1252
+ cursor.execute(
1253
+ sql,
1254
+ (NodeType.TSDB_SUMMARY.value, period_start.isoformat(), period_end.isoformat()),
1255
+ )
1256
+
1257
+ row = cursor.fetchone()
1258
+ conn.close()
1259
+
1260
+ if row:
1261
+ # Parse the node data
1262
+ import json
1263
+
1264
+ node_data = json.loads(row[0])
1265
+ attrs = node_data.get("attributes", {})
1266
+ # Return the summary data as a typed schema
1267
+ return TSDBPeriodSummary(
1268
+ metrics=attrs.get("metrics", {}),
1269
+ total_tokens=attrs.get("total_tokens", 0),
1270
+ total_cost_cents=attrs.get("total_cost_cents", 0),
1271
+ total_carbon_grams=attrs.get("total_carbon_grams", 0),
1272
+ total_energy_kwh=attrs.get("total_energy_kwh", 0),
1273
+ action_counts=attrs.get("action_counts", {}),
1274
+ source_node_count=attrs.get("source_node_count", 0),
1275
+ period_start=attrs.get("period_start", period_start.isoformat()),
1276
+ period_end=attrs.get("period_end", period_end.isoformat()),
1277
+ period_label=attrs.get("period_label", ""),
1278
+ conversations=attrs.get("conversations", []),
1279
+ traces=attrs.get("traces", []),
1280
+ audits=attrs.get("audits", []),
1281
+ tasks=attrs.get("tasks", []),
1282
+ memories=attrs.get("memories", []),
1283
+ )
1284
+ return None
1285
+ except Exception as e:
1286
+ logger.error(f"Error getting summary for period: {e}")
1287
+ return None
1288
+
1289
+ def get_service_type(self) -> ServiceType:
1290
+ """Get the service type."""
1291
+ return ServiceType.TELEMETRY
1292
+
1293
+ def _should_anonymize_node(self, node: GraphNode, period_end: datetime) -> bool:
1294
+ """Check if a node should be anonymized due to consent expiry."""
1295
+ if not hasattr(node, "consent_stream") or not hasattr(node, "expires_at"):
1296
+ return False
1297
+
1298
+ if node.consent_stream != ConsentStream.TEMPORARY or not node.expires_at:
1299
+ return False
1300
+
1301
+ return period_end > node.expires_at
1302
+
1303
+ def _generate_anonymized_id(self, original_id: str, consent_stream: ConsentStream) -> str:
1304
+ """Generate anonymized ID based on hash of original ID."""
1305
+ import hashlib
1306
+
1307
+ id_hash = hashlib.sha256(original_id.encode()).hexdigest()[:8]
1308
+
1309
+ if consent_stream == ConsentStream.TEMPORARY:
1310
+ return f"temporary_user_{id_hash}"
1311
+ else:
1312
+ return f"anonymous_user_{id_hash}"
1313
+
1314
+ def _remove_pii_from_attributes(self, node: GraphNode, period_end: datetime) -> None:
1315
+ """Remove PII fields from node attributes and add anonymization metadata."""
1316
+ if not hasattr(node, "attributes") or not isinstance(node.attributes, dict):
1317
+ return
1318
+
1319
+ # Remove PII fields
1320
+ pii_fields = ["email", "name", "phone", "address", "ip_address"]
1321
+ for field in pii_fields:
1322
+ if field in node.attributes:
1323
+ del node.attributes[field]
1324
+
1325
+ # Add anonymization metadata
1326
+ node.attributes["anonymized_at"] = period_end.isoformat()
1327
+ node.attributes["original_stream"] = node.consent_stream
1328
+
1329
+ async def _update_anonymized_node(self, node: GraphNode, old_id: str) -> None:
1330
+ """Update anonymized node in memory bus."""
1331
+ if not self._memory_bus:
1332
+ return
1333
+
1334
+ try:
1335
+ status = await self._memory_bus.memorize(node, handler_name="tsdb_consolidation")
1336
+ if status.status == MemoryOpStatus.SUCCESS:
1337
+ logger.info(f"Successfully anonymized node {old_id}")
1338
+ else:
1339
+ logger.warning(f"Failed to update anonymized node: {status.reason}")
1340
+ except Exception as e:
1341
+ logger.error(f"Error updating anonymized node: {e}")
1342
+
1343
+ async def _handle_consent_expiry(self, nodes_by_type: Dict[str, TSDBNodeQueryResult], period_end: datetime) -> None:
1344
+ """
1345
+ Handle consent expiry by anonymizing expired TEMPORARY nodes.
1346
+
1347
+ When a TEMPORARY node expires (14 days), it gets renamed from:
1348
+ - user_<id> -> temporary_user_<hash>
1349
+
1350
+ When transitioning to ANONYMOUS, it becomes:
1351
+ - user_<id> -> anonymous_user_<hash>
1352
+
1353
+ Args:
1354
+ nodes_by_type: All nodes in the period by type
1355
+ period_end: End of the consolidation period
1356
+ """
1357
+ # Check user nodes for expiry
1358
+ user_nodes = nodes_by_type.get(
1359
+ "user", TSDBNodeQueryResult(nodes=[], period_start=period_end, period_end=period_end)
1360
+ ).nodes
1361
+
1362
+ for node in user_nodes:
1363
+ if not self._should_anonymize_node(node, period_end):
1364
+ continue
1365
+
1366
+ # Node has expired - anonymize it
1367
+ old_id = node.id
1368
+ new_id = self._generate_anonymized_id(old_id, node.consent_stream)
1369
+
1370
+ logger.info(f"Anonymizing expired node: {old_id} -> {new_id}")
1371
+
1372
+ # Update the node ID
1373
+ node.id = new_id
1374
+
1375
+ # Clear any PII from attributes
1376
+ self._remove_pii_from_attributes(node, period_end)
1377
+
1378
+ # Update in memory bus
1379
+ await self._update_anonymized_node(node, old_id)
1380
+
1381
+ def _get_actions(self) -> List[str]:
1382
+ """Get list of actions this service can handle."""
1383
+ # Graph services typically don't handle actions through buses
1384
+ return []
1385
+
1386
+ async def _run_extensive_consolidation(self) -> None:
1387
+ """
1388
+ Run extensive consolidation - consolidates basic summaries from the past week.
1389
+ This reduces data volume by creating daily summaries (4 basic summaries → 1 daily summary).
1390
+ Creates 7 daily summaries for each node type.
1391
+ """
1392
+ from ciris_engine.logic.persistence.db.core import get_db_connection
1393
+ from ciris_engine.logic.services.graph.tsdb_consolidation.aggregation_helpers import (
1394
+ aggregate_action_counts,
1395
+ aggregate_metric_stats,
1396
+ aggregate_resource_usage,
1397
+ group_summaries_by_day,
1398
+ parse_summary_attributes,
1399
+ )
1400
+ from ciris_engine.logic.services.graph.tsdb_consolidation.date_calculation_helpers import calculate_week_period
1401
+ from ciris_engine.logic.services.graph.tsdb_consolidation.extensive_helpers import (
1402
+ check_daily_summary_exists,
1403
+ create_daily_summary_attributes,
1404
+ create_daily_summary_node,
1405
+ maintain_temporal_chain_to_daily,
1406
+ query_basic_summaries_in_period,
1407
+ )
1408
+ from ciris_engine.schemas.services.operations import MemoryOpStatus
1409
+
1410
+ consolidation_start = self._now()
1411
+ total_basic_summaries = 0
1412
+ daily_summaries_created = 0
1413
+
1414
+ try:
1415
+ logger.info("=" * 60)
1416
+ logger.info("Starting extensive (weekly) consolidation")
1417
+ logger.info(f"Started at: {consolidation_start.isoformat()}")
1418
+
1419
+ now = self._now()
1420
+
1421
+ # Calculate the previous week period using helper
1422
+ period_start, period_end = calculate_week_period(now)
1423
+
1424
+ week_start = period_start.date()
1425
+ week_end = period_end.date()
1426
+
1427
+ logger.info(f"Consolidating week: {week_start} to {week_end}")
1428
+ logger.info(f"Period: {period_start.isoformat()} to {period_end.isoformat()}")
1429
+
1430
+ # Try to acquire lock for this week to prevent duplicate consolidation
1431
+ week_identifier = week_start.isoformat() # e.g., "2023-10-01"
1432
+ lock_acquired = self._query_manager.acquire_consolidation_lock("extensive", week_identifier)
1433
+
1434
+ if not lock_acquired:
1435
+ logger.info(
1436
+ f"Extensive consolidation for week {week_identifier} is locked by another instance, skipping"
1437
+ )
1438
+ return
1439
+
1440
+ try:
1441
+ # Process summaries
1442
+ with get_db_connection(db_path=self.db_path) as conn:
1443
+ cursor = conn.cursor()
1444
+
1445
+ # Get all summary types to consolidate
1446
+ summary_types = [
1447
+ "tsdb_summary",
1448
+ "audit_summary",
1449
+ "trace_summary",
1450
+ "conversation_summary",
1451
+ "task_summary",
1452
+ ]
1453
+
1454
+ for summary_type in summary_types:
1455
+ # Query basic summaries using helper
1456
+ summaries = query_basic_summaries_in_period(cursor, summary_type, period_start, period_end)
1457
+
1458
+ if not summaries:
1459
+ logger.info(f"No {summary_type} summaries found for consolidation")
1460
+ continue
1461
+
1462
+ logger.info(f"Found {len(summaries)} {summary_type} summaries to consolidate")
1463
+ total_basic_summaries += len(summaries)
1464
+
1465
+ # Group summaries by day using helper
1466
+ summaries_by_day = group_summaries_by_day(summaries)
1467
+
1468
+ # Create daily summary for each day
1469
+ for day, day_summaries in summaries_by_day.items():
1470
+ if len(day_summaries) == 0:
1471
+ continue
1472
+
1473
+ # Convert date to datetime for helpers
1474
+ day_datetime = datetime.combine(day, datetime.min.time(), tzinfo=timezone.utc)
1475
+
1476
+ # Generate daily node ID
1477
+ daily_node_id = f"{summary_type}_daily_{day.strftime('%Y%m%d')}"
1478
+
1479
+ # Check if already exists using helper
1480
+ if check_daily_summary_exists(cursor, daily_node_id):
1481
+ logger.debug(f"Daily summary {daily_node_id} already exists, skipping")
1482
+ continue
1483
+
1484
+ # Parse summary attributes using helper
1485
+ summary_attrs_list = parse_summary_attributes(day_summaries)
1486
+
1487
+ # Aggregate metrics, resources, and actions using helpers
1488
+ daily_metrics = aggregate_metric_stats(summary_attrs_list)
1489
+ daily_resources = aggregate_resource_usage(summary_attrs_list)
1490
+ daily_action_counts = aggregate_action_counts(summary_attrs_list)
1491
+
1492
+ # Create daily summary attributes using helper
1493
+ daily_attrs = create_daily_summary_attributes(
1494
+ summary_type,
1495
+ day_datetime,
1496
+ day_summaries,
1497
+ daily_metrics,
1498
+ daily_resources,
1499
+ daily_action_counts,
1500
+ )
1501
+
1502
+ # Create daily summary node using helper
1503
+ daily_summary = create_daily_summary_node(summary_type, day_datetime, daily_attrs, now)
1504
+
1505
+ # Store in memory
1506
+ if self._memory_bus:
1507
+ result = await self._memory_bus.memorize(
1508
+ daily_summary, handler_name="tsdb_consolidation"
1509
+ )
1510
+ if result.status == MemoryOpStatus.OK:
1511
+ daily_summaries_created += 1
1512
+ logger.info(
1513
+ f"Created daily summary {daily_node_id} from {len(day_summaries)} basic summaries"
1514
+ )
1515
+
1516
+ # Final summary
1517
+ total_duration = (self._now() - consolidation_start).total_seconds()
1518
+ logger.info(f"Extensive consolidation complete in {total_duration:.2f}s:")
1519
+ logger.info(f" - Basic summaries processed: {total_basic_summaries}")
1520
+ logger.info(f" - Daily summaries created: {daily_summaries_created}")
1521
+ if total_basic_summaries > 0:
1522
+ compression_ratio = total_basic_summaries / max(daily_summaries_created, 1)
1523
+ logger.info(f" - Compression ratio: {compression_ratio:.1f}:1")
1524
+ logger.info("=" * 60)
1525
+
1526
+ # Maintain temporal chain using helper
1527
+ if daily_summaries_created > 0:
1528
+ edges_created = maintain_temporal_chain_to_daily(cursor, period_start)
1529
+ if edges_created > 0:
1530
+ logger.info(f"Created {edges_created} temporal chain edges")
1531
+
1532
+ finally:
1533
+ # Release lock
1534
+ self._query_manager.release_consolidation_lock("extensive", week_identifier)
1535
+
1536
+ except Exception as e:
1537
+ logger.error(f"Extensive consolidation failed: {e}", exc_info=True)
1538
+
1539
+ async def get_metrics(self) -> Dict[str, float]:
1540
+ """Get TSDB consolidation service metrics.
1541
+
1542
+ Returns exactly the 4 metrics from v1.4.3 API specification:
1543
+ - tsdb_consolidations_total: Total consolidations performed
1544
+ - tsdb_datapoints_processed: Total data points processed
1545
+ - tsdb_storage_saved_mb: Storage saved by consolidation (MB)
1546
+ - tsdb_uptime_seconds: Service uptime in seconds
1547
+ """
1548
+ # Calculate uptime
1549
+ uptime_seconds = 0.0
1550
+ if hasattr(self, "_start_time") and self._start_time:
1551
+ uptime_seconds = (self._now() - self._start_time).total_seconds()
1552
+
1553
+ # Calculate total consolidations performed
1554
+ total_consolidations = (
1555
+ self._basic_consolidations + self._extensive_consolidations + self._profound_consolidations
1556
+ )
1557
+
1558
+ # Calculate storage saved (estimate based on compression ratio and records processed)
1559
+ # Each record averages ~2KB, storage saved = records_deleted * avg_size_kb / 1024
1560
+ avg_record_size_kb = 2.0
1561
+ storage_saved_mb = (self._records_deleted * avg_record_size_kb) / 1024.0
1562
+
1563
+ return {
1564
+ "tsdb_consolidations_total": float(total_consolidations),
1565
+ "tsdb_datapoints_processed": float(self._records_consolidated),
1566
+ "tsdb_storage_saved_mb": storage_saved_mb,
1567
+ "tsdb_uptime_seconds": uptime_seconds,
1568
+ }
1569
+
1570
+ def _run_profound_consolidation(self) -> None:
1571
+ """
1572
+ Run profound consolidation - compresses existing daily summaries in-place.
1573
+ Target: Configurable MB per day of data retention.
1574
+
1575
+ This process compresses daily summaries to meet storage targets without
1576
+ creating new nodes. Future versions will handle multimedia compression.
1577
+ """
1578
+ from ciris_engine.logic.persistence.db.core import get_db_connection
1579
+ from ciris_engine.logic.services.graph.tsdb_consolidation.date_calculation_helpers import calculate_month_period
1580
+ from ciris_engine.logic.services.graph.tsdb_consolidation.profound_helpers import (
1581
+ calculate_storage_metrics,
1582
+ cleanup_old_basic_summaries,
1583
+ compress_and_update_summaries,
1584
+ query_extensive_summaries_in_month,
1585
+ )
1586
+
1587
+ from .compressor import SummaryCompressor
1588
+
1589
+ consolidation_start = self._now()
1590
+ total_daily_summaries = 0
1591
+ summaries_compressed = 0
1592
+ storage_before_mb = 0.0
1593
+ storage_after_mb = 0.0
1594
+
1595
+ try:
1596
+ logger.info("=" * 60)
1597
+ logger.info("Starting profound (monthly) consolidation")
1598
+ logger.info(f"Started at: {consolidation_start.isoformat()}")
1599
+
1600
+ now = self._now()
1601
+
1602
+ # Calculate the previous month period using helper
1603
+ month_start, month_end = calculate_month_period(now)
1604
+
1605
+ # Try to acquire lock for this month to prevent duplicate consolidation
1606
+ month_identifier = month_start.strftime("%Y-%m") # e.g., "2023-10"
1607
+ lock_acquired = self._query_manager.acquire_consolidation_lock("profound", month_identifier)
1608
+
1609
+ if not lock_acquired:
1610
+ logger.info(
1611
+ f"Profound consolidation for month {month_identifier} is locked by another instance, skipping"
1612
+ )
1613
+ return
1614
+
1615
+ try:
1616
+ # Initialize compressor
1617
+ compressor = SummaryCompressor(self._profound_target_mb_per_day)
1618
+
1619
+ # Query and process summaries
1620
+ with get_db_connection(db_path=self.db_path) as conn:
1621
+ cursor = conn.cursor()
1622
+
1623
+ # Query all extensive summaries from the month using helper
1624
+ summaries = query_extensive_summaries_in_month(cursor, month_start, month_end)
1625
+ total_daily_summaries = len(summaries)
1626
+
1627
+ if len(summaries) < 7: # Less than a week's worth
1628
+ logger.info(
1629
+ f"Not enough daily summaries for profound consolidation (found {len(summaries)}, need at least 7)"
1630
+ )
1631
+ return
1632
+
1633
+ logger.info(f"Found {total_daily_summaries} daily summaries to compress")
1634
+
1635
+ # Calculate current storage using helper
1636
+ days_in_period = (month_end - month_start).days + 1
1637
+ current_daily_mb, summary_attrs_list = calculate_storage_metrics(
1638
+ cursor, month_start, month_end, compressor
1639
+ )
1640
+ storage_before_mb = float(current_daily_mb * days_in_period)
1641
+ logger.info(f"Current storage: {current_daily_mb:.2f}MB/day ({storage_before_mb:.2f}MB total)")
1642
+ logger.info(f"Target: {self._profound_target_mb_per_day}MB/day")
1643
+
1644
+ # Check if compression is needed
1645
+ if not compressor.needs_compression(summary_attrs_list, days_in_period):
1646
+ logger.info("Daily summaries already meet storage target, skipping compression")
1647
+ return
1648
+
1649
+ # Compress summaries using helper
1650
+ compressed_count, total_reduction = compress_and_update_summaries(
1651
+ cursor, summaries, compressor, now
1652
+ )
1653
+ summaries_compressed = compressed_count
1654
+
1655
+ conn.commit()
1656
+
1657
+ # Calculate new storage using helper
1658
+ new_daily_mb, _ = calculate_storage_metrics(cursor, month_start, month_end, compressor)
1659
+ storage_after_mb = new_daily_mb * days_in_period
1660
+ avg_reduction = total_reduction / compressed_count if compressed_count > 0 else 0
1661
+
1662
+ # Final summary
1663
+ total_duration = (self._now() - consolidation_start).total_seconds()
1664
+ logger.info(f"Profound consolidation complete in {total_duration:.2f}s:")
1665
+ logger.info(f" - Daily summaries processed: {total_daily_summaries}")
1666
+ logger.info(f" - Summaries compressed: {summaries_compressed}")
1667
+ logger.info(f" - Average compression: {avg_reduction:.1%}")
1668
+ logger.info(
1669
+ f" - Storage before: {storage_before_mb:.2f}MB ({storage_before_mb/days_in_period:.2f}MB/day)"
1670
+ )
1671
+ logger.info(f" - Storage after: {storage_after_mb:.2f}MB ({new_daily_mb:.2f}MB/day)")
1672
+ if storage_before_mb > 0:
1673
+ logger.info(
1674
+ f" - Total reduction: {((storage_before_mb - storage_after_mb) / storage_before_mb * 100):.1f}%"
1675
+ )
1676
+
1677
+ # Clean up old basic summaries using helper
1678
+ cleanup_cutoff = now - timedelta(days=30)
1679
+ deleted = cleanup_old_basic_summaries(cursor, cleanup_cutoff)
1680
+
1681
+ if deleted > 0:
1682
+ logger.info(f"Cleaned up {deleted} old basic summaries")
1683
+ conn.commit()
1684
+
1685
+ self._last_profound_consolidation = now
1686
+
1687
+ finally:
1688
+ # Release lock
1689
+ self._query_manager.release_consolidation_lock("profound", month_identifier)
1690
+
1691
+ except Exception as e:
1692
+ logger.error(f"Profound consolidation failed: {e}", exc_info=True)