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,297 @@
1
+ """User-manager autostart for one validated private instance, without a supervisor."""
2
+ from __future__ import annotations
3
+
4
+ import hashlib
5
+ import json
6
+ import os
7
+ from pathlib import Path
8
+ import plistlib
9
+ import re
10
+ import socket
11
+ import subprocess
12
+ import sys
13
+ import tempfile
14
+ import time
15
+
16
+
17
+ class ServiceError(RuntimeError):
18
+ pass
19
+
20
+
21
+ def _private_write(path, content):
22
+ path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
23
+ fd, name = tempfile.mkstemp(prefix='.service-', dir=path.parent)
24
+ try:
25
+ with os.fdopen(fd, 'wb') as stream:
26
+ stream.write(content)
27
+ stream.flush()
28
+ os.fsync(stream.fileno())
29
+ os.replace(name, path)
30
+ finally:
31
+ Path(name).unlink(missing_ok=True)
32
+
33
+
34
+ def _systemd_quote(value):
35
+ value = str(value)
36
+ if any(c in value for c in '\n\r\0'):
37
+ raise ServiceError('Service paths must not contain line breaks or NUL')
38
+ return '"' + value.replace('\\', '\\\\').replace('"', '\\"').replace('%', '%%') + '"'
39
+
40
+
41
+ def _systemd_path(value):
42
+ # These scalar settings take the whole path, unlike ExecStart's argv parser.
43
+ value = str(value)
44
+ if any(c in value for c in '\n\r\0') or value.rstrip() != value or value.endswith('\\'):
45
+ raise ServiceError('Unsupported line ending in service path')
46
+ return value.replace('%', '%%')
47
+
48
+
49
+ class InstanceService:
50
+ def __init__(self, state, hermes_home, *, python=None, platform=None, home=None, runner=None):
51
+ self.state = Path(state).resolve()
52
+ self.hermes_home = Path(hermes_home).resolve()
53
+ # Resolving a venv's python symlink would select the system interpreter.
54
+ self.python = os.path.abspath(python or sys.executable)
55
+ self.platform = platform or sys.platform
56
+ self.home = Path(home or Path.home())
57
+ suffix = hashlib.sha256(os.fsencode(self.state)).hexdigest()[:20]
58
+ self.label = 'ai.apsimo.instance.' + suffix
59
+ if self.platform == 'darwin':
60
+ self.name = self.label + '.plist'
61
+ self.link = self.home / 'Library/LaunchAgents' / self.name
62
+ self.target = f'gui/{os.getuid()}/{self.label}'
63
+ elif self.platform.startswith('linux'):
64
+ self.name = 'apsimo-' + suffix + '.service'
65
+ config = Path(os.environ.get('XDG_CONFIG_HOME') or self.home / '.config')
66
+ self.link = config / 'systemd/user' / self.name
67
+ self.target = self.name
68
+ else:
69
+ raise ServiceError('Instance autostart supports Linux systemd user services and macOS launchd')
70
+ # Reuse the exact existing service identity instead of registering a
71
+ # second writer during a package rename. Fresh instances use Apsimo.
72
+ legacy_label = 'ai.colony.instance.' + suffix
73
+ legacy_name = (legacy_label + '.plist' if self.platform == 'darwin'
74
+ else 'colony-' + suffix + '.service')
75
+ legacy_link = self.link.with_name(legacy_name)
76
+ legacy_definition = self.state / 'service' / legacy_name
77
+ if (legacy_link.exists() or legacy_link.is_symlink()):
78
+ if self.link.exists() or self.link.is_symlink():
79
+ raise ServiceError('Both legacy and canonical instance services exist; reconcile them before selection')
80
+ self.label, self.name, self.link = legacy_label, legacy_name, legacy_link
81
+ self.target = (f'gui/{os.getuid()}/{self.label}' if self.platform == 'darwin' else self.name)
82
+ elif legacy_definition.exists() and not (self.state / 'service' / self.name).exists():
83
+ self.label, self.name, self.link = legacy_label, legacy_name, legacy_link
84
+ self.target = (f'gui/{os.getuid()}/{self.label}' if self.platform == 'darwin' else self.name)
85
+ self.definition = self.state / 'service' / self.name
86
+ self.backup = self.definition.with_name(self.name + '.previous')
87
+ self.log = self.state / 'service' / 'sidecar.log'
88
+ self.runner = runner or subprocess.run
89
+
90
+ @classmethod
91
+ def selected(cls):
92
+ from apsimo.util.instance import load_environment
93
+ # A legacy state-dir environment is never sufficient to own a service.
94
+ if not os.environ.get('COLONY_STATE_DIR'):
95
+ load_environment()
96
+ if not os.environ.get('COLONY_STATE_DIR'):
97
+ raise ServiceError('Select a configured private instance with --instance')
98
+ os.environ['COLONY_INSTANCE_SELECTED'] = '1'
99
+ load_environment()
100
+ state = Path(os.environ['COLONY_STATE_DIR'])
101
+ manifest = json.loads((state / 'instance.json').read_text())
102
+ return cls(state, manifest['hermes_home'])
103
+
104
+ def _run(self, *args, check=True):
105
+ try:
106
+ result = self.runner(list(args), capture_output=True, text=True, timeout=30)
107
+ except (OSError, subprocess.SubprocessError) as exc:
108
+ raise ServiceError(f'User service manager unavailable ({type(exc).__name__})') from None
109
+ if check and result.returncode:
110
+ raise ServiceError(f'User service command failed: {args[0]} {args[1]} (exit {result.returncode})')
111
+ return result
112
+
113
+ def _manager_ready(self):
114
+ if self.platform == 'darwin':
115
+ self._run('launchctl', 'print', f'gui/{os.getuid()}')
116
+ else:
117
+ self._run('systemctl', '--user', 'show-environment')
118
+
119
+ def _owned(self):
120
+ if not self.link.exists() and not self.link.is_symlink():
121
+ return False
122
+ if not self.link.is_symlink() or self.link.resolve() != self.definition:
123
+ raise ServiceError(f'Refusing an unowned service definition at {self.link}')
124
+ if not self.definition.is_file():
125
+ raise ServiceError('Private service definition is missing; reinstall it before managing the service')
126
+ return True
127
+
128
+ def render(self):
129
+ arguments = [self.python, '-m', 'apsimo', '--instance', str(self.state), 'start']
130
+ environment = {'HERMES_HOME': str(self.hermes_home), 'APSIMO_INSTANCE_SERVICE': self.label,
131
+ 'PYTHONUNBUFFERED': '1'}
132
+ if self.platform == 'darwin':
133
+ return plistlib.dumps({'Label': self.label, 'ProgramArguments': arguments,
134
+ 'WorkingDirectory': str(self.state), 'EnvironmentVariables': environment,
135
+ 'RunAtLoad': True, 'KeepAlive': True, 'ThrottleInterval': 5,
136
+ 'ExitTimeOut': 20, 'Umask': 0o077,
137
+ 'StandardOutPath': str(self.log), 'StandardErrorPath': str(self.log)}, sort_keys=True)
138
+ quote = _systemd_quote
139
+ return ('[Unit]\nDescription=Apsimo private instance ' + self.label + '\n\n[Service]\nType=exec\n'
140
+ 'WorkingDirectory=' + _systemd_path(self.state) + '\n'
141
+ # ':' disables dollar-variable substitution; %% escapes specifiers.
142
+ 'ExecStart=:' + ' '.join(quote(arg) for arg in arguments) + '\n'
143
+ 'Environment=' + ' '.join(quote(key + '=' + value) for key, value in environment.items()) + '\n'
144
+ 'Restart=always\nRestartSec=5\nTimeoutStopSec=20\nUMask=0077\n'
145
+ 'StandardOutput=append:' + _systemd_path(self.log) + '\n'
146
+ 'StandardError=append:' + _systemd_path(self.log) + '\n\n[Install]\nWantedBy=default.target\n').encode()
147
+
148
+ def status(self):
149
+ owned = self._owned()
150
+ self._manager_ready()
151
+ result = {'instance': str(self.state), 'manager': 'launchd' if self.platform == 'darwin' else 'systemd-user',
152
+ 'label': self.label, 'definition': str(self.definition), 'installed': owned,
153
+ 'loaded': False, 'running': False, 'pid': None, 'autostart_scope': 'user-session'}
154
+ if self.platform == 'darwin':
155
+ response = self._run('launchctl', 'print', self.target, check=False)
156
+ result['loaded'] = response.returncode == 0
157
+ pid = re.search(r'^\s*pid = ([0-9]+)\s*$', response.stdout, re.MULTILINE)
158
+ if pid:
159
+ result['pid'], result['running'] = int(pid[1]), True
160
+ else:
161
+ response = self._run('systemctl', '--user', 'show', self.name,
162
+ '--property=LoadState,ActiveState,SubState,MainPID,UnitFileState', '--no-pager', check=False)
163
+ fields = dict(line.split('=', 1) for line in response.stdout.splitlines() if '=' in line)
164
+ result.update(state=fields.get('ActiveState', 'unknown'), substate=fields.get('SubState', 'unknown'),
165
+ enabled=fields.get('UnitFileState') == 'enabled')
166
+ result['loaded'] = fields.get('LoadState') == 'loaded'
167
+ pid = fields.get('MainPID', '0')
168
+ result['pid'] = int(pid) if pid.isdigit() and int(pid) > 0 else None
169
+ result['running'] = bool(result['pid'] and fields.get('ActiveState') in {'active', 'activating'})
170
+ return result
171
+
172
+ def install(self):
173
+ content = self.render() # Validate every path before writing anything.
174
+ self._manager_ready()
175
+ owned = self._owned()
176
+ previous = self.definition.read_bytes() if self.definition.is_file() else None
177
+ if previous != content and owned:
178
+ status = self.status()
179
+ if status['running'] or (self.platform == 'darwin' and status['loaded']):
180
+ raise ServiceError('Stop this instance service before changing its Python environment')
181
+ try:
182
+ if previous is not None and previous != content:
183
+ _private_write(self.backup, previous)
184
+ _private_write(self.definition, content)
185
+ self.link.parent.mkdir(parents=True, exist_ok=True)
186
+ if not owned:
187
+ self.link.symlink_to(self.definition)
188
+ if not self.log.exists():
189
+ _private_write(self.log, b'')
190
+ if self.platform == 'darwin':
191
+ self._run('launchctl', 'enable', self.target)
192
+ else:
193
+ self._run('systemctl', '--user', 'daemon-reload')
194
+ self._run('systemctl', '--user', 'enable', self.name)
195
+ except (ServiceError, OSError):
196
+ # Preserve the previous definition; no instance was started here.
197
+ if previous is not None:
198
+ _private_write(self.definition, previous)
199
+ if not owned and self.link.is_symlink() and self.link.resolve() == self.definition:
200
+ if self.platform != 'darwin':
201
+ self._run('systemctl', '--user', 'disable', self.name, check=False)
202
+ # A failed enable may have created its exact wanted-by link.
203
+ if not owned and self.link.is_symlink() and self.link.resolve() == self.definition:
204
+ self.link.unlink()
205
+ if previous is None:
206
+ self.definition.unlink(missing_ok=True)
207
+ if self.platform != 'darwin':
208
+ self._run('systemctl', '--user', 'daemon-reload', check=False)
209
+ raise
210
+ return self.status()
211
+
212
+ def _require_installed(self):
213
+ if not self._owned():
214
+ raise ServiceError('Service is not installed for this instance; run apsimo service install')
215
+ self._manager_ready()
216
+
217
+ def healthy(self):
218
+ import httpx
219
+ host = os.environ.get('COLONY_SIDECAR_HOST', '127.0.0.1')
220
+ host = {'0.0.0.0': '127.0.0.1', '::': '::1'}.get(host, host)
221
+ if ':' in host:
222
+ host = '[' + host + ']'
223
+ key = os.environ.get('COLONY_CLIENT_API_KEY') or os.environ.get('COLONY_API_KEY', '')
224
+ try:
225
+ response = httpx.get(f'http://{host}:{os.environ.get("COLONY_SIDECAR_PORT", "7777")}/v1/host/health',
226
+ headers={'Authorization': 'Bearer ' + key}, timeout=1, trust_env=False, follow_redirects=False)
227
+ return response.status_code == 200 and response.json().get('status') == 'ok'
228
+ except (httpx.HTTPError, ValueError):
229
+ return False
230
+
231
+ def start(self, *, restart=False, timeout=30):
232
+ self._require_installed()
233
+ status = self.status()
234
+ if not status['running']:
235
+ host = os.environ.get('COLONY_SIDECAR_HOST', '127.0.0.1')
236
+ host = {'0.0.0.0': '127.0.0.1', '::': '::1'}.get(host, host)
237
+ try:
238
+ with socket.create_connection((host, int(os.environ.get('COLONY_SIDECAR_PORT', '7777'))), timeout=.2):
239
+ raise ServiceError('Instance port is already occupied; stop its current process before service start')
240
+ except OSError:
241
+ pass
242
+ if self.platform == 'darwin':
243
+ if status['loaded']:
244
+ if restart:
245
+ self._run('launchctl', 'kickstart', '-k', self.target)
246
+ else:
247
+ self._run('launchctl', 'bootstrap', f'gui/{os.getuid()}', str(self.link))
248
+ else:
249
+ self._run('systemctl', '--user', 'restart' if restart else 'start', self.name)
250
+ deadline = time.monotonic() + timeout
251
+ while time.monotonic() < deadline:
252
+ result = self.status()
253
+ if result['running'] and self.healthy():
254
+ return {**result, 'ready': True}
255
+ time.sleep(.2)
256
+ raise ServiceError(f'Service did not become HTTP-ready; inspect {self.log}. It remains installed for recovery.')
257
+
258
+ def stop(self):
259
+ self._require_installed()
260
+ if self.platform == 'darwin':
261
+ if self.status()['loaded']:
262
+ self._run('launchctl', 'bootout', self.target)
263
+ else:
264
+ self._run('systemctl', '--user', 'stop', self.name)
265
+ # launchctl bootout can return while its process is still shutting down.
266
+ # Do not remove the definition or report a stopped service prematurely.
267
+ deadline = time.monotonic() + 30
268
+ while time.monotonic() < deadline:
269
+ result = self.status()
270
+ if not result['running'] and (self.platform != 'darwin' or not result['loaded']):
271
+ return result
272
+ time.sleep(.1)
273
+ raise ServiceError('User manager has not finished stopping this instance; its registration is retained')
274
+
275
+ def uninstall(self):
276
+ if not self._owned():
277
+ return self.status()
278
+ self.stop()
279
+ if self.platform != 'darwin':
280
+ self._run('systemctl', '--user', 'disable', self.name)
281
+ # systemctl disable may remove this exact link itself.
282
+ if self.link.is_symlink() and self.link.resolve() == self.definition:
283
+ self.link.unlink()
284
+ if self.platform != 'darwin':
285
+ self._run('systemctl', '--user', 'daemon-reload')
286
+ return self.status()
287
+
288
+
289
+ def manage(action):
290
+ service = InstanceService.selected()
291
+ if action == 'restart':
292
+ result = service.start(restart=True)
293
+ else:
294
+ result = getattr(service, action)()
295
+ if action == 'status':
296
+ result['ready'] = bool(result['running'] and service.healthy())
297
+ print(json.dumps(result, sort_keys=True))
@@ -0,0 +1,22 @@
1
+ """Session isolation — per-contact and per-colony isolated contexts."""
2
+
3
+ from apsimo.sessions.isolated_session import (
4
+ IsolatedSession,
5
+ SessionState,
6
+ ConversationTurn,
7
+ )
8
+ from apsimo.sessions.store import IsolatedSessionStore, InMemorySessionStore
9
+ from apsimo.sessions.context_loader import SessionContext, SessionContextLoader
10
+ from apsimo.sessions.federation_session import FederationSession, FederationSessionState
11
+
12
+ __all__ = [
13
+ "IsolatedSession",
14
+ "SessionState",
15
+ "ConversationTurn",
16
+ "IsolatedSessionStore",
17
+ "InMemorySessionStore",
18
+ "SessionContext",
19
+ "SessionContextLoader",
20
+ "FederationSession",
21
+ "FederationSessionState",
22
+ ]
@@ -0,0 +1,13 @@
1
+ """Session TTL configuration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+
8
+ @dataclass
9
+ class SessionTTLConfig:
10
+ contact_session_ttl_hours: int = 24 # human-contact sessions
11
+ api_session_ttl_hours: int = 1 # machine/API sessions
12
+ websocket_idle_ttl_minutes: int = 30 # WebSocket idle timeout
13
+ cleanup_interval_minutes: int = 15 # background sweep frequency
@@ -0,0 +1,88 @@
1
+ """SessionContextLoader — loads all context for a session turn, respecting isolation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Optional
7
+
8
+ from apsimo.intelligence.relationships.trust_tiers import TrustTier
9
+ from apsimo.sessions.isolated_session import IsolatedSession
10
+
11
+
12
+ @dataclass
13
+ class SessionContext:
14
+ """The complete context package loaded for a session turn.
15
+
16
+ This object has no method to access other sessions.
17
+ All fields are scoped to the single contact.
18
+ """
19
+ session: IsolatedSession
20
+ person_node: dict # Person node from Neo4j
21
+ accessible_memories: list # memories tagged for this tier or lower
22
+ initiative_history: list # last N outreach events with this contact
23
+ relationship_trend: Optional[list] # 30-day score trend (SHOULD load)
24
+ anomaly_flags: list # current anomaly flags (SHOULD load)
25
+ style_model: Optional[dict] # communication style model (SHOULD load)
26
+
27
+
28
+ class SessionContextLoader:
29
+ """Loads all context for a session turn, respecting isolation boundaries."""
30
+
31
+ def __init__(
32
+ self,
33
+ session_store,
34
+ graph_client,
35
+ config,
36
+ ) -> None:
37
+ self._sessions = session_store
38
+ self._graph = graph_client
39
+ self._config = config
40
+
41
+ async def load(self, session_id: str) -> SessionContext:
42
+ """Load full context for a session. Raises if session not found."""
43
+ session = await self._sessions.get(session_id)
44
+ if session is None:
45
+ raise ValueError(f"Session {session_id!r} not found")
46
+
47
+ # MUST load
48
+ person_node = await self._graph.get_person_node(session.contact_id)
49
+ accessible_memories = await self._graph.get_memories_for_tier(
50
+ contact_id=session.contact_id,
51
+ tier=session.trust_tier,
52
+ exclude_visibility=["COLONY_ONLY"], # NEVER load private memories
53
+ )
54
+ initiative_history = await self._graph.get_initiative_history(
55
+ contact_id=session.contact_id,
56
+ limit=10,
57
+ )
58
+
59
+ # SHOULD load (best-effort)
60
+ relationship_trend = None
61
+ anomaly_flags = []
62
+ style_model = None
63
+ remaining_tokens = (
64
+ self._config.context_token_limit - session.context_token_count
65
+ )
66
+ if remaining_tokens > 5_000:
67
+ try:
68
+ relationship_trend = await self._graph.get_score_trend(
69
+ contact_id=session.contact_id, days=30
70
+ )
71
+ anomaly_flags = await self._graph.get_anomaly_flags(
72
+ contact_id=session.contact_id
73
+ )
74
+ style_model = await self._graph.get_style_model(
75
+ contact_id=session.contact_id
76
+ )
77
+ except Exception:
78
+ pass # SHOULD load — failures are non-fatal
79
+
80
+ return SessionContext(
81
+ session=session,
82
+ person_node=person_node,
83
+ accessible_memories=accessible_memories,
84
+ initiative_history=initiative_history,
85
+ relationship_trend=relationship_trend,
86
+ anomaly_flags=anomaly_flags,
87
+ style_model=style_model,
88
+ )
@@ -0,0 +1,75 @@
1
+ """FederationSession — colony-to-colony isolated session."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import uuid
6
+ from dataclasses import dataclass, field
7
+ from datetime import datetime, timezone
8
+ from enum import Enum
9
+ from typing import Optional
10
+
11
+
12
+ class FederationSessionState(str, Enum):
13
+ ACTIVE = "active"
14
+ IDLE = "idle"
15
+ ARCHIVED = "archived"
16
+ TRUST_SUSPENDED = "trust_suspended"
17
+
18
+
19
+ @dataclass
20
+ class FederationTurn:
21
+ turn_id: str
22
+ role: str # "local" | "remote"
23
+ content: str
24
+ timestamp: datetime
25
+ gate_decision: Optional[str] = None
26
+
27
+
28
+ @dataclass
29
+ class FederationSession:
30
+ """Colony-to-colony session. Contains ONLY bilateral negotiation content.
31
+
32
+ MUST NOT contain:
33
+ - Content from contact sessions.
34
+ - Internal deliberation memories (COLONY_ONLY).
35
+ - Any information about local contacts.
36
+ """
37
+ session_id: str
38
+ local_colony_id: str
39
+ remote_colony_id: str
40
+ federated_trust_tier: str # e.g. "trusted_colony" | "untrusted" | "newly_paired"
41
+ state: FederationSessionState = FederationSessionState.ACTIVE
42
+ history: list = field(default_factory=list)
43
+ mentioned_entities: set = field(default_factory=set)
44
+ shared_memory_ids: list = field(default_factory=list) # explicitly consented objects
45
+ trust_attestations: list = field(default_factory=list) # on-chain, read-only
46
+ last_active: datetime = field(default_factory=lambda: datetime.now(tz=timezone.utc))
47
+
48
+ @classmethod
49
+ def create(
50
+ cls,
51
+ local_colony_id: str,
52
+ remote_colony_id: str,
53
+ federated_trust_tier: str = "newly_paired",
54
+ ) -> "FederationSession":
55
+ return cls(
56
+ session_id=str(uuid.uuid4()),
57
+ local_colony_id=local_colony_id,
58
+ remote_colony_id=remote_colony_id,
59
+ federated_trust_tier=federated_trust_tier,
60
+ )
61
+
62
+ def add_turn(self, role: str, content: str, gate_decision: Optional[str] = None) -> FederationTurn:
63
+ turn = FederationTurn(
64
+ turn_id=str(uuid.uuid4()),
65
+ role=role,
66
+ content=content,
67
+ timestamp=datetime.now(tz=timezone.utc),
68
+ gate_decision=gate_decision,
69
+ )
70
+ self.history.append(turn)
71
+ self.last_active = turn.timestamp
72
+ return turn
73
+
74
+ def get_mentioned_entities_snapshot(self) -> frozenset:
75
+ return frozenset(self.mentioned_entities)
@@ -0,0 +1,98 @@
1
+ """IsolatedSession — per-contact sealed context."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import uuid
6
+ from dataclasses import dataclass, field
7
+ from datetime import datetime, timedelta, timezone
8
+ from enum import Enum
9
+ from typing import Optional
10
+
11
+ from apsimo.intelligence.relationships.trust_tiers import TrustTier
12
+
13
+
14
+ class SessionState(str, Enum):
15
+ ACTIVE = "active"
16
+ IDLE = "idle"
17
+ ARCHIVED = "archived"
18
+ SILENCED = "silenced"
19
+ NEEDS_OWNER_REVIEW = "needs_owner_review"
20
+ ESCALATION_PENDING = "escalation_pending"
21
+
22
+
23
+ @dataclass
24
+ class ConversationTurn:
25
+ turn_id: str
26
+ role: str # "contact" | "colony"
27
+ content: str
28
+ timestamp: datetime
29
+ gate_decision: Optional[str] = None # result code if this was an outbound turn
30
+
31
+
32
+ _DEFAULT_TTL_HOURS = 24
33
+
34
+
35
+ @dataclass
36
+ class IsolatedSession:
37
+ session_id: str
38
+ contact_id: str
39
+ gateway: str
40
+ trust_tier: TrustTier
41
+ state: SessionState = SessionState.ACTIVE
42
+ history: list = field(default_factory=list)
43
+ active_topics: set = field(default_factory=set)
44
+ mentioned_entities: set = field(default_factory=set)
45
+ context_start: datetime = field(default_factory=lambda: datetime.now(tz=timezone.utc))
46
+ context_token_count: int = 0
47
+ last_active: datetime = field(default_factory=lambda: datetime.now(tz=timezone.utc))
48
+ relationship_score: float = 0.0
49
+ owner_guidance: dict = field(default_factory=dict)
50
+ expires_at: datetime = field(
51
+ default_factory=lambda: datetime.now(tz=timezone.utc) + timedelta(hours=_DEFAULT_TTL_HOURS)
52
+ )
53
+ model_override: Optional[str] = None
54
+ model_provider_override: Optional[str] = None
55
+
56
+ @classmethod
57
+ def create(
58
+ cls,
59
+ contact_id: str,
60
+ gateway: str,
61
+ trust_tier: TrustTier,
62
+ ttl_hours: int = _DEFAULT_TTL_HOURS,
63
+ ) -> "IsolatedSession":
64
+ return cls(
65
+ session_id=str(uuid.uuid4()),
66
+ contact_id=contact_id,
67
+ gateway=gateway,
68
+ trust_tier=trust_tier,
69
+ expires_at=datetime.now(tz=timezone.utc) + timedelta(hours=ttl_hours),
70
+ )
71
+
72
+ def is_expired(self) -> bool:
73
+ return datetime.now(tz=timezone.utc) >= self.expires_at
74
+
75
+ def extend(self, ttl_hours: int = _DEFAULT_TTL_HOURS) -> None:
76
+ """Reset expiry on activity (token rotation equivalent for stateful sessions)."""
77
+ self.expires_at = datetime.now(tz=timezone.utc) + timedelta(hours=ttl_hours)
78
+ self.last_active = datetime.now(tz=timezone.utc)
79
+
80
+ def add_entity(self, entity: str) -> None:
81
+ """Add an entity to this session's mention set."""
82
+ self.mentioned_entities.add(entity.lower().strip())
83
+
84
+ def get_mentioned_entities_snapshot(self) -> frozenset:
85
+ """Return an immutable snapshot for gate cross-context checking."""
86
+ return frozenset(self.mentioned_entities)
87
+
88
+ def add_turn(self, role: str, content: str, gate_decision: Optional[str] = None) -> ConversationTurn:
89
+ turn = ConversationTurn(
90
+ turn_id=str(uuid.uuid4()),
91
+ role=role,
92
+ content=content,
93
+ timestamp=datetime.now(tz=timezone.utc),
94
+ gate_decision=gate_decision,
95
+ )
96
+ self.history.append(turn)
97
+ self.last_active = turn.timestamp
98
+ return turn
@@ -0,0 +1,84 @@
1
+ """Session report store — lightweight in-memory cross-session bridge.
2
+
3
+ Keeps the last N session summaries per contact. Older reports are evicted.
4
+ This is NOT long-term memory — for that, use the graph store.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass, field
10
+ from datetime import datetime, timedelta, timezone
11
+ from typing import Any, Dict, List
12
+
13
+
14
+ @dataclass
15
+ class SessionReport:
16
+ """A summary of a single agent session, written at session end."""
17
+
18
+ report_id: str
19
+ session_id: str
20
+ contact_id: str
21
+ started_at: datetime
22
+ ended_at: datetime | None
23
+ summary: str
24
+ topics: List[str]
25
+ resolutions: List[str]
26
+ pending: List[str]
27
+ notified_user: bool
28
+ metadata: Dict[str, Any]
29
+ created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
30
+
31
+
32
+ class SessionReportStore:
33
+ """In-memory store for recent agent session summaries.
34
+
35
+ Keeps last *max_per_contact* reports per contact_id. Older reports
36
+ are evicted on FIFO basis.
37
+ """
38
+
39
+ def __init__(self, max_per_contact: int = 20) -> None:
40
+ self._reports: Dict[str, List[SessionReport]] = {}
41
+ self._max_per_contact = max_per_contact
42
+
43
+ async def add_report(self, report: SessionReport) -> str:
44
+ """Store a report. Returns the report_id."""
45
+ if report.contact_id not in self._reports:
46
+ self._reports[report.contact_id] = []
47
+ self._reports[report.contact_id].append(report)
48
+ # Evict oldest if over limit
49
+ if len(self._reports[report.contact_id]) > self._max_per_contact:
50
+ self._reports[report.contact_id] = self._reports[
51
+ report.contact_id
52
+ ][-self._max_per_contact :]
53
+ return report.report_id
54
+
55
+ async def get_recent(
56
+ self,
57
+ contact_id: str,
58
+ hours: int = 24,
59
+ limit: int = 10,
60
+ ) -> List[SessionReport]:
61
+ """Return recent reports for a contact, filtered by age and capped."""
62
+ cutoff = datetime.now(timezone.utc) - timedelta(hours=hours)
63
+ reports = self._reports.get(contact_id, [])
64
+ recent = []
65
+ for r in reports:
66
+ # Defensive: ensure timezone-aware comparison
67
+ ended = r.ended_at
68
+ started = r.started_at
69
+ if ended is not None and ended.tzinfo is None:
70
+ ended = ended.replace(tzinfo=timezone.utc)
71
+ if started.tzinfo is None:
72
+ started = started.replace(tzinfo=timezone.utc)
73
+ if (
74
+ (ended is not None and ended > cutoff)
75
+ or (ended is None and started > cutoff)
76
+ ):
77
+ recent.append(r)
78
+ return recent[-limit:]
79
+
80
+ def to_dict(self) -> Dict[str, Any]:
81
+ return {
82
+ "contacts": len(self._reports),
83
+ "total_reports": sum(len(v) for v in self._reports.values()),
84
+ }