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,1762 @@
1
+ """
2
+ Agent interaction endpoints for CIRIS API v3.0 (Simplified).
3
+
4
+ Core endpoints for natural agent interaction.
5
+ """
6
+
7
+ import asyncio
8
+ import logging
9
+ import uuid
10
+ from datetime import datetime, timezone
11
+ from enum import Enum
12
+ from typing import Any, Dict, List, Optional, Tuple, Union
13
+
14
+ from fastapi import APIRouter, Depends, HTTPException, Query, Request, WebSocket, WebSocketDisconnect
15
+ from pydantic import BaseModel, Field
16
+
17
+ from ciris_engine.logic.adapters.base_observer import BillingServiceError, CreditCheckFailed, CreditDenied
18
+ from ciris_engine.schemas.api.agent import AgentLineage, MessageContext, ServiceAvailability
19
+ from ciris_engine.schemas.api.auth import ROLE_PERMISSIONS, AuthContext, Permission, UserRole
20
+ from ciris_engine.schemas.api.responses import SuccessResponse
21
+ from ciris_engine.schemas.runtime.messages import IncomingMessage
22
+ from ciris_engine.schemas.services.credit_gate import CreditAccount, CreditContext
23
+ from ciris_engine.schemas.types import JSONDict
24
+
25
+ from ..constants import DESC_CURRENT_COGNITIVE_STATE, ERROR_MEMORY_SERVICE_NOT_AVAILABLE
26
+ from ..dependencies.auth import require_observer
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ # Minimum uptime in seconds before defaulting task count
31
+ MIN_UPTIME_FOR_DEFAULT_TASKS = 60
32
+
33
+ router = APIRouter(prefix="/agent", tags=["agent"])
34
+
35
+ # Request/Response schemas
36
+
37
+
38
+ class MessageRejectionReason(str, Enum):
39
+ """Reasons why a message submission was rejected or not processed."""
40
+
41
+ AGENT_OWN_MESSAGE = "AGENT_OWN_MESSAGE" # Message from agent itself
42
+ FILTERED_OUT = "FILTERED_OUT" # Filtered by adaptive filter
43
+ CREDIT_DENIED = "CREDIT_DENIED" # Insufficient credits
44
+ CREDIT_CHECK_FAILED = "CREDIT_CHECK_FAILED" # Credit provider error
45
+ PROCESSOR_PAUSED = "PROCESSOR_PAUSED" # Agent processor paused
46
+ RATE_LIMITED = "RATE_LIMITED" # Rate limit exceeded
47
+ CHANNEL_RESTRICTED = "CHANNEL_RESTRICTED" # Channel access denied
48
+
49
+
50
+ class ImagePayload(BaseModel):
51
+ """Image payload for multimodal requests."""
52
+
53
+ data: str = Field(..., description="Base64-encoded image data or URL")
54
+ media_type: str = Field(default="image/jpeg", description="MIME type (image/jpeg, image/png, etc)")
55
+ filename: Optional[str] = Field(default=None, description="Optional filename")
56
+
57
+
58
+ class DocumentPayload(BaseModel):
59
+ """Document payload for text extraction from files."""
60
+
61
+ data: str = Field(..., description="Base64-encoded document data or URL")
62
+ media_type: str = Field(
63
+ default="application/pdf",
64
+ description="MIME type (application/pdf, application/vnd.openxmlformats-officedocument.wordprocessingml.document)",
65
+ )
66
+ filename: Optional[str] = Field(default=None, description="Optional filename (helps determine document type)")
67
+
68
+
69
+ class InteractRequest(BaseModel):
70
+ """Request to interact with the agent."""
71
+
72
+ message: str = Field(..., description="Message to send to the agent")
73
+ context: Optional[MessageContext] = Field(None, description="Optional context")
74
+ images: Optional[List[ImagePayload]] = Field(default=None, description="Optional images for multimodal interaction")
75
+ documents: Optional[List[DocumentPayload]] = Field(
76
+ default=None, description="Optional documents (PDF, DOCX) for text extraction"
77
+ )
78
+
79
+
80
+ class InteractResponse(BaseModel):
81
+ """Response from agent interaction."""
82
+
83
+ message_id: str = Field(..., description="Unique message ID")
84
+ response: str = Field(..., description="Agent's response")
85
+ state: str = Field(..., description="Agent's cognitive state after processing")
86
+ processing_time_ms: int = Field(..., description="Time taken to process")
87
+
88
+
89
+ class MessageRequest(BaseModel):
90
+ """Request to send a message to the agent (async pattern)."""
91
+
92
+ message: str = Field(..., description="Message to send to the agent")
93
+ context: Optional[MessageContext] = Field(None, description="Optional context")
94
+
95
+
96
+ class MessageSubmissionResponse(BaseModel):
97
+ """Response from message submission (returns immediately with task ID or rejection reason)."""
98
+
99
+ message_id: str = Field(..., description="Unique message ID for tracking")
100
+ task_id: Optional[str] = Field(None, description="Task ID created (if accepted)")
101
+ channel_id: str = Field(..., description="Channel where message was sent")
102
+ submitted_at: str = Field(..., description="ISO timestamp of submission")
103
+ accepted: bool = Field(..., description="Whether message was accepted for processing")
104
+ rejection_reason: Optional[MessageRejectionReason] = Field(None, description="Reason if rejected")
105
+ rejection_detail: Optional[str] = Field(None, description="Additional detail about rejection")
106
+
107
+
108
+ class ConversationMessage(BaseModel):
109
+ """Message in conversation history."""
110
+
111
+ id: str = Field(..., description="Message ID")
112
+ author: str = Field(..., description="Message author")
113
+ content: str = Field(..., description="Message content")
114
+ timestamp: datetime = Field(..., description="When sent")
115
+ is_agent: bool = Field(..., description="Whether this was from the agent")
116
+
117
+
118
+ class ConversationHistory(BaseModel):
119
+ """Conversation history."""
120
+
121
+ messages: List[ConversationMessage] = Field(..., description="Message history")
122
+ total_count: int = Field(..., description="Total messages")
123
+ has_more: bool = Field(..., description="Whether more messages exist")
124
+
125
+
126
+ class AgentStatus(BaseModel):
127
+ """Agent status and cognitive state."""
128
+
129
+ # Core identity
130
+ agent_id: str = Field(..., description="Agent identifier")
131
+ name: str = Field(..., description="Agent name")
132
+
133
+ # Version information
134
+ version: str = Field(..., description="CIRIS version (e.g., 1.0.4-beta)")
135
+ codename: str = Field(..., description="Release codename")
136
+ code_hash: Optional[str] = Field(None, description="Code hash for exact version")
137
+
138
+ # State information
139
+ cognitive_state: str = Field(..., description=DESC_CURRENT_COGNITIVE_STATE)
140
+ uptime_seconds: float = Field(..., description="Time since startup")
141
+
142
+ # Activity metrics
143
+ messages_processed: int = Field(..., description="Total messages processed")
144
+ last_activity: Optional[datetime] = Field(None, description="Last activity timestamp")
145
+ current_task: Optional[str] = Field(None, description="Current task description")
146
+
147
+ # System state
148
+ services_active: int = Field(..., description="Number of active services")
149
+ memory_usage_mb: float = Field(..., description="Current memory usage in MB")
150
+ multi_provider_services: Optional[JSONDict] = Field(None, description="Services with provider counts")
151
+
152
+
153
+ class AgentIdentity(BaseModel):
154
+ """Agent identity and capabilities."""
155
+
156
+ # Identity
157
+ agent_id: str = Field(..., description="Unique agent identifier")
158
+ name: str = Field(..., description="Agent name")
159
+ purpose: str = Field(..., description="Agent's purpose")
160
+ created_at: datetime = Field(..., description="When agent was created")
161
+ lineage: AgentLineage = Field(..., description="Agent lineage information")
162
+ variance_threshold: float = Field(..., description="Identity variance threshold")
163
+
164
+ # Capabilities
165
+ tools: List[str] = Field(..., description="Available tools")
166
+ handlers: List[str] = Field(..., description="Active handlers")
167
+ services: ServiceAvailability = Field(..., description="Service availability")
168
+ permissions: List[str] = Field(..., description="Agent permissions")
169
+
170
+
171
+ class ChannelInfo(BaseModel):
172
+ """Information about a communication channel."""
173
+
174
+ channel_id: str = Field(..., description="Unique channel identifier")
175
+ channel_type: str = Field(..., description="Type of channel (discord, api, cli)")
176
+ display_name: str = Field(..., description="Human-readable channel name")
177
+ is_active: bool = Field(..., description="Whether channel is currently active")
178
+ created_at: Optional[datetime] = Field(None, description="When channel was created")
179
+ last_activity: Optional[datetime] = Field(None, description="Last message in channel")
180
+ message_count: int = Field(0, description="Total messages in channel")
181
+
182
+
183
+ class ChannelList(BaseModel):
184
+ """List of active channels."""
185
+
186
+ channels: List[ChannelInfo] = Field(..., description="List of channels")
187
+ total_count: int = Field(..., description="Total number of channels")
188
+
189
+
190
+ # Message tracking for interact functionality
191
+ _message_responses: dict[str, str] = {}
192
+ _response_events: dict[str, asyncio.Event] = {}
193
+
194
+
195
+ async def store_message_response(message_id: str, response: str) -> None:
196
+ """Store a response and notify waiting request."""
197
+ import os
198
+
199
+ occurrence_id = os.environ.get("AGENT_OCCURRENCE_ID", "default")
200
+ logger.info(
201
+ f"[STORE_RESPONSE] occurrence={occurrence_id}, message_id={message_id}, response_len={len(response)}, current_keys={list(_message_responses.keys())}"
202
+ )
203
+ _message_responses[message_id] = response
204
+ event = _response_events.get(message_id)
205
+ if event:
206
+ logger.info(f"[STORE_RESPONSE] Event found for {message_id}, setting it")
207
+ event.set()
208
+ else:
209
+ logger.warning(f"[STORE_RESPONSE] No event found for {message_id}!")
210
+
211
+
212
+ # Endpoints
213
+
214
+
215
+ def _check_send_messages_permission(auth: AuthContext, request: Request) -> None:
216
+ """Check if user has SEND_MESSAGES permission and handle OAuth auto-request."""
217
+ if auth.has_permission(Permission.SEND_MESSAGES):
218
+ return
219
+
220
+ # Get auth service to check permission request status
221
+ auth_service = request.app.state.auth_service if hasattr(request.app.state, "auth_service") else None
222
+ user = auth_service.get_user(auth.user_id) if auth_service else None
223
+
224
+ # If user is an OAuth user without a permission request, automatically create one
225
+ if user and user.auth_type == "oauth" and user.permission_requested_at is None:
226
+ # Set permission request timestamp
227
+ user.permission_requested_at = datetime.now(timezone.utc)
228
+ # Store the updated user
229
+ if auth_service is not None and hasattr(auth_service, "_users"):
230
+ auth_service._users[user.wa_id] = user # Access private attribute for permission tracking
231
+
232
+ # Don't log potentially sensitive email addresses
233
+ logger.info(f"Auto-created permission request for OAuth user ID: {user.wa_id}")
234
+
235
+ # Build detailed error response
236
+ error_detail = {
237
+ "error": "insufficient_permissions",
238
+ "message": "You do not have permission to send messages to this agent.",
239
+ "discord_invite": "https://discord.gg/A3HVPMWd",
240
+ "can_request_permissions": user.permission_requested_at is None if user else True,
241
+ "permission_requested": user.permission_requested_at is not None if user else False,
242
+ "requested_at": user.permission_requested_at.isoformat() if user and user.permission_requested_at else None,
243
+ }
244
+
245
+ raise HTTPException(status_code=403, detail=error_detail)
246
+
247
+
248
+ async def _create_interaction_message(
249
+ auth: AuthContext, body: Union[InteractRequest, MessageRequest]
250
+ ) -> Tuple[str, str, IncomingMessage]:
251
+ """Create message ID, channel ID, and IncomingMessage for interaction."""
252
+ from ciris_engine.logic.adapters.api.api_document import get_api_document_helper
253
+ from ciris_engine.logic.adapters.api.api_vision import get_api_vision_helper
254
+
255
+ message_id = str(uuid.uuid4())
256
+ channel_id = f"api_{auth.user_id}" # User-specific channel
257
+
258
+ # Process images if provided (InteractRequest only)
259
+ images = []
260
+ if isinstance(body, InteractRequest) and body.images:
261
+ vision_helper = get_api_vision_helper()
262
+ for img_payload in body.images:
263
+ image_content = vision_helper.process_image_payload(
264
+ img_payload.data,
265
+ img_payload.media_type,
266
+ img_payload.filename,
267
+ )
268
+ if image_content:
269
+ images.append(image_content)
270
+ if images:
271
+ logger.info(f"Processed {len(images)} images for multimodal interaction")
272
+
273
+ # Process documents if provided (InteractRequest only)
274
+ additional_content = ""
275
+ if isinstance(body, InteractRequest) and body.documents:
276
+ document_helper = get_api_document_helper()
277
+ if document_helper.is_available():
278
+ doc_payloads = [
279
+ {"data": doc.data, "media_type": doc.media_type, "filename": doc.filename} for doc in body.documents
280
+ ]
281
+ document_text = await document_helper.process_document_list(doc_payloads)
282
+ if document_text:
283
+ additional_content = "\n\n[Document Analysis]\n" + document_text
284
+ logger.info(f"Processed {len(body.documents)} documents for interaction")
285
+ else:
286
+ logger.warning("Document processing requested but not available (missing libraries)")
287
+
288
+ # Combine message content with any extracted document text
289
+ final_content = body.message + additional_content
290
+
291
+ msg = IncomingMessage(
292
+ message_id=message_id,
293
+ author_id=auth.user_id,
294
+ author_name=auth.user_id,
295
+ content=final_content,
296
+ channel_id=channel_id,
297
+ timestamp=datetime.now(timezone.utc).isoformat(),
298
+ images=images,
299
+ )
300
+
301
+ return message_id, channel_id, msg
302
+
303
+
304
+ async def _handle_consent_for_user(auth: AuthContext, channel_id: str, request: Request) -> str:
305
+ """Handle consent checking and creation for user, return consent notice if applicable."""
306
+ try:
307
+ from ciris_engine.logic.services.governance.consent import ConsentNotFoundError, ConsentService
308
+ from ciris_engine.schemas.consent.core import ConsentRequest, ConsentStream
309
+
310
+ # Get consent manager
311
+ if hasattr(request.app.state, "consent_manager") and request.app.state.consent_manager:
312
+ consent_manager = request.app.state.consent_manager
313
+ else:
314
+ from ciris_engine.logic.services.lifecycle.time import TimeService
315
+
316
+ time_service = TimeService()
317
+ consent_manager = ConsentService(time_service=time_service)
318
+ request.app.state.consent_manager = consent_manager
319
+
320
+ # Check if user has consent
321
+ try:
322
+ consent_status = await consent_manager.get_consent(auth.user_id)
323
+ return "" # User already has consent
324
+ except ConsentNotFoundError:
325
+ # First interaction - create default TEMPORARY consent
326
+ consent_req = ConsentRequest(
327
+ user_id=auth.user_id,
328
+ stream=ConsentStream.TEMPORARY,
329
+ categories=[],
330
+ reason="Default TEMPORARY consent on first interaction",
331
+ )
332
+ consent_status = await consent_manager.grant_consent(consent_req, channel_id=channel_id)
333
+
334
+ # Return notice to add to response
335
+ return "\n\n📝 Privacy Notice: We forget about you in 14 days unless you say otherwise. Visit /v1/consent to manage your data preferences."
336
+
337
+ except Exception as e:
338
+ logger.warning(f"Could not check consent for user {auth.user_id}: {e}")
339
+ return ""
340
+
341
+
342
+ async def _track_air_interaction(
343
+ auth: AuthContext, channel_id: str, message_content: str, request: Request
344
+ ) -> Optional[str]:
345
+ """
346
+ Track interaction for AIR (Artificial Interaction Reminder) parasocial prevention.
347
+
348
+ This monitors 1:1 API interactions for:
349
+ - Time-based triggers (30 min continuous)
350
+ - Message-based triggers (20+ messages in 30 min window)
351
+
352
+ Returns reminder message if threshold exceeded, None otherwise.
353
+ """
354
+ try:
355
+ # Get consent manager which hosts the AIR manager
356
+ consent_manager = getattr(request.app.state, "consent_manager", None)
357
+ if not consent_manager:
358
+ return None
359
+
360
+ # Track interaction and get potential reminder
361
+ reminder: Optional[str] = await consent_manager.track_interaction(
362
+ user_id=auth.user_id,
363
+ channel_id=channel_id,
364
+ channel_type="api",
365
+ message_content=message_content,
366
+ )
367
+
368
+ if reminder:
369
+ logger.info(f"[AIR] Reminder triggered for user {auth.user_id}")
370
+
371
+ return reminder
372
+
373
+ except Exception as e:
374
+ logger.debug(f"AIR tracking error (non-fatal): {e}")
375
+ return None
376
+
377
+
378
+ def _derive_credit_account(
379
+ auth: AuthContext,
380
+ request: Request,
381
+ ) -> Tuple[CreditAccount, Dict[str, str]]:
382
+ """Build credit account metadata for the current request."""
383
+
384
+ auth_service = getattr(request.app.state, "auth_service", None)
385
+ user = None
386
+ if auth_service and hasattr(auth_service, "get_user"):
387
+ try:
388
+ user = auth_service.get_user(auth.user_id)
389
+ except Exception as exc: # pragma: no cover - defensive logging
390
+ logger.debug("Unable to load user for credit gating: %s", exc)
391
+
392
+ provider = "api"
393
+ account_id = auth.user_id
394
+ authority_id = None
395
+ tenant_id = None
396
+
397
+ if user:
398
+ authority_id = getattr(user, "wa_id", None) or auth.user_id
399
+ tenant_id = getattr(user, "wa_parent_id", None)
400
+
401
+ oauth_provider = getattr(user, "oauth_provider", None)
402
+ oauth_external_id = getattr(user, "oauth_external_id", None)
403
+ if oauth_provider and oauth_external_id:
404
+ provider = f"oauth:{oauth_provider}"
405
+ account_id = oauth_external_id
406
+ else:
407
+ provider = "wa"
408
+ account_id = getattr(user, "wa_id", None) or auth.user_id
409
+ else:
410
+ if auth.api_key_id:
411
+ provider = "api-key"
412
+ account_id = auth.api_key_id
413
+ elif auth.role == UserRole.SERVICE_ACCOUNT:
414
+ provider = "service-account"
415
+ account_id = auth.user_id
416
+
417
+ account = CreditAccount(
418
+ provider=provider,
419
+ account_id=account_id,
420
+ authority_id=authority_id,
421
+ tenant_id=tenant_id,
422
+ )
423
+
424
+ metadata: Dict[str, str] = {
425
+ "role": auth.role.value,
426
+ }
427
+
428
+ # Extract email address for billing backend user management
429
+ if user and hasattr(user, "oauth_email") and user.oauth_email:
430
+ metadata["email"] = user.oauth_email
431
+
432
+ # Extract marketing opt-in preference if available
433
+ if user and hasattr(user, "marketing_opt_in"):
434
+ metadata["marketing_opt_in"] = str(user.marketing_opt_in).lower()
435
+
436
+ if auth.api_key_id:
437
+ metadata["api_key_id"] = auth.api_key_id
438
+
439
+ return account, metadata
440
+
441
+
442
+ def _attach_credit_metadata(
443
+ msg: IncomingMessage,
444
+ request: Request,
445
+ auth: AuthContext,
446
+ channel_id: str,
447
+ ) -> IncomingMessage:
448
+ """Attach credit envelope data to the message for downstream processing."""
449
+
450
+ logger.debug(f"[CREDIT_ATTACH] Called for message {msg.message_id}")
451
+
452
+ resource_monitor = getattr(request.app.state, "resource_monitor", None)
453
+ logger.debug(f"[CREDIT_ATTACH] resource_monitor exists: {resource_monitor is not None}")
454
+
455
+ if not resource_monitor:
456
+ logger.critical(f"[CREDIT_ATTACH] NO RESOURCE MONITOR - credit metadata NOT attached to {msg.message_id}")
457
+ return msg
458
+
459
+ credit_provider = getattr(resource_monitor, "credit_provider", None)
460
+ logger.debug(
461
+ f"[CREDIT_ATTACH] credit_provider exists: {credit_provider is not None}, type={type(credit_provider).__name__ if credit_provider else 'None'}"
462
+ )
463
+
464
+ if not credit_provider:
465
+ # Try lazy initialization - token may have been written after server start
466
+ from ciris_engine.logic.adapters.api.routes.billing import _try_lazy_init_billing_provider
467
+
468
+ credit_provider = _try_lazy_init_billing_provider(request, resource_monitor)
469
+ if not credit_provider:
470
+ logger.critical(f"[CREDIT_ATTACH] NO CREDIT PROVIDER - credit metadata NOT attached to {msg.message_id}")
471
+ return msg
472
+ logger.info(f"[CREDIT_ATTACH] Lazily initialized billing provider for {msg.message_id}")
473
+
474
+ try:
475
+ account, _ = _derive_credit_account(auth, request)
476
+ logger.debug(f"[CREDIT_ATTACH] Derived credit account: {account.cache_key()}")
477
+
478
+ runtime = getattr(request.app.state, "runtime", None)
479
+ agent_identity = getattr(runtime, "agent_identity", None) if runtime else None
480
+ agent_id = getattr(agent_identity, "agent_id", None)
481
+
482
+ # Determine billing mode: Android uses "informational" (check only, billing via LLM usage)
483
+ # Hosted sites use "transactional" (check+spend per interaction)
484
+ from ciris_engine.logic.utils.path_resolution import is_android
485
+
486
+ billing_mode = "informational" if is_android() else "transactional"
487
+
488
+ logger.debug(
489
+ f"[CREDIT_ATTACH] Creating CreditContext with agent_id={agent_id}, channel_id={channel_id}, user_role={auth.role.value}, billing_mode={billing_mode}"
490
+ )
491
+
492
+ credit_context = CreditContext(
493
+ agent_id=agent_id,
494
+ channel_id=channel_id,
495
+ request_id=msg.message_id,
496
+ user_role=auth.role.value,
497
+ billing_mode=billing_mode,
498
+ )
499
+
500
+ logger.debug("[CREDIT_ATTACH] CreditContext created successfully")
501
+ logger.debug(
502
+ f"[CREDIT_ATTACH] Attaching credit metadata to message {msg.message_id}: account={account.cache_key()}"
503
+ )
504
+
505
+ updated_msg = msg.model_copy(
506
+ update={
507
+ "credit_account": account.model_dump(),
508
+ "credit_context": credit_context.model_dump(),
509
+ }
510
+ )
511
+
512
+ logger.debug(f"[CREDIT_ATTACH] Credit metadata SUCCESSFULLY attached to message {msg.message_id}")
513
+
514
+ # CRITICAL: Also attach resource_monitor to message for observer access
515
+ # The observer may be initialized before resource_monitor exists on runtime,
516
+ # so we attach it per-message to ensure credit enforcement works
517
+ updated_msg._resource_monitor = resource_monitor # type: ignore[attr-defined]
518
+ logger.debug(f"[CREDIT_ATTACH] resource_monitor attached to message {msg.message_id}")
519
+
520
+ return updated_msg
521
+
522
+ except Exception as e:
523
+ logger.critical(
524
+ f"[CREDIT_ATTACH] EXCEPTION for message {msg.message_id}: {type(e).__name__}: {e}", exc_info=True
525
+ )
526
+ # Return original message without metadata on error
527
+ return msg
528
+
529
+
530
+ def _get_runtime_processor(request: Request) -> Any:
531
+ """Get runtime processor if available and valid."""
532
+ runtime = getattr(request.app.state, "runtime", None)
533
+ if not (runtime and hasattr(runtime, "agent_processor") and runtime.agent_processor):
534
+ return None
535
+ return runtime.agent_processor
536
+
537
+
538
+ def _is_processor_paused(processor: Any) -> bool:
539
+ """Check if processor is in paused state."""
540
+ return hasattr(processor, "_is_paused") and processor._is_paused
541
+
542
+
543
+ async def _handle_paused_message(request: Request, msg: IncomingMessage) -> None:
544
+ """Route message to queue when processor is paused."""
545
+ if hasattr(request.app.state, "on_message"):
546
+ await request.app.state.on_message(msg)
547
+ else:
548
+ raise HTTPException(status_code=503, detail="Message handler not configured")
549
+
550
+
551
+ def _get_processor_cognitive_state(processor: Any) -> str:
552
+ """Get current cognitive state from processor with fallback."""
553
+ try:
554
+ if hasattr(processor, "get_current_state"):
555
+ state = processor.get_current_state()
556
+ return str(state) if state is not None else "WORK"
557
+ except Exception:
558
+ pass
559
+ return "WORK" # Default
560
+
561
+
562
+ def _create_paused_response(
563
+ message_id: str, cognitive_state: str, processing_time: int
564
+ ) -> SuccessResponse[InteractResponse]:
565
+ """Create response for paused processor state."""
566
+ return SuccessResponse(
567
+ data=InteractResponse(
568
+ message_id=message_id,
569
+ response="Processor paused - task added to queue. Resume processing to continue.",
570
+ state=cognitive_state,
571
+ processing_time_ms=processing_time,
572
+ )
573
+ )
574
+
575
+
576
+ async def _check_processor_pause_status(
577
+ request: Request, msg: IncomingMessage, message_id: str, start_time: datetime
578
+ ) -> Optional[SuccessResponse[InteractResponse]]:
579
+ """Check if processor is paused and handle accordingly. Returns response if paused, None if not paused."""
580
+ try:
581
+ processor = _get_runtime_processor(request)
582
+ if not processor or not _is_processor_paused(processor):
583
+ return None
584
+
585
+ # Processor is paused - route message and prepare response
586
+ await _handle_paused_message(request, msg)
587
+
588
+ # Clean up response tracking since we're returning immediately
589
+ _response_events.pop(message_id, None)
590
+
591
+ # Calculate processing time and get state
592
+ processing_time = int((datetime.now(timezone.utc) - start_time).total_seconds() * 1000)
593
+ cognitive_state = _get_processor_cognitive_state(processor)
594
+
595
+ return _create_paused_response(message_id, cognitive_state, processing_time)
596
+
597
+ except HTTPException:
598
+ # Re-raise HTTP exceptions (like 503 for missing message handler)
599
+ raise
600
+ except Exception as e:
601
+ logger.debug(f"Could not check pause state: {e}")
602
+
603
+ return None
604
+
605
+
606
+ def _get_interaction_timeout(request: Request) -> float:
607
+ """Get interaction timeout from config or return default."""
608
+ timeout = 55.0 # default timeout for longer processing
609
+ if hasattr(request.app.state, "api_config"):
610
+ timeout = request.app.state.api_config.interaction_timeout
611
+ return timeout
612
+
613
+
614
+ def _get_current_cognitive_state(request: Request) -> str:
615
+ """Get current cognitive state from request runtime."""
616
+ runtime = getattr(request.app.state, "runtime", None)
617
+ return _get_cognitive_state(runtime)
618
+
619
+
620
+ def _cleanup_interaction_tracking(message_id: str) -> None:
621
+ """Clean up interaction tracking for given message ID."""
622
+ _response_events.pop(message_id, None)
623
+ _message_responses.pop(message_id, None)
624
+
625
+
626
+ @router.post("/message", response_model=SuccessResponse[MessageSubmissionResponse])
627
+ async def submit_message(
628
+ request: Request, body: MessageRequest, auth: AuthContext = Depends(require_observer)
629
+ ) -> SuccessResponse[MessageSubmissionResponse]:
630
+ """
631
+ Submit a message to the agent (async pattern - returns immediately).
632
+
633
+ This endpoint returns immediately with a task_id for tracking or rejection reason.
634
+ Use GET /agent/history to poll for the agent's response.
635
+
636
+ This is the recommended way to interact with the agent via API,
637
+ as it doesn't block waiting for processing to complete.
638
+
639
+ Requires: SEND_MESSAGES permission (ADMIN+ by default, or OBSERVER with explicit grant)
640
+ """
641
+ from ciris_engine.schemas.runtime.messages import MessageHandlingStatus
642
+
643
+ # Check permissions
644
+ _check_send_messages_permission(auth, request)
645
+
646
+ # Create message and tracking
647
+ message_id, channel_id, msg = await _create_interaction_message(auth, body)
648
+ msg = _attach_credit_metadata(msg, request, auth, channel_id)
649
+
650
+ # Handle consent for user
651
+ await _handle_consent_for_user(auth, channel_id, request)
652
+
653
+ # Track interaction for AIR (parasocial attachment prevention)
654
+ # Note: For async pattern, reminder is logged but not returned (user polls for response)
655
+ await _track_air_interaction(auth, channel_id, body.message, request)
656
+
657
+ # Track submission time
658
+ submitted_at = datetime.now(timezone.utc)
659
+
660
+ # Check if processor is paused
661
+ pause_response = await _check_processor_pause_status(request, msg, message_id, submitted_at)
662
+ if pause_response:
663
+ # Return rejection for paused processor
664
+ response = MessageSubmissionResponse(
665
+ message_id=message_id,
666
+ task_id=None,
667
+ channel_id=channel_id,
668
+ submitted_at=submitted_at.isoformat(),
669
+ accepted=False,
670
+ rejection_reason=MessageRejectionReason.PROCESSOR_PAUSED,
671
+ rejection_detail="Agent processor is paused",
672
+ )
673
+ return SuccessResponse(data=response)
674
+
675
+ # Submit message and get result (with credit enforcement)
676
+ try:
677
+ if hasattr(request.app.state, "on_message"):
678
+ result = await request.app.state.on_message(msg)
679
+ else:
680
+ raise HTTPException(status_code=503, detail="Message handler not configured")
681
+ except CreditDenied as exc:
682
+ # Return rejection for credit denial
683
+ response = MessageSubmissionResponse(
684
+ message_id=message_id,
685
+ task_id=None,
686
+ channel_id=channel_id,
687
+ submitted_at=submitted_at.isoformat(),
688
+ accepted=False,
689
+ rejection_reason=MessageRejectionReason.CREDIT_DENIED,
690
+ rejection_detail=exc.reason,
691
+ )
692
+ return SuccessResponse(data=response)
693
+ except CreditCheckFailed as exc:
694
+ # Return rejection for credit check failure
695
+ response = MessageSubmissionResponse(
696
+ message_id=message_id,
697
+ task_id=None,
698
+ channel_id=channel_id,
699
+ submitted_at=submitted_at.isoformat(),
700
+ accepted=False,
701
+ rejection_reason=MessageRejectionReason.CREDIT_CHECK_FAILED,
702
+ rejection_detail=str(exc),
703
+ )
704
+ return SuccessResponse(data=response)
705
+
706
+ # Map MessageHandlingResult to MessageSubmissionResponse
707
+ accepted = result.status in [MessageHandlingStatus.TASK_CREATED, MessageHandlingStatus.UPDATED_EXISTING_TASK]
708
+ rejection_reason = None
709
+ rejection_detail = None
710
+
711
+ if not accepted:
712
+ # Map status to rejection reason
713
+ status_to_reason = {
714
+ MessageHandlingStatus.AGENT_OWN_MESSAGE: MessageRejectionReason.AGENT_OWN_MESSAGE,
715
+ MessageHandlingStatus.FILTERED_OUT: MessageRejectionReason.FILTERED_OUT,
716
+ MessageHandlingStatus.CHANNEL_RESTRICTED: MessageRejectionReason.CHANNEL_RESTRICTED,
717
+ MessageHandlingStatus.RATE_LIMITED: MessageRejectionReason.RATE_LIMITED,
718
+ }
719
+ rejection_reason = status_to_reason.get(result.status)
720
+ rejection_detail = result.filter_reasoning if result.filtered else None
721
+ elif result.status == MessageHandlingStatus.UPDATED_EXISTING_TASK:
722
+ # Add detail that existing task was updated
723
+ rejection_detail = "Existing task updated with new information"
724
+
725
+ # Return result
726
+ response = MessageSubmissionResponse(
727
+ message_id=message_id,
728
+ task_id=result.task_id,
729
+ channel_id=channel_id,
730
+ submitted_at=submitted_at.isoformat(),
731
+ accepted=accepted,
732
+ rejection_reason=rejection_reason,
733
+ rejection_detail=rejection_detail,
734
+ )
735
+
736
+ return SuccessResponse(data=response)
737
+
738
+
739
+ @router.post("/interact", response_model=SuccessResponse[InteractResponse])
740
+ async def interact(
741
+ request: Request, body: InteractRequest, auth: AuthContext = Depends(require_observer)
742
+ ) -> SuccessResponse[InteractResponse]:
743
+ """
744
+ Send message and get response.
745
+
746
+ This endpoint combines the old send/ask functionality into a single interaction.
747
+ It sends the message and waits for the agent's response (with a reasonable timeout).
748
+
749
+ Requires: SEND_MESSAGES permission (ADMIN+ by default, or OBSERVER with explicit grant)
750
+ """
751
+ # Check permissions
752
+ _check_send_messages_permission(auth, request)
753
+
754
+ # Create message and tracking
755
+ message_id, channel_id, msg = await _create_interaction_message(auth, body)
756
+ msg = _attach_credit_metadata(msg, request, auth, channel_id)
757
+
758
+ event = asyncio.Event()
759
+ _response_events[message_id] = event
760
+
761
+ # Handle consent for user
762
+ consent_notice = await _handle_consent_for_user(auth, channel_id, request)
763
+
764
+ # Track interaction for AIR (parasocial attachment prevention)
765
+ air_reminder = await _track_air_interaction(auth, channel_id, body.message, request)
766
+
767
+ # Track timing
768
+ start_time = datetime.now(timezone.utc)
769
+
770
+ # Check if processor is paused
771
+ pause_response = await _check_processor_pause_status(request, msg, message_id, start_time)
772
+ if pause_response:
773
+ return pause_response
774
+
775
+ try:
776
+ if hasattr(request.app.state, "on_message"):
777
+ await request.app.state.on_message(msg)
778
+ else:
779
+ raise HTTPException(status_code=503, detail="Message handler not configured")
780
+ except CreditDenied as exc:
781
+ _cleanup_interaction_tracking(message_id)
782
+ raise HTTPException(
783
+ status_code=402,
784
+ detail={
785
+ "error": "insufficient_credit",
786
+ "message": "Interaction blocked by credit policy.",
787
+ "reason": exc.reason,
788
+ },
789
+ ) from exc
790
+ except CreditCheckFailed as exc:
791
+ _cleanup_interaction_tracking(message_id)
792
+ raise HTTPException(status_code=503, detail="Credit provider unavailable") from exc
793
+ except BillingServiceError as exc:
794
+ _cleanup_interaction_tracking(message_id)
795
+ raise HTTPException(
796
+ status_code=402,
797
+ detail={
798
+ "error": "billing_error",
799
+ "message": "LLM billing service error. Please check your account or try again later.",
800
+ "reason": exc.message,
801
+ },
802
+ ) from exc
803
+
804
+ # Get timeout and wait for response
805
+ timeout = _get_interaction_timeout(request)
806
+
807
+ try:
808
+ await asyncio.wait_for(event.wait(), timeout=timeout)
809
+
810
+ # Get response
811
+ import os
812
+
813
+ occurrence_id = os.environ.get("AGENT_OCCURRENCE_ID", "default")
814
+ logger.info(
815
+ f"[RETRIEVE_RESPONSE] occurrence={occurrence_id}, message_id={message_id}, available_keys={list(_message_responses.keys())}"
816
+ )
817
+ response_content = _message_responses.get(message_id, "I'm processing your request. Please check back shortly.")
818
+ logger.info(
819
+ f"[RETRIEVE_RESPONSE] Retrieved content_len={len(response_content)}, content_preview={response_content[:100] if response_content else 'EMPTY'}"
820
+ )
821
+
822
+ # Add consent notice if this is first interaction
823
+ if consent_notice:
824
+ response_content += consent_notice
825
+
826
+ # Add AIR reminder if triggered (parasocial attachment prevention)
827
+ if air_reminder:
828
+ response_content += "\n\n---\n" + air_reminder
829
+
830
+ # Clean up and calculate timing
831
+ _cleanup_interaction_tracking(message_id)
832
+ processing_time_ms = int((datetime.now(timezone.utc) - start_time).total_seconds() * 1000)
833
+
834
+ # Build response
835
+ response = InteractResponse(
836
+ message_id=message_id,
837
+ response=response_content,
838
+ state=_get_current_cognitive_state(request),
839
+ processing_time_ms=processing_time_ms,
840
+ )
841
+
842
+ return SuccessResponse(data=response)
843
+
844
+ except asyncio.TimeoutError:
845
+ # Clean up
846
+ _cleanup_interaction_tracking(message_id)
847
+
848
+ # Return a timeout response rather than error
849
+ response = InteractResponse(
850
+ message_id=message_id,
851
+ response="Still processing. Check back later. Agent response is not guaranteed.",
852
+ state="WORK",
853
+ processing_time_ms=int(timeout * 1000), # Use actual timeout value
854
+ )
855
+
856
+ return SuccessResponse(data=response)
857
+
858
+
859
+ @router.get("/history", response_model=SuccessResponse[ConversationHistory])
860
+ async def get_history(
861
+ request: Request,
862
+ limit: int = Query(50, ge=1, le=200, description="Maximum messages to return"),
863
+ before: Optional[datetime] = Query(None, description="Get messages before this time"),
864
+ auth: AuthContext = Depends(require_observer),
865
+ ) -> SuccessResponse[ConversationHistory]:
866
+ """
867
+ Conversation history.
868
+
869
+ Get the conversation history for the current user.
870
+ """
871
+ # Build channels to query based on user role
872
+ channels_to_query = _build_channels_to_query(auth, request)
873
+ channel_id = f"api_{auth.user_id}"
874
+
875
+ logger.info(f"History query for user {auth.user_id} with role {auth.role}, channels: {channels_to_query}")
876
+
877
+ # Check for mock message history first
878
+ message_history = getattr(request.app.state, "message_history", None)
879
+ if message_history is not None:
880
+ history = await _get_history_from_mock(message_history, channels_to_query, limit)
881
+ return SuccessResponse(data=history)
882
+
883
+ # Get communication service
884
+ comm_service = getattr(request.app.state, "communication_service", None)
885
+ if not comm_service:
886
+ # Fallback: query from memory
887
+ memory_service = getattr(request.app.state, "memory_service", None)
888
+ if memory_service:
889
+ history = await _get_history_from_memory(memory_service, channel_id, limit)
890
+ return SuccessResponse(data=history)
891
+
892
+ try:
893
+ history = await _get_history_from_communication_service(comm_service, channels_to_query, limit, before)
894
+ return SuccessResponse(data=history)
895
+ except Exception as e:
896
+ raise HTTPException(status_code=500, detail=str(e))
897
+
898
+
899
+ def _get_cognitive_state(runtime: Any) -> str:
900
+ """Get the agent's cognitive state with proper None checking."""
901
+ if runtime is None:
902
+ logger.warning("Runtime is None")
903
+ return "UNKNOWN"
904
+
905
+ # State manager is on the agent processor, not directly on runtime
906
+ if hasattr(runtime, "agent_processor") and runtime.agent_processor:
907
+ if hasattr(runtime.agent_processor, "state_manager") and runtime.agent_processor.state_manager:
908
+ if hasattr(runtime.agent_processor.state_manager, "current_state"):
909
+ state = runtime.agent_processor.state_manager.current_state
910
+ # Convert AgentState enum to string if necessary
911
+ return str(state)
912
+ else:
913
+ logger.warning("Agent processor state_manager exists but has no current_state attribute")
914
+ else:
915
+ logger.warning("Agent processor has no state_manager or state_manager is None")
916
+ else:
917
+ logger.warning("Runtime has no agent_processor or agent_processor is None")
918
+ return "UNKNOWN" # Don't default to WORK - be explicit about unknown state
919
+
920
+
921
+ def _calculate_uptime(time_service: Any) -> float:
922
+ """Calculate the agent's uptime in seconds."""
923
+ if not time_service:
924
+ return 0.0
925
+
926
+ # Try to get uptime from time service status
927
+ if hasattr(time_service, "get_status"):
928
+ time_status = time_service.get_status()
929
+ if hasattr(time_status, "uptime_seconds"):
930
+ uptime = time_status.uptime_seconds
931
+ return float(uptime) if uptime is not None else 0.0
932
+
933
+ # Calculate uptime manually
934
+ if hasattr(time_service, "_start_time") and hasattr(time_service, "now"):
935
+ delta = time_service.now() - time_service._start_time
936
+ return float(delta.total_seconds())
937
+
938
+ return 0.0
939
+
940
+
941
+ def _count_wakeup_tasks(uptime: float) -> int:
942
+ """Count completed WAKEUP tasks."""
943
+ try:
944
+ from ciris_engine.logic import persistence
945
+ from ciris_engine.schemas.runtime.enums import TaskStatus
946
+
947
+ completed_tasks = persistence.get_tasks_by_status(TaskStatus.COMPLETED)
948
+
949
+ wakeup_prefixes = [
950
+ "VERIFY_IDENTITY",
951
+ "VALIDATE_INTEGRITY",
952
+ "EVALUATE_RESILIENCE",
953
+ "ACCEPT_INCOMPLETENESS",
954
+ "EXPRESS_GRATITUDE",
955
+ ]
956
+
957
+ count = sum(1 for task in completed_tasks if any(task.task_id.startswith(prefix) for prefix in wakeup_prefixes))
958
+
959
+ # If no wakeup tasks found but system has been running, assume standard cycle
960
+ if count == 0 and uptime > MIN_UPTIME_FOR_DEFAULT_TASKS:
961
+ return 5 # Standard wakeup cycle completes 5 tasks
962
+
963
+ return count
964
+ except Exception as e:
965
+ logger.warning(f"Failed to count completed tasks: {e}")
966
+ return 0
967
+
968
+
969
+ def _count_active_services(service_registry: Any) -> Tuple[int, JSONDict]:
970
+ """Count active services and get multi-provider service details."""
971
+ multi_provider_count = 0
972
+ multi_provider_services: JSONDict = {}
973
+
974
+ if service_registry:
975
+ from ciris_engine.schemas.runtime.enums import ServiceType
976
+
977
+ for service_type in list(ServiceType):
978
+ providers = service_registry.get_services_by_type(service_type)
979
+ count = len(providers)
980
+ if count > 0:
981
+ multi_provider_count += count
982
+ multi_provider_services[service_type.value] = {"providers": count, "type": "multi-provider"}
983
+
984
+ # CIRIS has AT LEAST 19 service types:
985
+ # Multi-provider services can have multiple instances + 12 singleton services
986
+ services_active = multi_provider_count + 12
987
+
988
+ return services_active, multi_provider_services
989
+
990
+
991
+ def _get_admin_channels(auth: AuthContext, request: Request) -> List[str]:
992
+ """Get additional admin channels for privileged users."""
993
+ channels = []
994
+ if auth.role in ["ADMIN", "AUTHORITY", "SYSTEM_ADMIN"]:
995
+ # Get default API channel from config
996
+ api_host = getattr(request.app.state, "api_host", "127.0.0.1")
997
+ api_port = getattr(request.app.state, "api_port", "8080")
998
+ default_channel = f"api_{api_host}_{api_port}"
999
+ channels.append(default_channel)
1000
+
1001
+ # Add common variations of the API channel
1002
+ channels.extend(
1003
+ [
1004
+ f"api_0.0.0.0_{api_port}", # Bind address
1005
+ f"api_127.0.0.1_{api_port}", # Localhost
1006
+ f"api_localhost_{api_port}", # Hostname variant
1007
+ ]
1008
+ )
1009
+ return channels
1010
+
1011
+
1012
+ def _build_channels_to_query(auth: AuthContext, request: Request) -> List[str]:
1013
+ """Build list of channels to query for conversation history."""
1014
+ channel_id = f"api_{auth.user_id}"
1015
+ channels_to_query = [channel_id]
1016
+ channels_to_query.extend(_get_admin_channels(auth, request))
1017
+ # Remove duplicates while preserving order
1018
+ seen = set()
1019
+ deduped = []
1020
+ for channel in channels_to_query:
1021
+ if channel not in seen:
1022
+ seen.add(channel)
1023
+ deduped.append(channel)
1024
+ return deduped
1025
+
1026
+
1027
+ def _convert_timestamp(timestamp: Any) -> datetime:
1028
+ """Convert timestamp string or datetime to datetime object."""
1029
+ if isinstance(timestamp, str):
1030
+ try:
1031
+ return datetime.fromisoformat(timestamp)
1032
+ except (ValueError, TypeError):
1033
+ return datetime.now(timezone.utc)
1034
+ elif isinstance(timestamp, datetime):
1035
+ return timestamp
1036
+ else:
1037
+ return datetime.now(timezone.utc)
1038
+
1039
+
1040
+ def _create_conversation_message_from_mock(msg: JSONDict, is_response: bool = False) -> ConversationMessage:
1041
+ """Create ConversationMessage from mock message data."""
1042
+ if is_response:
1043
+ return ConversationMessage(
1044
+ id=f"{msg['message_id']}_response",
1045
+ author="Scout",
1046
+ content=msg["response"],
1047
+ timestamp=_convert_timestamp(msg["timestamp"]),
1048
+ is_agent=True,
1049
+ )
1050
+ else:
1051
+ return ConversationMessage(
1052
+ id=msg["message_id"],
1053
+ author=msg["author_id"],
1054
+ content=msg["content"],
1055
+ timestamp=_convert_timestamp(msg["timestamp"]),
1056
+ is_agent=False,
1057
+ )
1058
+
1059
+
1060
+ def _expand_mock_messages(user_messages: List[JSONDict]) -> List[ConversationMessage]:
1061
+ """Expand mock messages into user message + response pairs."""
1062
+ all_messages = []
1063
+ for msg in user_messages:
1064
+ # Add user message
1065
+ all_messages.append(_create_conversation_message_from_mock(msg))
1066
+ # Add agent response if exists
1067
+ if msg.get("response"):
1068
+ all_messages.append(_create_conversation_message_from_mock(msg, is_response=True))
1069
+ return all_messages
1070
+
1071
+
1072
+ def _apply_message_limit(messages: List[ConversationMessage], limit: int) -> List[ConversationMessage]:
1073
+ """Apply message limit, taking the last N messages."""
1074
+ if len(messages) > limit:
1075
+ return messages[-limit:]
1076
+ return messages
1077
+
1078
+
1079
+ async def _get_history_from_mock(
1080
+ message_history: List[JSONDict], channels_to_query: List[str], limit: int
1081
+ ) -> ConversationHistory:
1082
+ """Process conversation history from mock data."""
1083
+ # Filter messages for requested channels
1084
+ user_messages = [m for m in message_history if m.get("channel_id") in channels_to_query]
1085
+
1086
+ # Expand all messages (user + response pairs)
1087
+ all_messages = _expand_mock_messages(user_messages)
1088
+
1089
+ # Apply limit
1090
+ limited_messages = _apply_message_limit(all_messages, limit)
1091
+
1092
+ return ConversationHistory(
1093
+ messages=limited_messages,
1094
+ total_count=len(user_messages),
1095
+ has_more=len(user_messages) > len(limited_messages),
1096
+ )
1097
+
1098
+
1099
+ async def _get_history_from_memory(memory_service: Any, channel_id: str, limit: int) -> ConversationHistory:
1100
+ """Query conversation history from memory service."""
1101
+ from ciris_engine.schemas.services.graph_core import GraphScope, NodeType
1102
+ from ciris_engine.schemas.services.operations import MemoryQuery
1103
+
1104
+ query = MemoryQuery(
1105
+ node_id=f"conversation_{channel_id}",
1106
+ scope=GraphScope.LOCAL,
1107
+ type=NodeType.CONVERSATION_SUMMARY,
1108
+ include_edges=True,
1109
+ depth=1,
1110
+ )
1111
+
1112
+ nodes = await memory_service.recall(query)
1113
+
1114
+ # Convert to conversation messages
1115
+ messages = []
1116
+ for node in nodes:
1117
+ attrs = node.attributes
1118
+ messages.append(
1119
+ ConversationMessage(
1120
+ id=attrs.get("message_id", node.id),
1121
+ author=attrs.get("author", "unknown"),
1122
+ content=attrs.get("content", ""),
1123
+ timestamp=datetime.fromisoformat(attrs.get("timestamp", node.created_at)),
1124
+ is_agent=attrs.get("is_agent", False),
1125
+ )
1126
+ )
1127
+
1128
+ return ConversationHistory(messages=messages, total_count=len(messages), has_more=len(messages) == limit)
1129
+
1130
+
1131
+ def _safe_convert_message_timestamp(msg: Any) -> datetime:
1132
+ """Safely convert message timestamp with fallback."""
1133
+ timestamp_val = msg.timestamp
1134
+ if isinstance(timestamp_val, datetime):
1135
+ return timestamp_val
1136
+ elif timestamp_val:
1137
+ try:
1138
+ return datetime.fromisoformat(str(timestamp_val))
1139
+ except (ValueError, TypeError):
1140
+ pass
1141
+ return datetime.now(timezone.utc)
1142
+
1143
+
1144
+ def _convert_service_message_to_conversation(msg: Any) -> ConversationMessage:
1145
+ """Convert communication service message to ConversationMessage."""
1146
+ return ConversationMessage(
1147
+ id=str(msg.message_id or ""),
1148
+ author=str(msg.author_name or msg.author_id or ""),
1149
+ content=str(msg.content or ""),
1150
+ timestamp=_safe_convert_message_timestamp(msg),
1151
+ is_agent=bool(getattr(msg, "is_agent_message", False) or getattr(msg, "is_bot", False)),
1152
+ )
1153
+
1154
+
1155
+ async def _fetch_messages_from_channels(comm_service: Any, channels_to_query: List[str], fetch_limit: int) -> List[Any]:
1156
+ """Fetch messages from all specified channels."""
1157
+ fetched_messages = []
1158
+ for channel in channels_to_query:
1159
+ try:
1160
+ logger.info(f"Fetching messages from channel: {channel}")
1161
+ if comm_service is None:
1162
+ logger.warning("Communication service is not available")
1163
+ continue
1164
+ channel_messages = await comm_service.fetch_messages(channel, limit=fetch_limit)
1165
+ logger.info(f"Retrieved {len(channel_messages)} messages from {channel}")
1166
+ fetched_messages.extend(channel_messages)
1167
+ except Exception as e:
1168
+ logger.warning(f"Failed to fetch from channel {channel}: {e}")
1169
+ continue
1170
+ return fetched_messages
1171
+
1172
+
1173
+ def _sort_and_filter_messages(fetched_messages: List[Any], before: Optional[datetime]) -> List[Any]:
1174
+ """Sort messages by timestamp and apply time filter."""
1175
+ # Sort messages by timestamp (newest first)
1176
+ sorted_messages = sorted(
1177
+ fetched_messages,
1178
+ key=lambda m: _safe_convert_message_timestamp(m),
1179
+ reverse=True,
1180
+ )
1181
+
1182
+ # Filter by time if specified
1183
+ if before:
1184
+ return [m for m in sorted_messages if _safe_convert_message_timestamp(m) < before]
1185
+ return sorted_messages
1186
+
1187
+
1188
+ async def _get_history_from_communication_service(
1189
+ comm_service: Any, channels_to_query: List[str], limit: int, before: Optional[datetime]
1190
+ ) -> ConversationHistory:
1191
+ """Get conversation history from communication service."""
1192
+ # Fetch more messages to allow filtering
1193
+ fetch_limit = limit * 2 if before else limit
1194
+
1195
+ # Fetch messages from all relevant channels
1196
+ fetched_messages = await _fetch_messages_from_channels(comm_service, channels_to_query, fetch_limit)
1197
+
1198
+ # Sort and filter messages
1199
+ filtered_messages = _sort_and_filter_messages(fetched_messages, before)
1200
+
1201
+ # Convert to conversation messages and apply final limit
1202
+ conv_messages = [_convert_service_message_to_conversation(msg) for msg in filtered_messages[:limit]]
1203
+
1204
+ return ConversationHistory(
1205
+ messages=conv_messages,
1206
+ total_count=len(filtered_messages),
1207
+ has_more=len(filtered_messages) > limit,
1208
+ )
1209
+
1210
+
1211
+ def _get_current_task_info(request: Request) -> Optional[str]:
1212
+ """Get current task information from task scheduler."""
1213
+ import inspect
1214
+
1215
+ task_scheduler = getattr(request.app.state, "task_scheduler", None)
1216
+ if task_scheduler and hasattr(task_scheduler, "get_current_task"):
1217
+ task = task_scheduler.get_current_task()
1218
+ # If the result is a coroutine (from AsyncMock in tests), close it and ignore
1219
+ # since the real TaskSchedulerService doesn't have this method
1220
+ if inspect.iscoroutine(task):
1221
+ task.close() # Properly close the coroutine to avoid warning
1222
+ return None
1223
+ return str(task) if task is not None else None
1224
+ return None
1225
+
1226
+
1227
+ def _get_memory_usage(request: Request) -> float:
1228
+ """Get current memory usage from resource monitor."""
1229
+ resource_monitor = getattr(request.app.state, "resource_monitor", None)
1230
+ if resource_monitor and hasattr(resource_monitor, "snapshot"):
1231
+ return float(resource_monitor.snapshot.memory_mb)
1232
+ return 0.0
1233
+
1234
+
1235
+ def _get_version_info() -> Tuple[str, str, Optional[str]]:
1236
+ """Get version information including codename and code hash."""
1237
+ from ciris_engine.constants import CIRIS_CODENAME, CIRIS_VERSION
1238
+
1239
+ try:
1240
+ from version import __version__ as code_hash_val
1241
+
1242
+ code_hash: Optional[str] = code_hash_val
1243
+ except ImportError:
1244
+ code_hash = None
1245
+
1246
+ return CIRIS_VERSION, CIRIS_CODENAME, code_hash
1247
+
1248
+
1249
+ async def _build_agent_status(
1250
+ request: Request, cognitive_state: str, uptime: float, messages_processed: int, runtime: Any
1251
+ ) -> AgentStatus:
1252
+ """Build AgentStatus object with all required information."""
1253
+ # Get current task (synchronous call, not awaitable)
1254
+ current_task = _get_current_task_info(request)
1255
+
1256
+ # Get resource usage
1257
+ memory_usage_mb = _get_memory_usage(request)
1258
+
1259
+ # Count services
1260
+ service_registry = getattr(request.app.state, "service_registry", None)
1261
+ services_active, multi_provider_services = _count_active_services(service_registry)
1262
+
1263
+ # Get identity
1264
+ agent_id, agent_name = _get_agent_identity_info(runtime)
1265
+
1266
+ # Get version information
1267
+ version, codename, code_hash = _get_version_info()
1268
+
1269
+ return AgentStatus(
1270
+ agent_id=agent_id,
1271
+ name=agent_name,
1272
+ version=version,
1273
+ codename=codename,
1274
+ code_hash=code_hash,
1275
+ cognitive_state=cognitive_state,
1276
+ uptime_seconds=uptime,
1277
+ messages_processed=messages_processed,
1278
+ last_activity=datetime.now(timezone.utc),
1279
+ current_task=current_task,
1280
+ services_active=services_active,
1281
+ memory_usage_mb=memory_usage_mb,
1282
+ multi_provider_services=multi_provider_services,
1283
+ )
1284
+
1285
+
1286
+ def _get_agent_identity_info(runtime: Any) -> Tuple[str, str]:
1287
+ """Get agent ID and name."""
1288
+ agent_id = "ciris_agent"
1289
+ agent_name = "CIRIS"
1290
+
1291
+ if hasattr(runtime, "agent_identity") and runtime.agent_identity:
1292
+ agent_id = runtime.agent_identity.agent_id
1293
+ # Try to get name from various sources
1294
+ if hasattr(runtime.agent_identity, "name"):
1295
+ agent_name = runtime.agent_identity.name
1296
+ elif hasattr(runtime.agent_identity, "core_profile"):
1297
+ # Use first part of description or role as name
1298
+ agent_name = runtime.agent_identity.core_profile.description.split(".")[0]
1299
+
1300
+ return agent_id, agent_name
1301
+
1302
+
1303
+ @router.get("/status", response_model=SuccessResponse[AgentStatus])
1304
+ async def get_status(request: Request, auth: AuthContext = Depends(require_observer)) -> SuccessResponse[AgentStatus]:
1305
+ """
1306
+ Agent status and cognitive state.
1307
+
1308
+ Get comprehensive agent status including state, metrics, and current activity.
1309
+ """
1310
+ runtime = getattr(request.app.state, "runtime", None)
1311
+ if not runtime:
1312
+ raise HTTPException(status_code=503, detail="Runtime not available")
1313
+
1314
+ try:
1315
+ # Get basic state information
1316
+ cognitive_state = _get_cognitive_state(runtime)
1317
+ time_service = getattr(request.app.state, "time_service", None)
1318
+ uptime = _calculate_uptime(time_service)
1319
+ messages_processed = _count_wakeup_tasks(uptime)
1320
+
1321
+ # Build comprehensive status
1322
+ status = await _build_agent_status(request, cognitive_state, uptime, messages_processed, runtime)
1323
+ return SuccessResponse(data=status)
1324
+
1325
+ except Exception as e:
1326
+ raise HTTPException(status_code=500, detail=str(e))
1327
+
1328
+
1329
+ @router.get("/identity", response_model=SuccessResponse[AgentIdentity])
1330
+ async def get_identity(
1331
+ request: Request, auth: AuthContext = Depends(require_observer)
1332
+ ) -> SuccessResponse[AgentIdentity]:
1333
+ """
1334
+ Agent identity and capabilities.
1335
+
1336
+ Get comprehensive agent identity including capabilities, tools, and permissions.
1337
+ """
1338
+ # Get memory service to query identity
1339
+ memory_service = getattr(request.app.state, "memory_service", None)
1340
+ if not memory_service:
1341
+ raise HTTPException(status_code=503, detail=ERROR_MEMORY_SERVICE_NOT_AVAILABLE)
1342
+
1343
+ try:
1344
+ # Query identity from graph
1345
+ from ciris_engine.schemas.services.graph_core import GraphScope
1346
+ from ciris_engine.schemas.services.operations import MemoryQuery
1347
+
1348
+ query = MemoryQuery(node_id="agent/identity", scope=GraphScope.IDENTITY, include_edges=False)
1349
+
1350
+ nodes = await memory_service.recall(query)
1351
+
1352
+ # Get identity data
1353
+ identity_data = {}
1354
+ if nodes:
1355
+ identity_node = nodes[0]
1356
+ identity_data = identity_node.attributes
1357
+ else:
1358
+ # Fallback to runtime identity
1359
+ runtime = getattr(request.app.state, "runtime", None)
1360
+ if runtime and hasattr(runtime, "agent_identity"):
1361
+ identity = runtime.agent_identity
1362
+ identity_data = {
1363
+ "agent_id": identity.agent_id,
1364
+ "name": getattr(identity, "name", identity.core_profile.description.split(".")[0]),
1365
+ "purpose": getattr(identity, "purpose", identity.core_profile.description),
1366
+ "created_at": identity.identity_metadata.created_at.isoformat(),
1367
+ "lineage": {
1368
+ "model": identity.identity_metadata.model,
1369
+ "version": identity.identity_metadata.version,
1370
+ "parent_id": getattr(identity.identity_metadata, "parent_id", None),
1371
+ "creation_context": getattr(identity.identity_metadata, "creation_context", "default"),
1372
+ "adaptations": getattr(identity.identity_metadata, "adaptations", []),
1373
+ },
1374
+ "variance_threshold": 0.2,
1375
+ }
1376
+
1377
+ # Get capabilities
1378
+
1379
+ # Get tool service for available tools
1380
+ tool_service = getattr(request.app.state, "tool_service", None)
1381
+ tools = []
1382
+ if tool_service:
1383
+ tools = await tool_service.list_tools()
1384
+
1385
+ # Get handlers (these are the core action handlers)
1386
+ handlers = [
1387
+ "observe",
1388
+ "speak",
1389
+ "tool",
1390
+ "reject",
1391
+ "ponder",
1392
+ "defer",
1393
+ "memorize",
1394
+ "recall",
1395
+ "forget",
1396
+ "task_complete",
1397
+ ]
1398
+
1399
+ # Get service availability
1400
+ services = ServiceAvailability()
1401
+ service_registry = getattr(request.app.state, "service_registry", None)
1402
+ if service_registry:
1403
+ from ciris_engine.schemas.runtime.enums import ServiceType
1404
+
1405
+ for service_type in ServiceType:
1406
+ providers = service_registry.get_services_by_type(service_type)
1407
+ count = len(providers)
1408
+ # Map to service categories
1409
+ if "graph" in service_type.value.lower() or service_type.value == "MEMORY":
1410
+ services.graph += count
1411
+ elif service_type.value in ["LLM", "SECRETS"]:
1412
+ services.core += count
1413
+ elif service_type.value in [
1414
+ "TIME",
1415
+ "SHUTDOWN",
1416
+ "INITIALIZATION",
1417
+ "VISIBILITY",
1418
+ "AUTHENTICATION",
1419
+ "RESOURCE_MONITOR",
1420
+ "RUNTIME_CONTROL",
1421
+ ]:
1422
+ services.infrastructure += count
1423
+ elif service_type.value == "WISE_AUTHORITY":
1424
+ services.governance += count
1425
+ else:
1426
+ services.special += count
1427
+
1428
+ # Get permissions (agent's core capabilities)
1429
+ permissions = ["communicate", "use_tools", "access_memory", "observe_environment", "learn", "adapt"]
1430
+
1431
+ # Build response
1432
+ lineage_data = identity_data.get("lineage", {})
1433
+ lineage = AgentLineage(
1434
+ model=lineage_data.get("model", "unknown"),
1435
+ version=lineage_data.get("version", "1.0"),
1436
+ parent_id=lineage_data.get("parent_id"),
1437
+ creation_context=lineage_data.get("creation_context", "default"),
1438
+ adaptations=lineage_data.get("adaptations", []),
1439
+ )
1440
+
1441
+ response = AgentIdentity(
1442
+ agent_id=identity_data.get("agent_id", "ciris_agent"),
1443
+ name=identity_data.get("name", "CIRIS"),
1444
+ purpose=identity_data.get("purpose", "Autonomous AI agent"),
1445
+ created_at=datetime.fromisoformat(identity_data.get("created_at", datetime.now(timezone.utc).isoformat())),
1446
+ lineage=lineage,
1447
+ variance_threshold=identity_data.get("variance_threshold", 0.2),
1448
+ tools=tools,
1449
+ handlers=handlers,
1450
+ services=services,
1451
+ permissions=permissions,
1452
+ )
1453
+
1454
+ return SuccessResponse(data=response)
1455
+
1456
+ except HTTPException:
1457
+ raise
1458
+ except Exception as e:
1459
+ raise HTTPException(status_code=500, detail=str(e))
1460
+
1461
+
1462
+ def _convert_to_channel_info(ch: Any, adapter_type: str) -> ChannelInfo:
1463
+ """Convert adapter channel data to ChannelInfo format."""
1464
+ if hasattr(ch, "channel_id"):
1465
+ # Pydantic model format
1466
+ return ChannelInfo(
1467
+ channel_id=ch.channel_id,
1468
+ channel_type=getattr(ch, "channel_type", adapter_type),
1469
+ display_name=getattr(ch, "display_name", ch.channel_id),
1470
+ is_active=getattr(ch, "is_active", True),
1471
+ created_at=getattr(ch, "created_at", None),
1472
+ last_activity=getattr(ch, "last_activity", None),
1473
+ message_count=getattr(ch, "message_count", 0),
1474
+ )
1475
+ else:
1476
+ # Dict format (legacy)
1477
+ return ChannelInfo(
1478
+ channel_id=ch.get("channel_id", ""),
1479
+ channel_type=ch.get("channel_type", adapter_type),
1480
+ display_name=ch.get("display_name", ch.get("channel_id", "")),
1481
+ is_active=ch.get("is_active", True),
1482
+ created_at=ch.get("created_at"),
1483
+ last_activity=ch.get("last_activity"),
1484
+ message_count=ch.get("message_count", 0),
1485
+ )
1486
+
1487
+
1488
+ async def _get_channels_from_adapter(adapter: Any, adapter_type: str) -> List[ChannelInfo]:
1489
+ """Get channels from a single adapter."""
1490
+ channels = []
1491
+ if hasattr(adapter, "get_active_channels"):
1492
+ try:
1493
+ adapter_channels = await adapter.get_active_channels()
1494
+ for ch in adapter_channels:
1495
+ channels.append(_convert_to_channel_info(ch, adapter_type))
1496
+ except Exception as e:
1497
+ logger.error(f"Error getting channels from adapter {adapter_type}: {e}")
1498
+ return channels
1499
+
1500
+
1501
+ async def _get_channels_from_bootstrap_adapters(runtime: Any) -> List[ChannelInfo]:
1502
+ """Get channels from bootstrap adapters."""
1503
+ channels = []
1504
+ if runtime and hasattr(runtime, "adapters"):
1505
+ logger.info(f"Checking {len(runtime.adapters)} bootstrap adapters for channels")
1506
+ for adapter in runtime.adapters:
1507
+ adapter_type = adapter.__class__.__name__.lower().replace("platform", "")
1508
+ channels.extend(await _get_channels_from_adapter(adapter, adapter_type))
1509
+ return channels
1510
+
1511
+
1512
+ def _get_control_service(runtime: Any, request: Request) -> Any:
1513
+ """Get the runtime control service from app state or registry."""
1514
+ # Try app state first
1515
+ control_service = getattr(request.app.state, "main_runtime_control_service", None)
1516
+ if control_service:
1517
+ return control_service
1518
+
1519
+ # Fallback to service registry
1520
+ if not runtime or not hasattr(runtime, "service_registry") or not runtime.service_registry:
1521
+ return None
1522
+
1523
+ from ciris_engine.schemas.runtime.enums import ServiceType
1524
+
1525
+ providers = runtime.service_registry.get_services_by_type(ServiceType.RUNTIME_CONTROL)
1526
+ return providers[0] if providers else None
1527
+
1528
+
1529
+ def _get_adapter_manager(control_service: Any) -> Any:
1530
+ """Get adapter manager from control service."""
1531
+ if not control_service:
1532
+ return None
1533
+ if not hasattr(control_service, "adapter_manager"):
1534
+ return None
1535
+ return control_service.adapter_manager
1536
+
1537
+
1538
+ async def _collect_unique_channels(adapter_manager: Any) -> List[ChannelInfo]:
1539
+ """Collect unique channels from loaded adapters."""
1540
+ if not adapter_manager or not hasattr(adapter_manager, "loaded_adapters"):
1541
+ return []
1542
+
1543
+ channels = []
1544
+ seen_channel_ids = set()
1545
+
1546
+ for adapter_id, instance in adapter_manager.loaded_adapters.items():
1547
+ adapter_channels = await _get_channels_from_adapter(instance.adapter, instance.adapter_type)
1548
+
1549
+ # Add only unique channels
1550
+ for ch in adapter_channels:
1551
+ if ch.channel_id not in seen_channel_ids:
1552
+ channels.append(ch)
1553
+ seen_channel_ids.add(ch.channel_id)
1554
+
1555
+ return channels
1556
+
1557
+
1558
+ async def _get_channels_from_dynamic_adapters(runtime: Any, request: Request) -> List[ChannelInfo]:
1559
+ """Get channels from dynamically loaded adapters."""
1560
+ control_service = _get_control_service(runtime, request)
1561
+ adapter_manager = _get_adapter_manager(control_service)
1562
+ return await _collect_unique_channels(adapter_manager)
1563
+
1564
+
1565
+ def _add_default_api_channels(channels: List[ChannelInfo], request: Request, auth: AuthContext) -> None:
1566
+ """Add default API channels if not already present."""
1567
+ # Default API channel
1568
+ api_host = getattr(request.app.state, "api_host", "127.0.0.1")
1569
+ api_port = getattr(request.app.state, "api_port", "8080")
1570
+ api_channel_id = f"api_{api_host}_{api_port}"
1571
+
1572
+ if not any(ch.channel_id == api_channel_id for ch in channels):
1573
+ channels.append(
1574
+ ChannelInfo(
1575
+ channel_id=api_channel_id,
1576
+ channel_type="api",
1577
+ display_name=f"API Channel ({api_host}:{api_port})",
1578
+ is_active=True,
1579
+ created_at=None,
1580
+ last_activity=datetime.now(timezone.utc),
1581
+ message_count=0,
1582
+ )
1583
+ )
1584
+
1585
+ # User-specific API channel
1586
+ user_channel_id = f"api_{auth.user_id}"
1587
+ if not any(ch.channel_id == user_channel_id for ch in channels):
1588
+ channels.append(
1589
+ ChannelInfo(
1590
+ channel_id=user_channel_id,
1591
+ channel_type="api",
1592
+ display_name=f"API Channel ({auth.user_id})",
1593
+ is_active=True,
1594
+ created_at=None,
1595
+ last_activity=None,
1596
+ message_count=0,
1597
+ )
1598
+ )
1599
+
1600
+
1601
+ @router.get("/channels", response_model=SuccessResponse[ChannelList])
1602
+ async def get_channels(request: Request, auth: AuthContext = Depends(require_observer)) -> SuccessResponse[ChannelList]:
1603
+ """
1604
+ List active communication channels.
1605
+
1606
+ Get all channels where the agent is currently active or has been active.
1607
+ """
1608
+ try:
1609
+ channels = []
1610
+ runtime = getattr(request.app.state, "runtime", None)
1611
+
1612
+ # Get channels from bootstrap adapters
1613
+ channels.extend(await _get_channels_from_bootstrap_adapters(runtime))
1614
+
1615
+ # Get channels from dynamically loaded adapters
1616
+ dynamic_channels = await _get_channels_from_dynamic_adapters(runtime, request)
1617
+ channels.extend(dynamic_channels)
1618
+
1619
+ # Add default API channels
1620
+ _add_default_api_channels(channels, request, auth)
1621
+
1622
+ # Sort channels by type and then by id
1623
+ channels.sort(key=lambda x: (x.channel_type, x.channel_id))
1624
+
1625
+ channel_list = ChannelList(channels=channels, total_count=len(channels))
1626
+ return SuccessResponse(data=channel_list)
1627
+
1628
+ except Exception as e:
1629
+ logger.error(f"Failed to get channels: {e}", exc_info=True)
1630
+ raise HTTPException(status_code=500, detail=str(e))
1631
+
1632
+
1633
+ # Helper function to notify interact responses
1634
+ async def notify_interact_response(message_id: str, content: str) -> None:
1635
+ """Notify waiting interact requests of responses."""
1636
+ if message_id in _response_events:
1637
+ _message_responses[message_id] = content
1638
+ _response_events[message_id].set()
1639
+
1640
+
1641
+ def _validate_websocket_authorization(websocket: WebSocket) -> Optional[str]:
1642
+ """Validate websocket authorization header and return API key."""
1643
+ authorization = websocket.headers.get("authorization")
1644
+ if not authorization:
1645
+ return None
1646
+ if not authorization.startswith("Bearer "):
1647
+ return None
1648
+ return authorization[7:] # Remove "Bearer " prefix
1649
+
1650
+
1651
+ async def _authenticate_websocket_user(websocket: WebSocket, api_key: str) -> Optional[AuthContext]:
1652
+ """Authenticate websocket user and return auth context."""
1653
+ auth_service = getattr(websocket.app.state, "auth_service", None)
1654
+ if not auth_service:
1655
+ return None
1656
+
1657
+ key_info = auth_service.validate_api_key(api_key)
1658
+ if not key_info:
1659
+ return None
1660
+
1661
+ return AuthContext(
1662
+ user_id=key_info.user_id,
1663
+ role=key_info.role,
1664
+ permissions=ROLE_PERMISSIONS.get(key_info.role, set()),
1665
+ api_key_id=auth_service._get_key_id(api_key),
1666
+ authenticated_at=datetime.now(timezone.utc),
1667
+ )
1668
+
1669
+
1670
+ async def _handle_websocket_subscription_action(
1671
+ websocket: WebSocket, data: JSONDict, subscribed_channels: set[str]
1672
+ ) -> None:
1673
+ """Handle websocket subscribe/unsubscribe actions."""
1674
+ action = data.get("action")
1675
+ channels_raw = data.get("channels", [])
1676
+ # Type narrow to list for set operations
1677
+ channels = channels_raw if isinstance(channels_raw, list) else []
1678
+
1679
+ if action == "subscribe":
1680
+ subscribed_channels.update(channels)
1681
+ elif action == "unsubscribe":
1682
+ subscribed_channels.difference_update(channels)
1683
+ elif action == "ping":
1684
+ await websocket.send_json({"type": "pong", "timestamp": datetime.now(timezone.utc).isoformat()})
1685
+ return
1686
+
1687
+ # Send subscription update for subscribe/unsubscribe
1688
+ if action in ["subscribe", "unsubscribe"]:
1689
+ await websocket.send_json(
1690
+ {
1691
+ "type": "subscription_update",
1692
+ "channels": list(subscribed_channels),
1693
+ "timestamp": datetime.now(timezone.utc).isoformat(),
1694
+ }
1695
+ )
1696
+
1697
+
1698
+ def _register_websocket_client(websocket: WebSocket, client_id: str) -> None:
1699
+ """Register websocket client with communication service."""
1700
+ comm_service = getattr(websocket.app.state, "communication_service", None)
1701
+ if comm_service and hasattr(comm_service, "register_websocket"):
1702
+ comm_service.register_websocket(client_id, websocket)
1703
+
1704
+
1705
+ def _unregister_websocket_client(websocket: WebSocket, client_id: str) -> None:
1706
+ """Unregister websocket client from communication service."""
1707
+ comm_service = getattr(websocket.app.state, "communication_service", None)
1708
+ if comm_service and hasattr(comm_service, "unregister_websocket"):
1709
+ comm_service.unregister_websocket(client_id)
1710
+
1711
+
1712
+ # WebSocket endpoint for streaming
1713
+
1714
+
1715
+ @router.websocket("/stream")
1716
+ async def websocket_stream(
1717
+ websocket: WebSocket,
1718
+ ) -> None:
1719
+ """
1720
+ WebSocket endpoint for real-time updates.
1721
+
1722
+ Clients can subscribe to different channels:
1723
+ - messages: Agent messages and responses
1724
+ - telemetry: Real-time metrics
1725
+ - reasoning: Reasoning traces
1726
+ - logs: System logs
1727
+ """
1728
+ # Validate authorization
1729
+ api_key = _validate_websocket_authorization(websocket)
1730
+ if not api_key:
1731
+ await websocket.close(code=1008, reason="Missing or invalid authorization header")
1732
+ return
1733
+
1734
+ # Authenticate user
1735
+ auth_context = await _authenticate_websocket_user(websocket, api_key)
1736
+ if not auth_context:
1737
+ await websocket.close(code=1008, reason="Authentication failed")
1738
+ return
1739
+
1740
+ # Check minimum role requirement (OBSERVER)
1741
+ if not auth_context.role.has_permission(UserRole.OBSERVER):
1742
+ await websocket.close(code=1008, reason="Insufficient permissions")
1743
+ return
1744
+
1745
+ await websocket.accept()
1746
+ client_id = f"ws_{id(websocket)}"
1747
+
1748
+ # Register websocket client
1749
+ _register_websocket_client(websocket, client_id)
1750
+
1751
+ subscribed_channels = set(["messages"]) # Default subscription
1752
+
1753
+ try:
1754
+ while True:
1755
+ # Receive and process client messages
1756
+ data = await websocket.receive_json()
1757
+ await _handle_websocket_subscription_action(websocket, data, subscribed_channels)
1758
+
1759
+ except WebSocketDisconnect:
1760
+ # Clean up on disconnect
1761
+ _unregister_websocket_client(websocket, client_id)
1762
+ logger.info(f"WebSocket client {client_id} disconnected")