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,354 @@
1
+ """Goal Inference Pipeline — detect implicit goals from conversation context.
2
+
3
+ Architecture:
4
+ 1. Rule-based pass (synchronous, < 5 ms) — keyword/pattern matching
5
+ 2. GoalDeduplicator — semantic similarity check against active goals
6
+
7
+ NOTE: an LLM second-pass for low-confidence candidates was envisioned but is
8
+ NOT implemented — there is no hook or override seam in this module. Candidates
9
+ below MIN_CONFIDENCE are simply dropped. (Wire one through the project's Anthropic
10
+ client if low-confidence recall becomes a need; keep the rule-based path's <5 ms
11
+ synchronous guarantee — any LLM call must be out of band.)
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import re
17
+ import time
18
+ from dataclasses import dataclass, field
19
+ from enum import Enum
20
+ from typing import Dict, List, Optional, Tuple
21
+
22
+ from apsimo.goals.models import Goal, GoalPriority, GoalSource
23
+
24
+
25
+ class IntentSignal(str, Enum):
26
+ """Conversation signal types that may indicate a goal."""
27
+ DESIRE = "desire" # "I want to / I need to / I'd like to"
28
+ OBLIGATION = "obligation" # "I have to / I must / I should"
29
+ PROBLEM = "problem" # "I'm struggling with / This is broken"
30
+ DEADLINE = "deadline" # Explicit time reference with task context
31
+ FRUSTRATION = "frustration" # Repeated problem mentions, negative sentiment
32
+ RECURRING = "recurring" # "Every week / daily / each morning"
33
+ DELEGATION = "delegation" # "Can you / Could you / Please"
34
+
35
+
36
+ @dataclass
37
+ class ConversationMessage:
38
+ """A single message in a conversation."""
39
+ message_id: str
40
+ role: str # "user" | "assistant" | "system"
41
+ content: str
42
+ timestamp: Optional[float] = None # Unix epoch
43
+
44
+
45
+ @dataclass
46
+ class InferenceCandidate:
47
+ """A candidate goal extracted from conversation context.
48
+
49
+ Attributes:
50
+ title: Inferred goal title.
51
+ description: Full description including supporting context.
52
+ signals: Which signal types contributed to this inference.
53
+ confidence: 0.0–1.0 confidence that this is a real goal.
54
+ source_messages: IDs of conversation messages that triggered inference.
55
+ suggested_deadline: Extracted deadline if any.
56
+ priority: Inferred priority based on signal type.
57
+ """
58
+ title: str
59
+ description: str
60
+ signals: List[IntentSignal]
61
+ confidence: float
62
+ source_messages: List[str]
63
+ suggested_deadline: Optional[str] = None # ISO-8601 if detected
64
+ priority: GoalPriority = GoalPriority.NORMAL
65
+
66
+ def should_auto_accept(self, threshold: float = 0.85) -> bool:
67
+ """Return True if confidence exceeds auto-accept threshold."""
68
+ return (
69
+ self.confidence >= threshold
70
+ and IntentSignal.DELEGATION in self.signals
71
+ )
72
+
73
+
74
+ @dataclass
75
+ class GoalSimilarity:
76
+ """Result of comparing two goals for deduplication."""
77
+ goal_id_a: str
78
+ goal_id_b: str
79
+ similarity_score: float # 0.0–1.0
80
+ shared_keywords: List[str]
81
+ recommendation: str # "duplicate" | "merge" | "related" | "distinct"
82
+
83
+
84
+ # ── Rule-based signal patterns ────────────────────────────────────────────────
85
+
86
+ _DESIRE_PATTERNS = re.compile(
87
+ r"\b(i want to|i need to|i'd like to|i would like to|i wish|i hope to|"
88
+ r"need to|want to|gotta|going to|planning to)\b",
89
+ re.IGNORECASE,
90
+ )
91
+ _OBLIGATION_PATTERNS = re.compile(
92
+ r"\b(i have to|i must|i should|i ought to|have to|must|should|"
93
+ r"need to finish|deadline|due by|due on)\b",
94
+ re.IGNORECASE,
95
+ )
96
+ _PROBLEM_PATTERNS = re.compile(
97
+ r"\b(struggling with|broken|not working|failing|error|bug|issue|problem|"
98
+ r"can't figure out|don't know how|stuck on|frustrated with|having trouble)\b",
99
+ re.IGNORECASE,
100
+ )
101
+ _DEADLINE_PATTERNS = re.compile(
102
+ r"\b(by (monday|tuesday|wednesday|thursday|friday|saturday|sunday)|"
103
+ r"by (tomorrow|tonight|end of (day|week|month))|"
104
+ r"before (the meeting|the call|the deadline)|"
105
+ r"(this|next) (monday|tuesday|wednesday|thursday|friday|week|month)|"
106
+ r"in (\d+) (hours?|days?|weeks?))\b",
107
+ re.IGNORECASE,
108
+ )
109
+ _RECURRING_PATTERNS = re.compile(
110
+ r"\b(every (day|week|month|morning|evening|monday|tuesday|wednesday|"
111
+ r"thursday|friday)|daily|weekly|monthly|each (morning|day|week))\b",
112
+ re.IGNORECASE,
113
+ )
114
+ _DELEGATION_PATTERNS = re.compile(
115
+ r"\b(can you|could you|please|would you|help me|remind me|schedule|"
116
+ r"set up|create|make|send|write|draft|prepare|organize)\b",
117
+ re.IGNORECASE,
118
+ )
119
+ _FRUSTRATION_PATTERNS = re.compile(
120
+ r"\b(frustrated|annoying|ridiculous|terrible|awful|keeps (breaking|failing)|"
121
+ r"still broken|why doesn'?t|why isn'?t|never works|always fails)\b",
122
+ re.IGNORECASE,
123
+ )
124
+
125
+ _PRIORITY_SIGNALS = {
126
+ IntentSignal.OBLIGATION: GoalPriority.HIGH,
127
+ IntentSignal.DEADLINE: GoalPriority.HIGH,
128
+ IntentSignal.FRUSTRATION: GoalPriority.HIGH,
129
+ IntentSignal.DELEGATION: GoalPriority.NORMAL,
130
+ IntentSignal.DESIRE: GoalPriority.NORMAL,
131
+ IntentSignal.PROBLEM: GoalPriority.NORMAL,
132
+ IntentSignal.RECURRING: GoalPriority.LOW,
133
+ }
134
+
135
+
136
+ def _extract_signals(text: str) -> List[Tuple[IntentSignal, float]]:
137
+ """Extract signals from text, returning (signal, weight) pairs."""
138
+ results: List[Tuple[IntentSignal, float]] = []
139
+ if _DESIRE_PATTERNS.search(text):
140
+ results.append((IntentSignal.DESIRE, 0.6))
141
+ if _OBLIGATION_PATTERNS.search(text):
142
+ results.append((IntentSignal.OBLIGATION, 0.7))
143
+ if _PROBLEM_PATTERNS.search(text):
144
+ results.append((IntentSignal.PROBLEM, 0.55))
145
+ if _DEADLINE_PATTERNS.search(text):
146
+ results.append((IntentSignal.DEADLINE, 0.65))
147
+ if _RECURRING_PATTERNS.search(text):
148
+ results.append((IntentSignal.RECURRING, 0.5))
149
+ if _DELEGATION_PATTERNS.search(text):
150
+ results.append((IntentSignal.DELEGATION, 0.75))
151
+ if _FRUSTRATION_PATTERNS.search(text):
152
+ results.append((IntentSignal.FRUSTRATION, 0.6))
153
+ return results
154
+
155
+
156
+ def _compute_confidence(signals: List[Tuple[IntentSignal, float]]) -> float:
157
+ """Combine multiple signal weights into a single confidence score."""
158
+ if not signals:
159
+ return 0.0
160
+ # Combine with diminishing returns: 1 - product(1 - w)
161
+ combined = 1.0
162
+ for _, w in signals:
163
+ combined *= (1.0 - w)
164
+ return round(1.0 - combined, 4)
165
+
166
+
167
+ def _infer_priority(signals: List[IntentSignal]) -> GoalPriority:
168
+ """Select highest priority implied by the signal set."""
169
+ best = GoalPriority.BACKGROUND
170
+ for sig in signals:
171
+ p = _PRIORITY_SIGNALS.get(sig, GoalPriority.NORMAL)
172
+ if p.value > best.value:
173
+ best = p
174
+ return best
175
+
176
+
177
+ def _extract_title(text: str) -> str:
178
+ """Produce a short title from the message text (max 80 chars)."""
179
+ # Strip leading/trailing whitespace and trim to 80 chars
180
+ cleaned = re.sub(r"\s+", " ", text.strip())
181
+ # Remove common filler phrases
182
+ cleaned = re.sub(
183
+ r"^(can you|could you|please|i need to|i want to|i have to|"
184
+ r"i should|i must|help me)\s+",
185
+ "",
186
+ cleaned,
187
+ flags=re.IGNORECASE,
188
+ ).strip()
189
+ # Capitalize
190
+ if cleaned:
191
+ cleaned = cleaned[0].upper() + cleaned[1:]
192
+ return cleaned[:80].rstrip(".,;:")
193
+
194
+
195
+ class GoalInferencePipeline:
196
+ """Detect implicit goals from conversation messages.
197
+
198
+ The rule-based pass runs synchronously on every user message. Candidates
199
+ below ``MIN_CONFIDENCE`` are dropped; there is no LLM second-pass wired in
200
+ (see module docstring).
201
+ """
202
+
203
+ MIN_CONFIDENCE = 0.35 # Below this, no candidate is produced
204
+
205
+ def __init__(self, min_confidence: float = MIN_CONFIDENCE) -> None:
206
+ self.min_confidence = min_confidence
207
+
208
+ def process_message(
209
+ self,
210
+ message: ConversationMessage,
211
+ history: Optional[List[ConversationMessage]] = None,
212
+ ) -> Optional[InferenceCandidate]:
213
+ """Run the rule-based inference pass on a single message.
214
+
215
+ Returns an InferenceCandidate if a goal-like signal is detected,
216
+ otherwise None.
217
+
218
+ Must complete in < 5 ms for typical inputs.
219
+ """
220
+ if message.role != "user":
221
+ return None
222
+
223
+ text = message.content
224
+ signals_weighted = _extract_signals(text)
225
+ if not signals_weighted:
226
+ return None
227
+
228
+ confidence = _compute_confidence(signals_weighted)
229
+ if confidence < self.min_confidence:
230
+ return None
231
+
232
+ signals = [s for s, _ in signals_weighted]
233
+ priority = _infer_priority(signals)
234
+ title = _extract_title(text)
235
+ if not title:
236
+ return None
237
+
238
+ # Extract deadline hint
239
+ deadline_match = _DEADLINE_PATTERNS.search(text)
240
+ suggested_deadline = deadline_match.group(0) if deadline_match else None
241
+
242
+ return InferenceCandidate(
243
+ title=title,
244
+ description=text.strip(),
245
+ signals=signals,
246
+ confidence=confidence,
247
+ source_messages=[message.message_id],
248
+ suggested_deadline=suggested_deadline,
249
+ priority=priority,
250
+ )
251
+
252
+ def process_history(
253
+ self,
254
+ history: List[ConversationMessage],
255
+ ) -> List[InferenceCandidate]:
256
+ """Process a conversation history, returning all detected candidates."""
257
+ candidates = []
258
+ for msg in history:
259
+ cand = self.process_message(msg, history=history)
260
+ if cand:
261
+ candidates.append(cand)
262
+ return candidates
263
+
264
+
265
+ # ── Goal Deduplication ────────────────────────────────────────────────────────
266
+
267
+ def _tokenize(text: str) -> set:
268
+ """Simple word-level tokenizer for keyword overlap scoring."""
269
+ # Remove stop words and short tokens
270
+ stop_words = {
271
+ "i", "a", "an", "the", "to", "is", "are", "was", "were",
272
+ "be", "been", "being", "have", "has", "had", "do", "does",
273
+ "did", "will", "would", "could", "should", "may", "might",
274
+ "shall", "can", "need", "want", "going", "get", "got",
275
+ "my", "me", "we", "us", "it", "its", "this", "that",
276
+ "and", "or", "but", "if", "in", "on", "at", "by", "for",
277
+ "with", "about", "as", "of", "from",
278
+ }
279
+ words = re.findall(r"[a-z]+", text.lower())
280
+ return {w for w in words if w not in stop_words and len(w) > 2}
281
+
282
+
283
+ def _jaccard_similarity(a: set, b: set) -> float:
284
+ if not a or not b:
285
+ return 0.0
286
+ return len(a & b) / len(a | b)
287
+
288
+
289
+ class GoalDeduplicator:
290
+ """Check and merge candidate goals against the active goal set."""
291
+
292
+ DUPLICATE_THRESHOLD = 0.90
293
+ MERGE_THRESHOLD = 0.70
294
+
295
+ def check(
296
+ self,
297
+ candidate: InferenceCandidate,
298
+ existing: List[Goal],
299
+ ) -> Optional[GoalSimilarity]:
300
+ """
301
+ Return a GoalSimilarity if the candidate overlaps with an existing goal.
302
+ Returns None if the candidate is distinct from all existing goals.
303
+ """
304
+ if not existing:
305
+ return None
306
+
307
+ candidate_tokens = _tokenize(candidate.title + " " + candidate.description)
308
+ best: Optional[GoalSimilarity] = None
309
+
310
+ for goal in existing:
311
+ if goal.is_terminal():
312
+ continue
313
+ goal_tokens = _tokenize(goal.title + " " + goal.description)
314
+ score = _jaccard_similarity(candidate_tokens, goal_tokens)
315
+ shared = sorted(candidate_tokens & goal_tokens)
316
+
317
+ if score >= self.MERGE_THRESHOLD:
318
+ if score >= self.DUPLICATE_THRESHOLD:
319
+ rec = "duplicate"
320
+ else:
321
+ rec = "merge"
322
+
323
+ sim = GoalSimilarity(
324
+ goal_id_a="candidate",
325
+ goal_id_b=goal.goal_id,
326
+ similarity_score=score,
327
+ shared_keywords=shared[:10],
328
+ recommendation=rec,
329
+ )
330
+ if best is None or score > best.similarity_score:
331
+ best = sim
332
+
333
+ return best
334
+
335
+ def merge(self, base: Goal, candidate: InferenceCandidate) -> Goal:
336
+ """Enrich an existing goal with additional context from a new candidate."""
337
+ # Extend description if the candidate has more detail
338
+ if len(candidate.description) > len(base.description):
339
+ base.description = candidate.description
340
+
341
+ # Adopt deadline if base has none
342
+ if candidate.suggested_deadline and base.deadline is None:
343
+ base.context["inferred_deadline_hint"] = candidate.suggested_deadline
344
+
345
+ # Upgrade priority if candidate is higher
346
+ if candidate.priority.value > base.priority.value:
347
+ base.priority = candidate.priority
348
+
349
+ # Merge source message IDs into context
350
+ existing_msgs = base.context.get("source_messages", [])
351
+ combined = list(set(existing_msgs + candidate.source_messages))
352
+ base.context["source_messages"] = combined
353
+
354
+ return base
apsimo/goals/models.py ADDED
@@ -0,0 +1,302 @@
1
+ """Colony Goal Engine — core data models.
2
+
3
+ Defines Goal, Subtask, GoalDAG and supporting types for the
4
+ Colony DAG-based goal decomposition and lifecycle management system.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import uuid
10
+ from dataclasses import dataclass, field
11
+ from datetime import datetime, timezone
12
+ from enum import Enum
13
+ from typing import Any, Dict, List, Optional
14
+
15
+
16
+ class GoalStatus(str, Enum):
17
+ """Goal lifecycle states."""
18
+ PROPOSED = "proposed" # Inferred or suggested; awaiting user acceptance
19
+ ACCEPTED = "accepted" # User confirmed; ready for decomposition
20
+ ACTIVE = "active" # Decomposed and executing
21
+ BLOCKED = "blocked" # Cannot proceed; waiting on external factor
22
+ COMPLETED = "completed" # All subtasks done; outcome achieved
23
+ ABANDONED = "abandoned" # Given up; reason recorded
24
+
25
+
26
+ class GoalSource(str, Enum):
27
+ """How the goal was created."""
28
+ EXPLICIT = "explicit" # User stated the goal directly
29
+ INFERRED = "inferred" # Detected from conversation context
30
+ RECURRING = "recurring" # Generated from a recurring schedule
31
+ DELEGATED = "delegated" # Sent by another Colony agent via federation
32
+
33
+
34
+ class GoalPriority(int, Enum):
35
+ """Goal scheduling priority. Higher = more urgent."""
36
+ BACKGROUND = 0
37
+ LOW = 10
38
+ NORMAL = 50
39
+ HIGH = 80
40
+ CRITICAL = 100
41
+
42
+
43
+ class SubtaskStatus(str, Enum):
44
+ """Subtask execution states within a goal's DAG."""
45
+ PENDING = "pending" # Not yet dispatched
46
+ DISPATCHED = "dispatched" # Job posted to task queue
47
+ RUNNING = "running" # Job claimed by a worker
48
+ COMPLETED = "completed" # Job finished successfully
49
+ FAILED = "failed" # Job failed; may trigger replan
50
+ SKIPPED = "skipped" # Bypassed via replan strategy
51
+ BLOCKED = "blocked" # Waiting on dependency subtasks
52
+
53
+
54
+ @dataclass
55
+ class GoalOutcome:
56
+ """Describes the desired end state of a completed goal."""
57
+ description: str
58
+ success_criteria: List[str] = field(default_factory=list)
59
+ measurable: bool = False
60
+ target_value: Optional[float] = None
61
+ target_unit: Optional[str] = None
62
+
63
+
64
+ @dataclass
65
+ class Goal:
66
+ """A user-level objective tracked by the Goal Engine.
67
+
68
+ Attributes:
69
+ goal_id: Globally unique identifier.
70
+ title: Short human-readable label (≤ 80 chars).
71
+ description: Full description of the objective.
72
+ source: How this goal was created.
73
+ status: Current lifecycle state.
74
+ priority: Scheduling priority score.
75
+ outcome: Desired end state description.
76
+ deadline: Optional absolute deadline.
77
+ parent_goal_id: If this is a sub-goal, the parent's ID.
78
+ tags: Arbitrary metadata for filtering and grouping.
79
+ context: Conversation or event context that generated this goal.
80
+ created_at: When the goal was created.
81
+ updated_at: When the goal was last modified.
82
+ accepted_at: When the user accepted the goal.
83
+ completed_at: When the goal reached COMPLETED.
84
+ abandoned_at: When the goal was abandoned.
85
+ abandon_reason: Why the goal was abandoned.
86
+ replan_count: Number of times the DAG has been replanned.
87
+ estimated_hours: Current completion time estimate in hours.
88
+ progress_pct: Fraction of subtasks completed (0.0–1.0).
89
+ """
90
+ goal_id: str = field(default_factory=lambda: str(uuid.uuid4()))
91
+ title: str = ""
92
+ description: str = ""
93
+ source: GoalSource = GoalSource.EXPLICIT
94
+ status: GoalStatus = GoalStatus.PROPOSED
95
+ priority: GoalPriority = GoalPriority.NORMAL
96
+ outcome: Optional[GoalOutcome] = None
97
+ deadline: Optional[datetime] = None
98
+ parent_goal_id: Optional[str] = None
99
+ tags: Dict[str, str] = field(default_factory=dict)
100
+ context: Dict[str, Any] = field(default_factory=dict)
101
+ created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
102
+ updated_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
103
+ accepted_at: Optional[datetime] = None
104
+ completed_at: Optional[datetime] = None
105
+ abandoned_at: Optional[datetime] = None
106
+ abandon_reason: Optional[str] = None
107
+ replan_count: int = 0
108
+ estimated_hours: Optional[float] = None
109
+ progress_pct: float = 0.0
110
+
111
+ # Initiative management fields (v0.7.10)
112
+ last_initiative_at: Optional[datetime] = None
113
+ snoozed_until: Optional[datetime] = None
114
+ snooze_count: int = 0
115
+ dismissal_reason: Optional[str] = None
116
+
117
+ def is_terminal(self) -> bool:
118
+ # Compare by string value so the check is robust if GoalStatus is patched
119
+ # in a test environment (e.g. pytest-xdist workers that share sys.modules).
120
+ status_val = self.status.value if hasattr(self.status, "value") else str(self.status)
121
+ return status_val in ("completed", "abandoned")
122
+
123
+ def is_overdue(self) -> bool:
124
+ if self.deadline is None:
125
+ return False
126
+ return datetime.now(timezone.utc) > self.deadline
127
+
128
+ def age_hours(self) -> float:
129
+ return (datetime.now(timezone.utc) - self.created_at).total_seconds() / 3600.0
130
+
131
+
132
+ @dataclass
133
+ class Subtask:
134
+ """A concrete unit of work within a goal's DAG.
135
+
136
+ Attributes:
137
+ subtask_id: Unique identifier within the goal.
138
+ goal_id: Parent goal.
139
+ title: Human-readable description.
140
+ job_type: Task queue job type for dispatch.
141
+ payload: Job-specific parameters.
142
+ capabilities: Required worker capabilities.
143
+ depends_on: List of subtask_ids that must complete first.
144
+ status: Current execution state.
145
+ job_id: Task queue job_id if dispatched.
146
+ result: Job result if completed.
147
+ depth: DAG depth level (root = 0).
148
+ is_critical_path: True if this subtask is on the critical path.
149
+ retry_count: How many times this subtask has been retried.
150
+ max_retries: Maximum retries before escalating to replan.
151
+ estimated_hours: Expected execution duration.
152
+ started_at: When the job started executing.
153
+ completed_at: When the job completed.
154
+ error: Error message if failed.
155
+ """
156
+ subtask_id: str = field(default_factory=lambda: str(uuid.uuid4()))
157
+ goal_id: str = ""
158
+ title: str = ""
159
+ job_type: str = "custom"
160
+ payload: Dict[str, Any] = field(default_factory=dict)
161
+ capabilities: List[str] = field(default_factory=list)
162
+ depends_on: List[str] = field(default_factory=list)
163
+ status: SubtaskStatus = SubtaskStatus.PENDING
164
+ job_id: Optional[str] = None
165
+ result: Optional[Dict[str, Any]] = None
166
+ depth: int = 0
167
+ is_critical_path: bool = False
168
+ retry_count: int = 0
169
+ max_retries: int = 2
170
+ estimated_hours: Optional[float] = None
171
+ started_at: Optional[datetime] = None
172
+ completed_at: Optional[datetime] = None
173
+ error: Optional[str] = None
174
+
175
+ def is_ready(self, completed_ids: set) -> bool:
176
+ """Return True when all dependencies have completed."""
177
+ return all(dep in completed_ids for dep in self.depends_on)
178
+
179
+ def duration_hours(self) -> Optional[float]:
180
+ if self.started_at and self.completed_at:
181
+ return (self.completed_at - self.started_at).total_seconds() / 3600.0
182
+ return None
183
+
184
+
185
+ @dataclass
186
+ class GoalDAG:
187
+ """The directed acyclic graph of subtasks for a single goal.
188
+
189
+ Attributes:
190
+ goal_id: Parent goal identifier.
191
+ subtasks: All subtask nodes keyed by subtask_id.
192
+ root_ids: Subtask IDs with no dependencies (entry points).
193
+ leaf_ids: Subtask IDs with no dependents (exit points).
194
+ critical_path: Ordered list of subtask_ids on the critical path.
195
+ max_depth: Deepest level in the DAG.
196
+ created_at: When this DAG version was created.
197
+ version: Incremented on each replan.
198
+ """
199
+ goal_id: str
200
+ subtasks: Dict[str, Subtask] = field(default_factory=dict)
201
+ root_ids: List[str] = field(default_factory=list)
202
+ leaf_ids: List[str] = field(default_factory=list)
203
+ critical_path: List[str] = field(default_factory=list)
204
+ max_depth: int = 0
205
+ created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
206
+ version: int = 1
207
+
208
+ def add_subtask(self, subtask: Subtask) -> None:
209
+ self.subtasks[subtask.subtask_id] = subtask
210
+
211
+ def ready_subtasks(self) -> List[Subtask]:
212
+ """Return all PENDING subtasks whose dependencies are met."""
213
+ completed = {
214
+ sid for sid, s in self.subtasks.items()
215
+ if s.status == SubtaskStatus.COMPLETED
216
+ }
217
+ return [
218
+ s for s in self.subtasks.values()
219
+ if s.status == SubtaskStatus.PENDING and s.is_ready(completed)
220
+ ]
221
+
222
+ def is_complete(self) -> bool:
223
+ return all(
224
+ s.status in {SubtaskStatus.COMPLETED, SubtaskStatus.SKIPPED}
225
+ for s in self.subtasks.values()
226
+ )
227
+
228
+ def completion_fraction(self) -> float:
229
+ if not self.subtasks:
230
+ return 0.0
231
+ done = sum(
232
+ 1 for s in self.subtasks.values()
233
+ if s.status in {SubtaskStatus.COMPLETED, SubtaskStatus.SKIPPED}
234
+ )
235
+ return done / len(self.subtasks)
236
+
237
+ def validate(self) -> List[str]:
238
+ """Validate DAG structure. Returns list of error messages."""
239
+ errors: List[str] = []
240
+ ids = set(self.subtasks.keys())
241
+
242
+ # All dependencies must reference known subtasks
243
+ for s in self.subtasks.values():
244
+ for dep in s.depends_on:
245
+ if dep not in ids:
246
+ errors.append(f"Subtask {s.subtask_id} has unknown dep {dep}")
247
+ # No self-dependency
248
+ if s.subtask_id in s.depends_on:
249
+ errors.append(f"Subtask {s.subtask_id} depends on itself")
250
+
251
+ # No cycles (DFS-based check)
252
+ visited: set = set()
253
+ rec_stack: set = set()
254
+
255
+ def has_cycle(node_id: str) -> bool:
256
+ visited.add(node_id)
257
+ rec_stack.add(node_id)
258
+ node = self.subtasks.get(node_id)
259
+ deps = node.depends_on if node else []
260
+ for dep in deps:
261
+ if dep not in visited:
262
+ if has_cycle(dep):
263
+ return True
264
+ elif dep in rec_stack:
265
+ return True
266
+ rec_stack.discard(node_id)
267
+ return False
268
+
269
+ for sid in ids:
270
+ if sid not in visited:
271
+ if has_cycle(sid):
272
+ errors.append(f"Cycle detected involving subtask {sid}")
273
+ break
274
+
275
+ return errors
276
+
277
+
278
+ @dataclass
279
+ class GoalTransitionRecord:
280
+ """An audit record for a goal state transition."""
281
+ goal_id: str
282
+ from_status: str
283
+ to_status: str
284
+ trigger: str
285
+ created_at: datetime
286
+ metadata: Dict[str, Any] = field(default_factory=dict)
287
+
288
+
289
+ @dataclass
290
+ class GoalSummary:
291
+ """Human-readable summary for the briefing system."""
292
+ goal_id: str
293
+ title: str
294
+ status: str
295
+ priority: int
296
+ progress_pct: float
297
+ estimated_hours: Optional[float]
298
+ deadline: Optional[datetime]
299
+ subtask_count: int
300
+ completed_subtasks: int
301
+ is_overdue: bool
302
+ replan_count: int