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,883 @@
1
+ """CompetenceStore -- per-domain outcome counts + latency, from real work.
2
+
3
+ Domains are capability classes: initiative types ("research", "follow_up"),
4
+ "project" (project steps/outcomes), "directed" (directed-action audits),
5
+ "delivery" (real pushes), and worker job types (item 5). Every recording site
6
+ is a completion/failure chokepoint that already exists; the store never polls.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import hashlib
12
+ import json
13
+ import logging
14
+ import math
15
+ import os
16
+ import sqlite3
17
+ import threading
18
+ import time
19
+ from typing import Any, Dict, Iterable, List, Optional, Tuple
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+ # Exponential moving average weight for latency (recent work dominates).
24
+ _EWMA_ALPHA = 0.3
25
+
26
+ _OUTCOMES = ("success", "failure", "timeout")
27
+
28
+ _DISPOSITIONS = ("invalidate", "replace")
29
+
30
+
31
+ def self_model_enabled() -> bool:
32
+ return os.environ.get("COLONY_SELF_MODEL_ENABLED", "true").strip().lower() != "false"
33
+
34
+
35
+ def _norm_outcome(outcome: str) -> str:
36
+ o = (outcome or "").strip().lower()
37
+ if o in ("success", "completed", "clean", "actioned", "done", "ok"):
38
+ return "success"
39
+ if o in ("timeout", "timed_out"):
40
+ return "timeout"
41
+ return "failure"
42
+
43
+
44
+ def _canonical_json(value: Any) -> str:
45
+ return json.dumps(value, sort_keys=True, separators=(",", ":"),
46
+ ensure_ascii=True)
47
+
48
+
49
+ def event_fingerprint(event: Dict[str, Any]) -> str:
50
+ """Stable identity for one immutable raw competence event.
51
+
52
+ The reconciliation tool requires this fingerprint as well as the row id,
53
+ so a plan made against one database cannot silently target a different
54
+ event after a restore or migration.
55
+ """
56
+ evidence = event.get("evidence")
57
+ if isinstance(evidence, str):
58
+ try:
59
+ evidence = json.loads(evidence)
60
+ except ValueError:
61
+ pass
62
+ payload = {
63
+ "id": int(event["id"]),
64
+ "domain": str(event["domain"]),
65
+ "outcome": str(event["outcome"]),
66
+ "shadow": int(event.get("shadow") or 0),
67
+ "violation": int(event.get("violation") or 0),
68
+ "stated_confidence": event.get("stated_confidence"),
69
+ "source": event.get("source") or "legacy_unattributed",
70
+ "source_ref": event.get("source_ref"),
71
+ "evidence_status": (
72
+ event.get("evidence_status") or "legacy_unattributed"),
73
+ "outcome_contract": (
74
+ event.get("outcome_contract") or "legacy.unversioned"),
75
+ "evidence": evidence,
76
+ "ts": float(event["ts"]),
77
+ }
78
+ return hashlib.sha256(_canonical_json(payload).encode("utf-8")).hexdigest()
79
+
80
+
81
+ def _ledger_id(payload: Dict[str, Any]) -> str:
82
+ digest = hashlib.sha256(
83
+ _canonical_json(payload).encode("utf-8")).hexdigest()
84
+ return f"cr_{digest[:32]}"
85
+
86
+
87
+ class CompetenceStore:
88
+ """SQLite persistence of per-domain success/failure/timeout + ewma latency."""
89
+
90
+ def __init__(self, db_path: Optional[str] = None) -> None:
91
+ self._lock = threading.RLock()
92
+ self._conn = sqlite3.connect(str(db_path) if db_path else ":memory:",
93
+ check_same_thread=False)
94
+ self._conn.row_factory = sqlite3.Row
95
+ with self._lock:
96
+ self._conn.execute(
97
+ """CREATE TABLE IF NOT EXISTS competence (
98
+ domain TEXT PRIMARY KEY,
99
+ success INTEGER DEFAULT 0,
100
+ failure INTEGER DEFAULT 0,
101
+ timeout INTEGER DEFAULT 0,
102
+ ewma_latency_secs REAL,
103
+ last_outcome TEXT,
104
+ last_outcome_at REAL
105
+ )""")
106
+ # Per-event log for windowed circuit-breaker queries and
107
+ # calibration-vs-real evidence separation (shadow=1 events are
108
+ # calibration runs; they graduate a class out of shadow but never
109
+ # count toward act-first confidence).
110
+ self._conn.execute(
111
+ """CREATE TABLE IF NOT EXISTS competence_events (
112
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
113
+ domain TEXT NOT NULL, outcome TEXT NOT NULL,
114
+ shadow INTEGER DEFAULT 0, violation INTEGER DEFAULT 0,
115
+ stated_confidence REAL,
116
+ source TEXT,
117
+ source_ref TEXT,
118
+ event_key TEXT,
119
+ evidence_status TEXT,
120
+ outcome_contract TEXT,
121
+ evidence TEXT,
122
+ ts REAL NOT NULL
123
+ )""")
124
+ # Additive migrations: old events stay byte-for-byte intact and
125
+ # are explicitly labelled legacy_unattributed on the read side.
126
+ try:
127
+ cols = {r[1] for r in self._conn.execute(
128
+ "PRAGMA table_info(competence_events)").fetchall()}
129
+ additions = {
130
+ "stated_confidence": "REAL",
131
+ "source": "TEXT",
132
+ "source_ref": "TEXT",
133
+ "event_key": "TEXT",
134
+ "evidence_status": "TEXT",
135
+ "outcome_contract": "TEXT",
136
+ "evidence": "TEXT",
137
+ }
138
+ for name, sql_type in additions.items():
139
+ if name in cols:
140
+ continue
141
+ self._conn.execute(
142
+ "ALTER TABLE competence_events "
143
+ f"ADD COLUMN {name} {sql_type}")
144
+ except Exception:
145
+ pass
146
+ self._conn.execute(
147
+ "CREATE INDEX IF NOT EXISTS idx_cev_domain_ts "
148
+ "ON competence_events(domain, ts)")
149
+ self._conn.execute(
150
+ "CREATE UNIQUE INDEX IF NOT EXISTS idx_cev_source_event_key "
151
+ "ON competence_events(source, event_key) "
152
+ "WHERE event_key IS NOT NULL")
153
+ # This is the sole correction mechanism. Rows are append-only;
154
+ # the raw event and the all-time aggregate are never rewritten.
155
+ self._conn.execute(
156
+ """CREATE TABLE IF NOT EXISTS competence_reconciliations (
157
+ seq INTEGER PRIMARY KEY AUTOINCREMENT,
158
+ reconciliation_id TEXT NOT NULL UNIQUE,
159
+ kind TEXT NOT NULL,
160
+ event_id INTEGER,
161
+ target_fingerprint TEXT,
162
+ disposition TEXT,
163
+ replacement_outcome TEXT,
164
+ domain TEXT,
165
+ since_ts REAL,
166
+ until_ts REAL,
167
+ supersedes TEXT,
168
+ reason TEXT NOT NULL,
169
+ provenance TEXT NOT NULL,
170
+ created_by TEXT NOT NULL,
171
+ created_at REAL NOT NULL,
172
+ CHECK (kind IN ('event_correction', 'evidence_gap',
173
+ 'gap_resolution')),
174
+ CHECK (disposition IS NULL OR
175
+ disposition IN ('invalidate', 'replace'))
176
+ )""")
177
+ self._conn.execute(
178
+ "CREATE UNIQUE INDEX IF NOT EXISTS idx_crec_supersedes "
179
+ "ON competence_reconciliations(supersedes) "
180
+ "WHERE supersedes IS NOT NULL")
181
+ self._conn.execute(
182
+ "CREATE INDEX IF NOT EXISTS idx_crec_event "
183
+ "ON competence_reconciliations(event_id, seq)")
184
+ self._conn.execute(
185
+ "CREATE INDEX IF NOT EXISTS idx_crec_gap "
186
+ "ON competence_reconciliations(domain, since_ts, until_ts)")
187
+ self._conn.execute(
188
+ """CREATE TRIGGER IF NOT EXISTS trg_crec_no_update
189
+ BEFORE UPDATE ON competence_reconciliations
190
+ BEGIN
191
+ SELECT RAISE(ABORT, 'competence ledger is append-only');
192
+ END""")
193
+ self._conn.execute(
194
+ """CREATE TRIGGER IF NOT EXISTS trg_crec_no_delete
195
+ BEFORE DELETE ON competence_reconciliations
196
+ BEGIN
197
+ SELECT RAISE(ABORT, 'competence ledger is append-only');
198
+ END""")
199
+ self._conn.commit()
200
+
201
+ def close(self) -> None:
202
+ with self._lock:
203
+ self._conn.close()
204
+
205
+ def record(self, domain: str, outcome: str,
206
+ latency_secs: Optional[float] = None,
207
+ shadow: bool = False, violation: bool = False,
208
+ stated_confidence: Optional[float] = None, *,
209
+ source: Optional[str] = None,
210
+ source_ref: Optional[str] = None,
211
+ event_key: Optional[str] = None,
212
+ evidence_status: str = "observed",
213
+ outcome_contract: str = "legacy.unversioned",
214
+ evidence: Optional[Dict[str, Any]] = None) -> bool:
215
+ """Record one outcome for a domain. Never raises.
216
+
217
+ stated_confidence: the confidence the model STATED before doing the
218
+ work (charter contract); stored per event so calibration (stated vs
219
+ realized) is measurable and autonomy is earned against it.
220
+ """
221
+ try:
222
+ domain = str(domain or "unknown").strip().lower()
223
+ outcome = _norm_outcome(str(outcome or ""))
224
+ source = str(source or "legacy_callsite").strip()[:128]
225
+ source_ref = str(source_ref or "").strip()[:512] or None
226
+ event_key = str(event_key or "").strip()[:512] or None
227
+ evidence_status = str(
228
+ evidence_status or "observed").strip().lower()
229
+ if evidence_status not in {
230
+ "verified", "observed", "unverified",
231
+ "legacy_unattributed"}:
232
+ evidence_status = "unverified"
233
+ outcome_contract = str(
234
+ outcome_contract or "legacy.unversioned").strip()[:128]
235
+ evidence_json = _canonical_json(evidence) if evidence else None
236
+ except Exception as exc:
237
+ logger.debug("competence record normalization failed: %s", exc)
238
+ return False
239
+ now = time.time()
240
+ try:
241
+ with self._lock:
242
+ if event_key is not None:
243
+ duplicate = self._conn.execute(
244
+ """SELECT 1 FROM competence_events
245
+ WHERE source = ? AND event_key = ? LIMIT 1""",
246
+ (source, event_key),
247
+ ).fetchone()
248
+ if duplicate is not None:
249
+ return False
250
+ row = self._conn.execute(
251
+ "SELECT ewma_latency_secs FROM competence WHERE domain=?",
252
+ (domain,)).fetchone()
253
+ ewma = row["ewma_latency_secs"] if row else None
254
+ if latency_secs is not None:
255
+ ewma = (float(latency_secs) if ewma is None else
256
+ _EWMA_ALPHA * float(latency_secs) + (1 - _EWMA_ALPHA) * float(ewma))
257
+ self._conn.execute(
258
+ f"""INSERT INTO competence
259
+ (domain, {outcome}, ewma_latency_secs, last_outcome, last_outcome_at)
260
+ VALUES (?, 1, ?, ?, ?)
261
+ ON CONFLICT(domain) DO UPDATE SET
262
+ {outcome}={outcome}+1, ewma_latency_secs=?,
263
+ last_outcome=?, last_outcome_at=?""",
264
+ (domain, ewma, outcome, now, ewma, outcome, now))
265
+ self._conn.execute(
266
+ "INSERT INTO competence_events (domain, outcome, shadow, "
267
+ "violation, stated_confidence, source, source_ref, event_key, "
268
+ "evidence_status, outcome_contract, evidence, ts) "
269
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
270
+ (domain, outcome, 1 if shadow else 0,
271
+ 1 if violation else 0, stated_confidence, source,
272
+ source_ref, event_key, evidence_status, outcome_contract,
273
+ evidence_json, now))
274
+ # Do not prune evidence here. The reconciliation ledger must
275
+ # remain auditable against the immutable event it targets.
276
+ self._conn.commit()
277
+ return True
278
+ except Exception as exc:
279
+ # The aggregate update and immutable event insert are one logical
280
+ # evidence write. Never leave an uncommitted aggregate increment
281
+ # that a later, unrelated record could accidentally commit.
282
+ try:
283
+ with self._lock:
284
+ self._conn.rollback()
285
+ except Exception:
286
+ logger.debug(
287
+ "competence rollback failed for %s", domain,
288
+ exc_info=True,
289
+ )
290
+ logger.debug("competence record failed for %s: %s", domain, exc)
291
+ return False
292
+
293
+ def has_event_key(self, source: str, event_key: str) -> bool:
294
+ """Whether one stable evidence event was already folded."""
295
+
296
+ source_value = str(source or "").strip()[:128]
297
+ key_value = str(event_key or "").strip()[:512]
298
+ if not source_value or not key_value:
299
+ return False
300
+ with self._lock:
301
+ row = self._conn.execute(
302
+ """SELECT 1 FROM competence_events
303
+ WHERE source = ? AND event_key = ? LIMIT 1""",
304
+ (source_value, key_value),
305
+ ).fetchone()
306
+ return row is not None
307
+
308
+ # -- append-only reconciliation -------------------------------------
309
+ @staticmethod
310
+ def _decode_json(value: Any) -> Any:
311
+ if value in (None, ""):
312
+ return None
313
+ try:
314
+ return json.loads(str(value))
315
+ except (TypeError, ValueError):
316
+ return value
317
+
318
+ def _ledger_rows(self) -> List[Dict[str, Any]]:
319
+ rows = self._conn.execute(
320
+ "SELECT * FROM competence_reconciliations ORDER BY seq ASC"
321
+ ).fetchall()
322
+ out = []
323
+ for raw in rows:
324
+ row = dict(raw)
325
+ row["provenance"] = self._decode_json(row.get("provenance"))
326
+ out.append(row)
327
+ return out
328
+
329
+ def reconciliation_ledger(self) -> List[Dict[str, Any]]:
330
+ """The immutable correction/gap audit trail, oldest first."""
331
+ with self._lock:
332
+ return self._ledger_rows()
333
+
334
+ @staticmethod
335
+ def _effective_corrections(
336
+ rows: Iterable[Dict[str, Any]]) -> Dict[int, Dict[str, Any]]:
337
+ effective: Dict[int, Dict[str, Any]] = {}
338
+ for row in rows:
339
+ if row.get("kind") != "event_correction":
340
+ continue
341
+ event_id = row.get("event_id")
342
+ if event_id is not None:
343
+ effective[int(event_id)] = row
344
+ return effective
345
+
346
+ def active_evidence_gaps(
347
+ self, domain: Optional[str] = None,
348
+ since: Optional[float] = None,
349
+ until: Optional[float] = None) -> List[Dict[str, Any]]:
350
+ """Unresolved half-open windows whose events cannot be trusted.
351
+
352
+ A gap is deliberately broader than an event invalidation: it says the
353
+ available provenance cannot identify the contaminated rows exactly.
354
+ Consumers must report the affected metric unavailable, not guess.
355
+ """
356
+ wanted = (domain or "").strip().lower() or None
357
+ with self._lock:
358
+ ledger = self._ledger_rows()
359
+ resolved = {
360
+ str(r.get("supersedes")) for r in ledger
361
+ if r.get("kind") == "gap_resolution" and r.get("supersedes")
362
+ }
363
+ out = []
364
+ for row in ledger:
365
+ if row.get("kind") != "evidence_gap":
366
+ continue
367
+ if row["reconciliation_id"] in resolved:
368
+ continue
369
+ if wanted is not None and row.get("domain") != wanted:
370
+ continue
371
+ gap_since = float(row["since_ts"])
372
+ gap_until = float(row["until_ts"])
373
+ if since is not None and gap_until <= float(since):
374
+ continue
375
+ if until is not None and gap_since >= float(until):
376
+ continue
377
+ out.append(row)
378
+ return out
379
+
380
+ def reconciliation_revision(
381
+ self, domains: Optional[Iterable[str]] = None,
382
+ since: Optional[float] = None,
383
+ until: Optional[float] = None) -> int:
384
+ """Highest ledger sequence affecting the requested evidence slice."""
385
+ wanted = None
386
+ if domains is not None:
387
+ wanted = {(d or "").strip().lower() for d in domains if d}
388
+ with self._lock:
389
+ ledger = self._ledger_rows()
390
+ events = {int(r["id"]): dict(r) for r in self._conn.execute(
391
+ "SELECT id, domain, ts FROM competence_events").fetchall()}
392
+ gaps = {r["reconciliation_id"]: r for r in ledger
393
+ if r.get("kind") == "evidence_gap"}
394
+
395
+ def in_slice(domain: str, start: float, end: float) -> bool:
396
+ if wanted is not None and domain not in wanted:
397
+ return False
398
+ if since is not None and end <= float(since):
399
+ return False
400
+ if until is not None and start >= float(until):
401
+ return False
402
+ return True
403
+
404
+ def point_in_slice(domain: str, ts: float) -> bool:
405
+ if wanted is not None and domain not in wanted:
406
+ return False
407
+ if since is not None and ts < float(since):
408
+ return False
409
+ if until is not None and ts >= float(until):
410
+ return False
411
+ return True
412
+
413
+ revision = 0
414
+ for row in ledger:
415
+ kind = row.get("kind")
416
+ if kind == "event_correction":
417
+ ev = events.get(int(row.get("event_id") or -1))
418
+ if ev and point_in_slice(
419
+ str(ev["domain"]), float(ev["ts"])):
420
+ revision = max(revision, int(row["seq"]))
421
+ elif kind == "evidence_gap":
422
+ if in_slice(str(row["domain"]), float(row["since_ts"]),
423
+ float(row["until_ts"])):
424
+ revision = max(revision, int(row["seq"]))
425
+ elif kind == "gap_resolution":
426
+ gap = gaps.get(str(row.get("supersedes") or ""))
427
+ if gap and in_slice(str(gap["domain"]),
428
+ float(gap["since_ts"]),
429
+ float(gap["until_ts"])):
430
+ revision = max(revision, int(row["seq"]))
431
+ return revision
432
+
433
+ def events(self, domain: str, since: Optional[float] = None,
434
+ include_shadow: bool = True, *,
435
+ until: Optional[float] = None,
436
+ include_invalid: bool = False,
437
+ include_unavailable: bool = False,
438
+ limit: Optional[int] = None) -> List[Dict[str, Any]]:
439
+ """Effective events for a domain, newest first.
440
+
441
+ Exact invalidations/replacements and unresolved evidence gaps are
442
+ applied at read time. Raw rows are never updated. There is no default
443
+ limit: weekly scorecards must not silently truncate a busy domain at
444
+ the old 1,000-event ceiling.
445
+ """
446
+ domain = (domain or "").strip().lower()
447
+ q = "SELECT * FROM competence_events WHERE domain=?"
448
+ params: List[Any] = [domain]
449
+ if since is not None:
450
+ q += " AND ts >= ?"
451
+ params.append(float(since))
452
+ if until is not None:
453
+ q += " AND ts < ?"
454
+ params.append(float(until))
455
+ if not include_shadow:
456
+ q += " AND shadow=0"
457
+ q += " ORDER BY ts DESC, id DESC"
458
+ if limit is not None:
459
+ q += " LIMIT ?"
460
+ params.append(max(1, int(limit)))
461
+ with self._lock:
462
+ raw_rows = [dict(r) for r in self._conn.execute(q, params).fetchall()]
463
+ ledger = self._ledger_rows()
464
+ corrections = self._effective_corrections(ledger)
465
+ gaps = self.active_evidence_gaps(domain, since=since, until=until)
466
+ out: List[Dict[str, Any]] = []
467
+ for row in raw_rows:
468
+ row["source"] = row.get("source") or "legacy_unattributed"
469
+ row["evidence_status"] = (
470
+ row.get("evidence_status") or "legacy_unattributed")
471
+ row["outcome_contract"] = (
472
+ row.get("outcome_contract") or "legacy.unversioned")
473
+ row["evidence"] = self._decode_json(row.get("evidence"))
474
+ row["fingerprint"] = event_fingerprint(row)
475
+ row["valid"] = True
476
+ row["evidence_available"] = True
477
+ correction = corrections.get(int(row["id"]))
478
+ if correction is not None:
479
+ row["recorded_outcome"] = row["outcome"]
480
+ row["reconciliation"] = {
481
+ "id": correction["reconciliation_id"],
482
+ "disposition": correction["disposition"],
483
+ "reason": correction["reason"],
484
+ "provenance": correction["provenance"],
485
+ "created_by": correction["created_by"],
486
+ "created_at": correction["created_at"],
487
+ }
488
+ if correction["disposition"] == "invalidate":
489
+ row["valid"] = False
490
+ else:
491
+ row["outcome"] = correction["replacement_outcome"]
492
+ covering = [g for g in gaps
493
+ if float(g["since_ts"]) <= float(row["ts"])
494
+ < float(g["until_ts"])]
495
+ if covering:
496
+ row["evidence_available"] = False
497
+ row["evidence_gap_ids"] = [
498
+ g["reconciliation_id"] for g in covering]
499
+ if not row["valid"] and not include_invalid:
500
+ continue
501
+ if not row["evidence_available"] and not include_unavailable:
502
+ continue
503
+ out.append(row)
504
+ return out
505
+
506
+ def inspect_events(self, domain: str, since: float,
507
+ until: float) -> List[Dict[str, Any]]:
508
+ """Raw, fingerprinted candidates for a human reconciliation plan."""
509
+ return self.events(
510
+ domain, since=since, until=until, include_shadow=True,
511
+ include_invalid=True, include_unavailable=True)
512
+
513
+ @staticmethod
514
+ def _provenance_text(value: Any) -> str:
515
+ if isinstance(value, str):
516
+ text = value.strip()
517
+ if not text:
518
+ raise ValueError("provenance must not be empty")
519
+ return _canonical_json({"statement": text})
520
+ if not isinstance(value, (dict, list)) or not value:
521
+ raise ValueError("provenance must be a non-empty object, list, or string")
522
+ try:
523
+ return _canonical_json(value)
524
+ except (TypeError, ValueError) as exc:
525
+ raise ValueError(f"provenance is not JSON-serializable: {exc}") from exc
526
+
527
+ def _prepare_reconciliation(
528
+ self, manifest: Dict[str, Any]) -> List[Dict[str, Any]]:
529
+ if not isinstance(manifest, dict):
530
+ raise ValueError("reconciliation manifest must be an object")
531
+ if manifest.get("schema") != "colony.competence-reconciliation/v1":
532
+ raise ValueError("unsupported reconciliation manifest schema")
533
+ created_by = str(manifest.get("created_by") or "").strip()
534
+ if not created_by:
535
+ raise ValueError("created_by is required")
536
+ default_reason = str(manifest.get("reason") or "").strip()
537
+ default_provenance = manifest.get("provenance")
538
+ ledger = self._ledger_rows()
539
+ by_id = {r["reconciliation_id"]: r for r in ledger}
540
+ current = self._effective_corrections(ledger)
541
+ active_gaps = {g["reconciliation_id"]: g
542
+ for g in self.active_evidence_gaps()}
543
+ prepared: List[Dict[str, Any]] = []
544
+ seen_event_ids = set()
545
+ groups: Dict[str, List[Dict[str, Any]]] = {}
546
+ for name in ("event_corrections", "evidence_gaps", "resolve_gaps"):
547
+ entries = manifest.get(name) or []
548
+ if not isinstance(entries, list) or any(
549
+ not isinstance(e, dict) for e in entries):
550
+ raise ValueError(f"{name} must be a list of objects")
551
+ groups[name] = entries
552
+ requested = sum(len(entries) for entries in groups.values())
553
+
554
+ def common(entry: Dict[str, Any]) -> Tuple[str, str]:
555
+ reason = str(entry.get("reason") or default_reason).strip()
556
+ if not reason:
557
+ raise ValueError("every reconciliation needs a reason")
558
+ provenance = self._provenance_text(
559
+ entry.get("provenance", default_provenance))
560
+ return reason[:2000], provenance
561
+
562
+ for entry in groups["event_corrections"]:
563
+ event_id = int(entry.get("event_id") or 0)
564
+ if event_id in seen_event_ids:
565
+ raise ValueError(
566
+ f"event {event_id} appears more than once in one manifest")
567
+ seen_event_ids.add(event_id)
568
+ raw = self._conn.execute(
569
+ "SELECT * FROM competence_events WHERE id=?", (event_id,)
570
+ ).fetchone()
571
+ if raw is None:
572
+ raise ValueError(f"event {event_id} does not exist")
573
+ raw_dict = dict(raw)
574
+ fingerprint = str(entry.get("target_fingerprint") or "").lower()
575
+ actual = event_fingerprint(raw_dict)
576
+ if fingerprint != actual:
577
+ raise ValueError(
578
+ f"event {event_id} fingerprint mismatch: expected {actual}")
579
+ disposition = str(entry.get("disposition") or "").lower()
580
+ if disposition not in _DISPOSITIONS:
581
+ raise ValueError(
582
+ f"event {event_id} disposition must be invalidate or replace")
583
+ replacement = entry.get("replacement_outcome")
584
+ if disposition == "replace":
585
+ replacement = str(replacement or "").strip().lower()
586
+ if replacement not in _OUTCOMES:
587
+ raise ValueError(
588
+ f"event {event_id} replacement_outcome is invalid")
589
+ if replacement == raw_dict["outcome"]:
590
+ raise ValueError(
591
+ f"event {event_id} replacement equals recorded outcome")
592
+ elif replacement not in (None, ""):
593
+ raise ValueError(
594
+ f"event {event_id} invalidation cannot have a replacement")
595
+ else:
596
+ replacement = None
597
+ supersedes = str(entry.get("supersedes") or "").strip() or None
598
+ reason, provenance = common(entry)
599
+ identity = {
600
+ "kind": "event_correction", "event_id": event_id,
601
+ "target_fingerprint": fingerprint,
602
+ "disposition": disposition,
603
+ "replacement_outcome": replacement,
604
+ "supersedes": supersedes, "reason": reason,
605
+ "provenance": provenance, "created_by": created_by,
606
+ }
607
+ identity["reconciliation_id"] = _ledger_id(identity)
608
+ if identity["reconciliation_id"] in by_id:
609
+ continue
610
+ prior = current.get(event_id)
611
+ if prior is None and supersedes is not None:
612
+ raise ValueError(
613
+ f"event {event_id} has no correction to supersede")
614
+ if prior is not None and supersedes != prior["reconciliation_id"]:
615
+ raise ValueError(
616
+ f"event {event_id} already corrected; explicitly supersede "
617
+ f"{prior['reconciliation_id']}")
618
+ prepared.append(identity)
619
+
620
+ for entry in groups["evidence_gaps"]:
621
+ domain = str(entry.get("domain") or "").strip().lower()
622
+ if not domain or domain == "*":
623
+ raise ValueError("evidence gap requires one exact domain")
624
+ since_ts = float(entry.get("since_ts"))
625
+ until_ts = float(entry.get("until_ts"))
626
+ if (not math.isfinite(since_ts) or not math.isfinite(until_ts)
627
+ or since_ts >= until_ts):
628
+ raise ValueError("evidence gap must have since_ts < until_ts")
629
+ reason, provenance = common(entry)
630
+ identity = {
631
+ "kind": "evidence_gap", "domain": domain,
632
+ "since_ts": since_ts, "until_ts": until_ts,
633
+ "reason": reason, "provenance": provenance,
634
+ "created_by": created_by,
635
+ }
636
+ identity["reconciliation_id"] = _ledger_id(identity)
637
+ prepared.append(identity)
638
+
639
+ for entry in groups["resolve_gaps"]:
640
+ gap_id = str(entry.get("gap_id") or "").strip()
641
+ reason, provenance = common(entry)
642
+ identity = {
643
+ "kind": "gap_resolution", "supersedes": gap_id,
644
+ "reason": reason, "provenance": provenance,
645
+ "created_by": created_by,
646
+ }
647
+ identity["reconciliation_id"] = _ledger_id(identity)
648
+ if identity["reconciliation_id"] in by_id:
649
+ continue
650
+ if gap_id not in active_gaps:
651
+ raise ValueError(f"gap {gap_id!r} is not active")
652
+ prepared.append(identity)
653
+
654
+ if requested == 0:
655
+ raise ValueError("manifest contains no reconciliation entries")
656
+ ids = [r["reconciliation_id"] for r in prepared]
657
+ if len(ids) != len(set(ids)):
658
+ raise ValueError("manifest contains duplicate entries")
659
+ # Re-applying the exact same gap/correction manifest is idempotent.
660
+ return [r for r in prepared if r["reconciliation_id"] not in by_id]
661
+
662
+ def plan_reconciliation(self, manifest: Dict[str, Any]) -> Dict[str, Any]:
663
+ """Validate a manifest without mutating the database."""
664
+ with self._lock:
665
+ rows = self._prepare_reconciliation(manifest)
666
+ return {"valid": True, "would_append": len(rows), "entries": rows}
667
+
668
+ def apply_reconciliation(self, manifest: Dict[str, Any]) -> Dict[str, Any]:
669
+ """Atomically append a validated manifest to the audit ledger."""
670
+ with self._lock:
671
+ try:
672
+ self._conn.execute("BEGIN IMMEDIATE")
673
+ rows = self._prepare_reconciliation(manifest)
674
+ now = time.time()
675
+ for row in rows:
676
+ self._conn.execute(
677
+ """INSERT INTO competence_reconciliations
678
+ (reconciliation_id, kind, event_id,
679
+ target_fingerprint, disposition,
680
+ replacement_outcome, domain, since_ts, until_ts,
681
+ supersedes, reason, provenance, created_by,
682
+ created_at)
683
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
684
+ (row["reconciliation_id"], row["kind"],
685
+ row.get("event_id"), row.get("target_fingerprint"),
686
+ row.get("disposition"),
687
+ row.get("replacement_outcome"), row.get("domain"),
688
+ row.get("since_ts"), row.get("until_ts"),
689
+ row.get("supersedes"), row["reason"],
690
+ row["provenance"], row["created_by"], now))
691
+ self._conn.commit()
692
+ except Exception:
693
+ self._conn.rollback()
694
+ raise
695
+ return {"applied": len(rows),
696
+ "reconciliation_ids": [r["reconciliation_id"] for r in rows]}
697
+
698
+ def calibration(self, domain: str) -> Optional[Dict[str, Any]]:
699
+ """Stated-vs-realized calibration for a domain: mean absolute error
700
+ between the confidence the model stated and the realized outcome
701
+ (success=1, else 0), over non-shadow events that stated one."""
702
+ events = [e for e in self.events(domain, include_shadow=False)
703
+ if e.get("stated_confidence") is not None]
704
+ if not events:
705
+ return None
706
+ errs = [abs(float(e["stated_confidence"])
707
+ - (1.0 if e["outcome"] == "success" else 0.0))
708
+ for e in events]
709
+ realized = sum(1.0 for e in events
710
+ if e["outcome"] == "success") / len(events)
711
+ return {"n": len(events),
712
+ "mean_abs_error": round(sum(errs) / len(errs), 3),
713
+ "mean_stated": round(sum(float(e["stated_confidence"])
714
+ for e in events) / len(events), 3),
715
+ "mean_realized": round(realized, 3)}
716
+
717
+ def get(self, domain: str) -> Optional[Dict[str, Any]]:
718
+ domain = (domain or "").strip().lower()
719
+ with self._lock:
720
+ row = self._conn.execute(
721
+ "SELECT * FROM competence WHERE domain=?", (domain,)).fetchone()
722
+ return self._annotate_domain(dict(row)) if row else None
723
+
724
+ def snapshot(self) -> List[Dict[str, Any]]:
725
+ with self._lock:
726
+ rows = self._conn.execute(
727
+ "SELECT * FROM competence ORDER BY last_outcome_at DESC").fetchall()
728
+ return [self._annotate_domain(dict(r)) for r in rows]
729
+
730
+ def _annotate_domain(self, d: Dict[str, Any]) -> Dict[str, Any]:
731
+ """Apply ledger deltas without rewriting the legacy aggregate row."""
732
+ domain = str(d.get("domain") or "")
733
+ base_n = sum(int(d.get(o) or 0) for o in _OUTCOMES)
734
+ effective = self.events(
735
+ domain, include_shadow=True, include_invalid=True,
736
+ include_unavailable=True)
737
+ excluded_ids = set()
738
+ corrected = 0
739
+ for event in effective:
740
+ reconciliation = event.get("reconciliation")
741
+ if reconciliation is not None:
742
+ corrected += 1
743
+ recorded = str(event.get("recorded_outcome") or "")
744
+ if recorded in _OUTCOMES:
745
+ d[recorded] = max(0, int(d.get(recorded) or 0) - 1)
746
+ if event.get("valid"):
747
+ replacement = str(event.get("outcome") or "")
748
+ if replacement in _OUTCOMES:
749
+ d[replacement] = int(d.get(replacement) or 0) + 1
750
+ else:
751
+ excluded_ids.add(int(event["id"]))
752
+ if (event.get("valid")
753
+ and not event.get("evidence_available", True)):
754
+ outcome = str(event.get("outcome") or "")
755
+ if outcome in _OUTCOMES:
756
+ d[outcome] = max(0, int(d.get(outcome) or 0) - 1)
757
+ excluded_ids.add(int(event["id"]))
758
+ gaps = self.active_evidence_gaps(domain)
759
+ available = self.events(domain, include_shadow=True)
760
+ if available:
761
+ d["last_outcome"] = available[0]["outcome"]
762
+ d["last_outcome_at"] = available[0]["ts"]
763
+ elif base_n:
764
+ d["last_outcome"] = None
765
+ d["last_outcome_at"] = None
766
+ n = sum(int(d.get(o) or 0) for o in _OUTCOMES)
767
+ d["n"] = n
768
+ # A declared gap means even the denominator is not knowable. Expose
769
+ # the remaining evidence counts, but never turn them into a rate.
770
+ d["success_rate"] = (
771
+ round(int(d.get("success") or 0) / n, 3)
772
+ if n and not gaps else None)
773
+ d["timeout_rate"] = (
774
+ round(int(d.get("timeout") or 0) / n, 3)
775
+ if n and not gaps else None)
776
+ d["evidence_available"] = not gaps
777
+ d["evidence_gaps"] = [g["reconciliation_id"] for g in gaps]
778
+ d["reconciled_events"] = corrected
779
+ d["excluded_events"] = len(excluded_ids)
780
+ d["event_history_complete"] = len(effective) >= base_n
781
+ return d
782
+
783
+
784
+ class SelfModel:
785
+ """Competence store + live load probe + brief rendering.
786
+
787
+ Load is read live from the wired subsystems (never cached): in-progress
788
+ executor initiatives, active projects, queued jobs. Any missing subsystem
789
+ contributes zero.
790
+ """
791
+
792
+ def __init__(self, store: CompetenceStore, registry: Any = None,
793
+ trust: Any = None, journal: Any = None) -> None:
794
+ self.store = store
795
+ self._registry = registry
796
+ self.trust = trust # TrustEngine (Amendment 1)
797
+ self.journal = journal # ActionJournal (Amendment 1)
798
+ self.perspective = None # Optional source-backed working judgments; never authority.
799
+
800
+ # -- recording (thin passthrough; trust engine hooks demotion) -------
801
+ def record(self, domain: str, outcome: str,
802
+ latency_secs: Optional[float] = None,
803
+ shadow: bool = False, violation: bool = False,
804
+ stated_confidence: Optional[float] = None, *,
805
+ source: Optional[str] = None,
806
+ source_ref: Optional[str] = None,
807
+ event_key: Optional[str] = None,
808
+ evidence_status: str = "observed",
809
+ outcome_contract: str = "legacy.unversioned",
810
+ evidence: Optional[Dict[str, Any]] = None,
811
+ defer_trust: bool = False) -> bool:
812
+ recorded = self.store.record(domain, outcome, latency_secs=latency_secs,
813
+ shadow=shadow, violation=violation,
814
+ stated_confidence=stated_confidence,
815
+ source=source, source_ref=source_ref,
816
+ event_key=event_key,
817
+ evidence_status=evidence_status,
818
+ outcome_contract=outcome_contract,
819
+ evidence=evidence)
820
+ trust = getattr(self, "trust", None)
821
+ if recorded and trust is not None and not defer_trust:
822
+ try:
823
+ trust.after_outcome(domain)
824
+ except Exception:
825
+ logger.debug("trust after_outcome failed", exc_info=True)
826
+ return recorded
827
+
828
+ def reconcile_trust(self, domain: str) -> None:
829
+ """Apply deterministic trust transitions and propagate failures."""
830
+
831
+ trust = getattr(self, "trust", None)
832
+ if trust is not None:
833
+ trust.after_outcome(domain)
834
+
835
+ # -- live load -------------------------------------------------------
836
+ def load(self) -> Dict[str, int]:
837
+ active_initiatives = 0
838
+ active_projects = 0
839
+ queued_jobs = 0
840
+ reg = self._registry
841
+ if reg is not None:
842
+ try:
843
+ istore = getattr(reg, "initiative_store", None)
844
+ if istore is not None and hasattr(istore, "count"):
845
+ active_initiatives = int(
846
+ istore.count(status=["assigned", "acknowledged"]) or 0)
847
+ except Exception:
848
+ pass
849
+ try:
850
+ pengine = getattr(reg, "project_engine", None)
851
+ pstore = getattr(pengine, "store", None)
852
+ if pstore is not None and hasattr(pstore, "count"):
853
+ active_projects = int(pstore.count(status="active") or 0)
854
+ except Exception:
855
+ pass
856
+ try:
857
+ queue = getattr(reg, "task_queue", None)
858
+ if queue is not None and hasattr(queue, "count_pending"):
859
+ queued_jobs = int(queue.count_pending() or 0)
860
+ except Exception:
861
+ pass
862
+ return {
863
+ "active_initiatives": active_initiatives,
864
+ "active_projects": active_projects,
865
+ "queued_jobs": queued_jobs,
866
+ "total": active_initiatives + active_projects + queued_jobs,
867
+ }
868
+
869
+ def status(self) -> Dict[str, Any]:
870
+ out = {"domains": self.store.snapshot(), "load": self.load()}
871
+ if self.trust is not None:
872
+ try:
873
+ out["trust"] = self.trust.snapshot()
874
+ except Exception:
875
+ pass
876
+ return out
877
+
878
+ def brief(self) -> str:
879
+ from apsimo.self_model.brief import self_brief
880
+ parts = [self_brief(self.store.snapshot(), self.load())]
881
+ if self.perspective is not None:
882
+ parts.append(self.perspective.brief())
883
+ return '\n'.join(part for part in parts if part)