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,16 @@
1
+ """Concrete JobHandler subclasses for Colony's distributed task queue."""
2
+
3
+ from apsimo.task_queue.handlers.inference import (
4
+ InferenceHandler,
5
+ ThoughtOnlyInferenceHandler,
6
+ )
7
+ from apsimo.task_queue.handlers.monitoring import MonitoringHandler
8
+ from apsimo.task_queue.handlers.system_maintenance import SystemMaintenanceHandler
9
+ from apsimo.task_queue.handlers.registry import build_default_handlers
10
+
11
+ __all__ = [
12
+ "InferenceHandler", "ThoughtOnlyInferenceHandler",
13
+ "MonitoringHandler",
14
+ "SystemMaintenanceHandler",
15
+ "build_default_handlers",
16
+ ]
@@ -0,0 +1,37 @@
1
+ """Shared helpers for job handlers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import logging
7
+ from typing import Any, Dict
8
+
9
+ from apsimo.task_queue.worker import JobHandler
10
+ from apsimo.task_queue.models import Job
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ async def run_subprocess(
16
+ cmd: list[str], timeout: float
17
+ ) -> tuple[int, str, str]:
18
+ """Run a subprocess, capture stdout/stderr, and return (returncode, stdout, stderr)."""
19
+ proc = await asyncio.create_subprocess_exec(
20
+ *cmd,
21
+ stdout=asyncio.subprocess.PIPE,
22
+ stderr=asyncio.subprocess.PIPE,
23
+ )
24
+ try:
25
+ stdout_b, stderr_b = await asyncio.wait_for(
26
+ proc.communicate(), timeout=timeout
27
+ )
28
+ except asyncio.TimeoutError:
29
+ proc.kill()
30
+ await proc.communicate()
31
+ raise asyncio.TimeoutError(
32
+ f"Subprocess {cmd[0]!r} timed out after {timeout}s"
33
+ )
34
+ return proc.returncode, stdout_b.decode(), stderr_b.decode()
35
+
36
+
37
+ __all__ = ["JobHandler", "Job", "run_subprocess"]
@@ -0,0 +1,640 @@
1
+ """InferenceHandler — context-enriched LLM inference via the Colony router."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import json
7
+ import logging
8
+ import os
9
+ import secrets
10
+ import time
11
+ import uuid as _uuid_mod
12
+ from types import SimpleNamespace
13
+ from typing import Any, Dict, List, Optional, TYPE_CHECKING
14
+
15
+ from apsimo.task_queue.handlers.base import JobHandler, Job
16
+ from apsimo.task_queue.models import JobType
17
+
18
+ if TYPE_CHECKING:
19
+ from apsimo.router.router import LLMRouter
20
+ from apsimo.world_model.store import WorldModelStore
21
+ from apsimo.world_model.entities import BaseEntity
22
+ from apsimo.contacts.store import ContactStore
23
+ from apsimo.contacts.models import Contact
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+ _COLONY_IDENTITY = (
28
+ "You are Apsimo, an intelligent personal AI assistant. "
29
+ "You help the user manage their relationships, tasks, and knowledge. "
30
+ "You have access to context about the user's world and the people in it."
31
+ )
32
+
33
+ _ENTITY_TYPE_MAP = {
34
+ "person": "PersonEntity",
35
+ "company": "CompanyEntity",
36
+ "location": "LocationEntity",
37
+ "product": "ProductEntity",
38
+ }
39
+
40
+
41
+ def _build_system_prompt(
42
+ contact: Optional["Contact"],
43
+ wm_entities: List["BaseEntity"],
44
+ explicit_prompt: Optional[str],
45
+ ) -> str:
46
+ parts = [_COLONY_IDENTITY]
47
+
48
+ if contact:
49
+ name = (
50
+ contact.display_name
51
+ or " ".join(p for p in [contact.given_name, contact.family_name] if p)
52
+ or "this contact"
53
+ )
54
+ lines = [f"\nContact context: You are communicating with {name}."]
55
+ lines.append(f"Recorded standing: {contact.trust_tier}. Follow the request's actual authority boundary.")
56
+ if contact.organization:
57
+ lines.append(f"Organization: {contact.organization}")
58
+ if contact.notes:
59
+ lines.append(f"Notes: {contact.notes}")
60
+ parts.append("\n".join(lines))
61
+
62
+ if wm_entities:
63
+ entity_lines = ["\nRelevant context from your world model:"]
64
+ for e in wm_entities[:5]:
65
+ desc = f"- {e.name} ({e.entity_type})"
66
+ bio = getattr(e, "bio_summary", None) or getattr(e, "description", None)
67
+ if bio:
68
+ desc += f": {bio}"
69
+ entity_lines.append(desc)
70
+ parts.append("\n".join(entity_lines))
71
+
72
+ if explicit_prompt:
73
+ parts.append(f"\n{explicit_prompt}")
74
+
75
+ return "\n".join(parts)
76
+
77
+
78
+ async def _update_world_model_async(
79
+ wm: "WorldModelStore",
80
+ user_text: str,
81
+ assistant_text: str,
82
+ source_id: str,
83
+ ) -> None:
84
+ """Extract entities from the exchange and write to world model.
85
+
86
+ Runs fire-and-forget. All errors are swallowed to never block the response.
87
+ """
88
+ try:
89
+ from apsimo.world_model.extraction.conversation_extractor import ConversationExtractor
90
+ from apsimo.world_model.entities import (
91
+ PersonEntity, CompanyEntity, LocationEntity, ProductEntity,
92
+ )
93
+
94
+ type_cls_map = {
95
+ "person": PersonEntity,
96
+ "company": CompanyEntity,
97
+ "location": LocationEntity,
98
+ "product": ProductEntity,
99
+ }
100
+
101
+ extractor = ConversationExtractor(min_message_length=20)
102
+ full_text = f"{user_text}\n{assistant_text}"
103
+ result = await extractor.extract(full_text, source_id=source_id)
104
+
105
+ for candidate in result.entities:
106
+ if candidate.confidence < 0.20:
107
+ continue
108
+ try:
109
+ existing = await wm.find_entities(
110
+ candidate.text,
111
+ entity_type=candidate.entity_type,
112
+ min_confidence=0.20,
113
+ limit=1,
114
+ )
115
+ if existing:
116
+ await wm.add_observation(
117
+ existing[0].id,
118
+ None,
119
+ f"Mentioned in conversation: {candidate.context_window}",
120
+ source="inference",
121
+ )
122
+ else:
123
+ entity_cls = type_cls_map.get(candidate.entity_type, PersonEntity)
124
+ ts = int(time.time() * 1000)
125
+ rand = secrets.token_hex(6)
126
+ new_entity = entity_cls(
127
+ id=f"we-{ts}-{rand}",
128
+ name=candidate.text,
129
+ confidence=candidate.confidence,
130
+ )
131
+ await wm.upsert_entity(new_entity)
132
+ await wm.add_observation(
133
+ new_entity.id,
134
+ None,
135
+ f"First mentioned in conversation: {candidate.context_window}",
136
+ source="inference",
137
+ )
138
+ logger.debug(
139
+ "World model: created entity %r (%s)",
140
+ candidate.text,
141
+ candidate.entity_type,
142
+ )
143
+ except Exception:
144
+ logger.debug(
145
+ "World model upsert failed for %r", candidate.text, exc_info=True
146
+ )
147
+
148
+ except Exception:
149
+ logger.debug("Post-inference world model update failed", exc_info=True)
150
+
151
+
152
+ class _InferenceGateSessionStore:
153
+ """Minimal session-store adapter for running the ResponseGate on task-queue jobs.
154
+
155
+ Inference jobs are internal and have no real gateway session. This adapter
156
+ creates ephemeral session records so that L1 (RecipientVerifier) and L3
157
+ (CrossContextDetector) can operate without failing on missing-session lookups.
158
+ """
159
+
160
+ def __init__(self) -> None:
161
+ self._sessions: dict = {}
162
+
163
+ def register(self, session_id: str, contact_id: str, gateway: str) -> None:
164
+ """Register an ephemeral session before calling gate.evaluate()."""
165
+ self._sessions[session_id] = SimpleNamespace(contact_id=contact_id, gateway=gateway)
166
+
167
+ async def get(self, session_id: str):
168
+ return self._sessions.get(session_id)
169
+
170
+ async def get_contact_gateways(self, contact_id: str) -> set:
171
+ return {"api"}
172
+
173
+ async def get_recent_other_sessions(
174
+ self, exclude_session_id: str, lookback_hours: int
175
+ ) -> dict:
176
+ return {}
177
+
178
+ async def get_display_name(self, contact_id: str) -> str:
179
+ return contact_id
180
+
181
+
182
+ class InferenceHandler(JobHandler):
183
+ """Run an LLM inference request via the Colony router.
184
+
185
+ Job payload keys:
186
+ messages (list[dict], optional): OpenAI-format messages list. Used as-is.
187
+ prompt (str, optional): Plain string prompt; wrapped into a user message.
188
+ One of ``messages`` or ``prompt`` must be provided.
189
+ model_tier (str): "small" | "medium" | "large" (default: "small").
190
+ system_prompt (str, optional): Additional system context appended after
191
+ the enriched Colony identity + contact + world model prompt.
192
+ max_tokens (int, optional): Limit (currently passed as context hint).
193
+ contact_id (str, optional): If provided, contact record is fetched and
194
+ included in the system prompt (name, trust tier, relationship score).
195
+
196
+ Returns:
197
+ {"result": str, "tokens_used": int, "model": str}
198
+ """
199
+
200
+ def __init__(
201
+ self,
202
+ router: "LLMRouter",
203
+ world_model_store: Optional["WorldModelStore"] = None,
204
+ contact_store: Optional["ContactStore"] = None,
205
+ response_gate: Optional[Any] = None,
206
+ gate_session_store: Optional["_InferenceGateSessionStore"] = None,
207
+ ) -> None:
208
+ self._router = router
209
+ self._wm = world_model_store
210
+ self._cs = contact_store
211
+ self._wm_connected = False
212
+ self._gate = response_gate
213
+ self._gate_sessions = gate_session_store
214
+ self._background_tasks: set = set() # strong refs to fire-and-forget tasks
215
+
216
+ async def _ensure_wm_connected(self) -> None:
217
+ """Connect the world model store on first use.
218
+
219
+ If no store was injected, create one from the default config so that
220
+ inference jobs self-initialize world model support without requiring
221
+ changes to startup code.
222
+ """
223
+ if self._wm_connected:
224
+ return
225
+ if self._wm is None:
226
+ try:
227
+ from apsimo.world_model.store import WorldModelStore
228
+ from apsimo.world_model.config import WorldModelConfig
229
+ import os
230
+ colony_home = os.environ.get("COLONY_HOME", os.path.expanduser("~/.colony"))
231
+ db_path = os.path.join(colony_home, "world_model.db")
232
+ self._wm = WorldModelStore(WorldModelConfig(sqlite_path=db_path))
233
+ except Exception:
234
+ logger.warning("Could not create WorldModelStore", exc_info=True)
235
+ return
236
+ try:
237
+ await self._wm.connect()
238
+ self._wm_connected = True
239
+ except Exception:
240
+ logger.warning("Could not connect world model store", exc_info=True)
241
+ self._wm = None
242
+
243
+ async def _execute_thought(self, job: Job) -> Dict[str, Any]:
244
+ """Execute exactly the digest-bound ThoughtJobV1 prompt contract."""
245
+
246
+ from apsimo.cognition.goal_spine import (
247
+ ThoughtJobV1,
248
+ bind_thought_output,
249
+ )
250
+ from apsimo.router.tiers import ModelTier
251
+
252
+ thought = ThoughtJobV1.from_payload(job.payload)
253
+ if job.job_id != thought.thought_job_id:
254
+ raise ValueError("thought queue job ID does not match authority digest")
255
+ messages = [
256
+ {"role": "system", "content": thought.system_prompt},
257
+ {"role": "user", "content": thought.prompt},
258
+ ]
259
+ response = await self._router.complete(
260
+ messages,
261
+ force_tier=(
262
+ None if getattr(self._router, "supports_function_routing", False) is True
263
+ else ModelTier.SMALL
264
+ ),
265
+ context={
266
+ "task": "thought_job",
267
+ "max_output_tokens": thought.max_output_tokens,
268
+ },
269
+ )
270
+ usage = response.usage or {}
271
+ tokens_used = int(usage.get("total_tokens", 0) or 0)
272
+ completion_tokens = int(
273
+ usage.get("completion_tokens", 0) or 0
274
+ )
275
+ if completion_tokens > thought.max_output_tokens:
276
+ raise ValueError("thought output exceeded its token budget")
277
+ bound_output = bind_thought_output(response.content, thought)
278
+ canonical_output = json.dumps(
279
+ bound_output.payload,
280
+ sort_keys=True,
281
+ separators=(",", ":"),
282
+ ensure_ascii=False,
283
+ allow_nan=False,
284
+ )
285
+ return {
286
+ "status": "completed",
287
+ "summary": canonical_output,
288
+ "action_plane": {"state": "completed"},
289
+ "result": canonical_output,
290
+ "thought_output": canonical_output,
291
+ "tokens_used": tokens_used,
292
+ "completion_tokens": completion_tokens,
293
+ "model": response.model_id,
294
+ }
295
+
296
+ async def _gate_context(
297
+ self,
298
+ messages: list[dict],
299
+ user_text: str,
300
+ force_tier: Optional[Any],
301
+ payload: Dict[str, Any],
302
+ *,
303
+ router_context: Optional[Dict[str, Any]] = None,
304
+ ) -> list[dict]:
305
+ """Shrink oversized message content to the target tier's useful window.
306
+
307
+ Uses the context gate (:mod:`apsimo.contextgate`): when the
308
+ assembled messages exceed the tier's ``useful_context_tokens`` the
309
+ largest message content is chunked and retrieved/sampled down to
310
+ budget. No-op when the gate is off, the budget is unknown, or the
311
+ input already fits. Jobs may opt out via ``payload["context_gate"]
312
+ = "off"`` and may pass an explicit ``payload["query"]`` to focus
313
+ retrieval.
314
+ """
315
+ if payload.get("context_gate") == "off":
316
+ return messages
317
+ try:
318
+ from apsimo.contextgate import (
319
+ GateConfig,
320
+ estimate_tokens,
321
+ prepare_context,
322
+ )
323
+
324
+ gcfg = GateConfig.from_env()
325
+ if gcfg.mode == "off":
326
+ return messages
327
+
328
+ # Budget from the tier the call will actually use
329
+ tier = force_tier
330
+ if force_tier is None and getattr(self._router, 'supports_function_routing', False) is True:
331
+ tier_cfg = self._router.function_config(context=router_context)
332
+ tier = getattr(tier_cfg, "tier", None)
333
+ else:
334
+ if tier is None:
335
+ try:
336
+ tier = self._router.route(user_text, router_context or {})[0]
337
+ except Exception:
338
+ return messages
339
+ tier_cfg = getattr(self._router, "tier_config", lambda _t: None)(tier)
340
+ budget = getattr(tier_cfg, "useful_context_tokens", 0) or gcfg.default_budget_tokens
341
+ if budget <= 0:
342
+ return messages
343
+
344
+ est_total = sum(
345
+ estimate_tokens(str(m.get("content") or "")) for m in messages
346
+ )
347
+ if est_total <= budget * gcfg.headroom:
348
+ return messages
349
+
350
+ # Gate the single largest content block (usually the document)
351
+ idx = max(
352
+ range(len(messages)),
353
+ key=lambda i: len(str(messages[i].get("content") or "")),
354
+ )
355
+ big = str(messages[idx].get("content") or "")
356
+ # Query focus: explicit payload query wins; if the oversized
357
+ # message is itself the user turn, its tail usually carries the
358
+ # actual question, otherwise the whole user text is the query.
359
+ query = payload.get("query") or (
360
+ user_text[-1000:] if big == user_text else user_text[:2000]
361
+ )
362
+ overhead = est_total - estimate_tokens(big)
363
+ prepared = await prepare_context(
364
+ content=big,
365
+ query=query,
366
+ budget_tokens=max(1, budget - overhead),
367
+ task_kind=payload.get("task_kind"),
368
+ config=gcfg,
369
+ )
370
+ from apsimo.contextgate import GateDecision
371
+
372
+ if prepared.decision != GateDecision.PASS_THROUGH:
373
+ gated = list(messages)
374
+ gated[idx] = {**messages[idx], "content": prepared.text}
375
+ logger.info(
376
+ "Inference context gated: %s, %d -> %d est tokens (tier=%s)",
377
+ prepared.decision.value,
378
+ prepared.est_tokens_in,
379
+ prepared.est_tokens_out,
380
+ getattr(tier, "value", tier),
381
+ )
382
+ return gated
383
+ return messages
384
+ except Exception:
385
+ logger.warning("Context gate failed — sending messages ungated", exc_info=True)
386
+ return messages
387
+
388
+ async def execute(self, job: Job) -> Dict[str, Any]:
389
+ payload = job.payload
390
+ if (
391
+ job.job_type is JobType.THOUGHT
392
+ or payload.get("schema") == "ThoughtJobV1"
393
+ ):
394
+ if job.job_type is not JobType.THOUGHT:
395
+ raise ValueError("ThoughtJobV1 requires the thought job type")
396
+ return await self._execute_thought(job)
397
+ cognition_read_only = payload.get("cognition_read_only") is True
398
+ read_capabilities = set(payload.get("allowed_read_capabilities") or ())
399
+ if cognition_read_only:
400
+ invalid = [
401
+ capability for capability in read_capabilities
402
+ if capability not in {
403
+ "concerns:read", "directives:read", "memory:read",
404
+ "projects:read", "reasoning", "situation:read",
405
+ "web:read", "world_model:read",
406
+ }
407
+ ]
408
+ if invalid:
409
+ raise ValueError("read-only cognition job contains invalid capabilities")
410
+ model_tier = payload.get("model_tier")
411
+ contact_id: Optional[str] = payload.get("contact_id")
412
+ explicit_system = payload.get("system_prompt")
413
+
414
+ # ── Resolve tier ───────────────────────────────────────────────────
415
+ force_tier = None
416
+ try:
417
+ from apsimo.router.tiers import ModelTier
418
+ force_tier = ModelTier(model_tier)
419
+ except (ImportError, ValueError):
420
+ pass
421
+
422
+ # ── Extract user text ──────────────────────────────────────────────
423
+ if "messages" in payload:
424
+ messages_in: list[dict] = list(payload["messages"])
425
+ user_text = next(
426
+ (m.get("content", "") for m in reversed(messages_in) if m.get("role") == "user"),
427
+ "",
428
+ )
429
+ else:
430
+ user_text = payload.get("prompt", "")
431
+ messages_in = None
432
+
433
+ # ── Ensure world model is connected ────────────────────────────────
434
+ if not cognition_read_only or "world_model:read" in read_capabilities:
435
+ await self._ensure_wm_connected()
436
+
437
+ # ── Contact lookup ─────────────────────────────────────────────────
438
+ contact: Optional["Contact"] = None
439
+ if contact_id and self._cs and not cognition_read_only:
440
+ try:
441
+ contact = await self._cs.get(contact_id)
442
+ except Exception:
443
+ logger.debug("Contact lookup failed for %s", contact_id, exc_info=True)
444
+
445
+ # ── World model entity search ──────────────────────────────────────
446
+ # Extract entity names from the user message first, then look each up
447
+ # individually. Passing the full message as an FTS query fails because
448
+ # stop words prevent name matching.
449
+ wm_entities: List["BaseEntity"] = []
450
+ if self._wm and user_text and (
451
+ not cognition_read_only or "world_model:read" in read_capabilities
452
+ ):
453
+ try:
454
+ from apsimo.world_model.extraction.conversation_extractor import (
455
+ ConversationExtractor,
456
+ )
457
+ extractor = ConversationExtractor(min_message_length=5)
458
+ extraction = await extractor.extract(user_text, source_id="pre-inference")
459
+ seen_ids: set = set()
460
+ for candidate in extraction.entities[:8]:
461
+ hits = await self._wm.find_entities(
462
+ candidate.text, limit=2, min_confidence=0.20
463
+ )
464
+ for e in hits:
465
+ if e.id not in seen_ids:
466
+ seen_ids.add(e.id)
467
+ wm_entities.append(e)
468
+ if len(wm_entities) >= 5:
469
+ break
470
+ if len(wm_entities) >= 5:
471
+ break
472
+ except Exception:
473
+ logger.debug("World model query failed", exc_info=True)
474
+
475
+ # ── Build enriched system prompt ───────────────────────────────────
476
+ enriched_system = _build_system_prompt(contact, wm_entities, explicit_system)
477
+
478
+ # ── Assemble messages ──────────────────────────────────────────────
479
+ if messages_in is not None:
480
+ messages: list[dict] = messages_in
481
+ if not any(m.get("role") == "system" for m in messages):
482
+ messages.insert(0, {"role": "system", "content": enriched_system})
483
+ else:
484
+ messages = [
485
+ {"role": "system", "content": enriched_system},
486
+ {"role": "user", "content": user_text},
487
+ ]
488
+
489
+ router_context: Dict[str, Any] = {}
490
+ if cognition_read_only:
491
+ router_context = {
492
+ "task": "thought_job",
493
+ "max_output_tokens": int(payload.get("max_output_tokens") or 768),
494
+ }
495
+
496
+ # ── Context gate: fit input to the dispatch role's useful window ──
497
+ messages = await self._gate_context(
498
+ messages, user_text, force_tier, payload, router_context=router_context,
499
+ )
500
+
501
+ # ── LLM call ──────────────────────────────────────────────────────
502
+ response = await self._router.complete(
503
+ messages, force_tier=force_tier, context=router_context,
504
+ )
505
+ tokens_used = response.usage.get("total_tokens", 0) if response.usage else 0
506
+ completion_tokens = (
507
+ response.usage.get("completion_tokens", 0) if response.usage else 0
508
+ )
509
+ if cognition_read_only and completion_tokens > int(
510
+ payload.get("max_output_tokens") or 768
511
+ ):
512
+ raise ValueError("thought output exceeded its token budget")
513
+
514
+ # ── GAP-14: Log tier selection + feed RouterSelfLearner ────────────
515
+ # Logging lets ops trace routing decisions; record_outcome lets the
516
+ # self-learner improve future tier thresholds from inference-path data.
517
+ logger.info(
518
+ "Inference tier=%s model=%s tokens=%d cost_usd=%.6f latency_ms=%d job_id=%s",
519
+ response.tier_used.value,
520
+ response.model_id,
521
+ tokens_used,
522
+ response.cost_usd,
523
+ response.latency_ms,
524
+ getattr(job, "job_id", "?"),
525
+ )
526
+ # Latency/tokens above are telemetry. Do not self-award a perfect
527
+ # quality label merely because the model returned; RouterSelfLearner
528
+ # must wait for downstream or owner evidence.
529
+
530
+ # Queue inference is an observation path. It must not hide a world-
531
+ # model mutation behind a read-looking completion. Entity ingestion
532
+ # remains owned by the explicit conversation/memory ingestion paths.
533
+ job_id_str = str(getattr(job, "job_id", None) or getattr(job, "id", _uuid_mod.uuid4()))
534
+
535
+ # ── Response Gate evaluation ────────────────────────────────────────
536
+ if (not cognition_read_only
537
+ and self._gate is not None and self._gate_sessions is not None):
538
+ from apsimo.gate.models import GatePayload
539
+ from apsimo.intelligence.relationships.trust_tiers import TrustTier
540
+
541
+ session_id = payload.get("session_id") or f"inf-{job_id_str}"
542
+ gateway = payload.get("gateway", "api")
543
+ gate_contact_id = contact_id or "internal"
544
+
545
+ # Resolve trust tier from contact record (fall back to PERIPHERAL)
546
+ tier = TrustTier.PERIPHERAL
547
+ if contact is not None:
548
+ try:
549
+ tier = TrustTier(contact.trust_tier)
550
+ except (ValueError, TypeError):
551
+ pass
552
+
553
+ self._gate_sessions.register(session_id, gate_contact_id, gateway)
554
+ gate_payload = GatePayload(
555
+ response_text=response.content,
556
+ target_contact_id=gate_contact_id,
557
+ target_gateway=gateway,
558
+ session_id=session_id,
559
+ trust_tier=tier,
560
+ mentioned_entities=frozenset(e.name for e in wm_entities),
561
+ turn_id=job_id_str,
562
+ incoming_message_text=user_text,
563
+ )
564
+ try:
565
+ gate_decision = await self._gate.evaluate(gate_payload)
566
+ if gate_decision.blocked:
567
+ logger.warning(
568
+ "Gate blocked inference response: layer=%d reason=%s turn_id=%s",
569
+ gate_decision.blocking_layer,
570
+ gate_decision.block_reason,
571
+ job_id_str,
572
+ )
573
+ return {
574
+ "status": "skipped",
575
+ "summary": "response blocked by the configured response gate",
576
+ "action_plane": {"state": "skipped"},
577
+ "result": f"[Response blocked by gate layer {gate_decision.blocking_layer}: {gate_decision.block_reason}]",
578
+ "tokens_used": tokens_used,
579
+ "model": response.model_id,
580
+ "gate_blocked": True,
581
+ "gate_reason": gate_decision.block_reason,
582
+ "gate_layer": gate_decision.blocking_layer,
583
+ }
584
+ logger.debug(
585
+ "Gate passed inference response: turn_id=%s layers_evaluated=7",
586
+ job_id_str,
587
+ )
588
+ except Exception:
589
+ logger.warning(
590
+ "Gate evaluation failed; applying recipient fallback: turn_id=%s",
591
+ job_id_str,
592
+ exc_info=True,
593
+ )
594
+ owner_id = os.environ.get(
595
+ "COLONY_OWNER_PERSON_ID",
596
+ os.environ.get("COLONY_OWNER_CONTACT_ID", "owner"),
597
+ ).strip() or "owner"
598
+ if contact_id and contact_id not in {"internal", owner_id}:
599
+ return {
600
+ "status": "skipped",
601
+ "summary": (
602
+ "response held because the recipient gate was "
603
+ "unavailable"
604
+ ),
605
+ "action_plane": {"state": "skipped"},
606
+ "result": "[Response held for recipient review]",
607
+ "tokens_used": tokens_used,
608
+ "model": response.model_id,
609
+ "gate_blocked": True,
610
+ "gate_reason": "recipient_gate_unavailable",
611
+ }
612
+
613
+ return {
614
+ "status": "completed",
615
+ "summary": response.content,
616
+ "action_plane": {"state": "completed"},
617
+ "result": response.content,
618
+ "tokens_used": tokens_used,
619
+ "completion_tokens": completion_tokens,
620
+ "model": response.model_id,
621
+ }
622
+
623
+
624
+ class ThoughtOnlyInferenceHandler(InferenceHandler):
625
+ """P3 handler that refuses every non-ThoughtJobV1 lane.
626
+
627
+ This is a deployment boundary, not merely a branch in the generic
628
+ inference handler. A worker carrying only this handler can advertise only
629
+ ``thought`` and cannot race general workers for other queue lanes.
630
+ """
631
+
632
+ thought_only = True
633
+ handler_contract = "thought_only:v1"
634
+
635
+ async def execute(self, job: Job) -> Dict[str, Any]:
636
+ if job.job_type is not JobType.THOUGHT:
637
+ raise ValueError("thought-only handler refuses non-thought job type")
638
+ if (job.payload or {}).get("schema") != "ThoughtJobV1":
639
+ raise ValueError("thought-only handler requires ThoughtJobV1")
640
+ return await self._execute_thought(job)