apsimo 1.3.0__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 (614) hide show
  1. apsimo/__init__.py +38 -0
  2. apsimo/__main__.py +6 -0
  3. apsimo/agent/__init__.py +6 -0
  4. apsimo/agent/client.py +276 -0
  5. apsimo/agent/models.py +46 -0
  6. apsimo/agents/__init__.py +20 -0
  7. apsimo/agents/models.py +264 -0
  8. apsimo/agents/store.py +861 -0
  9. apsimo/agents/websocket.py +522 -0
  10. apsimo/api/__init__.py +1 -0
  11. apsimo/api/auth_telemetry.py +287 -0
  12. apsimo/api/authority.py +1203 -0
  13. apsimo/api/contact_grants.py +347 -0
  14. apsimo/api/middleware.py +483 -0
  15. apsimo/api/routers/__init__.py +1 -0
  16. apsimo/api/routers/commitment_work.py +265 -0
  17. apsimo/api/routers/context_gate.py +123 -0
  18. apsimo/api/routers/executions.py +140 -0
  19. apsimo/api/routers/followup_plans.py +147 -0
  20. apsimo/api/routers/governed_actions.py +162 -0
  21. apsimo/api/routers/host.py +14473 -0
  22. apsimo/api/routers/initiative_work.py +115 -0
  23. apsimo/api/routers/mining.py +104 -0
  24. apsimo/api/routers/observations.py +110 -0
  25. apsimo/api/routers/social_state.py +225 -0
  26. apsimo/api/routers/task_queue.py +2715 -0
  27. apsimo/api/routers/temporal_followups.py +251 -0
  28. apsimo/api/routers/transport.py +110 -0
  29. apsimo/api/routers/transport_ingress_api.py +240 -0
  30. apsimo/api/schemas/__init__.py +1 -0
  31. apsimo/api/schemas/host.py +1949 -0
  32. apsimo/autonomy/cli.py +110 -0
  33. apsimo/autonomy/condition_worker.py +437 -0
  34. apsimo/autonomy/config.py +424 -0
  35. apsimo/autonomy/loop.py +4316 -0
  36. apsimo/autonomy/registry.py +339 -0
  37. apsimo/autonomy/scheduler.py +1822 -0
  38. apsimo/autonomy/synthesis.py +449 -0
  39. apsimo/backup.py +962 -0
  40. apsimo/beliefs/__init__.py +23 -0
  41. apsimo/beliefs/contradictions.py +109 -0
  42. apsimo/beliefs/decay.py +61 -0
  43. apsimo/beliefs/engine.py +479 -0
  44. apsimo/beliefs/models.py +67 -0
  45. apsimo/beliefs/promotion.py +41 -0
  46. apsimo/beliefs/resolve.py +58 -0
  47. apsimo/beliefs/source_claims.py +690 -0
  48. apsimo/beliefs/source_projection.py +883 -0
  49. apsimo/beliefs/source_time.py +208 -0
  50. apsimo/beliefs/store.py +133 -0
  51. apsimo/briefings/aggregators.py +824 -0
  52. apsimo/briefings/composer.py +420 -0
  53. apsimo/briefings/config.py +55 -0
  54. apsimo/briefings/delivery.py +439 -0
  55. apsimo/briefings/engagement.py +97 -0
  56. apsimo/briefings/engine.py +274 -0
  57. apsimo/briefings/enhancer.py +99 -0
  58. apsimo/briefings/models.py +183 -0
  59. apsimo/briefings/scheduler.py +382 -0
  60. apsimo/briefings/store.py +435 -0
  61. apsimo/chain/__init__.py +48 -0
  62. apsimo/chain/block.py +100 -0
  63. apsimo/chain/cli.py +704 -0
  64. apsimo/chain/genesis.py +443 -0
  65. apsimo/chain/identity.py +416 -0
  66. apsimo/chain/keys.py +1025 -0
  67. apsimo/chain/local_keys.py +187 -0
  68. apsimo/chain/manager.py +290 -0
  69. apsimo/chain/node.py +163 -0
  70. apsimo/chain/plugin_transactions.py +371 -0
  71. apsimo/chain/protocol.py +220 -0
  72. apsimo/chain/state_machine.py +676 -0
  73. apsimo/chain/storage.py +503 -0
  74. apsimo/chain/transactions.py +250 -0
  75. apsimo/chain/validation.py +397 -0
  76. apsimo/channels/__init__.py +1 -0
  77. apsimo/channels/manifest.py +31 -0
  78. apsimo/channels/migrations/001_channels_schema.sql +12 -0
  79. apsimo/channels/phone_gateways.py +42 -0
  80. apsimo/channels/presence.py +188 -0
  81. apsimo/channels/router.py +235 -0
  82. apsimo/channels/store.py +231 -0
  83. apsimo/cli.py +2688 -0
  84. apsimo/cognition/__init__.py +11 -0
  85. apsimo/cognition/charter.py +398 -0
  86. apsimo/cognition/drive_governance.py +3530 -0
  87. apsimo/cognition/evidence_pipeline.py +1627 -0
  88. apsimo/cognition/external_events.py +932 -0
  89. apsimo/cognition/goal_spine.py +3488 -0
  90. apsimo/cognition/introspection.py +214 -0
  91. apsimo/cognition/prompt.py +150 -0
  92. apsimo/cognition/runtime.py +108 -0
  93. apsimo/cognition/trigger.py +154 -0
  94. apsimo/commitments/__init__.py +18 -0
  95. apsimo/commitments/local_work.py +355 -0
  96. apsimo/commitments/store.py +1052 -0
  97. apsimo/commitments/work.py +91 -0
  98. apsimo/compat.py +53 -0
  99. apsimo/compression/__init__.py +467 -0
  100. apsimo/connectors/__init__.py +21 -0
  101. apsimo/connectors/base.py +152 -0
  102. apsimo/connectors/caldav_calendar.py +125 -0
  103. apsimo/connectors/fs_documents.py +85 -0
  104. apsimo/connectors/imap_email.py +138 -0
  105. apsimo/connectors/manager.py +218 -0
  106. apsimo/connectors/webhook_pull.py +88 -0
  107. apsimo/contacts/__init__.py +33 -0
  108. apsimo/contacts/comms.py +357 -0
  109. apsimo/contacts/config.py +79 -0
  110. apsimo/contacts/exporters/__init__.py +1 -0
  111. apsimo/contacts/exporters/vcard.py +71 -0
  112. apsimo/contacts/identity_links.py +251 -0
  113. apsimo/contacts/importer.py +280 -0
  114. apsimo/contacts/importers/__init__.py +1 -0
  115. apsimo/contacts/importers/batch.py +43 -0
  116. apsimo/contacts/importers/macos_contacts.py +101 -0
  117. apsimo/contacts/migrations/001_contacts_schema.sql +141 -0
  118. apsimo/contacts/migrations/002_trust_scopes.sql +36 -0
  119. apsimo/contacts/migrations/003_open_gateway_enum.sql +32 -0
  120. apsimo/contacts/migrations/004_contact_provision_operations.sql +18 -0
  121. apsimo/contacts/migrations/005_identity_links.sql +27 -0
  122. apsimo/contacts/models.py +308 -0
  123. apsimo/contacts/scoring.py +16 -0
  124. apsimo/contacts/store.py +1623 -0
  125. apsimo/contacts/transport_ingress.py +252 -0
  126. apsimo/contacts/world_bridge.py +314 -0
  127. apsimo/contextgate/__init__.py +69 -0
  128. apsimo/contextgate/chunker.py +169 -0
  129. apsimo/contextgate/estimate.py +54 -0
  130. apsimo/contextgate/gate.py +313 -0
  131. apsimo/contextgate/retrieve.py +115 -0
  132. apsimo/delivery/__init__.py +16 -0
  133. apsimo/delivery/bridge.py +1260 -0
  134. apsimo/delivery/channels.py +526 -0
  135. apsimo/delivery/classification.py +50 -0
  136. apsimo/delivery/rate_limiter.py +268 -0
  137. apsimo/delivery/reachout_policy.py +206 -0
  138. apsimo/directed/__init__.py +22 -0
  139. apsimo/directed/audit.py +167 -0
  140. apsimo/directed/intake.py +95 -0
  141. apsimo/directed/models.py +191 -0
  142. apsimo/directed/service.py +509 -0
  143. apsimo/directives/__init__.py +25 -0
  144. apsimo/directives/evidence.py +87 -0
  145. apsimo/directives/extractor.py +188 -0
  146. apsimo/directives/guard.py +364 -0
  147. apsimo/directives/models.py +206 -0
  148. apsimo/directives/service.py +372 -0
  149. apsimo/directives/store.py +167 -0
  150. apsimo/doctor.py +2173 -0
  151. apsimo/environment.py +43 -0
  152. apsimo/events/__init__.py +33 -0
  153. apsimo/events/broadcaster.py +98 -0
  154. apsimo/events/bus.py +217 -0
  155. apsimo/events/journal.py +863 -0
  156. apsimo/events/stream.py +131 -0
  157. apsimo/events/types.py +150 -0
  158. apsimo/execution_results.py +357 -0
  159. apsimo/feedback/__init__.py +5 -0
  160. apsimo/feedback/store.py +76 -0
  161. apsimo/feeds/__init__.py +19 -0
  162. apsimo/feeds/cli.py +84 -0
  163. apsimo/feeds/engine.py +437 -0
  164. apsimo/feeds/example-feed.yaml +77 -0
  165. apsimo/feeds/hermes_cron.py +126 -0
  166. apsimo/feeds/manager.py +235 -0
  167. apsimo/feeds/spec.py +250 -0
  168. apsimo/feeds/template.py +202 -0
  169. apsimo/gate/__init__.py +18 -0
  170. apsimo/gate/audit.py +61 -0
  171. apsimo/gate/communication_policy.py +166 -0
  172. apsimo/gate/config.py +72 -0
  173. apsimo/gate/context_provenance.py +170 -0
  174. apsimo/gate/env_risk.py +226 -0
  175. apsimo/gate/guard_audit.py +353 -0
  176. apsimo/gate/layers/__init__.py +1 -0
  177. apsimo/gate/layers/base.py +15 -0
  178. apsimo/gate/layers/l1_recipient.py +66 -0
  179. apsimo/gate/layers/l2_pii.py +134 -0
  180. apsimo/gate/layers/l3_cross_context.py +50 -0
  181. apsimo/gate/layers/l4_trust_tier.py +78 -0
  182. apsimo/gate/layers/l5_injection.py +199 -0
  183. apsimo/gate/layers/l6_review.py +86 -0
  184. apsimo/gate/layers/l7_delay.py +100 -0
  185. apsimo/gate/layers/tom2_epistemic.py +185 -0
  186. apsimo/gate/models.py +64 -0
  187. apsimo/gate/pending_dispatch.py +5 -0
  188. apsimo/gate/pipeline.py +206 -0
  189. apsimo/gate/rejection.py +259 -0
  190. apsimo/gate/response_guard.py +700 -0
  191. apsimo/gate/rulesets/injection_v1.yaml +51 -0
  192. apsimo/gate/surface_policy.py +189 -0
  193. apsimo/gate/taint.py +226 -0
  194. apsimo/genesis.json +9 -0
  195. apsimo/goals/__init__.py +100 -0
  196. apsimo/goals/config.py +38 -0
  197. apsimo/goals/decomposer.py +421 -0
  198. apsimo/goals/engine.py +617 -0
  199. apsimo/goals/inference.py +354 -0
  200. apsimo/goals/models.py +302 -0
  201. apsimo/goals/priority.py +270 -0
  202. apsimo/goals/queue_bridge.py +149 -0
  203. apsimo/goals/replan.py +450 -0
  204. apsimo/goals/schema.sql +89 -0
  205. apsimo/goals/store.py +692 -0
  206. apsimo/governed_actions.py +1708 -0
  207. apsimo/harness_integration/__init__.py +45 -0
  208. apsimo/harness_integration/context.py +41 -0
  209. apsimo/harness_integration/skills.py +231 -0
  210. apsimo/identity/__init__.py +26 -0
  211. apsimo/identity/participants.py +181 -0
  212. apsimo/identity/resolver.py +329 -0
  213. apsimo/identity_bootstrap/__init__.py +5 -0
  214. apsimo/identity_bootstrap/builder.py +208 -0
  215. apsimo/identity_bootstrap/corpus.py +443 -0
  216. apsimo/identity_bootstrap/models.py +54 -0
  217. apsimo/identity_bootstrap/runner.py +353 -0
  218. apsimo/identity_bootstrap/seeders/__init__.py +25 -0
  219. apsimo/identity_bootstrap/seeders/briefings.py +109 -0
  220. apsimo/identity_bootstrap/seeders/chain.py +57 -0
  221. apsimo/identity_bootstrap/seeders/goals.py +128 -0
  222. apsimo/identity_bootstrap/seeders/memory.py +191 -0
  223. apsimo/identity_bootstrap/seeders/neo4j_cognition.py +79 -0
  224. apsimo/identity_bootstrap/seeders/relationship.py +152 -0
  225. apsimo/identity_bootstrap/seeders/sessions.py +67 -0
  226. apsimo/identity_bootstrap/seeders/skills.py +92 -0
  227. apsimo/identity_bootstrap/seeders/task_queue.py +72 -0
  228. apsimo/identity_bootstrap/seeders/world_model.py +143 -0
  229. apsimo/identity_bootstrap/self_query.py +92 -0
  230. apsimo/identity_bootstrap/self_reflection.py +155 -0
  231. apsimo/identity_bootstrap/skill.py +37 -0
  232. apsimo/identity_bootstrap/verifier.py +436 -0
  233. apsimo/initiatives/__init__.py +20 -0
  234. apsimo/initiatives/action_registry.py +454 -0
  235. apsimo/initiatives/approval_authority.py +2105 -0
  236. apsimo/initiatives/approval_policy.py +123 -0
  237. apsimo/initiatives/assignment.py +263 -0
  238. apsimo/initiatives/backup_evidence.py +100 -0
  239. apsimo/initiatives/context_freshness.py +103 -0
  240. apsimo/initiatives/models.py +318 -0
  241. apsimo/initiatives/native_work.py +270 -0
  242. apsimo/initiatives/standing_approvals.py +232 -0
  243. apsimo/initiatives/store.py +1081 -0
  244. apsimo/initiatives/temporal_followup.py +410 -0
  245. apsimo/intelligence/__init__.py +1 -0
  246. apsimo/intelligence/cognition/__init__.py +24 -0
  247. apsimo/intelligence/cognition/gap_detector.py +148 -0
  248. apsimo/intelligence/cognition/metalearner.py +547 -0
  249. apsimo/intelligence/cognition/metrics_collector.py +217 -0
  250. apsimo/intelligence/cognition/performance_index.py +299 -0
  251. apsimo/intelligence/cognition/registry.py +192 -0
  252. apsimo/intelligence/cognition/strategy_adjuster.py +222 -0
  253. apsimo/intelligence/cognition/types.py +16 -0
  254. apsimo/intelligence/components/__init__.py +66 -0
  255. apsimo/intelligence/components/anomaly_detector.py +413 -0
  256. apsimo/intelligence/components/initiative_engine.py +2643 -0
  257. apsimo/intelligence/components/preference_learner.py +521 -0
  258. apsimo/intelligence/components/research_orchestrator.py +358 -0
  259. apsimo/intelligence/components/self_directed_thinker.py +221 -0
  260. apsimo/intelligence/components/self_reflector.py +252 -0
  261. apsimo/intelligence/components/session_continuity.py +154 -0
  262. apsimo/intelligence/components/task_planner.py +320 -0
  263. apsimo/intelligence/components/tool_learner.py +217 -0
  264. apsimo/intelligence/graph/__init__.py +79 -0
  265. apsimo/intelligence/graph/client.py +2483 -0
  266. apsimo/intelligence/graph/consolidator.py +405 -0
  267. apsimo/intelligence/graph/distiller.py +312 -0
  268. apsimo/intelligence/graph/migrations.py +129 -0
  269. apsimo/intelligence/graph/queries.py +248 -0
  270. apsimo/intelligence/graph/recall.py +281 -0
  271. apsimo/intelligence/graph/reconciler.py +144 -0
  272. apsimo/intelligence/graph/schema.py +337 -0
  273. apsimo/intelligence/graph/selection.py +252 -0
  274. apsimo/intelligence/learning/__init__.py +17 -0
  275. apsimo/intelligence/learning/continuous_learner.py +245 -0
  276. apsimo/intelligence/learning/feedback_store.py +321 -0
  277. apsimo/intelligence/mind_model/__init__.py +1 -0
  278. apsimo/intelligence/mind_model/graph_baseline.py +136 -0
  279. apsimo/intelligence/mind_model/signal_collector.py +361 -0
  280. apsimo/intelligence/relationships/__init__.py +11 -0
  281. apsimo/intelligence/relationships/profiler.py +389 -0
  282. apsimo/intelligence/relationships/scorer.py +560 -0
  283. apsimo/intelligence/relationships/signal_floor.py +66 -0
  284. apsimo/intelligence/relationships/trust_tiers.py +300 -0
  285. apsimo/intelligence/synthesis/__init__.py +40 -0
  286. apsimo/intelligence/synthesis/connection_discoverer.py +379 -0
  287. apsimo/intelligence/synthesis/cross_domain_analyzer.py +287 -0
  288. apsimo/intelligence/synthesis/insight_deliverer.py +171 -0
  289. apsimo/intelligence/synthesis/insight_store.py +79 -0
  290. apsimo/intelligence/synthesis/insight_validator.py +183 -0
  291. apsimo/intelligence/synthesis/novelty_scorer.py +267 -0
  292. apsimo/intelligence/turn_middleware/__init__.py +15 -0
  293. apsimo/intelligence/turn_middleware/memory_sync.py +119 -0
  294. apsimo/mcp/__init__.py +41 -0
  295. apsimo/mcp/__main__.py +6 -0
  296. apsimo/mcp/config.py +287 -0
  297. apsimo/mcp/server.py +501 -0
  298. apsimo/migrations.py +187 -0
  299. apsimo/mining/__init__.py +27 -0
  300. apsimo/mining/corpus.py +239 -0
  301. apsimo/mining/escalations.py +289 -0
  302. apsimo/mining/models.py +169 -0
  303. apsimo/mining/store.py +210 -0
  304. apsimo/models/__init__.py +30 -0
  305. apsimo/models/memory.py +80 -0
  306. apsimo/models/mesh.py +72 -0
  307. apsimo/models/person.py +104 -0
  308. apsimo/models/signal.py +108 -0
  309. apsimo/observations/__init__.py +15 -0
  310. apsimo/observations/store.py +277 -0
  311. apsimo/patterns/__init__.py +6 -0
  312. apsimo/patterns/extract.py +187 -0
  313. apsimo/patterns/store.py +227 -0
  314. apsimo/persona/__init__.py +1 -0
  315. apsimo/persona/engine.py +611 -0
  316. apsimo/persona/manifest.py +140 -0
  317. apsimo/projects/__init__.py +28 -0
  318. apsimo/projects/engine.py +1681 -0
  319. apsimo/projects/event_outbox.py +188 -0
  320. apsimo/projects/models.py +216 -0
  321. apsimo/projects/planner.py +181 -0
  322. apsimo/projects/store.py +1446 -0
  323. apsimo/proposals/__init__.py +12 -0
  324. apsimo/proposals/engine.py +114 -0
  325. apsimo/proposals/models.py +207 -0
  326. apsimo/qualification/__init__.py +1 -0
  327. apsimo/qualification/cases.py +75 -0
  328. apsimo/qualification/cli.py +51 -0
  329. apsimo/qualification/memory_cases.py +209 -0
  330. apsimo/qualification/records.py +92 -0
  331. apsimo/qualification/report.py +87 -0
  332. apsimo/qualification/runner.py +311 -0
  333. apsimo/qualification/structured_cases.py +131 -0
  334. apsimo/reasoning/__init__.py +13 -0
  335. apsimo/reasoning/executor.py +506 -0
  336. apsimo/reasoning/loop.py +373 -0
  337. apsimo/reasoning/native_tools/__init__.py +16 -0
  338. apsimo/reasoning/native_tools/calculate.py +141 -0
  339. apsimo/reasoning/native_tools/file_ops.py +150 -0
  340. apsimo/reasoning/native_tools/web_search.py +49 -0
  341. apsimo/reasoning/tool_policy.py +182 -0
  342. apsimo/redact/__init__.py +176 -0
  343. apsimo/repos/__init__.py +5 -0
  344. apsimo/repos/mirrors.py +204 -0
  345. apsimo/research/__init__.py +41 -0
  346. apsimo/research/artifact.py +482 -0
  347. apsimo/research/gatherer.py +387 -0
  348. apsimo/research/pipeline.py +513 -0
  349. apsimo/research/search/__init__.py +7 -0
  350. apsimo/research/search/base.py +41 -0
  351. apsimo/research/search/brave.py +59 -0
  352. apsimo/research/search/cache.py +51 -0
  353. apsimo/research/search/duckduckgo.py +103 -0
  354. apsimo/research/search/orchestrator.py +119 -0
  355. apsimo/research/search/serpapi.py +59 -0
  356. apsimo/research/search/tavily.py +59 -0
  357. apsimo/research/synthesizer.py +309 -0
  358. apsimo/router/__init__.py +30 -0
  359. apsimo/router/complexity_scorer.py +148 -0
  360. apsimo/router/endpoints.py +153 -0
  361. apsimo/router/fallback.py +58 -0
  362. apsimo/router/functions.py +243 -0
  363. apsimo/router/native_policy.py +52 -0
  364. apsimo/router/router.py +762 -0
  365. apsimo/router/self_learning.py +174 -0
  366. apsimo/router/tiers.py +677 -0
  367. apsimo/sandbox/__init__.py +21 -0
  368. apsimo/sandbox/backend.py +195 -0
  369. apsimo/sandbox/manager.py +173 -0
  370. apsimo/scope_bounds.py +7 -0
  371. apsimo/secrets/__init__.py +6 -0
  372. apsimo/secrets/backends/__init__.py +8 -0
  373. apsimo/secrets/backends/base.py +42 -0
  374. apsimo/secrets/backends/env.py +110 -0
  375. apsimo/secrets/backends/keyring.py +72 -0
  376. apsimo/secrets/backends/onepassword.py +232 -0
  377. apsimo/secrets/cli.py +191 -0
  378. apsimo/secrets/manager.py +160 -0
  379. apsimo/secrets/migration.py +101 -0
  380. apsimo/secrets/types.py +98 -0
  381. apsimo/seed.py +41 -0
  382. apsimo/self_model/__init__.py +37 -0
  383. apsimo/self_model/appraisals.py +673 -0
  384. apsimo/self_model/benchmark.py +1314 -0
  385. apsimo/self_model/brief.py +40 -0
  386. apsimo/self_model/event_concerns.py +1128 -0
  387. apsimo/self_model/execution_forecasts.py +353 -0
  388. apsimo/self_model/expectations.py +1595 -0
  389. apsimo/self_model/experiments.py +1150 -0
  390. apsimo/self_model/journal.py +148 -0
  391. apsimo/self_model/judgments.py +705 -0
  392. apsimo/self_model/native_outcomes.py +55 -0
  393. apsimo/self_model/params.py +220 -0
  394. apsimo/self_model/perspective.py +246 -0
  395. apsimo/self_model/reconcile.py +183 -0
  396. apsimo/self_model/reply_forecasts.py +381 -0
  397. apsimo/self_model/runtime_forecasts.py +296 -0
  398. apsimo/self_model/runtime_models.py +67 -0
  399. apsimo/self_model/settlement.py +207 -0
  400. apsimo/self_model/situation.py +1731 -0
  401. apsimo/self_model/store.py +883 -0
  402. apsimo/self_model/supervised.py +137 -0
  403. apsimo/self_model/thinker.py +99 -0
  404. apsimo/self_model/trust.py +388 -0
  405. apsimo/self_model/workspace.py +2388 -0
  406. apsimo/server.py +4197 -0
  407. apsimo/services/__init__.py +1 -0
  408. apsimo/services/agent_bridge.py +474 -0
  409. apsimo/services/initiative_executor.py +914 -0
  410. apsimo/services/instance.py +297 -0
  411. apsimo/sessions/__init__.py +22 -0
  412. apsimo/sessions/config.py +13 -0
  413. apsimo/sessions/context_loader.py +88 -0
  414. apsimo/sessions/federation_session.py +75 -0
  415. apsimo/sessions/isolated_session.py +98 -0
  416. apsimo/sessions/reports.py +84 -0
  417. apsimo/sessions/store.py +148 -0
  418. apsimo/setup.py +2818 -0
  419. apsimo/setup_hermes.py +879 -0
  420. apsimo/setup_local_work.py +218 -0
  421. apsimo/setup_native_goals.py +134 -0
  422. apsimo/setup_native_reviews.py +115 -0
  423. apsimo/skills/__init__.py +10 -0
  424. apsimo/skills/base.py +108 -0
  425. apsimo/skills/budget.py +28 -0
  426. apsimo/skills/executor.py +493 -0
  427. apsimo/skills/executors/__init__.py +1 -0
  428. apsimo/skills/executors/behavioral_correction.py +75 -0
  429. apsimo/skills/executors/capability_gap.py +38 -0
  430. apsimo/skills/executors/data_quality.py +163 -0
  431. apsimo/skills/executors/knowledge_acquisition.py +41 -0
  432. apsimo/skills/executors/operational_hygiene.py +185 -0
  433. apsimo/skills/executors/subsystem_health.py +169 -0
  434. apsimo/skills/hermes_export.py +431 -0
  435. apsimo/skills/index.py +123 -0
  436. apsimo/skills/learning/__init__.py +21 -0
  437. apsimo/skills/learning/novelty_detector.py +206 -0
  438. apsimo/skills/learning/pattern_extractor.py +199 -0
  439. apsimo/skills/learning/triggers.py +159 -0
  440. apsimo/skills/loader.py +246 -0
  441. apsimo/skills/migrations/002_progressive_loading.sql +6 -0
  442. apsimo/skills/migrations/backfill_triggers.py +20 -0
  443. apsimo/skills/models.py +202 -0
  444. apsimo/skills/packager.py +128 -0
  445. apsimo/skills/protocols.py +70 -0
  446. apsimo/skills/registry.py +191 -0
  447. apsimo/skills/runtime.py +58 -0
  448. apsimo/skills/sandbox_runner.py +229 -0
  449. apsimo/skills/scheduler.py +129 -0
  450. apsimo/skills/schema.py +79 -0
  451. apsimo/skills/security/__init__.py +12 -0
  452. apsimo/skills/security/guards.py +53 -0
  453. apsimo/skills/security/scanner.py +223 -0
  454. apsimo/skills_memory/__init__.py +26 -0
  455. apsimo/skills_memory/distill.py +159 -0
  456. apsimo/skills_memory/models.py +85 -0
  457. apsimo/skills_memory/retrieve.py +62 -0
  458. apsimo/skills_memory/store.py +172 -0
  459. apsimo/surprise/__init__.py +6 -0
  460. apsimo/surprise/accumulation.py +57 -0
  461. apsimo/surprise/scorer.py +102 -0
  462. apsimo/surprise/store.py +203 -0
  463. apsimo/task_queue/__init__.py +69 -0
  464. apsimo/task_queue/action_receipts.py +148 -0
  465. apsimo/task_queue/approval_relay_canary.py +108 -0
  466. apsimo/task_queue/config.py +85 -0
  467. apsimo/task_queue/contract.py +361 -0
  468. apsimo/task_queue/events.py +130 -0
  469. apsimo/task_queue/governor.py +1031 -0
  470. apsimo/task_queue/handlers/__init__.py +16 -0
  471. apsimo/task_queue/handlers/base.py +37 -0
  472. apsimo/task_queue/handlers/inference.py +640 -0
  473. apsimo/task_queue/handlers/monitoring.py +116 -0
  474. apsimo/task_queue/handlers/registry.py +75 -0
  475. apsimo/task_queue/handlers/subtask_handler.py +173 -0
  476. apsimo/task_queue/handlers/system_maintenance.py +147 -0
  477. apsimo/task_queue/mesh_integration.py +111 -0
  478. apsimo/task_queue/models.py +317 -0
  479. apsimo/task_queue/queue_manager.py +8286 -0
  480. apsimo/task_queue/routing.py +287 -0
  481. apsimo/task_queue/scheduler.py +252 -0
  482. apsimo/task_queue/schema.sql +197 -0
  483. apsimo/task_queue/work_control.py +342 -0
  484. apsimo/task_queue/worker.py +993 -0
  485. apsimo/telemetry.py +145 -0
  486. apsimo/tom/__init__.py +6 -0
  487. apsimo/tom/affect.py +387 -0
  488. apsimo/tom/approvals.py +171 -0
  489. apsimo/tom/arcs.py +896 -0
  490. apsimo/tom/asymmetry.py +131 -0
  491. apsimo/tom/eligibility.py +248 -0
  492. apsimo/tom/engagement.py +214 -0
  493. apsimo/tom/exposure.py +214 -0
  494. apsimo/tom/extractor.py +306 -0
  495. apsimo/tom/fact_adapters.py +144 -0
  496. apsimo/tom/facts.py +326 -0
  497. apsimo/tom/integration.py +592 -0
  498. apsimo/tom/leveled.py +118 -0
  499. apsimo/tom/levels.py +247 -0
  500. apsimo/tom/recipient_audit.py +995 -0
  501. apsimo/tom/recipient_simulator.py +593 -0
  502. apsimo/tom/source_lineage.py +93 -0
  503. apsimo/tom/tom2.py +277 -0
  504. apsimo/tom/visibility.py +559 -0
  505. apsimo/tom/visibility_store.py +414 -0
  506. apsimo/tools/__init__.py +0 -0
  507. apsimo/tools/definitions.py +740 -0
  508. apsimo/tools/handlers.py +943 -0
  509. apsimo/toolsmith/__init__.py +26 -0
  510. apsimo/toolsmith/authority.py +166 -0
  511. apsimo/toolsmith/engine.py +559 -0
  512. apsimo/toolsmith/integrity.py +100 -0
  513. apsimo/toolsmith/miner.py +145 -0
  514. apsimo/toolsmith/policy.py +110 -0
  515. apsimo/toolsmith/registry.py +635 -0
  516. apsimo/turns/__init__.py +17 -0
  517. apsimo/turns/audio.py +134 -0
  518. apsimo/turns/documents.py +235 -0
  519. apsimo/turns/executions.py +486 -0
  520. apsimo/turns/hermes_history.py +245 -0
  521. apsimo/turns/hermes_kanban.py +268 -0
  522. apsimo/turns/hermes_work.py +96 -0
  523. apsimo/turns/idempotency.py +752 -0
  524. apsimo/turns/local_work.py +115 -0
  525. apsimo/turns/media.py +581 -0
  526. apsimo/turns/reported_workers.py +196 -0
  527. apsimo/turns/source_annotations.py +283 -0
  528. apsimo/turns/source_attribution.py +154 -0
  529. apsimo/turns/source_read.py +351 -0
  530. apsimo/turns/source_vectors.py +263 -0
  531. apsimo/turns/video.py +210 -0
  532. apsimo/util/autonomy_preset.py +220 -0
  533. apsimo/util/instance.py +92 -0
  534. apsimo/util/model_output.py +25 -0
  535. apsimo/util/quiet_hours.py +27 -0
  536. apsimo/util/session_safety.py +37 -0
  537. apsimo/util/temporal.py +343 -0
  538. apsimo/vector/__init__.py +75 -0
  539. apsimo/vector/backfill.py +171 -0
  540. apsimo/vector/caption.py +114 -0
  541. apsimo/vector/collections.py +51 -0
  542. apsimo/vector/config.py +102 -0
  543. apsimo/vector/embedder.py +670 -0
  544. apsimo/vector/image_preprocess.py +406 -0
  545. apsimo/vector/image_store.py +296 -0
  546. apsimo/vector/indexes.py +162 -0
  547. apsimo/vector/migrate.py +334 -0
  548. apsimo/vector/multimodal_provider.py +417 -0
  549. apsimo/vector/multimodal_types.py +87 -0
  550. apsimo/vector/openai_provider.py +119 -0
  551. apsimo/vector/query.py +49 -0
  552. apsimo/vector/reranker.py +565 -0
  553. apsimo/vector/safety_image.py +159 -0
  554. apsimo/vector/scanner.py +197 -0
  555. apsimo/vector/setup.py +289 -0
  556. apsimo/vector/store.py +533 -0
  557. apsimo/vector/tiers.py +263 -0
  558. apsimo/work_orders.py +925 -0
  559. apsimo/workers/__init__.py +21 -0
  560. apsimo/workers/agent_bridge.py +640 -0
  561. apsimo/workers/colony_worker.py +382 -0
  562. apsimo/workers/queue_worker.py +441 -0
  563. apsimo/workers/skills_sync.py +152 -0
  564. apsimo/world_model/__init__.py +71 -0
  565. apsimo/world_model/causal_maintenance.py +131 -0
  566. apsimo/world_model/causal_policy.py +43 -0
  567. apsimo/world_model/causal_query.py +125 -0
  568. apsimo/world_model/confidence.py +54 -0
  569. apsimo/world_model/config.py +64 -0
  570. apsimo/world_model/constants.py +97 -0
  571. apsimo/world_model/entities.py +145 -0
  572. apsimo/world_model/expectation_resolvers.py +177 -0
  573. apsimo/world_model/extraction/__init__.py +7 -0
  574. apsimo/world_model/extraction/base.py +62 -0
  575. apsimo/world_model/extraction/conversation_extractor.py +262 -0
  576. apsimo/world_model/extraction/detector.py +74 -0
  577. apsimo/world_model/extraction/document_extractor.py +78 -0
  578. apsimo/world_model/extraction/formats/__init__.py +24 -0
  579. apsimo/world_model/extraction/formats/csv_fmt.py +68 -0
  580. apsimo/world_model/extraction/formats/html_fmt.py +72 -0
  581. apsimo/world_model/extraction/formats/json_fmt.py +68 -0
  582. apsimo/world_model/extraction/formats/pdf.py +43 -0
  583. apsimo/world_model/extraction/formats/text.py +27 -0
  584. apsimo/world_model/extraction/llm_extractor.py +164 -0
  585. apsimo/world_model/extraction/pipeline.py +73 -0
  586. apsimo/world_model/integrations/__init__.py +5 -0
  587. apsimo/world_model/integrations/mind_model_bridge.py +115 -0
  588. apsimo/world_model/integrations/social_intel_bridge.py +120 -0
  589. apsimo/world_model/jobs/__init__.py +4 -0
  590. apsimo/world_model/jobs/extraction_job.py +168 -0
  591. apsimo/world_model/llm_extract.py +572 -0
  592. apsimo/world_model/neo4j/__init__.py +5 -0
  593. apsimo/world_model/neo4j/backend.py +654 -0
  594. apsimo/world_model/observations.py +155 -0
  595. apsimo/world_model/populator.py +307 -0
  596. apsimo/world_model/postgres/__init__.py +1 -0
  597. apsimo/world_model/postgres/backend.py +683 -0
  598. apsimo/world_model/relationships.py +25 -0
  599. apsimo/world_model/resolution/__init__.py +13 -0
  600. apsimo/world_model/resolution/entity_resolver.py +232 -0
  601. apsimo/world_model/resolution/merge_audit.py +16 -0
  602. apsimo/world_model/resolution/merge_workflow.py +117 -0
  603. apsimo/world_model/source_reports.py +121 -0
  604. apsimo/world_model/sqlite/__init__.py +4 -0
  605. apsimo/world_model/sqlite/backend.py +855 -0
  606. apsimo/world_model/sqlite/schema.sql +132 -0
  607. apsimo/world_model/store.py +545 -0
  608. apsimo-1.3.0.dist-info/METADATA +78 -0
  609. apsimo-1.3.0.dist-info/RECORD +614 -0
  610. apsimo-1.3.0.dist-info/WHEEL +5 -0
  611. apsimo-1.3.0.dist-info/entry_points.txt +11 -0
  612. apsimo-1.3.0.dist-info/licenses/LICENSE +21 -0
  613. apsimo-1.3.0.dist-info/top_level.txt +2 -0
  614. colony_sidecar/__init__.py +4 -0
@@ -0,0 +1,358 @@
1
+ """Research Orchestrator — coordinate research across multiple sources.
2
+
3
+ Capabilities:
4
+ - Source prioritization
5
+ - Result synthesis
6
+ - Citation tracking
7
+ """
8
+
9
+ from dataclasses import dataclass, field
10
+ from datetime import datetime
11
+ from enum import Enum
12
+ from typing import Any, Awaitable, Callable, Dict, List, Optional
13
+
14
+ import logging
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class SourceType(str, Enum):
20
+ """Types of research sources."""
21
+
22
+ WEB = "web"
23
+ KNOWLEDGE_GRAPH = "knowledge_graph"
24
+ MEMORY = "memory"
25
+ API = "api"
26
+
27
+
28
+ @dataclass
29
+ class ResearchSource:
30
+ """A research source.
31
+
32
+ Attributes:
33
+ type: Category of source
34
+ name: Human-readable source name
35
+ priority: Trust/quality weight (0-1, higher = more trusted)
36
+ rate_limit: Optional requests-per-minute cap
37
+ """
38
+
39
+ type: SourceType
40
+ name: str
41
+ priority: float = 0.5
42
+ rate_limit: Optional[int] = None
43
+
44
+
45
+ @dataclass
46
+ class ResearchResult:
47
+ """A result from a single research source.
48
+
49
+ Attributes:
50
+ source: Name of the source that produced this result
51
+ content: The actual content/finding
52
+ confidence: How confident the source is (0-1)
53
+ timestamp: When the result was obtained
54
+ citations: Reference URLs or identifiers
55
+ """
56
+
57
+ source: str
58
+ content: str
59
+ confidence: float
60
+ timestamp: datetime = field(default_factory=datetime.now)
61
+ citations: List[str] = field(default_factory=list)
62
+
63
+
64
+ @dataclass
65
+ class ResearchReport:
66
+ """Aggregated research report.
67
+
68
+ Attributes:
69
+ query: The original research query
70
+ results: Individual results from each source
71
+ synthesized_summary: Combined summary across all results
72
+ confidence: Overall confidence score (average of results)
73
+ """
74
+
75
+ query: str
76
+ results: List[ResearchResult] = field(default_factory=list)
77
+ synthesized_summary: Optional[str] = None
78
+ confidence: float = 0.0
79
+
80
+
81
+ class ResearchOrchestrator:
82
+ """Orchestrate multi-source research.
83
+
84
+ Manages a registry of research sources, queries them in priority
85
+ order, synthesizes findings, and tracks citations.
86
+
87
+ Source routing:
88
+ - MEMORY: queries graph memory via ``graph.recall()``
89
+ - KNOWLEDGE_GRAPH: returns structured knowledge graph context
90
+ - WEB: queries the injected ``SearchOrchestrator`` if one is provided.
91
+ - API: delegates to a handler registered via ``register_api_handler``.
92
+
93
+ Args:
94
+ graph_client: Colony graph client for knowledge graph queries
95
+ event_bus: Colony event bus for research lifecycle events
96
+ search_orchestrator: Optional SearchOrchestrator for WEB sources.
97
+ """
98
+
99
+ def __init__(
100
+ self,
101
+ graph_client: Any = None,
102
+ event_bus: Any = None,
103
+ search_orchestrator: Any = None,
104
+ ) -> None:
105
+ self.graph = graph_client
106
+ self.events = event_bus
107
+ self.search = search_orchestrator
108
+ self._sources: List[ResearchSource] = []
109
+ self._api_handlers: Dict[str, Callable[[str], Awaitable[Optional[ResearchResult]]]] = {}
110
+
111
+ def register_api_handler(
112
+ self,
113
+ name: str,
114
+ handler: Callable[[str], Awaitable[Optional[ResearchResult]]],
115
+ ) -> None:
116
+ """Register an async handler for an API-typed source.
117
+
118
+ The handler receives the raw query string and must return a
119
+ ``ResearchResult`` or ``None``.
120
+ """
121
+ self._api_handlers[name] = handler
122
+
123
+ def register_source(self, source: ResearchSource) -> None:
124
+ """Register a research source.
125
+
126
+ Args:
127
+ source: Source to add to the registry
128
+ """
129
+ self._sources.append(source)
130
+ logger.debug("Registered research source: %s (%s)", source.name, source.type.value)
131
+
132
+ def unregister_source(self, name: str) -> None:
133
+ """Remove a research source by name.
134
+
135
+ Args:
136
+ name: Name of the source to remove
137
+ """
138
+ self._sources = [s for s in self._sources if s.name != name]
139
+
140
+ async def research(self, query: str, max_sources: int = 3) -> ResearchReport:
141
+ """Conduct research across registered sources.
142
+
143
+ Queries sources in priority order (up to max_sources),
144
+ then synthesizes results into a unified report.
145
+
146
+ Args:
147
+ query: The research question
148
+ max_sources: Maximum number of sources to query
149
+
150
+ Returns:
151
+ Aggregated research report with synthesized findings
152
+ """
153
+ sorted_sources = sorted(
154
+ self._sources,
155
+ key=lambda s: s.priority,
156
+ reverse=True,
157
+ )[:max_sources]
158
+
159
+ results: List[ResearchResult] = []
160
+ for source in sorted_sources:
161
+ result = await self._query_source(source, query)
162
+ if result:
163
+ results.append(result)
164
+
165
+ summary = await self._synthesize(results, query)
166
+
167
+ confidence = (
168
+ sum(r.confidence for r in results) / len(results) if results else 0.0
169
+ )
170
+
171
+ report = ResearchReport(
172
+ query=query,
173
+ results=results,
174
+ synthesized_summary=summary,
175
+ confidence=confidence,
176
+ )
177
+
178
+ logger.debug(
179
+ "Research complete for '%s': %d results, confidence=%.2f",
180
+ query[:50],
181
+ len(results),
182
+ confidence,
183
+ )
184
+ return report
185
+
186
+ async def _query_source(
187
+ self,
188
+ source: ResearchSource,
189
+ query: str,
190
+ ) -> Optional[ResearchResult]:
191
+ """Query a single source, routing by source type.
192
+
193
+ Returns:
194
+ A ResearchResult if the source returned data, else None.
195
+ """
196
+ try:
197
+ if source.type == SourceType.MEMORY:
198
+ return await self._query_memory_source(source, query)
199
+ elif source.type == SourceType.KNOWLEDGE_GRAPH:
200
+ return await self._query_graph_source(source, query)
201
+ elif source.type == SourceType.WEB:
202
+ return await self._query_web_source(source, query)
203
+ elif source.type == SourceType.API:
204
+ return await self._query_api_source(source, query)
205
+ except Exception as exc:
206
+ logger.warning("Source %s query failed: %s", source.name, exc)
207
+ return None
208
+
209
+ async def _query_web_source(
210
+ self,
211
+ source: ResearchSource,
212
+ query: str,
213
+ ) -> Optional[ResearchResult]:
214
+ """Query the injected SearchOrchestrator for web results."""
215
+ if self.search is None or not getattr(self.search, "has_providers", False):
216
+ return None
217
+ try:
218
+ results = await self.search.search(query, max_results=5)
219
+ if not results:
220
+ return None
221
+ parts: List[str] = []
222
+ citations: List[str] = []
223
+ for r in results[:5]:
224
+ title = getattr(r, "title", "") or ""
225
+ snippet = getattr(r, "snippet", "") or ""
226
+ if title or snippet:
227
+ parts.append(f"{title}: {snippet}" if title else snippet)
228
+ url = getattr(r, "url", "")
229
+ if url:
230
+ citations.append(url)
231
+ content = "\n".join(parts).strip()
232
+ if not content:
233
+ return None
234
+ return ResearchResult(
235
+ source=source.name,
236
+ content=content,
237
+ confidence=min(0.8, source.priority * 0.8),
238
+ citations=citations,
239
+ )
240
+ except Exception as exc:
241
+ logger.debug("Web source query failed: %s", exc)
242
+ return None
243
+
244
+ async def _query_api_source(
245
+ self,
246
+ source: ResearchSource,
247
+ query: str,
248
+ ) -> Optional[ResearchResult]:
249
+ """Dispatch to a registered API handler (if any) for this source."""
250
+ handler = self._api_handlers.get(source.name)
251
+ if handler is None:
252
+ return None
253
+ try:
254
+ return await handler(query)
255
+ except Exception as exc:
256
+ logger.debug("API source '%s' handler failed: %s", source.name, exc)
257
+ return None
258
+
259
+ async def _query_memory_source(
260
+ self,
261
+ source: ResearchSource,
262
+ query: str,
263
+ ) -> Optional[ResearchResult]:
264
+ """Query graph memory for relevant information."""
265
+ try:
266
+ memories = await self.graph.recall(query, limit=5)
267
+ if not memories:
268
+ return None
269
+ # Combine up to 3 most relevant memories into one result
270
+ content = "\n".join(
271
+ m.get("content", "") if isinstance(m, dict) else str(m)
272
+ for m in memories[:3]
273
+ if m
274
+ )
275
+ if not content.strip():
276
+ return None
277
+ return ResearchResult(
278
+ source=source.name,
279
+ content=content,
280
+ confidence=min(0.9, source.priority * 0.9),
281
+ )
282
+ except Exception as exc:
283
+ logger.debug("Memory source query failed: %s", exc)
284
+ return None
285
+
286
+ async def _query_graph_source(
287
+ self,
288
+ source: ResearchSource,
289
+ query: str,
290
+ ) -> Optional[ResearchResult]:
291
+ """Query the knowledge graph using entity and type recall.
292
+
293
+ Runs RECALL_BY_ENTITY (treating the query as an entity name) and
294
+ RECALL_BY_TYPE (treating the query as a memory type) against Neo4j,
295
+ then combines up to 3 results into a single ResearchResult.
296
+ """
297
+ from apsimo.intelligence.graph.queries import RECALL_BY_ENTITY, RECALL_BY_TYPE
298
+
299
+ try:
300
+ memories: List[Any] = []
301
+ async with self.graph.driver.session(database=self.graph.database) as session:
302
+ entity_result = await session.run(
303
+ RECALL_BY_ENTITY,
304
+ entity_name=query,
305
+ min_strength=0.1,
306
+ limit=5,
307
+ )
308
+ memories.extend([record["memory"] async for record in entity_result])
309
+
310
+ type_result = await session.run(
311
+ RECALL_BY_TYPE,
312
+ memory_type=query,
313
+ min_strength=0.1,
314
+ limit=5,
315
+ )
316
+ memories.extend([record["memory"] async for record in type_result])
317
+
318
+ if not memories:
319
+ return None
320
+
321
+ content = "\n".join(
322
+ m.get("content", "") if isinstance(m, dict) else str(m)
323
+ for m in memories[:3]
324
+ if m
325
+ )
326
+ if not content.strip():
327
+ return None
328
+
329
+ return ResearchResult(
330
+ source=source.name,
331
+ content=content,
332
+ confidence=min(0.85, source.priority * 0.85),
333
+ )
334
+ except Exception as exc:
335
+ logger.debug("Graph source query failed: %s", exc)
336
+ return None
337
+
338
+ async def _synthesize(
339
+ self,
340
+ results: List[ResearchResult],
341
+ query: str,
342
+ ) -> Optional[str]:
343
+ """Synthesize results into a coherent summary.
344
+
345
+ Combines the top results (by confidence) into a single narrative,
346
+ prefixed with a summary header.
347
+ """
348
+ if not results:
349
+ return None
350
+
351
+ sorted_results = sorted(results, key=lambda r: r.confidence, reverse=True)
352
+ parts = [f"Research summary for '{query[:60]}' — {len(results)} source(s):"]
353
+ for r in sorted_results[:3]:
354
+ if r.content:
355
+ excerpt = r.content[:200].replace("\n", " ")
356
+ parts.append(f"[{r.source}] {excerpt}")
357
+
358
+ return "\n".join(parts)
@@ -0,0 +1,221 @@
1
+ """Self-directed thinking — Colony's internal cognition phase (v0.17.0).
2
+
3
+ The InitiativeEngine's generators are data-reactive: they scan existing
4
+ graph state for known shapes of work (stale goals, neglected contacts,
5
+ failing CI). The thinker is the complement: on a slow cadence it hands
6
+ an LLM a snapshot of Colony's situation — goals, commitments, recent
7
+ work, capability gaps, open questions — and asks what work SHOULD exist
8
+ that nothing has surfaced yet. Output becomes ordinary initiatives that
9
+ flow through the same store, delivery, and approval machinery as
10
+ everything else.
11
+
12
+ Safety posture:
13
+
14
+ - Disabled unless ``COLONY_ENABLE_INTERNAL_THINKING=true``.
15
+ - Runs at most once per ``COLONY_THINKING_INTERVAL_SECS`` (default 1h),
16
+ not every loop tick.
17
+ - At most ``COLONY_THINKING_MAX_INITIATIVES`` (default 3) per cycle.
18
+ - Proposals are capped at priority 0.85 and may never carry an
19
+ ``action_hint`` — thought-up work always lands as review/decide
20
+ initiatives, never as directly executable agent actions. Anything
21
+ mutating or outbound therefore still crosses the action registry and
22
+ the owner-approval gate before an agent can touch it.
23
+ - Dedup keys remember recent proposals so repeated thinking cycles
24
+ don't re-pitch the same idea.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import json
30
+ import logging
31
+ import os
32
+ import re
33
+ import time
34
+ import uuid
35
+ from typing import Any, Dict, List, Optional
36
+
37
+ from apsimo.intelligence.components.initiative_engine import (
38
+ Initiative,
39
+ InitiativeType,
40
+ )
41
+
42
+ logger = logging.getLogger(__name__)
43
+
44
+ # Types the LLM may propose. Deliberately excludes AGENT_ACTION (and
45
+ # anything else that auto-executes) — see module docstring.
46
+ _ALLOWED_TYPES = {
47
+ "research": InitiativeType.RESEARCH,
48
+ "task": InitiativeType.TASK,
49
+ "project": InitiativeType.PROJECT,
50
+ "system": InitiativeType.SYSTEM,
51
+ "knowledge_acquisition": InitiativeType.KNOWLEDGE_ACQUISITION,
52
+ "capability_gap": InitiativeType.CAPABILITY_GAP,
53
+ }
54
+
55
+ _MAX_PRIORITY = 0.85
56
+ _RECENT_KEY_CAP = 200
57
+
58
+
59
+ class SelfDirectedThinker:
60
+ """Generates novel initiatives from periodic LLM reflection.
61
+
62
+ Prompted through the shared cognition charter (role "thinker"); the
63
+ output schema REQUIRES per-item confidence (trust-engine calibration
64
+ input) and grounding evidence (items without it are dropped).
65
+ """
66
+
67
+ def __init__(self, router: Any,
68
+ interval_secs: Optional[int] = None,
69
+ max_per_cycle: Optional[int] = None,
70
+ self_brief_fn: Any = None,
71
+ boundaries_fn: Any = None) -> None:
72
+ self._router = router
73
+ self._interval = interval_secs if interval_secs is not None else int(
74
+ os.environ.get("COLONY_THINKING_INTERVAL_SECS", "3600"))
75
+ self._max = max_per_cycle if max_per_cycle is not None else int(
76
+ os.environ.get("COLONY_THINKING_MAX_INITIATIVES", "3"))
77
+ # Optional zero-arg callables -> str, evaluated fresh each cycle.
78
+ self._self_brief_fn = self_brief_fn
79
+ self._boundaries_fn = boundaries_fn
80
+ self._last_run: Optional[float] = None
81
+ self._recent_keys: List[str] = []
82
+
83
+ # -- scheduling ---------------------------------------------------------
84
+
85
+ def due(self, now: Optional[float] = None) -> bool:
86
+ now = now if now is not None else time.monotonic()
87
+ if self._last_run is None:
88
+ return True
89
+ return (now - self._last_run) >= self._interval
90
+
91
+ def mark_ran(self, now: Optional[float] = None) -> None:
92
+ self._last_run = now if now is not None else time.monotonic()
93
+
94
+ # -- thinking -----------------------------------------------------------
95
+
96
+ async def think(self, situation: Dict[str, Any]) -> List[Initiative]:
97
+ """One reflection cycle: situation report in, initiatives out."""
98
+ if self._router is None:
99
+ return []
100
+ prompt = self._build_messages(situation)
101
+ try:
102
+ response = await self._router.complete(
103
+ prompt, context={"task": "internal_thinking"})
104
+ content = getattr(response, "content", "") or ""
105
+ except Exception as exc:
106
+ logger.warning("Self-directed thinking LLM call failed: %s", exc)
107
+ return []
108
+
109
+ initiatives: List[Initiative] = []
110
+ for item in self._parse(content)[: self._max]:
111
+ initiative = self._to_initiative(item)
112
+ if initiative is None:
113
+ continue
114
+ if initiative.dedup_key in self._recent_keys:
115
+ logger.debug("Thinking dedup: %s", initiative.dedup_key)
116
+ continue
117
+ self._remember(initiative.dedup_key)
118
+ initiatives.append(initiative)
119
+ if initiatives:
120
+ logger.info("Self-directed thinking produced %d initiative(s): %s",
121
+ len(initiatives),
122
+ "; ".join(i.description[:60] for i in initiatives))
123
+ return initiatives
124
+
125
+ # -- internals ----------------------------------------------------------
126
+
127
+ def _build_messages(self, situation: Dict[str, Any]) -> List[dict]:
128
+ sections = []
129
+ for name, value in situation.items():
130
+ if value in (None, "", [], {}):
131
+ continue
132
+ try:
133
+ rendered = json.dumps(value, default=str, indent=None)[:4000]
134
+ except (TypeError, ValueError):
135
+ rendered = str(value)[:4000]
136
+ sections.append(f"## {name}\n{rendered}")
137
+ report = "\n\n".join(sections) or "(no situation data available)"
138
+
139
+ def _call(fn) -> str:
140
+ if fn is None:
141
+ return ""
142
+ try:
143
+ return str(fn() or "")
144
+ except Exception:
145
+ return ""
146
+
147
+ from apsimo.cognition.charter import build_system_prompt
148
+ system = build_system_prompt(
149
+ "thinker",
150
+ self_brief=_call(self._self_brief_fn) or None,
151
+ boundaries=_call(self._boundaries_fn) or None,
152
+ max_items=self._max, allowed=sorted(_ALLOWED_TYPES))
153
+ return [
154
+ {"role": "system", "content": system},
155
+ {"role": "user", "content": f"SITUATION REPORT\n\n{report}"},
156
+ ]
157
+
158
+ @staticmethod
159
+ def _parse(content: str) -> List[dict]:
160
+ text = content.strip()
161
+ # Tolerate markdown fences and leading prose despite instructions.
162
+ if "```" in text:
163
+ match = re.search(r"```(?:json)?\s*(.*?)```", text, re.DOTALL)
164
+ if match:
165
+ text = match.group(1).strip()
166
+ if not text.startswith("["):
167
+ match = re.search(r"\[.*\]", text, re.DOTALL)
168
+ if not match:
169
+ return []
170
+ text = match.group(0)
171
+ try:
172
+ data = json.loads(text)
173
+ except json.JSONDecodeError:
174
+ logger.warning("Thinking output was not valid JSON; dropping")
175
+ return []
176
+ return [d for d in data if isinstance(d, dict)] if isinstance(data, list) else []
177
+
178
+ def _to_initiative(self, item: dict) -> Optional[Initiative]:
179
+ title = str(item.get("title", "")).strip()
180
+ type_key = str(item.get("type", "")).strip().lower()
181
+ rationale = str(item.get("rationale", "")).strip()
182
+ evidence = str(item.get("evidence", "")).strip()
183
+ if not title or type_key not in _ALLOWED_TYPES:
184
+ return None
185
+ # Charter contract: a proposal without grounding evidence does not
186
+ # ship (the schema demands the report lines that ground it).
187
+ if not evidence:
188
+ logger.debug("Thinking item dropped (no grounding evidence): %s",
189
+ title[:60])
190
+ return None
191
+ try:
192
+ priority = float(item.get("priority", 0.5))
193
+ except (TypeError, ValueError):
194
+ priority = 0.5
195
+ priority = max(0.0, min(_MAX_PRIORITY, priority))
196
+ try:
197
+ confidence = max(0.0, min(1.0, float(item.get("confidence", 0.5))))
198
+ except (TypeError, ValueError):
199
+ confidence = 0.5
200
+ slug = re.sub(r"[^a-z0-9]+", "-", title.lower()).strip("-")[:60]
201
+ return Initiative(
202
+ id=f"init-think-{uuid.uuid4().hex[:12]}",
203
+ type=_ALLOWED_TYPES[type_key],
204
+ description=title,
205
+ priority=priority,
206
+ rationale=f"[self-directed thinking] {rationale}" if rationale
207
+ else "[self-directed thinking]",
208
+ action_hint=None, # never directly executable (see docstring)
209
+ dedup_key=f"thinking:{slug}",
210
+ # Stated confidence + evidence ride along for trust-engine
211
+ # calibration (stated vs realized) and provenance.
212
+ trigger_data={"stated_confidence": confidence,
213
+ "evidence": evidence[:400]},
214
+ )
215
+
216
+ def _remember(self, key: Optional[str]) -> None:
217
+ if not key:
218
+ return
219
+ self._recent_keys.append(key)
220
+ if len(self._recent_keys) > _RECENT_KEY_CAP:
221
+ self._recent_keys = self._recent_keys[-_RECENT_KEY_CAP:]