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,2269 @@
1
+ """Runtime control service for processor and adapter management."""
2
+
3
+ import asyncio
4
+ import logging
5
+ from datetime import datetime
6
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union, cast
7
+
8
+ from ciris_engine.schemas.types import JSONDict
9
+
10
+ if TYPE_CHECKING:
11
+ from ciris_engine.logic.services.graph.config_service import GraphConfigService
12
+ from ciris_engine.logic.runtime.runtime_interface import RuntimeInterface
13
+
14
+ from ciris_engine.logic.runtime.adapter_manager import RuntimeAdapterManager
15
+ from ciris_engine.logic.services.base_service import BaseService
16
+ from ciris_engine.protocols.services import RuntimeControlService as RuntimeControlServiceProtocol
17
+ from ciris_engine.protocols.services import TimeServiceProtocol
18
+ from ciris_engine.schemas.adapters.tools import ToolInfo, ToolParameterSchema
19
+ from ciris_engine.schemas.runtime.adapter_management import AdapterConfig
20
+ from ciris_engine.schemas.runtime.enums import ServiceType
21
+ from ciris_engine.schemas.services.core import ServiceCapabilities, ServiceStatus
22
+ from ciris_engine.schemas.services.core.runtime import (
23
+ AdapterInfo,
24
+ AdapterOperationResponse,
25
+ AdapterStatus,
26
+ ConfigBackup,
27
+ ConfigOperationResponse,
28
+ ConfigReloadResult,
29
+ ConfigSnapshot,
30
+ ConfigValidationLevel,
31
+ ConfigValidationResponse,
32
+ ProcessorControlResponse,
33
+ ProcessorQueueStatus,
34
+ ProcessorStatus,
35
+ RuntimeEvent,
36
+ RuntimeStateSnapshot,
37
+ RuntimeStatusResponse,
38
+ ServiceHealthStatus,
39
+ ServiceSelectionExplanation,
40
+ )
41
+ from ciris_engine.schemas.services.runtime_control import (
42
+ CircuitBreakerResetResponse,
43
+ CircuitBreakerState,
44
+ CircuitBreakerStatus,
45
+ ConfigBackupData,
46
+ ConfigValueMap,
47
+ ServicePriorityUpdateResponse,
48
+ ServiceProviderInfo,
49
+ ServiceProviderUpdate,
50
+ ServiceRegistryInfoResponse,
51
+ WAPublicKeyMap,
52
+ )
53
+ from ciris_engine.schemas.services.shutdown import EmergencyShutdownStatus, KillSwitchConfig, WASignedCommand
54
+ from ciris_engine.schemas.types import ConfigDict, ConfigValue
55
+
56
+ # GraphConfigService is injected via dependency injection to avoid circular imports
57
+
58
+
59
+ logger = logging.getLogger(__name__)
60
+
61
+ # Error message constants
62
+ _ERROR_AGENT_PROCESSOR_NOT_AVAILABLE = "Agent processor not available"
63
+ _ERROR_SERVICE_REGISTRY_NOT_AVAILABLE = "Service registry not available"
64
+
65
+
66
+ # Internal dataclass for provider lookup results
67
+ from dataclasses import dataclass
68
+
69
+
70
+ @dataclass
71
+ class _ProviderLookupResult:
72
+ """Internal: Result from finding a provider in the registry."""
73
+
74
+ provider: Any
75
+ providers_list: List[Any]
76
+ service_type: str
77
+
78
+
79
+ class RuntimeControlService(BaseService, RuntimeControlServiceProtocol):
80
+ """Service for runtime control of processor, adapters, and configuration."""
81
+
82
+ def __init__(
83
+ self,
84
+ runtime: Optional["RuntimeInterface"] = None,
85
+ adapter_manager: Optional[RuntimeAdapterManager] = None,
86
+ config_manager: Optional["GraphConfigService"] = None,
87
+ time_service: Optional[TimeServiceProtocol] = None,
88
+ ) -> None:
89
+ # Always create a time service if not provided for BaseService
90
+ if time_service is None:
91
+ from ciris_engine.logic.services.lifecycle.time import TimeService
92
+
93
+ time_service = TimeService()
94
+
95
+ super().__init__(time_service=time_service)
96
+
97
+ self.runtime: Optional["RuntimeInterface"] = runtime
98
+ self.adapter_manager = adapter_manager
99
+ if not self.adapter_manager and runtime:
100
+ self.adapter_manager = RuntimeAdapterManager(runtime, self._time_service) # type: ignore[arg-type]
101
+ self.config_manager: Optional["GraphConfigService"] = config_manager
102
+
103
+ self._processor_status = ProcessorStatus.RUNNING
104
+ self._last_config_change: Optional[datetime] = None
105
+ self._events_history: List[RuntimeEvent] = []
106
+
107
+ # Enhanced metrics tracking variables
108
+ self._queue_depth = 0
109
+ self._thoughts_processed = 0
110
+ self._thoughts_pending = 0
111
+ self._average_thought_time_ms = 0.0
112
+ self._thought_times: List[float] = [] # Track last N thought processing times
113
+ self._max_thought_history = 100
114
+ self._messages_processed = 0
115
+ # Note: _message_times removed - not applicable since messages can be REJECTed
116
+ self._service_overrides = 0
117
+ self._runtime_errors = 0
118
+ self._single_steps = 0
119
+ self._pause_resume_cycles = 0
120
+
121
+ # State transition tracking for v1.4.3 metrics
122
+ self._state_transitions = 0
123
+ self._commands_processed = 0
124
+
125
+ # Kill switch configuration
126
+ self._kill_switch_config = KillSwitchConfig(
127
+ enabled=True,
128
+ trust_tree_depth=3,
129
+ allow_relay=True,
130
+ max_shutdown_time_ms=30000,
131
+ command_expiry_seconds=300,
132
+ require_reason=True,
133
+ log_to_audit=True,
134
+ allow_override=False,
135
+ )
136
+ # Initialize WA public key map
137
+ self._wa_key_map = WAPublicKeyMap()
138
+
139
+ def _get_config_manager(self) -> "GraphConfigService":
140
+ """Get config manager with lazy initialization to avoid circular imports."""
141
+ if self.config_manager is None:
142
+ # Config manager must be injected, cannot create without dependencies
143
+ raise RuntimeError("Config manager not available - must be injected via dependency injection")
144
+ return self.config_manager
145
+
146
+ async def _initialize(self) -> None:
147
+ """Initialize the runtime control service."""
148
+ try:
149
+ # Config manager is already initialized by service initializer
150
+ logger.info("Runtime control service initialized")
151
+ except Exception as e:
152
+ logger.error(f"Failed to initialize runtime control service: {e}")
153
+ raise
154
+
155
+ async def single_step(self) -> ProcessorControlResponse:
156
+ """Execute a single processing step."""
157
+ try:
158
+ _start_time = self._now()
159
+
160
+ # Get the agent processor from runtime
161
+ if not self.runtime or not hasattr(self.runtime, "agent_processor"):
162
+ return ProcessorControlResponse(
163
+ success=False,
164
+ processor_name="agent",
165
+ operation="single_step",
166
+ new_status=self._processor_status,
167
+ error=_ERROR_AGENT_PROCESSOR_NOT_AVAILABLE,
168
+ )
169
+
170
+ # Ensure processor is paused
171
+ if not self.runtime.agent_processor.is_paused():
172
+ return ProcessorControlResponse(
173
+ success=False,
174
+ processor_name="agent",
175
+ operation="single_step",
176
+ new_status=self._processor_status,
177
+ error="Cannot single-step unless processor is paused",
178
+ )
179
+
180
+ result = await self.runtime.agent_processor.single_step()
181
+
182
+ # Track thought processing time if a thought was processed
183
+ if result.success and result.processing_time_ms:
184
+ processing_time = result.processing_time_ms
185
+
186
+ # Add to thought times list
187
+ self._thought_times.append(processing_time)
188
+
189
+ # Trim list to max history
190
+ if len(self._thought_times) > self._max_thought_history:
191
+ self._thought_times = self._thought_times[-self._max_thought_history :]
192
+
193
+ # Update average
194
+ self._average_thought_time_ms = sum(self._thought_times) / len(self._thought_times)
195
+
196
+ # Track metrics
197
+ self._thoughts_processed += 1
198
+
199
+ self._single_steps += 1
200
+ self._commands_processed += 1
201
+ # Convert result to dict for event recording
202
+ result_dict = result.model_dump() if hasattr(result, "model_dump") else result
203
+ await self._record_event("processor_control", "single_step", success=True, result=result_dict)
204
+
205
+ # Return the full step result data instead of discarding it
206
+ # Validate step_results - only include if they match StepResultData schema
207
+ raw_step_results = result.step_results if hasattr(result, "step_results") else []
208
+ validated_step_results = []
209
+
210
+ from ciris_engine.schemas.services.runtime_control import StepResultData
211
+
212
+ for step_result in raw_step_results:
213
+ try:
214
+ # Try to validate as StepResultData
215
+ if isinstance(step_result, dict):
216
+ validated = StepResultData(**step_result)
217
+ validated_step_results.append(validated)
218
+ elif isinstance(step_result, StepResultData):
219
+ validated_step_results.append(step_result)
220
+ except Exception:
221
+ # Skip invalid results - this happens during thought initiation
222
+ # when step_results only contain {"thought_id": ..., "initiated": True}
223
+ pass
224
+
225
+ # Map SingleStepResult fields to ProcessorControlResponse
226
+ # SingleStepResult has: success, message, thoughts_advanced (not thoughts_processed)
227
+ # When success=False, use message as error
228
+ error_text = None if result.success else result.message
229
+
230
+ return ProcessorControlResponse(
231
+ success=result.success if hasattr(result, "success") else False,
232
+ processor_name="agent",
233
+ operation="single_step",
234
+ new_status=self._processor_status,
235
+ error=error_text,
236
+ # Pass through all the H3ERE step data
237
+ step_point=getattr(result, "step_point", None),
238
+ step_results=validated_step_results if validated_step_results else None,
239
+ thoughts_processed=self._thoughts_processed, # Use internal counter, not result field
240
+ processing_time_ms=getattr(result, "processing_time_ms", 0.0),
241
+ pipeline_state=getattr(result, "pipeline_state", {}),
242
+ current_round=getattr(result, "current_round", None),
243
+ pipeline_empty=getattr(result, "pipeline_empty", False),
244
+ )
245
+
246
+ except Exception as e:
247
+ logger.error(f"Failed to execute single step: {e}", exc_info=True)
248
+ await self._record_event("processor_control", "single_step", success=False, error=str(e))
249
+ return ProcessorControlResponse(
250
+ success=False,
251
+ processor_name="agent",
252
+ operation="single_step",
253
+ new_status=self._processor_status,
254
+ error=str(e),
255
+ )
256
+
257
+ async def pause_processing(self) -> ProcessorControlResponse:
258
+ """Pause the processor."""
259
+ try:
260
+ _start_time = self._now()
261
+
262
+ # Get the agent processor from runtime
263
+ if not self.runtime or not hasattr(self.runtime, "agent_processor"):
264
+ return ProcessorControlResponse(
265
+ success=False,
266
+ processor_name="agent",
267
+ operation="pause",
268
+ new_status=self._processor_status,
269
+ error=_ERROR_AGENT_PROCESSOR_NOT_AVAILABLE,
270
+ )
271
+
272
+ success = await self.runtime.agent_processor.pause_processing()
273
+ if success:
274
+ old_status = self._processor_status
275
+ self._processor_status = ProcessorStatus.PAUSED
276
+ if old_status != ProcessorStatus.PAUSED:
277
+ self._state_transitions += 1
278
+ self._pause_resume_cycles += 1
279
+ self._commands_processed += 1
280
+ await self._record_event("processor_control", "pause", success=success)
281
+
282
+ return ProcessorControlResponse(
283
+ success=success,
284
+ processor_name="agent",
285
+ operation="pause",
286
+ new_status=self._processor_status,
287
+ error=None if success else "Failed to pause processor",
288
+ )
289
+
290
+ except Exception as e:
291
+ logger.error(f"Failed to pause processing: {e}", exc_info=True)
292
+ await self._record_event("processor_control", "pause", success=False, error=str(e))
293
+ return ProcessorControlResponse(
294
+ success=False,
295
+ processor_name="agent",
296
+ operation="pause",
297
+ new_status=self._processor_status,
298
+ error=str(e),
299
+ )
300
+
301
+ async def resume_processing(self) -> ProcessorControlResponse:
302
+ """Resume the processor."""
303
+ try:
304
+ _start_time = self._now()
305
+
306
+ # Get the agent processor from runtime
307
+ if not self.runtime or not hasattr(self.runtime, "agent_processor"):
308
+ return ProcessorControlResponse(
309
+ success=False,
310
+ processor_name="agent",
311
+ operation="resume",
312
+ new_status=self._processor_status,
313
+ error=_ERROR_AGENT_PROCESSOR_NOT_AVAILABLE,
314
+ )
315
+
316
+ success = await self.runtime.agent_processor.resume_processing()
317
+ if success:
318
+ old_status = self._processor_status
319
+ self._processor_status = ProcessorStatus.RUNNING
320
+ if old_status != ProcessorStatus.RUNNING:
321
+ self._state_transitions += 1
322
+ self._pause_resume_cycles += 1
323
+ self._commands_processed += 1
324
+ await self._record_event("processor_control", "resume", success=success)
325
+
326
+ return ProcessorControlResponse(
327
+ success=success,
328
+ processor_name="agent",
329
+ operation="resume",
330
+ new_status=self._processor_status,
331
+ error=None if success else "Failed to resume processor",
332
+ )
333
+
334
+ except Exception as e:
335
+ logger.error(f"Failed to resume processing: {e}", exc_info=True)
336
+ await self._record_event("processor_control", "resume", success=False, error=str(e))
337
+ return ProcessorControlResponse(
338
+ success=False,
339
+ processor_name="agent",
340
+ operation="resume",
341
+ new_status=self._processor_status,
342
+ error=str(e),
343
+ )
344
+
345
+ async def request_state_transition(self, target_state: str, reason: str) -> bool:
346
+ """Request a cognitive state transition.
347
+
348
+ Args:
349
+ target_state: Target state name (e.g., "DREAM", "PLAY", "SOLITUDE", "WORK")
350
+ reason: Reason for the transition request
351
+
352
+ Returns:
353
+ True if transition was successful, False otherwise
354
+ """
355
+ try:
356
+ if not self.runtime or not hasattr(self.runtime, "agent_processor"):
357
+ logger.error("Cannot transition state: agent processor not available")
358
+ return False
359
+
360
+ agent_processor = self.runtime.agent_processor
361
+ if not agent_processor:
362
+ logger.error("Cannot transition state: agent processor is None")
363
+ return False
364
+
365
+ # Convert string to AgentState enum (values are lowercase)
366
+ from ciris_engine.schemas.processors.states import AgentState
367
+
368
+ try:
369
+ target = AgentState(target_state.lower())
370
+ except ValueError:
371
+ logger.error(f"Invalid target state: {target_state}")
372
+ return False
373
+
374
+ current_state = agent_processor.state_manager.get_state()
375
+ logger.info(f"State transition requested: {current_state.value} -> {target.value} (reason: {reason})")
376
+
377
+ # Use the agent processor's _handle_state_transition method to properly
378
+ # start/stop state-specific processors (like DreamProcessor)
379
+ if hasattr(agent_processor, "_handle_state_transition"):
380
+ await agent_processor._handle_state_transition(target)
381
+ # Verify the transition happened
382
+ success = agent_processor.state_manager.get_state() == target
383
+ else:
384
+ # Fallback to just state manager if _handle_state_transition not available
385
+ success = await agent_processor.state_manager.transition_to(target)
386
+
387
+ if success:
388
+ self._state_transitions += 1
389
+ logger.info(f"State transition successful: {current_state.value} -> {target.value}")
390
+ else:
391
+ logger.warning(f"State transition failed: {current_state.value} -> {target.value}")
392
+
393
+ return bool(success)
394
+
395
+ except Exception as e:
396
+ logger.error(f"State transition failed: {e}", exc_info=True)
397
+ return False
398
+
399
+ async def get_processor_queue_status(self) -> ProcessorQueueStatus:
400
+ """Get processor queue status."""
401
+ try:
402
+ if not self.runtime or not hasattr(self.runtime, "agent_processor"):
403
+ return ProcessorQueueStatus(
404
+ processor_name="unknown",
405
+ queue_size=0,
406
+ max_size=0,
407
+ processing_rate=0.0,
408
+ average_latency_ms=0.0,
409
+ oldest_message_age_seconds=None,
410
+ )
411
+
412
+ # Check if agent processor is available
413
+ if not hasattr(self.runtime, "agent_processor") or self.runtime.agent_processor is None:
414
+ logger.debug("Agent processor not yet initialized, returning empty queue status")
415
+ return ProcessorQueueStatus(
416
+ processor_name="agent",
417
+ queue_size=0,
418
+ max_size=1000,
419
+ processing_rate=0.0,
420
+ average_latency_ms=0.0,
421
+ oldest_message_age_seconds=None,
422
+ )
423
+
424
+ # Get queue status from agent processor
425
+ queue_status = self.runtime.agent_processor.get_queue_status()
426
+
427
+ await self._record_event("processor_query", "queue_status", success=True)
428
+
429
+ return ProcessorQueueStatus(
430
+ processor_name="agent",
431
+ queue_size=queue_status.pending_thoughts + queue_status.pending_tasks,
432
+ max_size=1000, # Default max size
433
+ processing_rate=self._calculate_processing_rate(), # Seconds per thought (5-15 typical)
434
+ average_latency_ms=self._calculate_average_latency(),
435
+ oldest_message_age_seconds=None,
436
+ )
437
+ except Exception as e:
438
+ logger.error(f"Failed to get queue status: {e}", exc_info=True)
439
+ await self._record_event("processor_query", "queue_status", success=False, error=str(e))
440
+ return ProcessorQueueStatus(
441
+ processor_name="agent",
442
+ queue_size=0,
443
+ max_size=0,
444
+ processing_rate=0.0,
445
+ average_latency_ms=0.0,
446
+ oldest_message_age_seconds=None,
447
+ )
448
+
449
+ async def shutdown_runtime(self, reason: str = "Runtime shutdown requested") -> ProcessorControlResponse:
450
+ """Shutdown the entire runtime system."""
451
+ try:
452
+ _start_time = self._now()
453
+
454
+ logger.critical(f"RUNTIME SHUTDOWN INITIATED: {reason}")
455
+
456
+ # Record the shutdown event
457
+ await self._record_event("processor_control", "shutdown", success=True, result={"reason": reason})
458
+
459
+ # Request global shutdown through the shutdown service
460
+ if self.runtime and hasattr(self.runtime, "service_registry"):
461
+ shutdown_service = self.runtime.service_registry.get_service("ShutdownService")
462
+ if shutdown_service:
463
+ shutdown_service.request_shutdown(f"Runtime control: {reason}")
464
+ else:
465
+ logger.error("ShutdownService not available in registry")
466
+
467
+ # Set processor status to stopped
468
+ self._processor_status = ProcessorStatus.STOPPED
469
+
470
+ return ProcessorControlResponse(
471
+ success=True,
472
+ processor_name="agent",
473
+ operation="shutdown",
474
+ new_status=self._processor_status,
475
+ error=None,
476
+ )
477
+
478
+ except Exception as e:
479
+ logger.error(f"Failed to initiate shutdown: {e}", exc_info=True)
480
+ await self._record_event("processor_control", "shutdown", success=False, error=str(e))
481
+ return ProcessorControlResponse(
482
+ success=False,
483
+ processor_name="agent",
484
+ operation="shutdown",
485
+ new_status=self._processor_status,
486
+ error=str(e),
487
+ )
488
+
489
+ async def handle_emergency_shutdown(self, command: WASignedCommand) -> EmergencyShutdownStatus:
490
+ """
491
+ Handle WA-authorized emergency shutdown command.
492
+
493
+ Verifies WA signature and calls the shutdown service immediately.
494
+
495
+ Args:
496
+ command: Signed emergency shutdown command from WA
497
+
498
+ Returns:
499
+ Status of emergency shutdown process
500
+ """
501
+ logger.critical(f"EMERGENCY SHUTDOWN COMMAND RECEIVED from WA {command.wa_id}")
502
+
503
+ # Initialize status
504
+ now = self._now()
505
+
506
+ status = EmergencyShutdownStatus(
507
+ command_received=now,
508
+ command_verified=False,
509
+ verification_error=None,
510
+ shutdown_initiated=None,
511
+ data_persisted=False,
512
+ final_message_sent=False,
513
+ shutdown_completed=None,
514
+ exit_code=None,
515
+ )
516
+
517
+ try:
518
+ # Verify WA signature
519
+ if not self._verify_wa_signature(command):
520
+ status.command_verified = False
521
+ status.verification_error = "Invalid WA signature"
522
+ logger.error(f"Emergency shutdown rejected: Invalid signature from {command.wa_id}")
523
+ return status
524
+
525
+ status.command_verified = True
526
+ status.shutdown_initiated = self._now()
527
+
528
+ # Record emergency event
529
+ await self._record_event(
530
+ "emergency_shutdown",
531
+ "command_verified",
532
+ success=True,
533
+ result={"wa_id": command.wa_id, "command_id": command.command_id, "reason": command.reason},
534
+ )
535
+
536
+ # Call the existing shutdown mechanism
537
+ # This will trigger all registered shutdown handlers
538
+ shutdown_reason = f"WA EMERGENCY SHUTDOWN: {command.reason} (WA: {command.wa_id})"
539
+
540
+ # Get shutdown service from registry if available
541
+ if self.runtime and hasattr(self.runtime, "service_registry"):
542
+ # Fix: Provide both handler and service_type parameters
543
+ shutdown_service = await self.runtime.service_registry.get_service(
544
+ handler="default", service_type=ServiceType.SHUTDOWN
545
+ )
546
+ if shutdown_service:
547
+ shutdown_service.request_shutdown(shutdown_reason)
548
+ status.shutdown_completed = self._now()
549
+ status.exit_code = 0
550
+ logger.info("Emergency shutdown delegated to ShutdownService")
551
+ return status
552
+
553
+ # Fallback to direct shutdown
554
+ await self.shutdown_runtime(shutdown_reason)
555
+ status.shutdown_completed = self._now()
556
+ status.exit_code = 0
557
+
558
+ return status
559
+
560
+ except Exception as e:
561
+ logger.critical(f"Emergency shutdown failed: {e}")
562
+ status.verification_error = str(e)
563
+ status.shutdown_completed = self._now()
564
+ status.exit_code = 1
565
+ return status
566
+
567
+ def _verify_wa_signature(self, command: WASignedCommand) -> bool:
568
+ """
569
+ Verify the WA signature on an emergency command.
570
+
571
+ Args:
572
+ command: The signed command to verify
573
+
574
+ Returns:
575
+ True if signature is valid, False otherwise
576
+ """
577
+ try:
578
+ # Check if WA is authorized
579
+ if not self._wa_key_map.has_key(command.wa_id):
580
+ logger.error(f"WA {command.wa_id} not in authorized keys")
581
+ return False
582
+
583
+ # Get public key PEM and convert to key object
584
+ key_pem = self._wa_key_map.get_key(command.wa_id)
585
+ if not key_pem:
586
+ return False
587
+
588
+ from cryptography.hazmat.primitives import serialization
589
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
590
+
591
+ public_key = serialization.load_pem_public_key(key_pem.encode("utf-8"))
592
+
593
+ # Ensure it's an Ed25519 key
594
+ if not isinstance(public_key, Ed25519PublicKey):
595
+ logger.error(f"WA {command.wa_id} key is not Ed25519")
596
+ return False
597
+
598
+ # Reconstruct signed data (canonical form)
599
+ signed_data = "|".join(
600
+ [
601
+ f"command_id:{command.command_id}",
602
+ f"command_type:{command.command_type}",
603
+ f"wa_id:{command.wa_id}",
604
+ f"issued_at:{command.issued_at.isoformat()}",
605
+ f"reason:{command.reason}",
606
+ ]
607
+ )
608
+
609
+ if command.target_agent_id:
610
+ signed_data += f"|target_agent_id:{command.target_agent_id}"
611
+
612
+ # Verify signature
613
+ from cryptography.exceptions import InvalidSignature
614
+
615
+ try:
616
+ signature_bytes = bytes.fromhex(command.signature)
617
+ public_key.verify(signature_bytes, signed_data.encode("utf-8"))
618
+ return True
619
+ except InvalidSignature:
620
+ logger.error("Invalid signature on emergency command")
621
+ return False
622
+
623
+ except Exception as e:
624
+ logger.error(f"Signature verification failed: {e}")
625
+ return False
626
+
627
+ def _configure_kill_switch(self, config: KillSwitchConfig) -> None:
628
+ """
629
+ Configure the emergency kill switch.
630
+
631
+ Args:
632
+ config: Kill switch configuration including root WA keys
633
+ """
634
+ self._kill_switch_config = config
635
+
636
+ # Parse and store WA public keys
637
+ from cryptography.hazmat.primitives import serialization
638
+
639
+ self._wa_key_map.clear()
640
+ for key_pem in config.root_wa_public_keys:
641
+ try:
642
+ # Validate that it's a valid Ed25519 key
643
+ public_key = serialization.load_pem_public_key(key_pem.encode("utf-8"))
644
+ # Import the actual type for isinstance check
645
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey as Ed25519PublicKeyImpl
646
+
647
+ if isinstance(public_key, Ed25519PublicKeyImpl):
648
+ # Extract WA ID from comment or use hash
649
+ wa_id = self._extract_wa_id_from_pem(key_pem)
650
+ self._wa_key_map.add_key(wa_id, key_pem)
651
+ except Exception as e:
652
+ logger.error(f"Failed to load WA public key: {e}")
653
+
654
+ logger.info(f"Kill switch configured with {self._wa_key_map.count()} root WA keys")
655
+
656
+ def _extract_wa_id_from_pem(self, key_pem: str) -> str:
657
+ """Extract WA ID from PEM comment or generate from hash."""
658
+ for line in key_pem.split("\n"):
659
+ if line.startswith("# WA-ID:"):
660
+ return line.split(":", 1)[1].strip()
661
+
662
+ # Fallback to hash
663
+ import hashlib
664
+
665
+ return hashlib.sha256(key_pem.encode()).hexdigest()[:16]
666
+
667
+ def _ensure_adapter_manager(self) -> bool:
668
+ """Lazy-initialize adapter_manager if needed. Returns True if available."""
669
+ if self.adapter_manager:
670
+ return True
671
+
672
+ if not self.runtime:
673
+ return False
674
+
675
+ if self._time_service is None:
676
+ from ciris_engine.logic.services.lifecycle.time import TimeService
677
+
678
+ self._time_service = TimeService()
679
+
680
+ from ciris_engine.logic.runtime.ciris_runtime import CIRISRuntime
681
+
682
+ self.adapter_manager = RuntimeAdapterManager(cast(CIRISRuntime, self.runtime), self._time_service)
683
+ logger.info("Lazy-initialized adapter_manager")
684
+ return True
685
+
686
+ def _convert_to_adapter_config(
687
+ self, adapter_type: str, config: Optional[Dict[str, object]]
688
+ ) -> Optional[AdapterConfig]:
689
+ """Convert config dict to AdapterConfig if needed."""
690
+ if not config:
691
+ return None
692
+
693
+ if isinstance(config, AdapterConfig):
694
+ return config
695
+
696
+ if "adapter_type" in config:
697
+ return AdapterConfig(**config)
698
+
699
+ return AdapterConfig(
700
+ adapter_type=adapter_type,
701
+ enabled=True,
702
+ adapter_config=config,
703
+ )
704
+
705
+ # Adapter Management Methods
706
+ async def load_adapter(
707
+ self,
708
+ adapter_type: str,
709
+ adapter_id: Optional[str] = None,
710
+ config: Optional[Dict[str, object]] = None,
711
+ auto_start: bool = True,
712
+ ) -> AdapterOperationResponse:
713
+ """Load a new adapter instance."""
714
+ if not self._ensure_adapter_manager():
715
+ return AdapterOperationResponse(
716
+ success=False,
717
+ timestamp=self._now(),
718
+ adapter_id=adapter_id or "unknown",
719
+ adapter_type=adapter_type,
720
+ status=AdapterStatus.ERROR,
721
+ error="Adapter manager not available",
722
+ )
723
+
724
+ assert self.adapter_manager is not None # Guaranteed by _ensure_adapter_manager
725
+ adapter_config = self._convert_to_adapter_config(adapter_type, config)
726
+ result = await self.adapter_manager.load_adapter(adapter_type, adapter_id or "", adapter_config)
727
+
728
+ return AdapterOperationResponse(
729
+ success=result.success,
730
+ adapter_id=result.adapter_id,
731
+ adapter_type=adapter_type,
732
+ timestamp=self._now(),
733
+ status=AdapterStatus.RUNNING if result.success else AdapterStatus.ERROR,
734
+ error=result.error,
735
+ )
736
+
737
+ async def unload_adapter(self, adapter_id: str, force: bool = False) -> AdapterOperationResponse:
738
+ """Unload an adapter instance."""
739
+ logger.warning(
740
+ f"RuntimeControlService.unload_adapter called: adapter_id={adapter_id}, "
741
+ f"has_adapter_manager={self.adapter_manager is not None}, "
742
+ f"adapter_manager_id={id(self.adapter_manager) if self.adapter_manager else None}, "
743
+ f"has_runtime={self.runtime is not None}, "
744
+ f"service_id={id(self)}"
745
+ )
746
+
747
+ if not self._ensure_adapter_manager():
748
+ return AdapterOperationResponse(
749
+ success=False,
750
+ timestamp=self._now(),
751
+ adapter_id=adapter_id or "unknown",
752
+ adapter_type="unknown",
753
+ status=AdapterStatus.ERROR,
754
+ error="Adapter manager not available",
755
+ )
756
+
757
+ assert self.adapter_manager is not None # Guaranteed by _ensure_adapter_manager
758
+ result = await self.adapter_manager.unload_adapter(adapter_id)
759
+
760
+ return AdapterOperationResponse(
761
+ success=result.success,
762
+ adapter_id=result.adapter_id,
763
+ adapter_type=result.adapter_type or "unknown",
764
+ timestamp=self._now(),
765
+ status=AdapterStatus.STOPPED if result.success else AdapterStatus.ERROR,
766
+ error=result.error,
767
+ )
768
+
769
+ async def list_adapters(self) -> List[AdapterInfo]:
770
+ """List all loaded adapters including bootstrap adapters."""
771
+ self._ensure_adapter_manager_initialized()
772
+
773
+ adapters_list: List[AdapterInfo] = []
774
+ adapters_list.extend(await self._get_bootstrap_adapters())
775
+ adapters_list.extend(await self._get_managed_adapters())
776
+
777
+ return adapters_list
778
+
779
+ def _ensure_adapter_manager_initialized(self) -> None:
780
+ """Ensure adapter manager is initialized lazily."""
781
+ if not self.adapter_manager and self.runtime:
782
+ if self._time_service is None:
783
+ from ciris_engine.logic.services.lifecycle.time import TimeService
784
+
785
+ self._time_service = TimeService()
786
+
787
+ from ciris_engine.logic.runtime.ciris_runtime import CIRISRuntime
788
+
789
+ self.adapter_manager = RuntimeAdapterManager(cast(CIRISRuntime, self.runtime), self._time_service)
790
+ logger.info("Lazy-initialized adapter_manager in list_adapters")
791
+
792
+ async def _get_bootstrap_adapters(self) -> List[AdapterInfo]:
793
+ """Get bootstrap adapters from runtime."""
794
+ adapters: List[AdapterInfo] = []
795
+
796
+ if not (self.runtime and hasattr(self.runtime, "adapters")):
797
+ return adapters
798
+
799
+ for adapter in self.runtime.adapters:
800
+ adapter_type = self._extract_adapter_type(adapter)
801
+
802
+ if await self._should_skip_bootstrap_adapter(adapter_type):
803
+ continue
804
+
805
+ adapter_info = await self._create_bootstrap_adapter_info(adapter, adapter_type)
806
+ adapters.append(adapter_info)
807
+
808
+ return adapters
809
+
810
+ def _extract_adapter_type(self, adapter: Any) -> str:
811
+ """Extract adapter type from class name."""
812
+ class_name: str = adapter.__class__.__name__
813
+ return class_name.lower().replace("platform", "").replace("adapter", "")
814
+
815
+ async def _should_skip_bootstrap_adapter(self, adapter_type: str) -> bool:
816
+ """Check if bootstrap adapter should be skipped because it's managed."""
817
+ if adapter_type != "discord" or not self.adapter_manager:
818
+ return False
819
+
820
+ adapter_list = await self.adapter_manager.list_adapters()
821
+ return any(a.adapter_type == "discord" for a in adapter_list)
822
+
823
+ async def _create_bootstrap_adapter_info(self, adapter: Any, adapter_type: str) -> AdapterInfo:
824
+ """Create AdapterInfo for a bootstrap adapter."""
825
+ tools = await self._extract_adapter_tools(adapter, adapter_type)
826
+
827
+ return AdapterInfo(
828
+ adapter_id=f"{adapter_type}_bootstrap",
829
+ adapter_type=adapter_type,
830
+ status=AdapterStatus.RUNNING, # Bootstrap adapters are always running
831
+ started_at=self._start_time, # Use service start time
832
+ messages_processed=0, # Tracked via telemetry service
833
+ error_count=0,
834
+ last_error=None,
835
+ tools=tools,
836
+ )
837
+
838
+ async def _extract_adapter_tools(self, adapter: Any, adapter_type: str) -> List[ToolInfo]:
839
+ """Extract tools from adapter tool service."""
840
+ tools: List[ToolInfo] = []
841
+
842
+ if not (hasattr(adapter, "tool_service") and adapter.tool_service):
843
+ return tools
844
+
845
+ try:
846
+ if hasattr(adapter.tool_service, "list_tools"):
847
+ tool_names = await adapter.tool_service.list_tools()
848
+ for tool_name in tool_names:
849
+ tool_info = await self._create_tool_info(adapter.tool_service, tool_name)
850
+ if tool_info:
851
+ tools.append(tool_info)
852
+ except Exception as e:
853
+ logger.debug(f"Could not get tools from {adapter_type}: {e}")
854
+
855
+ return tools
856
+
857
+ async def _create_tool_info(self, tool_service: Any, tool_name: str) -> Optional[ToolInfo]:
858
+ """Create ToolInfo object from tool service."""
859
+ try:
860
+ # Default parameters schema
861
+ default_parameters = ToolParameterSchema(
862
+ type="object",
863
+ properties={},
864
+ required=[],
865
+ )
866
+
867
+ # Try to get schema from tool service
868
+ parameters = default_parameters
869
+ if hasattr(tool_service, "get_tool_schema"):
870
+ schema = await tool_service.get_tool_schema(tool_name)
871
+ if schema:
872
+ # Convert schema to ToolParameterSchema
873
+ if isinstance(schema, dict):
874
+ parameters = ToolParameterSchema(
875
+ type=schema.get("type", "object"),
876
+ properties=schema.get("properties", {}),
877
+ required=schema.get("required", []),
878
+ )
879
+ elif hasattr(schema, "model_dump"):
880
+ schema_dict = schema.model_dump()
881
+ parameters = ToolParameterSchema(
882
+ type=schema_dict.get("type", "object"),
883
+ properties=schema_dict.get("properties", {}),
884
+ required=schema_dict.get("required", []),
885
+ )
886
+
887
+ return ToolInfo(
888
+ name=tool_name,
889
+ description=f"{tool_name} tool",
890
+ parameters=parameters,
891
+ )
892
+ except Exception as e:
893
+ logger.debug(f"Could not create ToolInfo for {tool_name}: {e}")
894
+ return None
895
+
896
+ async def _get_managed_adapters(self) -> List[AdapterInfo]:
897
+ """Get adapters from adapter manager."""
898
+ if not self.adapter_manager:
899
+ return []
900
+
901
+ adapters = []
902
+ adapters_raw = await self.adapter_manager.list_adapters()
903
+
904
+ for adapter_status in adapters_raw:
905
+ adapter_info = self._convert_managed_adapter_status(adapter_status)
906
+ adapters.append(adapter_info)
907
+
908
+ return adapters
909
+
910
+ def _convert_managed_adapter_status(self, adapter_status: Any) -> AdapterInfo:
911
+ """Convert adapter manager status to AdapterInfo."""
912
+ status = AdapterStatus.RUNNING if adapter_status.is_running else AdapterStatus.STOPPED
913
+
914
+ return AdapterInfo(
915
+ adapter_id=adapter_status.adapter_id,
916
+ adapter_type=adapter_status.adapter_type,
917
+ status=status,
918
+ started_at=adapter_status.loaded_at,
919
+ messages_processed=self._safe_get_metric(adapter_status, "messages_processed", 0),
920
+ error_count=self._safe_get_metric(adapter_status, "errors_count", 0),
921
+ last_error=self._safe_get_metric(adapter_status, "last_error", None),
922
+ tools=adapter_status.tools if hasattr(adapter_status, "tools") else None,
923
+ )
924
+
925
+ def _safe_get_metric(self, adapter_status: Any, metric_name: str, default: Any) -> Any:
926
+ """Safely get metric value from adapter status."""
927
+ if adapter_status.metrics and hasattr(adapter_status.metrics, "get"):
928
+ return adapter_status.metrics.get(metric_name, default)
929
+ return default
930
+
931
+ async def get_adapter_info(self, adapter_id: str) -> Optional[AdapterInfo]:
932
+ """Get detailed information about a specific adapter."""
933
+ # Lazy initialization of adapter_manager if needed
934
+ if not self.adapter_manager and self.runtime:
935
+ if self._time_service is None:
936
+ from ciris_engine.logic.services.lifecycle.time import TimeService
937
+
938
+ self._time_service = TimeService()
939
+ from ciris_engine.logic.runtime.ciris_runtime import CIRISRuntime
940
+
941
+ self.adapter_manager = RuntimeAdapterManager(cast(CIRISRuntime, self.runtime), self._time_service)
942
+ logger.info("Lazy-initialized adapter_manager in get_adapter_info")
943
+
944
+ if not self.adapter_manager:
945
+ return None
946
+
947
+ info = self.adapter_manager.get_adapter_info(adapter_id)
948
+ if info is None:
949
+ return None
950
+
951
+ # Convert adapter_management.AdapterInfo to core.runtime.AdapterInfo
952
+ from datetime import datetime
953
+
954
+ return AdapterInfo(
955
+ adapter_id=info.adapter_id,
956
+ adapter_type=info.adapter_type,
957
+ status=AdapterStatus.RUNNING if info.is_running else AdapterStatus.STOPPED,
958
+ started_at=datetime.fromisoformat(info.load_time) if info.load_time else None,
959
+ messages_processed=0, # Not tracked in adapter_management.AdapterInfo
960
+ error_count=0, # Not tracked in adapter_management.AdapterInfo
961
+ last_error=None,
962
+ tools=None,
963
+ )
964
+
965
+ # Configuration Management Methods
966
+ async def get_config(self, path: Optional[str] = None, include_sensitive: bool = False) -> ConfigSnapshot:
967
+ """Get configuration value(s)."""
968
+ try:
969
+ # Get all configs or specific config
970
+ config_value_map = ConfigValueMap()
971
+
972
+ if path:
973
+ config_node = await self._get_config_manager().get_config(path)
974
+ if config_node:
975
+ # Extract actual value from ConfigValue wrapper
976
+ actual_value = config_node.value.value
977
+ if actual_value is not None:
978
+ config_value_map.set(path, actual_value)
979
+ else:
980
+ # list_configs returns Dict[str, Union[str, int, float, bool, List, Dict]]
981
+ all_configs = await self._get_config_manager().list_configs()
982
+ config_value_map.update(all_configs)
983
+
984
+ # Determine sensitive keys
985
+ sensitive_keys = []
986
+ if not include_sensitive:
987
+ # Mark which keys would be sensitive
988
+ from ciris_engine.schemas.api.config_security import ConfigSecurity
989
+
990
+ for key in config_value_map.keys():
991
+ if ConfigSecurity.is_sensitive(key):
992
+ sensitive_keys.append(key)
993
+
994
+ return ConfigSnapshot(
995
+ configs=config_value_map.configs,
996
+ version=self.config_version if hasattr(self, "config_version") else "1.0.0",
997
+ sensitive_keys=sensitive_keys,
998
+ metadata={"path_filter": path, "include_sensitive": include_sensitive},
999
+ )
1000
+ except Exception as e:
1001
+ logger.error(f"Failed to get config: {e}")
1002
+ return ConfigSnapshot(configs={}, version="unknown", metadata={"error": str(e)})
1003
+
1004
+ async def update_config(
1005
+ self,
1006
+ path: str,
1007
+ value: object,
1008
+ scope: str = "runtime",
1009
+ validation_level: str = "full",
1010
+ reason: Optional[str] = None,
1011
+ ) -> ConfigOperationResponse:
1012
+ """Update a configuration value."""
1013
+ try:
1014
+ # GraphConfigService uses set_config, not update_config_value
1015
+ # Convert object to appropriate type
1016
+ config_value = value if isinstance(value, (str, int, float, bool, list, dict)) else str(value)
1017
+ await self._get_config_manager().set_config(path, config_value, updated_by="RuntimeControlService")
1018
+ result = ConfigOperationResponse(
1019
+ success=True,
1020
+ operation="update_config",
1021
+ config_path=path,
1022
+ details={
1023
+ "scope": scope,
1024
+ "validation_level": validation_level,
1025
+ "reason": reason,
1026
+ "timestamp": self._now().isoformat(),
1027
+ },
1028
+ error=None,
1029
+ )
1030
+ if result.success:
1031
+ self._last_config_change = self._now()
1032
+ return result
1033
+ except Exception as e:
1034
+ logger.error(f"Failed to update config: {e}")
1035
+ return ConfigOperationResponse(
1036
+ success=False,
1037
+ operation="update_config",
1038
+ config_path=path,
1039
+ details={"timestamp": self._now().isoformat()},
1040
+ error=str(e),
1041
+ )
1042
+
1043
+ async def validate_config(
1044
+ self, config_data: Dict[str, object], config_path: Optional[str] = None
1045
+ ) -> ConfigValidationResponse:
1046
+ """Validate configuration data."""
1047
+ try:
1048
+ # GraphConfigService doesn't have validate_config, do basic validation
1049
+ return ConfigValidationResponse(
1050
+ valid=True, validation_level=ConfigValidationLevel.SYNTAX, errors=[], warnings=[], suggestions=[]
1051
+ )
1052
+ except Exception as e:
1053
+ logger.error(f"Failed to validate config: {e}")
1054
+ return ConfigValidationResponse(
1055
+ valid=False, validation_level=ConfigValidationLevel.SYNTAX, errors=[str(e)], warnings=[], suggestions=[]
1056
+ )
1057
+
1058
+ async def backup_config(self, backup_name: Optional[str] = None) -> ConfigOperationResponse:
1059
+ """Create a configuration backup."""
1060
+ try:
1061
+ # GraphConfigService doesn't have backup_config, store as special config
1062
+ all_configs = await self._get_config_manager().list_configs()
1063
+ backup_key = f"backup_{backup_name or self._now().strftime('%Y%m%d_%H%M%S')}"
1064
+
1065
+ # Create backup data using the schema
1066
+ backup_data = ConfigBackupData(
1067
+ configs=all_configs, backup_version="1.0.0", backup_by="RuntimeControlService"
1068
+ )
1069
+
1070
+ # Store the backup
1071
+ await self._get_config_manager().set_config(
1072
+ backup_key, backup_data.to_config_value(), updated_by="RuntimeControlService"
1073
+ )
1074
+
1075
+ # Convert ConfigBackupData to ConfigOperationResponse
1076
+ return ConfigOperationResponse(
1077
+ success=True,
1078
+ operation="backup_config",
1079
+ config_path="config",
1080
+ details={
1081
+ "timestamp": backup_data.backup_timestamp.isoformat(),
1082
+ "backup_id": backup_key,
1083
+ "backup_name": backup_name,
1084
+ "size_bytes": len(str(all_configs)),
1085
+ },
1086
+ error=None,
1087
+ )
1088
+ except Exception as e:
1089
+ logger.error(f"Failed to backup config: {e}")
1090
+ return ConfigOperationResponse(
1091
+ success=False,
1092
+ operation="backup_config",
1093
+ config_path="config",
1094
+ details={"timestamp": self._now().isoformat()},
1095
+ error=str(e),
1096
+ )
1097
+
1098
+ async def restore_config(self, backup_name: str) -> ConfigOperationResponse:
1099
+ """Restore configuration from backup."""
1100
+ try:
1101
+ backup_config = await self._get_config_manager().get_config(backup_name)
1102
+ if not backup_config:
1103
+ raise ValueError(f"Backup '{backup_name}' not found")
1104
+
1105
+ actual_backup = self._extract_backup_configs(backup_config)
1106
+ await self._restore_configs_from_backup(actual_backup)
1107
+
1108
+ return ConfigOperationResponse(
1109
+ success=True,
1110
+ operation="restore_config",
1111
+ config_path="config",
1112
+ details={
1113
+ "backup_name": backup_name,
1114
+ "timestamp": self._now().isoformat(),
1115
+ "message": f"Restored from backup {backup_name}",
1116
+ },
1117
+ error=None,
1118
+ )
1119
+ except Exception as e:
1120
+ logger.error(f"Failed to restore config: {e}")
1121
+ return ConfigOperationResponse(
1122
+ success=False,
1123
+ operation="restore_config",
1124
+ config_path="config",
1125
+ details={"backup_name": backup_name, "timestamp": self._now().isoformat()},
1126
+ error=str(e),
1127
+ )
1128
+
1129
+ def _extract_backup_configs(self, backup_config: Any) -> ConfigDict:
1130
+ """Extract backup configuration data from ConfigValue wrapper."""
1131
+ backup_raw = backup_config.value
1132
+ backup_value = backup_raw.value if hasattr(backup_raw, "value") else backup_raw
1133
+
1134
+ if isinstance(backup_value, dict) and "configs" in backup_value:
1135
+ return self._parse_structured_backup(backup_value)
1136
+ elif isinstance(backup_value, dict):
1137
+ # Filter out None values to match the expected type
1138
+ result: ConfigDict = {}
1139
+ for k, v in backup_value.items():
1140
+ if v is not None and isinstance(v, (str, int, float, bool, list, dict)):
1141
+ result[k] = v
1142
+ return result
1143
+ else:
1144
+ raise ValueError("Backup data is not in expected format")
1145
+
1146
+ def _parse_structured_backup(self, backup_value: JSONDict) -> ConfigDict:
1147
+ """Parse structured backup data with metadata."""
1148
+ timestamp_str = backup_value.get("backup_timestamp")
1149
+ if not isinstance(timestamp_str, str):
1150
+ raise ValueError("backup_timestamp must be a string")
1151
+
1152
+ backup_data = ConfigBackupData(
1153
+ configs=backup_value["configs"],
1154
+ backup_timestamp=datetime.fromisoformat(timestamp_str),
1155
+ backup_version=str(backup_value.get("backup_version", "1.0.0")),
1156
+ backup_by=str(backup_value.get("backup_by", "RuntimeControlService")),
1157
+ )
1158
+ return backup_data.configs
1159
+
1160
+ async def _restore_configs_from_backup(self, configs: ConfigDict) -> None:
1161
+ """Restore individual configuration values from backup."""
1162
+ for key, value in configs.items():
1163
+ if not key.startswith("backup_"): # Don't restore backups
1164
+ config_val = value if isinstance(value, (str, int, float, bool, list, dict)) else str(value)
1165
+ await self._get_config_manager().set_config(key, config_val, "RuntimeControlService")
1166
+
1167
+ async def list_config_backups(self) -> List[ConfigBackup]:
1168
+ """List available configuration backups."""
1169
+ try:
1170
+ # List all backup configs
1171
+ all_configs = await self._get_config_manager().list_configs(prefix="backup_")
1172
+ backups = []
1173
+ for key, value in all_configs.items():
1174
+ backup = ConfigBackup(
1175
+ backup_id=key,
1176
+ created_at=self._now(), # Would need to store this in config
1177
+ config_version="1.0.0",
1178
+ size_bytes=len(str(value)),
1179
+ path=key,
1180
+ description=None,
1181
+ )
1182
+ backups.append(backup)
1183
+ return backups
1184
+ except Exception as e:
1185
+ logger.error(f"Failed to list config backups: {e}")
1186
+ return []
1187
+
1188
+ async def get_runtime_status(self) -> RuntimeStatusResponse:
1189
+ """Get current runtime status."""
1190
+ try:
1191
+ current_time = self._now()
1192
+ uptime = (current_time - self._start_time).total_seconds() # type: ignore[operator]
1193
+
1194
+ # Get adapter information
1195
+ adapters = []
1196
+ if self.adapter_manager:
1197
+ adapters = await self.adapter_manager.list_adapters()
1198
+ _active_adapters = [a.adapter_id for a in adapters if a.is_running]
1199
+ _loaded_adapters = [a.adapter_id for a in adapters]
1200
+
1201
+ # Agent identity is now stored in graph, not profiles
1202
+ _current_profile = "identity-based"
1203
+
1204
+ # Get agent processor state and queue info
1205
+ processor_paused = False
1206
+ cognitive_state = None
1207
+ queue_depth = 0
1208
+
1209
+ if self.runtime and hasattr(self.runtime, "agent_processor") and self.runtime.agent_processor:
1210
+ processor_paused = getattr(self.runtime.agent_processor, "_is_paused", False)
1211
+ cognitive_state = str(getattr(self.runtime.agent_processor, "_current_state", None))
1212
+
1213
+ # Get actual queue depth using existing processor queue status method
1214
+ try:
1215
+ processor_queue_status = await self.get_processor_queue_status()
1216
+ queue_depth = processor_queue_status.queue_size
1217
+ except Exception as e:
1218
+ logger.warning(f"Failed to get queue depth from processor queue status: {e}")
1219
+ queue_depth = 0
1220
+
1221
+ # Determine processor status
1222
+ if processor_paused:
1223
+ processor_status = ProcessorStatus.PAUSED
1224
+ elif self._processor_status == ProcessorStatus.RUNNING:
1225
+ processor_status = ProcessorStatus.RUNNING
1226
+ else:
1227
+ processor_status = self._processor_status
1228
+
1229
+ return RuntimeStatusResponse(
1230
+ is_running=self._processor_status == ProcessorStatus.RUNNING,
1231
+ uptime_seconds=uptime,
1232
+ processor_count=1, # Single agent processor
1233
+ adapter_count=len(adapters),
1234
+ total_messages_processed=self._messages_processed,
1235
+ current_load=self._calculate_current_load(),
1236
+ processor_status=processor_status,
1237
+ cognitive_state=cognitive_state,
1238
+ queue_depth=queue_depth,
1239
+ )
1240
+
1241
+ except Exception as e:
1242
+ logger.error(f"Failed to get runtime status: {e}")
1243
+ return RuntimeStatusResponse(
1244
+ is_running=False,
1245
+ uptime_seconds=0.0,
1246
+ processor_count=1,
1247
+ adapter_count=0,
1248
+ total_messages_processed=0,
1249
+ current_load=0.0,
1250
+ processor_status=ProcessorStatus.ERROR,
1251
+ cognitive_state=None,
1252
+ queue_depth=0,
1253
+ )
1254
+
1255
+ async def get_runtime_snapshot(self) -> RuntimeStateSnapshot:
1256
+ """Get complete runtime state snapshot."""
1257
+ try:
1258
+ current_time = self._now()
1259
+
1260
+ # Get runtime status
1261
+ runtime_status = await self.get_runtime_status()
1262
+
1263
+ # Get processor queue status
1264
+ processor_queue = await self.get_processor_queue_status()
1265
+ processors = [processor_queue]
1266
+
1267
+ # Get adapters
1268
+ adapters = await self.list_adapters()
1269
+
1270
+ # Get config version
1271
+ config_snapshot = await self.get_config()
1272
+ config_version = config_snapshot.version
1273
+
1274
+ # Get health summary
1275
+ health_summary = await self.get_service_health_status()
1276
+
1277
+ return RuntimeStateSnapshot(
1278
+ timestamp=current_time,
1279
+ runtime_status=runtime_status,
1280
+ processors=processors,
1281
+ adapters=adapters,
1282
+ config_version=config_version,
1283
+ health_summary=health_summary,
1284
+ )
1285
+
1286
+ except Exception as e:
1287
+ logger.error(f"Failed to get runtime snapshot: {e}")
1288
+ raise
1289
+
1290
+ async def _get_service_registry_info(
1291
+ self, handler: Optional[str] = None, service_type: Optional[str] = None
1292
+ ) -> ServiceRegistryInfoResponse:
1293
+ """Get information about registered services in the service registry."""
1294
+ try:
1295
+ if (
1296
+ not self.runtime
1297
+ or not hasattr(self.runtime, "service_registry")
1298
+ or self.runtime.service_registry is None
1299
+ ):
1300
+ # Return a valid ServiceRegistryInfoResponse with empty data
1301
+ return ServiceRegistryInfoResponse(
1302
+ total_services=0, services_by_type={}, handlers={}, healthy_services=0, circuit_breaker_states={}
1303
+ )
1304
+
1305
+ info = self.runtime.service_registry.get_provider_info(handler, service_type)
1306
+
1307
+ # Convert the dict to ServiceRegistryInfoResponse
1308
+ if isinstance(info, dict):
1309
+ # Extract handler services with full details
1310
+ handlers_dict: Dict[str, Dict[str, List[ServiceProviderInfo]]] = {}
1311
+ for handler_name, services in info.get("handlers", {}).items():
1312
+ service_dict: Dict[str, List[ServiceProviderInfo]] = {}
1313
+ for service_type_name, providers in services.items():
1314
+ # Convert provider dicts to ServiceProviderInfo objects
1315
+ provider_infos = [ServiceProviderInfo(**p) for p in providers]
1316
+ service_dict[service_type_name] = provider_infos
1317
+ handlers_dict[handler_name] = service_dict
1318
+
1319
+ # Extract global services if present
1320
+ global_services: Optional[Dict[str, List[ServiceProviderInfo]]] = None
1321
+ if "global_services" in info:
1322
+ global_services_dict: Dict[str, List[ServiceProviderInfo]] = {}
1323
+ for service_type_name, providers in info["global_services"].items():
1324
+ provider_infos = [ServiceProviderInfo(**p) for p in providers]
1325
+ global_services_dict[service_type_name] = provider_infos
1326
+ global_services = global_services_dict
1327
+
1328
+ return ServiceRegistryInfoResponse(
1329
+ total_services=info.get("total_services", 0),
1330
+ services_by_type=info.get("services_by_type", {}),
1331
+ handlers=handlers_dict,
1332
+ global_services=global_services,
1333
+ healthy_services=info.get("healthy_services", 0),
1334
+ circuit_breaker_states=info.get("circuit_breaker_states", {}),
1335
+ )
1336
+ else:
1337
+ # Fallback if info is not a dict
1338
+ return ServiceRegistryInfoResponse(
1339
+ total_services=0, services_by_type={}, handlers={}, healthy_services=0, circuit_breaker_states={}
1340
+ )
1341
+ except Exception as e:
1342
+ logger.error(f"Failed to get service registry info: {e}")
1343
+ # Return empty ServiceRegistryInfoResponse on error
1344
+ return ServiceRegistryInfoResponse(
1345
+ total_services=0,
1346
+ services_by_type={},
1347
+ handlers={},
1348
+ healthy_services=0,
1349
+ circuit_breaker_states={},
1350
+ error=str(e),
1351
+ )
1352
+
1353
+ async def update_service_priority(
1354
+ self,
1355
+ provider_name: str,
1356
+ new_priority: str,
1357
+ new_priority_group: Optional[int] = None,
1358
+ new_strategy: Optional[str] = None,
1359
+ ) -> ServicePriorityUpdateResponse:
1360
+ """Update service provider priority and selection strategy."""
1361
+ try:
1362
+ if not self._has_service_registry():
1363
+ return ServicePriorityUpdateResponse(
1364
+ success=False, provider_name=provider_name, error=_ERROR_SERVICE_REGISTRY_NOT_AVAILABLE
1365
+ )
1366
+
1367
+ validation_result = self._validate_priority_and_strategy(provider_name, new_priority, new_strategy)
1368
+ if validation_result is not None:
1369
+ return validation_result
1370
+
1371
+ new_priority_enum, new_strategy_enum = self._parse_priority_and_strategy(new_priority, new_strategy)
1372
+
1373
+ update_result = await self._update_provider_priority(
1374
+ provider_name, new_priority_enum, new_priority_group, new_strategy_enum
1375
+ )
1376
+
1377
+ return update_result
1378
+
1379
+ except Exception as e:
1380
+ logger.error(f"Failed to update service priority: {e}")
1381
+ await self._record_event("service_management", "update_priority", success=False, error=str(e))
1382
+ return ServicePriorityUpdateResponse(success=False, provider_name=provider_name, error=str(e))
1383
+
1384
+ def _has_service_registry(self) -> bool:
1385
+ """Check if service registry is available."""
1386
+ return self.runtime is not None and hasattr(self.runtime, "service_registry")
1387
+
1388
+ def _validate_priority_and_strategy(
1389
+ self, provider_name: str, new_priority: str, new_strategy: Optional[str]
1390
+ ) -> Optional[ServicePriorityUpdateResponse]:
1391
+ """Validate priority and strategy parameters. Return error response if invalid, None if valid."""
1392
+ from ciris_engine.logic.registries.base import Priority, SelectionStrategy
1393
+
1394
+ # Validate priority
1395
+ try:
1396
+ Priority[new_priority.upper()]
1397
+ except KeyError:
1398
+ valid_priorities = [p.name for p in Priority]
1399
+ return ServicePriorityUpdateResponse(
1400
+ success=False,
1401
+ provider_name=provider_name,
1402
+ error=f"Invalid priority '{new_priority}'. Valid priorities: {valid_priorities}",
1403
+ )
1404
+
1405
+ # Validate strategy if provided
1406
+ if new_strategy:
1407
+ try:
1408
+ SelectionStrategy[new_strategy.upper()]
1409
+ except KeyError:
1410
+ valid_strategies = [s.name for s in SelectionStrategy]
1411
+ return ServicePriorityUpdateResponse(
1412
+ success=False,
1413
+ provider_name=provider_name,
1414
+ error=f"Invalid strategy '{new_strategy}'. Valid strategies: {valid_strategies}",
1415
+ )
1416
+
1417
+ return None # Valid
1418
+
1419
+ def _parse_priority_and_strategy(self, new_priority: str, new_strategy: Optional[str]) -> tuple[Any, Optional[Any]]:
1420
+ """Parse and return priority and strategy enums."""
1421
+ from ciris_engine.logic.registries.base import Priority, SelectionStrategy
1422
+
1423
+ new_priority_enum = Priority[new_priority.upper()]
1424
+ new_strategy_enum = SelectionStrategy[new_strategy.upper()] if new_strategy else None
1425
+
1426
+ return new_priority_enum, new_strategy_enum
1427
+
1428
+ async def _update_provider_priority(
1429
+ self,
1430
+ provider_name: str,
1431
+ new_priority_enum: Any,
1432
+ new_priority_group: Optional[int],
1433
+ new_strategy_enum: Optional[Any],
1434
+ ) -> ServicePriorityUpdateResponse:
1435
+ """Update the provider priority in the registry."""
1436
+ if not self.runtime or not hasattr(self.runtime, "service_registry"):
1437
+ return ServicePriorityUpdateResponse(
1438
+ success=False, provider_name=provider_name, error="Service registry not available"
1439
+ )
1440
+
1441
+ registry = self.runtime.service_registry
1442
+
1443
+ provider_info = self._find_provider_in_registry(registry, provider_name)
1444
+ if not provider_info:
1445
+ return ServicePriorityUpdateResponse(
1446
+ success=False,
1447
+ provider_name=provider_name,
1448
+ error=f"Service provider '{provider_name}' not found in registry",
1449
+ )
1450
+
1451
+ updated_info = self._apply_priority_updates(
1452
+ provider_info, new_priority_enum, new_priority_group, new_strategy_enum
1453
+ )
1454
+
1455
+ await self._record_event(
1456
+ "service_management", "update_priority", success=True, result=updated_info.model_dump()
1457
+ )
1458
+ logger.info(
1459
+ f"Updated service provider '{provider_name}' priority from "
1460
+ f"{updated_info.old_priority} to {updated_info.new_priority}"
1461
+ )
1462
+
1463
+ return ServicePriorityUpdateResponse(
1464
+ success=True,
1465
+ message=f"Successfully updated provider '{provider_name}' priority",
1466
+ provider_name=provider_name,
1467
+ changes=updated_info,
1468
+ timestamp=self._now(),
1469
+ )
1470
+
1471
+ def _find_provider_in_registry(self, registry: Any, provider_name: str) -> Optional[_ProviderLookupResult]:
1472
+ """Find a provider in the service registry."""
1473
+ for service_type, providers in registry._services.items():
1474
+ for provider in providers:
1475
+ if provider.name == provider_name:
1476
+ return _ProviderLookupResult(
1477
+ provider=provider, providers_list=providers, service_type=str(service_type)
1478
+ )
1479
+ return None
1480
+
1481
+ def _apply_priority_updates(
1482
+ self,
1483
+ provider_info: _ProviderLookupResult,
1484
+ new_priority_enum: Any,
1485
+ new_priority_group: Optional[int],
1486
+ new_strategy_enum: Optional[Any],
1487
+ ) -> ServiceProviderUpdate:
1488
+ """Apply priority and strategy updates to provider."""
1489
+ provider = provider_info.provider
1490
+ providers_list = provider_info.providers_list
1491
+
1492
+ old_priority = provider.priority.name
1493
+ old_priority_group = provider.priority_group
1494
+ old_strategy = provider.strategy.name
1495
+
1496
+ # Update provider attributes
1497
+ provider.priority = new_priority_enum
1498
+ if new_priority_group is not None:
1499
+ provider.priority_group = new_priority_group
1500
+ if new_strategy_enum is not None:
1501
+ provider.strategy = new_strategy_enum
1502
+
1503
+ # Re-sort providers by priority
1504
+ providers_list.sort(key=lambda x: (x.priority_group, x.priority.value))
1505
+
1506
+ return ServiceProviderUpdate(
1507
+ service_type=provider_info.service_type,
1508
+ old_priority=old_priority,
1509
+ new_priority=provider.priority.name,
1510
+ old_priority_group=old_priority_group,
1511
+ new_priority_group=provider.priority_group,
1512
+ old_strategy=old_strategy,
1513
+ new_strategy=provider.strategy.name,
1514
+ )
1515
+
1516
+ async def reset_circuit_breakers(self, service_type: Optional[str] = None) -> CircuitBreakerResetResponse:
1517
+ """Reset circuit breakers for services."""
1518
+ try:
1519
+ if not self._has_circuit_breaker_registry():
1520
+ return CircuitBreakerResetResponse(
1521
+ success=False,
1522
+ message=_ERROR_SERVICE_REGISTRY_NOT_AVAILABLE,
1523
+ timestamp=self._now(),
1524
+ service_type=service_type,
1525
+ error=_ERROR_SERVICE_REGISTRY_NOT_AVAILABLE,
1526
+ )
1527
+
1528
+ if service_type:
1529
+ reset_result = await self._reset_specific_service_type_breakers(service_type)
1530
+ else:
1531
+ reset_result = await self._reset_all_circuit_breakers()
1532
+
1533
+ if not reset_result.success:
1534
+ return reset_result
1535
+
1536
+ await self._record_event(
1537
+ "service_management",
1538
+ "reset_circuit_breakers",
1539
+ True,
1540
+ result={"service_type": service_type, "providers_reset": reset_result.reset_count},
1541
+ )
1542
+
1543
+ return reset_result
1544
+
1545
+ except Exception as e:
1546
+ logger.error(f"Failed to reset circuit breakers: {e}")
1547
+ await self._record_event("service_management", "reset_circuit_breakers", False, error=str(e))
1548
+ return CircuitBreakerResetResponse(
1549
+ success=False,
1550
+ message=f"Failed to reset circuit breakers: {str(e)}",
1551
+ timestamp=self._now(),
1552
+ service_type=service_type,
1553
+ error=str(e),
1554
+ )
1555
+
1556
+ def _has_circuit_breaker_registry(self) -> bool:
1557
+ """Check if service registry is available for circuit breaker operations."""
1558
+ return (
1559
+ self.runtime is not None
1560
+ and hasattr(self.runtime, "service_registry")
1561
+ and self.runtime.service_registry is not None
1562
+ )
1563
+
1564
+ async def _reset_specific_service_type_breakers(self, service_type: str) -> CircuitBreakerResetResponse:
1565
+ """Reset circuit breakers for a specific service type."""
1566
+ from ciris_engine.schemas.runtime.enums import ServiceType
1567
+
1568
+ try:
1569
+ service_type_enum = ServiceType(service_type)
1570
+ providers_reset = self._reset_providers_by_service_type(service_type_enum)
1571
+ message = f"Reset {len(providers_reset)} circuit breakers for {service_type} services"
1572
+
1573
+ return CircuitBreakerResetResponse(
1574
+ success=True,
1575
+ message=message,
1576
+ timestamp=self._now(),
1577
+ service_type=service_type,
1578
+ reset_count=len(providers_reset),
1579
+ )
1580
+ except ValueError:
1581
+ return CircuitBreakerResetResponse(
1582
+ success=False,
1583
+ message=f"Invalid service type: {service_type}",
1584
+ timestamp=self._now(),
1585
+ service_type=service_type,
1586
+ error=f"Invalid service type: {service_type}",
1587
+ )
1588
+
1589
+ def _reset_providers_by_service_type(self, service_type_enum: Any) -> List[str]:
1590
+ """Reset circuit breakers for providers of a specific service type."""
1591
+ providers_reset: List[str] = []
1592
+
1593
+ if not self.runtime or not hasattr(self.runtime, "service_registry"):
1594
+ return providers_reset
1595
+
1596
+ if service_type_enum in self.runtime.service_registry._services:
1597
+ for provider in self.runtime.service_registry._services[service_type_enum]:
1598
+ if provider.circuit_breaker:
1599
+ provider.circuit_breaker.reset()
1600
+ providers_reset.append(provider.name)
1601
+
1602
+ return providers_reset
1603
+
1604
+ async def _reset_all_circuit_breakers(self) -> CircuitBreakerResetResponse:
1605
+ """Reset all circuit breakers in the registry."""
1606
+ if not self.runtime or not hasattr(self.runtime, "service_registry"):
1607
+ return CircuitBreakerResetResponse(
1608
+ success=False,
1609
+ message="Service registry not available",
1610
+ timestamp=self._now(),
1611
+ service_type=None,
1612
+ error="Service registry not available",
1613
+ )
1614
+
1615
+ self.runtime.service_registry.reset_circuit_breakers()
1616
+ providers_reset = list(self.runtime.service_registry._circuit_breakers.keys())
1617
+ message = f"Reset all {len(providers_reset)} circuit breakers"
1618
+
1619
+ return CircuitBreakerResetResponse(
1620
+ success=True,
1621
+ message=message,
1622
+ timestamp=self._now(),
1623
+ service_type=None,
1624
+ reset_count=len(providers_reset),
1625
+ )
1626
+
1627
+ async def get_circuit_breaker_status(self, service_type: Optional[str] = None) -> Dict[str, CircuitBreakerStatus]:
1628
+ """Get circuit breaker status for services."""
1629
+ try:
1630
+ if (
1631
+ not self.runtime
1632
+ or not hasattr(self.runtime, "service_registry")
1633
+ or self.runtime.service_registry is None
1634
+ ):
1635
+ return {}
1636
+
1637
+ registry_info = self.runtime.service_registry.get_provider_info(service_type=service_type)
1638
+ circuit_breakers: Dict[str, CircuitBreakerStatus] = {}
1639
+
1640
+ # Process handler services
1641
+ for handler, services in registry_info.get("handlers", {}).items():
1642
+ for svc_type, providers in services.items():
1643
+ if service_type and svc_type != service_type:
1644
+ continue
1645
+
1646
+ for provider in providers:
1647
+ service_name = f"{handler}.{svc_type}.{provider['name']}"
1648
+ cb_state_str = provider.get("circuit_breaker_state", "closed")
1649
+
1650
+ # Map string state to enum
1651
+ if cb_state_str == "closed":
1652
+ cb_state = CircuitBreakerState.CLOSED
1653
+ elif cb_state_str == "open":
1654
+ cb_state = CircuitBreakerState.OPEN
1655
+ else:
1656
+ cb_state = CircuitBreakerState.HALF_OPEN
1657
+
1658
+ circuit_breakers[service_name] = CircuitBreakerStatus(
1659
+ state=cb_state,
1660
+ failure_count=0, # Would need to get from actual circuit breaker
1661
+ service_name=service_name,
1662
+ trip_threshold=5,
1663
+ reset_timeout_seconds=60.0,
1664
+ )
1665
+
1666
+ # Process global services
1667
+ for svc_type, providers in registry_info.get("global_services", {}).items():
1668
+ if service_type and svc_type != service_type:
1669
+ continue
1670
+
1671
+ for provider in providers:
1672
+ service_name = f"global.{svc_type}.{provider['name']}"
1673
+ cb_state_str = provider.get("circuit_breaker_state", "closed")
1674
+
1675
+ # Map string state to enum
1676
+ if cb_state_str == "closed":
1677
+ cb_state = CircuitBreakerState.CLOSED
1678
+ elif cb_state_str == "open":
1679
+ cb_state = CircuitBreakerState.OPEN
1680
+ else:
1681
+ cb_state = CircuitBreakerState.HALF_OPEN
1682
+
1683
+ circuit_breakers[service_name] = CircuitBreakerStatus(
1684
+ state=cb_state,
1685
+ failure_count=0,
1686
+ service_name=service_name,
1687
+ trip_threshold=5,
1688
+ reset_timeout_seconds=60.0,
1689
+ )
1690
+
1691
+ return circuit_breakers
1692
+
1693
+ except Exception as e:
1694
+ logger.error(f"Failed to get circuit breaker status: {e}")
1695
+ return {}
1696
+
1697
+ async def get_service_selection_explanation(self) -> ServiceSelectionExplanation:
1698
+ """Get explanation of service selection logic."""
1699
+ try:
1700
+
1701
+ explanation = ServiceSelectionExplanation(
1702
+ overview="CIRIS uses a sophisticated multi-level service selection system with priority groups, priorities, and selection strategies.",
1703
+ priority_groups={
1704
+ 0: "Primary services - tried first",
1705
+ 1: "Secondary/backup services - used when primary unavailable",
1706
+ 2: "Tertiary/fallback services - last resort (e.g., mock providers)",
1707
+ },
1708
+ priorities={
1709
+ "CRITICAL": {"value": 0, "description": "Highest priority - always tried first within a group"},
1710
+ "HIGH": {"value": 1, "description": "High priority services"},
1711
+ "NORMAL": {"value": 2, "description": "Standard priority (default)"},
1712
+ "LOW": {"value": 3, "description": "Low priority services"},
1713
+ "FALLBACK": {"value": 9, "description": "Last resort services within a group"},
1714
+ },
1715
+ selection_strategies={
1716
+ "FALLBACK": "First available strategy - try services in priority order until one succeeds",
1717
+ "ROUND_ROBIN": "Load balancing - rotate through services to distribute load",
1718
+ },
1719
+ selection_flow=[
1720
+ "1. Group services by priority_group (0, 1, 2...)",
1721
+ "2. Within each group, sort by Priority (CRITICAL, HIGH, NORMAL, LOW, FALLBACK)",
1722
+ "3. Apply the group's selection strategy (FALLBACK or ROUND_ROBIN)",
1723
+ "4. Check if service is healthy (if health check available)",
1724
+ "5. Check if circuit breaker is closed (not tripped)",
1725
+ "6. Verify service has required capabilities",
1726
+ "7. If all checks pass, use the service",
1727
+ "8. If service fails, try next according to strategy",
1728
+ "9. If all services in group fail, try next group",
1729
+ ],
1730
+ circuit_breaker_info={
1731
+ "purpose": "Prevents repeated calls to failing services",
1732
+ "states": {
1733
+ "CLOSED": "Normal operation - service is available",
1734
+ "OPEN": "Service is unavailable - too many recent failures",
1735
+ "HALF_OPEN": "Testing if service has recovered",
1736
+ },
1737
+ "configuration": "Configurable failure threshold, timeout, and half-open test interval",
1738
+ },
1739
+ examples=[
1740
+ {
1741
+ "scenario": "LLM Service Selection",
1742
+ "setup": "3 LLM providers: OpenAI (group 0, HIGH), Anthropic (group 0, NORMAL), MockLLM (group 1, NORMAL)",
1743
+ "result": "System tries OpenAI first, then Anthropic, then MockLLM only if both group 0 providers fail",
1744
+ },
1745
+ {
1746
+ "scenario": "Round Robin Load Balancing",
1747
+ "setup": "2 Memory providers in group 0 with ROUND_ROBIN strategy",
1748
+ "result": "Requests alternate between the two providers to distribute load",
1749
+ },
1750
+ ],
1751
+ configuration_tips=[
1752
+ "Use priority groups to separate production services (group 0) from fallback services (group 1+)",
1753
+ "Set CRITICAL priority for essential services that should always be tried first",
1754
+ "Use ROUND_ROBIN strategy for stateless services to distribute load",
1755
+ "Configure circuit breakers with appropriate thresholds based on service reliability",
1756
+ "Place mock/test services in higher priority groups (2+) to ensure they're only used as last resort",
1757
+ ],
1758
+ )
1759
+
1760
+ await self._record_event("service_query", "get_selection_explanation", success=True)
1761
+ return explanation
1762
+
1763
+ except Exception as e:
1764
+ logger.error(f"Failed to get service selection explanation: {e}")
1765
+ await self._record_event("service_query", "get_selection_explanation", success=False, error=str(e))
1766
+ # Return a minimal explanation on error
1767
+ return ServiceSelectionExplanation(
1768
+ overview="Error retrieving service selection explanation",
1769
+ priority_groups={},
1770
+ priorities={},
1771
+ selection_strategies={},
1772
+ selection_flow=[],
1773
+ circuit_breaker_info={},
1774
+ examples=[],
1775
+ configuration_tips=[],
1776
+ )
1777
+
1778
+ async def get_service_health_status(self) -> ServiceHealthStatus:
1779
+ """Get health status of all registered services."""
1780
+ try:
1781
+ if not self.runtime:
1782
+ return ServiceHealthStatus(
1783
+ overall_health="critical",
1784
+ healthy_services=0,
1785
+ unhealthy_services=0,
1786
+ service_details={},
1787
+ recommendations=["Runtime not available"],
1788
+ )
1789
+
1790
+ service_details = {}
1791
+ healthy_count = 0
1792
+ unhealthy_count = 0
1793
+
1794
+ # First, get all direct services from runtime properties
1795
+ direct_services = [
1796
+ # Graph Services (6)
1797
+ ("memory_service", "graph", "MemoryService"),
1798
+ ("config_service", "graph", "ConfigService"),
1799
+ ("telemetry_service", "graph", "TelemetryService"),
1800
+ ("audit_service", "graph", "AuditService"),
1801
+ ("incident_management_service", "graph", "IncidentManagementService"),
1802
+ ("tsdb_consolidation_service", "graph", "TSDBConsolidationService"),
1803
+ # Infrastructure Services (7)
1804
+ ("time_service", "infrastructure", "TimeService"),
1805
+ ("shutdown_service", "infrastructure", "ShutdownService"),
1806
+ ("initialization_service", "infrastructure", "InitializationService"),
1807
+ ("authentication_service", "infrastructure", "AuthenticationService"),
1808
+ ("resource_monitor", "infrastructure", "ResourceMonitorService"),
1809
+ ("maintenance_service", "infrastructure", "DatabaseMaintenanceService"),
1810
+ ("secrets_service", "infrastructure", "SecretsService"),
1811
+ # Governance Services (4)
1812
+ ("wa_auth_system", "governance", "WiseAuthorityService"),
1813
+ ("adaptive_filter_service", "governance", "AdaptiveFilterService"),
1814
+ ("visibility_service", "governance", "VisibilityService"),
1815
+ ("self_observation_service", "governance", "SelfObservationService"),
1816
+ # Runtime Services (3)
1817
+ ("llm_service", "runtime", "LLMService"),
1818
+ ("runtime_control_service", "runtime", "RuntimeControlService"),
1819
+ ("task_scheduler", "runtime", "TaskSchedulerService"),
1820
+ # Tool Services (1)
1821
+ ("secrets_tool_service", "tool", "SecretsToolService"),
1822
+ ]
1823
+
1824
+ # Check each direct service
1825
+ for attr_name, category, display_name in direct_services:
1826
+ service = getattr(self.runtime, attr_name, None)
1827
+ if service:
1828
+ service_key = f"direct.{category}.{display_name}"
1829
+ try:
1830
+ # Check if service has is_healthy method
1831
+ if hasattr(service, "is_healthy"):
1832
+ is_healthy = (
1833
+ await service.is_healthy()
1834
+ if asyncio.iscoroutinefunction(service.is_healthy)
1835
+ else service.is_healthy()
1836
+ )
1837
+ elif hasattr(service, "_started"):
1838
+ is_healthy = service._started
1839
+ else:
1840
+ is_healthy = True # Assume healthy if no health check
1841
+
1842
+ service_details[service_key] = {
1843
+ "healthy": is_healthy,
1844
+ "circuit_breaker_state": "closed", # Direct services don't use circuit breakers
1845
+ "priority": "DIRECT", # Direct call, no priority
1846
+ "priority_group": -1, # Not applicable
1847
+ "strategy": "DIRECT", # Direct call
1848
+ }
1849
+
1850
+ if is_healthy:
1851
+ healthy_count += 1
1852
+ else:
1853
+ unhealthy_count += 1
1854
+ except Exception as e:
1855
+ logger.error(f"Error checking health of {display_name}: {e}")
1856
+ service_details[service_key] = {
1857
+ "healthy": False,
1858
+ "circuit_breaker_state": "error",
1859
+ "priority": "DIRECT",
1860
+ "priority_group": -1,
1861
+ "strategy": "DIRECT",
1862
+ "error": str(e),
1863
+ }
1864
+ unhealthy_count += 1
1865
+
1866
+ # Then get registry services (bus-based services)
1867
+ if hasattr(self.runtime, "service_registry") and self.runtime.service_registry:
1868
+ registry_info = self.runtime.service_registry.get_provider_info()
1869
+ logger.debug(f"Registry info keys: {list(registry_info.keys())}")
1870
+
1871
+ unhealthy_services_list = []
1872
+ healthy_services_list = []
1873
+
1874
+ # New format: all services are under "services" key
1875
+ for service_type, providers in registry_info.get("services", {}).items():
1876
+ for provider in providers:
1877
+ service_key = f"registry.{service_type}.{provider['name']}"
1878
+ cb_state = provider.get("circuit_breaker_state", "closed")
1879
+ is_healthy = cb_state == "closed"
1880
+
1881
+ service_details[service_key] = {
1882
+ "healthy": is_healthy,
1883
+ "circuit_breaker_state": cb_state,
1884
+ "priority": provider.get("priority", "NORMAL"),
1885
+ "priority_group": provider.get("priority_group", 0),
1886
+ "strategy": provider.get("strategy", "FALLBACK"),
1887
+ }
1888
+
1889
+ if is_healthy:
1890
+ healthy_count += 1
1891
+ healthy_services_list.append(service_key)
1892
+ else:
1893
+ unhealthy_count += 1
1894
+ unhealthy_services_list.append(service_key)
1895
+
1896
+ # Determine overall health
1897
+ overall_health = "healthy"
1898
+ recommendations = []
1899
+
1900
+ if unhealthy_count > 0:
1901
+ if unhealthy_count > healthy_count:
1902
+ overall_health = "unhealthy"
1903
+ recommendations.append("Critical: More unhealthy services than healthy ones")
1904
+ else:
1905
+ overall_health = "degraded"
1906
+ recommendations.append(f"Warning: {unhealthy_count} services are unhealthy")
1907
+
1908
+ recommendations.append("Consider resetting circuit breakers for failed services")
1909
+ recommendations.append("Check service logs for error details")
1910
+
1911
+ return ServiceHealthStatus(
1912
+ overall_health=overall_health,
1913
+ healthy_services=healthy_count,
1914
+ unhealthy_services=unhealthy_count,
1915
+ service_details=service_details,
1916
+ recommendations=recommendations,
1917
+ )
1918
+
1919
+ except Exception as e:
1920
+ logger.error(f"Failed to get service health status: {e}")
1921
+ return ServiceHealthStatus(
1922
+ overall_health="critical",
1923
+ healthy_services=0,
1924
+ unhealthy_services=0,
1925
+ service_details={},
1926
+ recommendations=[f"Critical error while checking service health: {str(e)}"],
1927
+ )
1928
+
1929
+ async def _get_service_selection_explanation(self) -> ServiceSelectionExplanation:
1930
+ """Get explanation of how service selection works with priorities and strategies."""
1931
+ return ServiceSelectionExplanation(
1932
+ overview="Services are selected using a multi-tier priority system with configurable selection strategies",
1933
+ priority_groups={
1934
+ 0: "Primary services - tried first",
1935
+ 1: "Secondary services - used when primary unavailable",
1936
+ 2: "Tertiary services - last resort options",
1937
+ },
1938
+ selection_strategies={
1939
+ "FALLBACK": "Use first available healthy service in priority order",
1940
+ "ROUND_ROBIN": "Rotate through services at same priority level",
1941
+ },
1942
+ examples=[
1943
+ {
1944
+ "scenario": "fallback_strategy",
1945
+ "description": "Two LLM services: OpenAI (CRITICAL) and LocalLLM (NORMAL)",
1946
+ "behavior": "Always try OpenAI first, fall back to LocalLLM if OpenAI fails",
1947
+ },
1948
+ {
1949
+ "scenario": "round_robin_strategy",
1950
+ "description": "Three load-balanced API services all at NORMAL priority",
1951
+ "behavior": "Rotate requests: API1 -> API2 -> API3 -> API1 -> ...",
1952
+ },
1953
+ {
1954
+ "scenario": "multi_group_example",
1955
+ "description": "Priority Group 0: Critical services, Priority Group 1: Backup services",
1956
+ "behavior": "Only use Group 1 services if all Group 0 services are unavailable",
1957
+ },
1958
+ ],
1959
+ configuration_tips=[
1960
+ "Use priority groups to separate primary and backup services",
1961
+ "Set CRITICAL priority for essential services within a group",
1962
+ "Use ROUND_ROBIN strategy for load balancing similar services",
1963
+ "Configure circuit breakers to handle transient failures gracefully",
1964
+ ],
1965
+ )
1966
+
1967
+ # Helper Methods
1968
+ async def _record_event(
1969
+ self,
1970
+ category: str,
1971
+ action: str,
1972
+ success: bool,
1973
+ result: Optional[Dict[str, object]] = None,
1974
+ error: Optional[str] = None,
1975
+ ) -> None:
1976
+ """Record an event in the history."""
1977
+ try:
1978
+ event = RuntimeEvent(
1979
+ event_type=f"{category}:{action}",
1980
+ timestamp=self._now(),
1981
+ source="RuntimeControlService",
1982
+ details={"result": result, "success": success} if result else {"success": success},
1983
+ severity="error" if error else "info",
1984
+ )
1985
+ # Store additional fields in details since they're not in the schema
1986
+ if error:
1987
+ event.details["error"] = error
1988
+
1989
+ self._events_history.append(event)
1990
+
1991
+ if len(self._events_history) > 1000:
1992
+ self._events_history = self._events_history[-1000:]
1993
+
1994
+ except Exception as e:
1995
+ logger.error(f"Failed to record event: {e}")
1996
+
1997
+ def get_events_history(self, limit: int = 100) -> List[RuntimeEvent]:
1998
+ """Get recent events history."""
1999
+ return self._events_history[-limit:]
2000
+
2001
+ # Legacy method to maintain compatibility
2002
+ async def _reload_config(self, config_path: Optional[str] = None) -> ConfigReloadResult:
2003
+ """Reload system configuration."""
2004
+ try:
2005
+ await self._record_event(
2006
+ "config_reload", "reload", success=False, error="Legacy method - use specific config operations instead"
2007
+ )
2008
+
2009
+ return ConfigReloadResult(
2010
+ success=False,
2011
+ config_version="unknown",
2012
+ changes_applied=0,
2013
+ warnings=["Legacy method - use specific config operations instead"],
2014
+ error="Use specific configuration management endpoints instead",
2015
+ )
2016
+
2017
+ except Exception as e:
2018
+ logger.error(f"Failed to reload config: {e}", exc_info=True)
2019
+ return ConfigReloadResult(
2020
+ success=False, config_version="unknown", changes_applied=0, warnings=[], error=str(e)
2021
+ )
2022
+
2023
+ # Service interface methods required by Service base class
2024
+ def get_service_type(self) -> ServiceType:
2025
+ """Get the service type enum value."""
2026
+ return ServiceType.RUNTIME_CONTROL
2027
+
2028
+ def _check_dependencies(self) -> bool:
2029
+ """Check if all required dependencies are available."""
2030
+ # Runtime is optional - service can function without it
2031
+ return True
2032
+
2033
+ def _register_dependencies(self) -> None:
2034
+ """Register service dependencies."""
2035
+ super()._register_dependencies()
2036
+ if hasattr(self, "config_manager") and self.config_manager:
2037
+ self._dependencies.add("GraphConfigService")
2038
+ if hasattr(self, "adapter_manager") and self.adapter_manager:
2039
+ self._dependencies.add("RuntimeAdapterManager")
2040
+
2041
+ def _collect_custom_metrics(self) -> Dict[str, float]:
2042
+ """Collect enhanced runtime control metrics."""
2043
+ # Get queue depth if processor exists
2044
+ queue_depth = 0
2045
+ if self.runtime and hasattr(self.runtime, "agent_processor") and self.runtime.agent_processor:
2046
+ if hasattr(self.runtime.agent_processor, "queue") and self.runtime.agent_processor.queue:
2047
+ queue_depth = len(self.runtime.agent_processor.queue)
2048
+
2049
+ # Map cognitive state to float
2050
+ cognitive_state = 0.0
2051
+ if self.runtime and hasattr(self.runtime, "agent_processor") and self.runtime.agent_processor:
2052
+ if hasattr(self.runtime.agent_processor, "state"):
2053
+ state_map = {"WAKEUP": 1.0, "WORK": 2.0, "PLAY": 3.0, "SOLITUDE": 4.0, "DREAM": 5.0, "SHUTDOWN": 6.0}
2054
+ cognitive_state = state_map.get(str(self.runtime.agent_processor.state), 0.0)
2055
+
2056
+ metrics = {
2057
+ # Original metrics
2058
+ "events_count": float(len(self._events_history)),
2059
+ "processor_status": 1.0 if self._processor_status == ProcessorStatus.RUNNING else 0.0,
2060
+ "adapters_loaded": float(len(self.adapter_manager.loaded_adapters)) if self.adapter_manager else 0.0,
2061
+ # Enhanced metrics
2062
+ "queue_depth": float(queue_depth),
2063
+ "thoughts_processed": float(self._thoughts_processed),
2064
+ "thoughts_pending": float(self._thoughts_pending),
2065
+ "cognitive_state": cognitive_state,
2066
+ "average_thought_time_ms": self._calculate_average_thought_time(),
2067
+ "runtime_paused": 1.0 if self._processor_status == ProcessorStatus.PAUSED else 0.0,
2068
+ "runtime_step_mode": 1.0 if hasattr(self, "_step_mode") and self._step_mode else 0.0,
2069
+ "service_overrides_active": float(self._service_overrides),
2070
+ "runtime_errors": float(self._runtime_errors),
2071
+ "messages_processed": float(self._messages_processed),
2072
+ "average_message_latency_ms": self._calculate_average_latency(),
2073
+ "seconds_per_thought": self._calculate_processing_rate(), # Renamed to be clear
2074
+ "system_load": self._calculate_current_load(),
2075
+ }
2076
+
2077
+ return metrics
2078
+
2079
+ async def get_metrics(self) -> Dict[str, float]:
2080
+ """
2081
+ Get all runtime control service metrics including base, custom, and v1.4.3 specific.
2082
+ """
2083
+ # Get all base + custom metrics
2084
+ metrics = self._collect_metrics()
2085
+ # Get queue size from agent processor
2086
+ queue_size = 0
2087
+ if self.runtime and hasattr(self.runtime, "agent_processor") and self.runtime.agent_processor:
2088
+ if hasattr(self.runtime.agent_processor, "queue") and self.runtime.agent_processor.queue:
2089
+ queue_size = len(self.runtime.agent_processor.queue)
2090
+ elif hasattr(self.runtime.agent_processor, "get_queue_status"):
2091
+ try:
2092
+ queue_status = self.runtime.agent_processor.get_queue_status()
2093
+ queue_size = queue_status.pending_thoughts + queue_status.pending_tasks
2094
+ except Exception:
2095
+ queue_size = 0
2096
+
2097
+ # Map processor status to int (matching cognitive state pattern)
2098
+ current_state = 0
2099
+ if self._processor_status == ProcessorStatus.RUNNING:
2100
+ current_state = 1
2101
+ elif self._processor_status == ProcessorStatus.PAUSED:
2102
+ current_state = 2
2103
+ elif self._processor_status == ProcessorStatus.STOPPED:
2104
+ current_state = 3
2105
+
2106
+ # Calculate uptime in seconds
2107
+ uptime_seconds = self._calculate_uptime()
2108
+
2109
+ # Add v1.4.3 specific runtime metrics
2110
+ metrics.update(
2111
+ {
2112
+ "runtime_state_transitions": float(self._state_transitions),
2113
+ "runtime_commands_processed": float(self._commands_processed),
2114
+ "runtime_current_state": float(current_state),
2115
+ "runtime_queue_size": float(queue_size),
2116
+ "runtime_uptime_seconds": float(uptime_seconds),
2117
+ }
2118
+ )
2119
+
2120
+ return metrics
2121
+
2122
+ def _track_thought_processing_time(self, processing_time_ms: float) -> None:
2123
+ """
2124
+ Callback to track thought processing times.
2125
+ Called by AgentProcessor when a thought completes.
2126
+ """
2127
+ # Add to thought times list
2128
+ self._thought_times.append(processing_time_ms)
2129
+
2130
+ # Trim list to max history
2131
+ if len(self._thought_times) > self._max_thought_history:
2132
+ self._thought_times = self._thought_times[-self._max_thought_history :]
2133
+
2134
+ # Update average
2135
+ if self._thought_times:
2136
+ self._average_thought_time_ms = sum(self._thought_times) / len(self._thought_times)
2137
+
2138
+ # Track metrics
2139
+ self._thoughts_processed += 1
2140
+
2141
+ def _calculate_average_thought_time(self) -> float:
2142
+ """Calculate average thought processing time."""
2143
+ if self._thought_times:
2144
+ return sum(self._thought_times) / len(self._thought_times)
2145
+ return self._average_thought_time_ms
2146
+
2147
+ def _calculate_average_latency(self) -> float:
2148
+ """Calculate average thought processing latency."""
2149
+ return self._calculate_average_thought_time()
2150
+
2151
+ def _calculate_processing_rate(self) -> float:
2152
+ """Calculate seconds per thought (not thoughts per second!).
2153
+
2154
+ Thoughts take 5-15 seconds each, so this returns the average
2155
+ time in seconds to process one thought.
2156
+ """
2157
+ if self._thought_times and self._average_thought_time_ms > 0:
2158
+ # Convert milliseconds to seconds
2159
+ avg_time_seconds = self._average_thought_time_ms / 1000.0
2160
+ return avg_time_seconds # Seconds per thought (5-15 typical)
2161
+ return 10.0 # Default: 10 seconds per thought
2162
+
2163
+ def _calculate_current_load(self) -> float:
2164
+ """Calculate current system load (0.0 to 1.0)."""
2165
+ # Load based on queue depth and processing rate
2166
+ if self.runtime and hasattr(self.runtime, "agent_processor") and self.runtime.agent_processor:
2167
+ if hasattr(self.runtime.agent_processor, "queue") and self.runtime.agent_processor.queue:
2168
+ queue_depth = len(self.runtime.agent_processor.queue)
2169
+ # Normalize to 0-1 range (assume 100 messages is full load)
2170
+ return min(queue_depth / 100.0, 1.0)
2171
+ return 0.0
2172
+
2173
+ def _get_actions(self) -> List[str]:
2174
+ """Get list of actions this service provides."""
2175
+ return [
2176
+ "single_step",
2177
+ "pause_processing",
2178
+ "resume_processing",
2179
+ "get_processor_queue_status",
2180
+ "shutdown_runtime",
2181
+ "load_adapter",
2182
+ "unload_adapter",
2183
+ "list_adapters",
2184
+ "get_adapter_info",
2185
+ "get_config",
2186
+ "update_config",
2187
+ "validate_config",
2188
+ "backup_config",
2189
+ "restore_config",
2190
+ "list_config_backups",
2191
+ "reload_config_profile",
2192
+ "get_runtime_status",
2193
+ "get_runtime_snapshot",
2194
+ "update_service_priority",
2195
+ "reset_circuit_breakers",
2196
+ "get_service_health_status",
2197
+ ]
2198
+
2199
+ def get_capabilities(self) -> ServiceCapabilities:
2200
+ """Get service capabilities with custom metadata."""
2201
+ # Get base capabilities
2202
+ capabilities = super().get_capabilities()
2203
+
2204
+ # Add custom metadata using model_copy
2205
+ if capabilities.metadata is not None:
2206
+ capabilities.metadata = capabilities.metadata.model_copy(
2207
+ update={
2208
+ "description": "Runtime control and management service",
2209
+ "features": ["processor_control", "adapter_management", "config_management", "health_monitoring"],
2210
+ }
2211
+ )
2212
+
2213
+ return capabilities
2214
+
2215
+ def get_status(self) -> ServiceStatus:
2216
+ """Get current service status."""
2217
+ return ServiceStatus(
2218
+ service_name="RuntimeControlService",
2219
+ service_type="CORE",
2220
+ is_healthy=self.runtime is not None,
2221
+ uptime_seconds=self._calculate_uptime(),
2222
+ last_error=self._last_error,
2223
+ metrics={
2224
+ "events_count": float(len(self._events_history)),
2225
+ "adapters_loaded": float(
2226
+ len(self.adapter_manager.loaded_adapters)
2227
+ if self.adapter_manager and hasattr(self.adapter_manager, "loaded_adapters")
2228
+ else 0
2229
+ ),
2230
+ },
2231
+ last_health_check=self._last_health_check,
2232
+ )
2233
+
2234
+ def _set_runtime(self, runtime: "RuntimeInterface") -> None:
2235
+ """Set the runtime reference after initialization (private method)."""
2236
+ self.runtime = runtime
2237
+
2238
+ # NOTE: agent_processor doesn't exist yet during initialization phase
2239
+ # The callback will be set up later via setup_thought_tracking()
2240
+
2241
+ # If adapter manager exists, update its runtime reference too
2242
+ if self.adapter_manager:
2243
+ from ciris_engine.logic.runtime.ciris_runtime import CIRISRuntime
2244
+
2245
+ self.adapter_manager.runtime = cast(CIRISRuntime, runtime)
2246
+ # Re-register config listener with updated runtime
2247
+ self.adapter_manager._register_config_listener()
2248
+ logger.info("Runtime reference set in RuntimeControlService")
2249
+
2250
+ def setup_thought_tracking(self) -> None:
2251
+ """Set up thought processing callback after agent_processor is created.
2252
+
2253
+ This must be called AFTER Phase 6 (COMPONENTS) when agent_processor exists.
2254
+ Called during Phase 5 (SERVICES) would cause a race condition.
2255
+ """
2256
+ if self.runtime and hasattr(self.runtime, "agent_processor") and self.runtime.agent_processor:
2257
+ self.runtime.agent_processor.set_thought_processing_callback(self._track_thought_processing_time)
2258
+ logger.debug("Thought processing callback registered with agent_processor")
2259
+
2260
+ async def _on_start(self) -> None:
2261
+ """Custom startup logic for runtime control service."""
2262
+ await self._initialize()
2263
+ logger.info("Runtime control service started")
2264
+
2265
+ async def _on_stop(self) -> None:
2266
+ """Custom cleanup logic for runtime control service."""
2267
+ logger.info("Runtime control service stopping")
2268
+ # Clean up any resources if needed
2269
+ self._events_history.clear()