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,108 @@
1
+ """Behavioral signal models.
2
+
3
+ Signals are atomic observations about a person's communication patterns,
4
+ sentiment, and availability. They feed into the mind model for state
5
+ estimation and relationship scoring.
6
+
7
+ Colony is a first-class participant in its own signal stream — not merely an
8
+ observer. Colony-sourced signals (``direction="colony"``) capture Colony's own
9
+ behavior: when it reaches out, how effective its actions are, how well its
10
+ adapted style is received. Contact-sourced signals (``direction="contact"``)
11
+ capture the other person's behavior. Bilateral signals (``direction="bilateral"``)
12
+ describe the exchange pattern between Colony and a contact.
13
+ """
14
+
15
+ from dataclasses import dataclass
16
+ from datetime import datetime
17
+ from enum import Enum
18
+ from typing import Any, Dict, Optional
19
+
20
+
21
+ class SignalType(str, Enum):
22
+ """Categories of behavioral signals.
23
+
24
+ Communication patterns track message flow characteristics.
25
+ Sentiment signals capture emotional tone.
26
+ Interaction quality measures depth of engagement.
27
+ Availability tracks when someone is typically reachable.
28
+
29
+ Colony signals (new) capture Colony's own behavior in the signal stream.
30
+ These feed the colony_initiative_response, colony_effectiveness, and
31
+ colony_style_fit scoring dimensions.
32
+ """
33
+
34
+ # Communication patterns
35
+ MESSAGE_FREQUENCY = "message_frequency"
36
+ RESPONSE_TIME = "response_time"
37
+ INITIATION_RATE = "initiation_rate"
38
+
39
+ # Sentiment
40
+ SENTIMENT_POSITIVE = "sentiment_positive"
41
+ SENTIMENT_NEGATIVE = "sentiment_negative"
42
+ SENTIMENT_NEUTRAL = "sentiment_neutral"
43
+
44
+ # Interaction quality
45
+ ENGAGEMENT_DEPTH = "engagement_depth"
46
+ TOPIC_DIVERSITY = "topic_diversity"
47
+
48
+ # Availability
49
+ TYPICAL_ACTIVE_HOURS = "typical_active_hours"
50
+ AVAILABILITY_PATTERN = "availability_pattern"
51
+
52
+ # Colony-sourced signals (Colony is a participant, not just an observer)
53
+ COLONY_INITIATIVE = "colony_initiative" # Colony reached out first
54
+ COLONY_SUGGESTION = "colony_suggestion" # Colony made a suggestion/action
55
+ COLONY_EFFECTIVENESS = "colony_effectiveness" # Outcome of Colony's action
56
+ CONTACT_RESPONSE_TO_COLONY = "contact_response_to_colony" # Response to Colony-origin
57
+ COLONY_STYLE_SIGNAL = "colony_style_signal" # Style adaptation feedback
58
+
59
+ # Legacy aliases kept for migration compatibility
60
+ OWNER_EXPLICIT = "owner_explicit"
61
+
62
+
63
+ class SignalStrength(str, Enum):
64
+ """Confidence level of a signal observation.
65
+
66
+ WEAK: Low confidence, sparse data
67
+ MODERATE: Reasonable confidence, sufficient data
68
+ STRONG: High confidence, abundant data
69
+ """
70
+
71
+ WEAK = "weak"
72
+ MODERATE = "moderate"
73
+ STRONG = "strong"
74
+
75
+
76
+ @dataclass
77
+ class Signal:
78
+ """A single behavioral observation about a person.
79
+
80
+ Signals are the raw building blocks of Colony's understanding.
81
+ Each signal captures one dimension of behavior at a point in time.
82
+
83
+ Attributes:
84
+ id: Unique identifier
85
+ person_id: The person this signal relates to
86
+ signal_type: What kind of behavior was observed
87
+ value: Normalized value between 0 and 1
88
+ raw_value: Original unnormalized value (for debugging/recalibration)
89
+ strength: Confidence level of this observation
90
+ source: How the signal was generated ("observed", "inferred", "explicit")
91
+ timestamp: When the signal was captured
92
+ context: Additional metadata (channel, message_id, etc.)
93
+ direction: Who the signal is about.
94
+ "contact" — describes the contact's behavior (existing signals).
95
+ "colony" — describes Colony's own behavior (colony_initiative etc.).
96
+ "bilateral" — describes the exchange pattern (e.g. initiation_ratio).
97
+ """
98
+
99
+ id: str
100
+ person_id: str
101
+ signal_type: SignalType
102
+ value: float
103
+ raw_value: Optional[float] = None
104
+ strength: SignalStrength = SignalStrength.MODERATE
105
+ source: str = "observed"
106
+ timestamp: Optional[datetime] = None
107
+ context: Optional[Dict[str, Any]] = None
108
+ direction: str = "contact" # "contact" | "colony" | "bilateral"
@@ -0,0 +1,15 @@
1
+ """Observation store (v0.16.0) — the agent is Colony's sensor array.
2
+
3
+ Colony does not own external API clients. The agent observes the world
4
+ through its existing Hermes connections (github, terminal, web, ...)
5
+ and reports domain snapshots here. Colony's context loaders read
6
+ observations, never external APIs.
7
+ """
8
+
9
+ from apsimo.observations.store import (
10
+ OBSERVATION_DOMAINS,
11
+ Observation,
12
+ ObservationStore,
13
+ )
14
+
15
+ __all__ = ["Observation", "ObservationStore", "OBSERVATION_DOMAINS"]
@@ -0,0 +1,277 @@
1
+ """SQLite store for agent-reported domain observations (v0.16.0).
2
+
3
+ One row per (domain, entity_id) — the latest snapshot wins. Colony's
4
+ initiative generators read these; the autonomy loop requests refreshes
5
+ by posting read-only ``agent_sync_<domain>`` jobs when a domain's
6
+ newest observation outlives its sync interval.
7
+
8
+ Expected payload shapes per domain (generators are defensive — extra
9
+ keys pass through into initiative context, missing keys skip rules):
10
+
11
+ - coding: {title, url, ci_status, review_requested, draft, author}
12
+ - task: {title, url, state, assignee, due, stale_days}
13
+ - calendar: {title, start_time, location, attendees, needs_prep}
14
+ - research: {title, url, status, last_checked}
15
+ - project: {title, due_on, open_issues, closed_issues}
16
+ - system: {status, latency_ms, error_rate, message}
17
+ - skills: {description, tags, path, source} (v0.18.0 Hermes bridge)
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import json
23
+ import logging
24
+ import sqlite3
25
+ from dataclasses import dataclass
26
+ from datetime import datetime, timezone
27
+ from pathlib import Path
28
+ from typing import Any, Dict, List, Optional
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+ # Domains the sensor loop knows about. Adding a domain means: a sync
33
+ # action in the action registry, a generator in the initiative engine,
34
+ # and (if volatile) a durability entry in context_freshness.py.
35
+ OBSERVATION_DOMAINS = (
36
+ "coding",
37
+ "task",
38
+ "calendar",
39
+ "research",
40
+ "project",
41
+ "system",
42
+ # v0.18.0: the agent's installed Hermes skill index, reported by the
43
+ # OpenClaw plugin. Push-only — the plugin scans ~/.hermes/skills on
44
+ # its own schedule, so this domain deliberately has NO entry in
45
+ # OBSERVATION_SYNC_ACTIONS and is NOT in the COLONY_SYNC_DOMAINS
46
+ # defaults; the autonomy loop never posts agent_sync jobs for it.
47
+ "skills",
48
+ )
49
+
50
+ # How old a domain's newest observation may be before the autonomy loop
51
+ # requests a fresh sync from the agent (seconds). Coarser than the
52
+ # context-freshness TTLs — those govern acting on one snapshot, these
53
+ # govern re-scanning a whole domain.
54
+ OBSERVATION_SYNC_INTERVALS: Dict[str, int] = {
55
+ "coding": 900,
56
+ "task": 3600,
57
+ "calendar": 900,
58
+ "research": 86400,
59
+ "project": 86400,
60
+ "system": 300,
61
+ # Informational only: the plugin pushes the Hermes skill index every
62
+ # 24h; the loop never requests a sync for this push-only domain.
63
+ "skills": 86400,
64
+ }
65
+
66
+
67
+ @dataclass
68
+ class Observation:
69
+ domain: str
70
+ entity_id: str
71
+ payload: Dict[str, Any]
72
+ observed_at: Optional[datetime]
73
+ reported_by: Optional[str] = None
74
+
75
+ @classmethod
76
+ def from_row(cls, row: dict) -> "Observation":
77
+ payload_raw = row.get("payload") or "{}"
78
+ try:
79
+ payload = json.loads(payload_raw) if isinstance(payload_raw, str) else payload_raw
80
+ except (ValueError, TypeError):
81
+ payload = {}
82
+ observed_at = row.get("observed_at")
83
+ if isinstance(observed_at, str):
84
+ try:
85
+ observed_at = datetime.fromisoformat(observed_at.replace("Z", "+00:00"))
86
+ except (ValueError, TypeError):
87
+ observed_at = None
88
+ return cls(
89
+ domain=row["domain"],
90
+ entity_id=row["entity_id"],
91
+ payload=payload,
92
+ observed_at=observed_at if isinstance(observed_at, datetime) else None,
93
+ reported_by=row.get("reported_by"),
94
+ )
95
+
96
+ def to_dict(self) -> Dict[str, Any]:
97
+ return {
98
+ "domain": self.domain,
99
+ "entity_id": self.entity_id,
100
+ "payload": self.payload,
101
+ "observed_at": self.observed_at.isoformat() if self.observed_at else None,
102
+ "reported_by": self.reported_by,
103
+ }
104
+
105
+
106
+ class ObservationStore:
107
+ """Latest-snapshot-per-entity observation persistence."""
108
+
109
+ def __init__(self, state_dir: Optional[Path] = None):
110
+ if state_dir is None:
111
+ from apsimo.initiatives.store import get_state_dir
112
+ state_dir = get_state_dir()
113
+ self._state_dir = Path(state_dir)
114
+ self._state_dir.mkdir(parents=True, exist_ok=True)
115
+ self._db_path = self._state_dir / "observations.db"
116
+ self._db = self._connect()
117
+
118
+ def _connect(self) -> sqlite3.Connection:
119
+ conn = sqlite3.connect(self._db_path, check_same_thread=False)
120
+ conn.row_factory = sqlite3.Row
121
+ conn.execute("PRAGMA journal_mode=WAL")
122
+ conn.execute("PRAGMA busy_timeout=5000")
123
+ conn.execute(
124
+ """
125
+ CREATE TABLE IF NOT EXISTS observations (
126
+ domain TEXT NOT NULL,
127
+ entity_id TEXT NOT NULL,
128
+ payload TEXT NOT NULL DEFAULT '{}',
129
+ observed_at TEXT NOT NULL,
130
+ reported_by TEXT,
131
+ PRIMARY KEY (domain, entity_id)
132
+ )
133
+ """
134
+ )
135
+ conn.execute(
136
+ "CREATE INDEX IF NOT EXISTS idx_observations_domain "
137
+ "ON observations(domain, observed_at DESC)"
138
+ )
139
+ conn.commit()
140
+ return conn
141
+
142
+ # ------------------------------------------------------------------
143
+ # Writes
144
+ # ------------------------------------------------------------------
145
+
146
+ def record(
147
+ self,
148
+ domain: str,
149
+ entity_id: str,
150
+ payload: Dict[str, Any],
151
+ reported_by: Optional[str] = None,
152
+ observed_at: Optional[datetime] = None,
153
+ ) -> Observation:
154
+ """Upsert the latest snapshot for one entity."""
155
+ observed_at = observed_at or datetime.now(timezone.utc)
156
+ self._db.execute(
157
+ """
158
+ INSERT INTO observations (domain, entity_id, payload, observed_at, reported_by)
159
+ VALUES (?, ?, ?, ?, ?)
160
+ ON CONFLICT(domain, entity_id) DO UPDATE SET
161
+ payload = excluded.payload,
162
+ observed_at = excluded.observed_at,
163
+ reported_by = excluded.reported_by
164
+ """,
165
+ [domain, entity_id, json.dumps(payload), observed_at.isoformat(), reported_by],
166
+ )
167
+ self._db.commit()
168
+ return Observation(domain, entity_id, payload, observed_at, reported_by)
169
+
170
+ def record_batch(
171
+ self,
172
+ domain: str,
173
+ observations: List[Dict[str, Any]],
174
+ reported_by: Optional[str] = None,
175
+ ) -> int:
176
+ """Record many snapshots for one domain. Returns rows written."""
177
+ written = 0
178
+ for obs in observations:
179
+ entity_id = obs.get("entity_id")
180
+ if not entity_id:
181
+ continue
182
+ observed_at = obs.get("observed_at")
183
+ if isinstance(observed_at, str):
184
+ try:
185
+ observed_at = datetime.fromisoformat(observed_at.replace("Z", "+00:00"))
186
+ except (ValueError, TypeError):
187
+ continue # An invalid supplied time is not a fresh observation.
188
+ self.record(
189
+ domain=domain,
190
+ entity_id=str(entity_id),
191
+ payload=obs.get("payload") or {},
192
+ reported_by=reported_by,
193
+ observed_at=observed_at,
194
+ )
195
+ written += 1
196
+ return written
197
+
198
+ def delete(self, domain: str, entity_id: str) -> bool:
199
+ cur = self._db.execute(
200
+ "DELETE FROM observations WHERE domain = ? AND entity_id = ?",
201
+ [domain, entity_id],
202
+ )
203
+ self._db.commit()
204
+ return cur.rowcount > 0
205
+
206
+ def prune(self, older_than_days: float = 30.0) -> int:
207
+ """Drop snapshots nothing has refreshed in a long time."""
208
+ cutoff = datetime.now(timezone.utc).timestamp() - older_than_days * 86400
209
+ cutoff_iso = datetime.fromtimestamp(cutoff, tz=timezone.utc).isoformat()
210
+ cur = self._db.execute(
211
+ "DELETE FROM observations WHERE observed_at < ?", [cutoff_iso]
212
+ )
213
+ self._db.commit()
214
+ return cur.rowcount
215
+
216
+ # ------------------------------------------------------------------
217
+ # Reads
218
+ # ------------------------------------------------------------------
219
+
220
+ def get(self, domain: str, entity_id: str) -> Optional[Observation]:
221
+ cur = self._db.execute(
222
+ "SELECT * FROM observations WHERE domain = ? AND entity_id = ?",
223
+ [domain, entity_id],
224
+ )
225
+ row = cur.fetchone()
226
+ return Observation.from_row(dict(row)) if row else None
227
+
228
+ def list(self, domain: str, limit: int = 100) -> List[Observation]:
229
+ cur = self._db.execute(
230
+ "SELECT * FROM observations WHERE domain = ? "
231
+ "ORDER BY observed_at DESC LIMIT ?",
232
+ [domain, limit],
233
+ )
234
+ return [Observation.from_row(dict(r)) for r in cur.fetchall()]
235
+
236
+ def domain_age_seconds(self, domain: str) -> Optional[float]:
237
+ """Seconds since the NEWEST observation in a domain.
238
+
239
+ None when the domain has never been observed — callers treat
240
+ that as maximally stale (a sync is needed to prime it).
241
+ """
242
+ cur = self._db.execute(
243
+ "SELECT MAX(observed_at) AS newest FROM observations WHERE domain = ?",
244
+ [domain],
245
+ )
246
+ row = cur.fetchone()
247
+ newest = row["newest"] if row else None
248
+ if not newest:
249
+ return None
250
+ try:
251
+ stamp = datetime.fromisoformat(str(newest).replace("Z", "+00:00"))
252
+ except (ValueError, TypeError):
253
+ return None
254
+ if stamp.tzinfo is None:
255
+ stamp = stamp.replace(tzinfo=timezone.utc)
256
+ return (datetime.now(timezone.utc) - stamp).total_seconds()
257
+
258
+ def summary(self) -> Dict[str, Dict[str, Any]]:
259
+ """Per-domain counts and freshness."""
260
+ cur = self._db.execute(
261
+ "SELECT domain, COUNT(*) AS n, MAX(observed_at) AS newest "
262
+ "FROM observations GROUP BY domain"
263
+ )
264
+ out: Dict[str, Dict[str, Any]] = {}
265
+ for row in cur.fetchall():
266
+ out[row["domain"]] = {
267
+ "count": row["n"],
268
+ "newest_observed_at": row["newest"],
269
+ "age_seconds": self.domain_age_seconds(row["domain"]),
270
+ }
271
+ return out
272
+
273
+ def close(self) -> None:
274
+ try:
275
+ self._db.close()
276
+ except Exception:
277
+ pass
@@ -0,0 +1,6 @@
1
+ """Pattern Extraction — identify recurring structures in the world model."""
2
+
3
+ from apsimo.patterns.store import PatternStore
4
+ from apsimo.patterns.extract import extract_patterns
5
+
6
+ __all__ = ["PatternStore", "extract_patterns"]
@@ -0,0 +1,187 @@
1
+ """Pattern extraction — pull patterns from the world model."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import Any, Dict, List, Optional
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ def extract_patterns(
12
+ world_store: Any = None,
13
+ pattern_store: Any = None,
14
+ ) -> Dict[str, Any]:
15
+ """Run pattern extraction against the world model.
16
+
17
+ If world_store or pattern_store is None, returns a no-op result.
18
+ Returns {"new": N, "updated": M, "total": T}.
19
+ """
20
+ if world_store is None or pattern_store is None:
21
+ return {"new": 0, "updated": 0, "total": 0, "reason": "stores_not_wired"}
22
+
23
+ results = {"new": 0, "updated": 0, "total": 0}
24
+
25
+ # Extract entity cooccurrence patterns.
26
+ try:
27
+ cooc = _extract_entity_cooccurrence(world_store)
28
+ for pattern in cooc:
29
+ result = pattern_store.create_pattern(**pattern)
30
+ if result.get("frequency", 1) > 1:
31
+ results["updated"] += 1
32
+ else:
33
+ results["new"] += 1
34
+ except Exception as exc:
35
+ logger.debug("entity cooccurrence extraction failed: %s", exc)
36
+
37
+ # Extract relation frequency patterns.
38
+ try:
39
+ rels = _extract_relation_frequency(world_store)
40
+ for pattern in rels:
41
+ result = pattern_store.create_pattern(**pattern)
42
+ if result.get("frequency", 1) > 1:
43
+ results["updated"] += 1
44
+ else:
45
+ results["new"] += 1
46
+ except Exception as exc:
47
+ logger.debug("relation frequency extraction failed: %s", exc)
48
+
49
+ # Extract attribute cluster patterns.
50
+ try:
51
+ attrs = _extract_attribute_clusters(world_store)
52
+ for pattern in attrs:
53
+ result = pattern_store.create_pattern(**pattern)
54
+ if result.get("frequency", 1) > 1:
55
+ results["updated"] += 1
56
+ else:
57
+ results["new"] += 1
58
+ except Exception as exc:
59
+ logger.debug("attribute cluster extraction failed: %s", exc)
60
+
61
+ total = pattern_store.list_patterns(active_only=True)
62
+ results["total"] = total.get("total", 0)
63
+ return results
64
+
65
+
66
+ def _extract_entity_cooccurrence(world_store: Any) -> List[Dict[str, Any]]:
67
+ """Find entities that share relationships."""
68
+ patterns: List[Dict[str, Any]] = []
69
+
70
+ try:
71
+ entities = world_store.list_entities(limit=200)
72
+ except Exception as exc:
73
+ logger.debug("list_entities failed in pattern extraction: %s", exc)
74
+ return patterns
75
+
76
+ if not entities:
77
+ return patterns
78
+
79
+ # Group entities by relationship target.
80
+ target_map: Dict[str, List[str]] = {}
81
+ for entity in entities:
82
+ name = entity.get("name", "")
83
+ etype = entity.get("entity_type", "")
84
+ if not name:
85
+ continue
86
+ # Use entity type as a grouping key for cooccurrence.
87
+ if etype not in target_map:
88
+ target_map[etype] = []
89
+ target_map[etype].append(name)
90
+
91
+ # Entities that share a type are cooccurring.
92
+ for etype, names in target_map.items():
93
+ if len(names) < 2:
94
+ continue
95
+ # Create pairwise patterns for top entities.
96
+ for i in range(min(len(names), 5)):
97
+ for j in range(i + 1, min(len(names), 5)):
98
+ key = f"cooc:{sorted([names[i], names[j]])[0]}→{sorted([names[i], names[j]])[1]}"
99
+ patterns.append({
100
+ "pattern_type": "entity_cooccurrence",
101
+ "description": f"{names[i]} and {names[j]} both appear as {etype}",
102
+ "pattern_key": key,
103
+ "confidence": 0.6,
104
+ "source": "extraction",
105
+ "metadata": {"type": etype, "entities": [names[i], names[j]]},
106
+ })
107
+
108
+ return patterns
109
+
110
+
111
+ def _extract_relation_frequency(world_store: Any) -> List[Dict[str, Any]]:
112
+ """Find frequently occurring relationship types."""
113
+ patterns: List[Dict[str, Any]] = []
114
+
115
+ try:
116
+ entities = world_store.list_entities(limit=200)
117
+ except Exception as exc:
118
+ logger.debug("list_entities failed in pattern extraction: %s", exc)
119
+ return patterns
120
+
121
+ # Count relationship types.
122
+ rel_counts: Dict[str, int] = {}
123
+ for entity in entities:
124
+ rels = entity.get("relationships", [])
125
+ if isinstance(rels, list):
126
+ for rel in rels:
127
+ rtype = rel.get("type", "") if isinstance(rel, dict) else str(rel)
128
+ if rtype:
129
+ rel_counts[rtype] = rel_counts.get(rtype, 0) + 1
130
+
131
+ for rtype, count in rel_counts.items():
132
+ if count < 2:
133
+ continue
134
+ patterns.append({
135
+ "pattern_type": "relation_frequency",
136
+ "description": f"Relationship type '{rtype}' appears {count} times",
137
+ "pattern_key": f"rel_freq:{rtype}",
138
+ "frequency": count,
139
+ "confidence": min(1.0, 0.3 + count * 0.1),
140
+ "source": "extraction",
141
+ "metadata": {"relation_type": rtype, "count": count},
142
+ })
143
+
144
+ return patterns
145
+
146
+
147
+ def _extract_attribute_clusters(world_store: Any) -> List[Dict[str, Any]]:
148
+ """Find entities of the same type that share attribute keys."""
149
+ patterns: List[Dict[str, Any]] = []
150
+
151
+ try:
152
+ entities = world_store.list_entities(limit=200)
153
+ except Exception as exc:
154
+ logger.debug("list_entities failed in pattern extraction: %s", exc)
155
+ return patterns
156
+
157
+ # Group by type, collect attribute keys.
158
+ type_attrs: Dict[str, Dict[str, int]] = {}
159
+ for entity in entities:
160
+ etype = entity.get("entity_type", "")
161
+ attrs = entity.get("attributes", {})
162
+ if not isinstance(attrs, dict):
163
+ continue
164
+ if etype not in type_attrs:
165
+ type_attrs[etype] = {}
166
+ for key in attrs:
167
+ type_attrs[etype][key] = type_attrs[etype].get(key, 0) + 1
168
+
169
+ for etype, attr_counts in type_attrs.items():
170
+ if len(attr_counts) < 2:
171
+ continue
172
+ # Only include attributes that appear in multiple entities.
173
+ common = [k for k, v in attr_counts.items() if v >= 2]
174
+ if len(common) < 2:
175
+ continue
176
+ common.sort()
177
+ key = f"attr_cluster:{etype}:{','.join(common[:5])}"
178
+ patterns.append({
179
+ "pattern_type": "attribute_cluster",
180
+ "description": f"{etype} entities share attributes: {', '.join(common[:5])}",
181
+ "pattern_key": key,
182
+ "confidence": 0.7,
183
+ "source": "extraction",
184
+ "metadata": {"entity_type": etype, "attributes": common[:10]},
185
+ })
186
+
187
+ return patterns