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,195 @@
1
+ """Sandbox backends: pluggable isolated code execution (Phase B item 6).
2
+
3
+ The backend -- NOT the caller -- enforces containment: no egress, no
4
+ credentials, capped CPU/memory/pids/wall-clock, read-only rootfs with a single
5
+ writable workdir. A tool can ask to run a script; it can never widen these
6
+ limits (they come from env/policy, resolved by the manager, and are passed to
7
+ the backend which applies them literally).
8
+
9
+ Reference backend is Docker; a DisabledSandbox is the default when Docker is
10
+ absent so the feature degrades to a clear "unavailable" rather than an unsafe
11
+ host-side exec.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import logging
17
+ import os
18
+ import shutil
19
+ import subprocess
20
+ import tempfile
21
+ from abc import ABC, abstractmethod
22
+ from dataclasses import dataclass, field
23
+ from typing import Dict, List, Optional
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+ _LANG_INTERP = {
28
+ "python": ["python", "/work/script"],
29
+ "py": ["python", "/work/script"],
30
+ "bash": ["sh", "/work/script"],
31
+ "sh": ["sh", "/work/script"],
32
+ "node": ["node", "/work/script"],
33
+ "javascript": ["node", "/work/script"],
34
+ }
35
+
36
+
37
+ @dataclass
38
+ class SandboxLimits:
39
+ """Containment limits, resolved from policy/env and applied by the backend."""
40
+ image: str = "python:3.12-slim"
41
+ cpus: float = 1.0
42
+ memory: str = "512m"
43
+ timeout_secs: int = 30
44
+ pids_limit: int = 128
45
+ egress: str = "none" # none | allowlist
46
+ max_artifact_bytes: int = 1_048_576 # 1 MiB total read-back cap
47
+
48
+ @property
49
+ def network(self) -> str:
50
+ # Only "none" is honored as a hard no-egress; allowlist requires an
51
+ # egress proxy the deployment provides (documented, not default).
52
+ return "none" if self.egress == "none" else self.egress
53
+
54
+
55
+ @dataclass
56
+ class SandboxResult:
57
+ stdout: str = ""
58
+ stderr: str = ""
59
+ exit_code: Optional[int] = None
60
+ timed_out: bool = False
61
+ artifacts: Dict[str, str] = field(default_factory=dict)
62
+ error: str = ""
63
+
64
+ def as_dict(self) -> Dict[str, object]:
65
+ return {
66
+ "stdout": self.stdout, "stderr": self.stderr,
67
+ "exit_code": self.exit_code, "timed_out": self.timed_out,
68
+ "artifacts": list(self.artifacts.keys()),
69
+ "artifact_preview": {k: v[:500] for k, v in self.artifacts.items()},
70
+ "error": self.error,
71
+ }
72
+
73
+
74
+ class SandboxBackend(ABC):
75
+ name = "abstract"
76
+
77
+ @abstractmethod
78
+ def available(self) -> bool:
79
+ ...
80
+
81
+ @abstractmethod
82
+ def run(self, script: str, lang: str, limits: SandboxLimits) -> SandboxResult:
83
+ ...
84
+
85
+
86
+ class DisabledSandbox(SandboxBackend):
87
+ """Default backend when no isolation is available. Never executes."""
88
+ name = "disabled"
89
+
90
+ def __init__(self, reason: str = "no container runtime available") -> None:
91
+ self._reason = reason
92
+
93
+ def available(self) -> bool:
94
+ return False
95
+
96
+ def run(self, script: str, lang: str, limits: SandboxLimits) -> SandboxResult:
97
+ return SandboxResult(error=f"sandbox unavailable: {self._reason}")
98
+
99
+
100
+ class DockerSandbox(SandboxBackend):
101
+ """Ephemeral-container backend: no egress, no creds, capped resources."""
102
+ name = "docker"
103
+
104
+ def __init__(self, docker_bin: str = "docker") -> None:
105
+ self._docker = docker_bin
106
+
107
+ def available(self) -> bool:
108
+ return shutil.which(self._docker) is not None
109
+
110
+ def build_command(self, workdir: str, lang: str,
111
+ limits: SandboxLimits) -> List[str]:
112
+ """The exact docker invocation (pure -- unit-tested for containment).
113
+
114
+ Guarantees: --network none (no egress), no -e/env (no credentials),
115
+ --read-only rootfs with a single writable bind workdir, dropped
116
+ capabilities + no-new-privileges, hard CPU/memory/pids caps, and a
117
+ wall-clock timeout inside the container.
118
+ """
119
+ interp = _LANG_INTERP.get((lang or "python").lower(),
120
+ _LANG_INTERP["python"])
121
+ return [
122
+ self._docker, "run", "--rm",
123
+ "--network", limits.network,
124
+ "--read-only",
125
+ "--tmpfs", "/tmp:rw,size=64m",
126
+ "-v", f"{workdir}:/work:rw",
127
+ "-w", "/work",
128
+ "--cpus", str(limits.cpus),
129
+ "--memory", str(limits.memory),
130
+ "--pids-limit", str(limits.pids_limit),
131
+ "--security-opt", "no-new-privileges",
132
+ "--cap-drop", "ALL",
133
+ limits.image,
134
+ "timeout", str(limits.timeout_secs), *interp,
135
+ ]
136
+
137
+ def run(self, script: str, lang: str, limits: SandboxLimits) -> SandboxResult:
138
+ workdir = tempfile.mkdtemp(prefix="colony-sandbox-")
139
+ try:
140
+ script_path = os.path.join(workdir, "script")
141
+ with open(script_path, "w") as f:
142
+ f.write(script or "")
143
+ cmd = self.build_command(workdir, lang, limits)
144
+ try:
145
+ proc = subprocess.run(
146
+ cmd, capture_output=True, text=True,
147
+ timeout=limits.timeout_secs + 10) # outer guard > inner
148
+ except subprocess.TimeoutExpired:
149
+ return SandboxResult(timed_out=True,
150
+ error="wall-clock timeout")
151
+ except Exception as exc:
152
+ return SandboxResult(error=f"backend error: {exc}")
153
+ result = SandboxResult(
154
+ stdout=(proc.stdout or "")[:100_000],
155
+ stderr=(proc.stderr or "")[:20_000],
156
+ exit_code=proc.returncode,
157
+ timed_out=(proc.returncode == 124)) # timeout(1) exit code
158
+ self._read_artifacts(workdir, script_path, limits, result)
159
+ return result
160
+ finally:
161
+ shutil.rmtree(workdir, ignore_errors=True)
162
+
163
+ @staticmethod
164
+ def _read_artifacts(workdir: str, script_path: str,
165
+ limits: SandboxLimits, result: SandboxResult) -> None:
166
+ budget = limits.max_artifact_bytes
167
+ for root, _dirs, files in os.walk(workdir):
168
+ for fn in files:
169
+ path = os.path.join(root, fn)
170
+ if path == script_path:
171
+ continue
172
+ if budget <= 0:
173
+ result.error = (result.error
174
+ or "artifact size cap reached").strip()
175
+ return
176
+ try:
177
+ with open(path, "rb") as fh:
178
+ data = fh.read(budget + 1)
179
+ except Exception:
180
+ continue
181
+ if len(data) > budget:
182
+ result.error = "artifact size cap reached"
183
+ data = data[:budget]
184
+ budget -= len(data)
185
+ rel = os.path.relpath(path, workdir)
186
+ result.artifacts[rel] = data.decode("utf-8", "replace")
187
+
188
+
189
+ def select_backend(mode: str) -> SandboxBackend:
190
+ """Pick a backend: Docker when present, else Disabled. Mode gates whether
191
+ the manager actually executes, not which backend exists."""
192
+ docker = DockerSandbox()
193
+ if docker.available():
194
+ return docker
195
+ return DisabledSandbox()
@@ -0,0 +1,173 @@
1
+ """SandboxManager -- policy front end for gated isolated execution (item 6).
2
+
3
+ Wraps a backend with the safety envelope:
4
+ - mode gate: off (never), dry_run (validate + log the command, run nothing),
5
+ live (execute in the backend).
6
+ - boundary gate: the purpose + script are checked against DirectiveGuard
7
+ (ACT capability, fail-closed); a boundaried subject blocks the run.
8
+ - approval tiering: an owner-directed experiment or a contained read-only
9
+ evaluation runs AUTO; other mutations are FLAGGED for owner approval.
10
+ - server-side limits: the caller cannot widen containment -- limits are
11
+ resolved here from env and handed to the backend, which applies them.
12
+ - never mounts secrets: the backend passes no env/credentials into the
13
+ container.
14
+
15
+ Every run (auto, flagged, dry-run, blocked) is journaled through the
16
+ self-model action journal.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import logging
22
+ import os
23
+ from typing import Any, Dict, Optional
24
+
25
+ from apsimo.sandbox.backend import (
26
+ SandboxLimits, SandboxResult, select_backend,
27
+ )
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ def sandbox_mode() -> str:
33
+ from apsimo.util.autonomy_preset import resolve
34
+ return resolve("COLONY_SANDBOX_MODE", ("off", "dry_run", "live"), "off")
35
+
36
+
37
+ def _fenv(name: str, default: float) -> float:
38
+ try:
39
+ return float(os.environ.get(name, str(default)))
40
+ except (TypeError, ValueError):
41
+ return default
42
+
43
+
44
+ def _ienv(name: str, default: int) -> int:
45
+ try:
46
+ return int(os.environ.get(name, str(default)))
47
+ except (TypeError, ValueError):
48
+ return default
49
+
50
+
51
+ def resolve_limits() -> SandboxLimits:
52
+ """Containment limits from env/policy. The caller can never widen these."""
53
+ return SandboxLimits(
54
+ image=os.environ.get("COLONY_SANDBOX_IMAGE", "python:3.12-slim"),
55
+ cpus=_fenv("COLONY_SANDBOX_CPUS", 1.0),
56
+ memory=os.environ.get("COLONY_SANDBOX_MEMORY", "512m"),
57
+ timeout_secs=_ienv("COLONY_SANDBOX_TIMEOUT", 30),
58
+ pids_limit=_ienv("COLONY_SANDBOX_PIDS", 128),
59
+ egress=os.environ.get("COLONY_SANDBOX_EGRESS", "none").strip().lower(),
60
+ max_artifact_bytes=_ienv("COLONY_SANDBOX_MAX_ARTIFACT_BYTES", 1_048_576),
61
+ )
62
+
63
+
64
+ class SandboxManager:
65
+ def __init__(self, *, directive_manager: Any = None,
66
+ self_model: Any = None) -> None:
67
+ self._directives = directive_manager
68
+ self._self_model = self_model
69
+ self._backend = select_backend(sandbox_mode())
70
+
71
+ def backend_name(self) -> str:
72
+ return self._backend.name
73
+
74
+ # -- approval tiering -------------------------------------------------
75
+ @staticmethod
76
+ def approval_tier(owner_directed: bool, read_only: bool = False) -> str:
77
+ """Keep contained read-only work fast without inventing authority."""
78
+
79
+ return "auto" if owner_directed or read_only else "flagged"
80
+
81
+ # -- boundary gate ----------------------------------------------------
82
+ def _boundary_ok(self, purpose: str, script: str) -> Dict[str, Any]:
83
+ if self._directives is None:
84
+ # No directive manager: nothing was evaluated. Never report the
85
+ # fabricated "ok" of a real allow — callers/journals must be able
86
+ # to tell "unchecked" from "checked and allowed".
87
+ return {"allowed": True, "reason": "boundary_unchecked"}
88
+ try:
89
+ from apsimo.directives import Action
90
+ verdict = self._directives.check(Action(
91
+ kind="execute_tool",
92
+ text=f"run sandbox script: {purpose}",
93
+ target=purpose,
94
+ args={"script": (script or "")[:500]},
95
+ high_risk=True))
96
+ return {"allowed": bool(verdict.allowed), "reason": verdict.reason}
97
+ except Exception:
98
+ logger.warning("sandbox boundary check failed closed", exc_info=True)
99
+ return {"allowed": False, "reason": "boundary_check_error"}
100
+
101
+ # -- the run entry point ----------------------------------------------
102
+ def run(self, script: str, lang: str = "python", *, purpose: str = "",
103
+ owner_directed: bool = False,
104
+ approved: bool = False,
105
+ read_only: bool = False) -> Dict[str, Any]:
106
+ mode = sandbox_mode()
107
+ limits = resolve_limits()
108
+ tier = self.approval_tier(owner_directed, read_only)
109
+
110
+ if mode == "off":
111
+ self._journal("sandbox", purpose, decision="held",
112
+ reasoning="sandbox mode off")
113
+ return {"ran": False, "reason": "sandbox_off", "mode": mode}
114
+
115
+ boundary = self._boundary_ok(purpose, script)
116
+ if not boundary["allowed"]:
117
+ self._journal("sandbox", purpose, decision="blocked",
118
+ reasoning=f"boundary: {boundary['reason']}")
119
+ return {"ran": False, "reason": "boundary_blocked",
120
+ "detail": boundary["reason"], "mode": mode}
121
+
122
+ if tier == "flagged" and not approved:
123
+ self._journal("sandbox", purpose, decision="asked",
124
+ reasoning="not owner-directed -> owner approval required")
125
+ return {"ran": False, "reason": "approval_required",
126
+ "tier": tier, "mode": mode}
127
+
128
+ if mode == "dry_run":
129
+ from apsimo.sandbox.backend import DockerSandbox
130
+ command = DockerSandbox().build_command("<workdir>", lang, limits)
131
+ self._journal("sandbox", purpose, decision="held",
132
+ reasoning="dry_run: validated, executed nothing")
133
+ return {"ran": False, "dry_run": True, "tier": tier,
134
+ "read_only": bool(read_only),
135
+ "command": command, "limits": limits.__dict__, "mode": mode}
136
+
137
+ # live
138
+ if not self._backend.available():
139
+ self._journal("sandbox", purpose, decision="held",
140
+ reasoning=f"backend {self._backend.name} unavailable")
141
+ return {"ran": False, "reason": "backend_unavailable",
142
+ "backend": self._backend.name, "mode": mode}
143
+
144
+ result: SandboxResult = self._backend.run(script, lang, limits)
145
+ outcome = ("timeout" if result.timed_out else
146
+ "success" if (result.exit_code == 0 and not result.error)
147
+ else "failure")
148
+ if self._self_model is not None:
149
+ try:
150
+ self._self_model.record("sandbox", outcome)
151
+ except Exception:
152
+ logger.debug("sandbox self-model record failed", exc_info=True)
153
+ self._journal("sandbox", purpose, decision="acted", outcome=outcome,
154
+ reasoning=f"tier={tier}, exit={result.exit_code}")
155
+ return {"ran": True, "tier": tier, "read_only": bool(read_only),
156
+ "mode": mode, "outcome": outcome,
157
+ "result": result.as_dict()}
158
+
159
+ def status(self) -> Dict[str, Any]:
160
+ return {"mode": sandbox_mode(), "backend": self._backend.name,
161
+ "backend_available": self._backend.available(),
162
+ "limits": resolve_limits().__dict__}
163
+
164
+ # -- helpers ----------------------------------------------------------
165
+ def _journal(self, domain: str, description: str, **kw: Any) -> None:
166
+ journal = getattr(self._self_model, "journal", None)
167
+ if journal is None:
168
+ return
169
+ try:
170
+ journal.record(domain, description or "(sandbox run)",
171
+ reversibility="reversible", **kw)
172
+ except Exception:
173
+ logger.debug("sandbox journal failed", exc_info=True)
apsimo/scope_bounds.py ADDED
@@ -0,0 +1,7 @@
1
+ """Shared bounds for authority-bearing person and visibility scopes."""
2
+
3
+ SUBJECT_PERSON_ID_MAX_CHARS = 128
4
+ VIEWER_SCOPE_MAX_CHARS = 192
5
+
6
+
7
+ __all__ = ("SUBJECT_PERSON_ID_MAX_CHARS", "VIEWER_SCOPE_MAX_CHARS")
@@ -0,0 +1,6 @@
1
+ """Colony secrets management — pluggable credential vault."""
2
+
3
+ from apsimo.secrets.manager import SecretsManager
4
+ from apsimo.secrets.types import SecretType, ALL_SECRET_KEYS, VAULT_NAME
5
+
6
+ __all__ = ["SecretsManager", "SecretType", "ALL_SECRET_KEYS", "VAULT_NAME"]
@@ -0,0 +1,8 @@
1
+ """Secrets backend implementations."""
2
+
3
+ from apsimo.secrets.backends.base import SecretsBackend
4
+ from apsimo.secrets.backends.env import EnvBackend
5
+ from apsimo.secrets.backends.keyring import KeyringBackend
6
+ from apsimo.secrets.backends.onepassword import OnePasswordBackend
7
+
8
+ __all__ = ["SecretsBackend", "EnvBackend", "KeyringBackend", "OnePasswordBackend"]
@@ -0,0 +1,42 @@
1
+ """Abstract base class for Colony secrets backends."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+
7
+ from apsimo.secrets.types import SecretType
8
+
9
+
10
+ class SecretsBackend(ABC):
11
+ """Abstract secrets storage backend.
12
+
13
+ All backends must implement get/set/delete/list and is_available.
14
+ Backends are selected during onboarding and persisted in config.yaml.
15
+ """
16
+
17
+ name: str = "abstract"
18
+
19
+ @abstractmethod
20
+ def get(self, key: str, default: str | None = None) -> str | None:
21
+ """Retrieve a secret by key. Returns default if not found."""
22
+ ...
23
+
24
+ @abstractmethod
25
+ def set(self, key: str, value: str, *, secret_type: SecretType | None = None) -> None:
26
+ """Store a secret."""
27
+ ...
28
+
29
+ @abstractmethod
30
+ def delete(self, key: str) -> None:
31
+ """Remove a secret."""
32
+ ...
33
+
34
+ @abstractmethod
35
+ def list(self) -> list[str]:
36
+ """List all known secret keys (not values)."""
37
+ ...
38
+
39
+ @abstractmethod
40
+ def is_available(self) -> bool:
41
+ """Return True if this backend can be used on the current system."""
42
+ ...
@@ -0,0 +1,110 @@
1
+ """Legacy .env file secrets backend."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from pathlib import Path
7
+
8
+ from apsimo.secrets.backends.base import SecretsBackend
9
+ from apsimo.secrets.types import SecretType
10
+
11
+
12
+ class EnvBackend(SecretsBackend):
13
+ """Legacy .env file backend.
14
+
15
+ Reads from environment variables (which may be loaded from .env by
16
+ python-dotenv at startup). Writes update the .env file in place.
17
+
18
+ This backend is maintained for backward compatibility and for users
19
+ in containerized environments who inject secrets via environment variables.
20
+ It is the DEFAULT backend when no other backend is configured, ensuring
21
+ zero-friction upgrade from V1.
22
+
23
+ The .env file is always written with mode 0o600 (SEC-007).
24
+ """
25
+
26
+ name = "env"
27
+
28
+ def __init__(self, env_path: str | None = None) -> None:
29
+ self._env_path = env_path or str(
30
+ Path.home() / ".colony" / ".env"
31
+ )
32
+
33
+ def is_available(self) -> bool:
34
+ return True # Always available
35
+
36
+ def get(self, key: str, default: str | None = None) -> str | None:
37
+ v = os.environ.get(key)
38
+ if v is not None:
39
+ return v
40
+ # Fall back to the .env file itself: set() writes there, and a fresh
41
+ # process (CLI, migration) has NOT loaded it into os.environ — without
42
+ # this read-back, get() returns None for every secret set() stored.
43
+ env_path = Path(self._env_path)
44
+ if env_path.exists():
45
+ try:
46
+ for line in env_path.read_text(encoding="utf-8").splitlines():
47
+ if line.startswith(f"{key}="):
48
+ return line.split("=", 1)[1]
49
+ except OSError:
50
+ pass
51
+ return default
52
+
53
+ def set(self, key: str, value: str, *, secret_type: SecretType | None = None) -> None:
54
+ """Write to .env file with 0o600 permissions (SEC-007 pattern)."""
55
+ env_path = Path(self._env_path)
56
+ env_path.parent.mkdir(parents=True, exist_ok=True)
57
+
58
+ lines: list[str] = []
59
+ if env_path.exists():
60
+ lines = env_path.read_text(encoding="utf-8").splitlines(keepends=True)
61
+
62
+ key_found = False
63
+ for i, line in enumerate(lines):
64
+ if line.startswith(f"{key}="):
65
+ lines[i] = f"{key}={value}\n"
66
+ key_found = True
67
+ break
68
+ if not key_found:
69
+ lines.append(f"{key}={value}\n")
70
+
71
+ # Write atomically with 0o600 (SEC-007: no TOCTOU window)
72
+ fd = os.open(
73
+ str(env_path),
74
+ os.O_WRONLY | os.O_CREAT | os.O_TRUNC,
75
+ 0o600,
76
+ )
77
+ with os.fdopen(fd, "w", encoding="utf-8") as f:
78
+ f.writelines(lines)
79
+
80
+ os.environ[key] = value
81
+
82
+ def delete(self, key: str) -> None:
83
+ env_path = Path(self._env_path)
84
+ if not env_path.exists():
85
+ os.environ.pop(key, None)
86
+ return
87
+ lines = [
88
+ line
89
+ for line in env_path.read_text(encoding="utf-8").splitlines(keepends=True)
90
+ if not line.startswith(f"{key}=")
91
+ ]
92
+ fd = os.open(
93
+ str(env_path),
94
+ os.O_WRONLY | os.O_CREAT | os.O_TRUNC,
95
+ 0o600,
96
+ )
97
+ with os.fdopen(fd, "w", encoding="utf-8") as f:
98
+ f.writelines(lines)
99
+ os.environ.pop(key, None)
100
+
101
+ def list(self) -> list[str]:
102
+ env_path = Path(self._env_path)
103
+ if not env_path.exists():
104
+ return []
105
+ keys = []
106
+ for line in env_path.read_text(encoding="utf-8").splitlines():
107
+ stripped = line.strip()
108
+ if stripped and not stripped.startswith("#") and "=" in stripped:
109
+ keys.append(stripped.split("=", 1)[0])
110
+ return keys
@@ -0,0 +1,72 @@
1
+ """System keyring secrets backend (macOS Keychain, Linux SecretService, Windows Credential Manager)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+
7
+ from apsimo.secrets.backends.base import SecretsBackend
8
+ from apsimo.secrets.types import SecretType, ALL_SECRET_KEYS
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ _SERVICE = "colony-ai"
13
+
14
+
15
+ class KeyringBackend(SecretsBackend):
16
+ """System keyring backend using the ``keyring`` library.
17
+
18
+ macOS: stores in Keychain.
19
+ Linux: uses SecretService (GNOME Keyring or KWallet).
20
+ Windows: uses Windows Credential Manager.
21
+
22
+ Requires: pip install keyring (or colony[keyring] optional dep)
23
+ """
24
+
25
+ name = "keyring"
26
+
27
+ def is_available(self) -> bool:
28
+ try:
29
+ import keyring
30
+ import keyring.errors
31
+ except ImportError:
32
+ return False
33
+ try:
34
+ # Probe with a read to verify the backend is actually usable.
35
+ # get_keyring() alone doesn't raise on headless systems; a real
36
+ # read/write attempt is required to confirm a backend exists.
37
+ keyring.get_password(_SERVICE, "_colony_probe_")
38
+ return True
39
+ except keyring.errors.NoKeyringError:
40
+ return False
41
+ except Exception:
42
+ return False
43
+
44
+ def get(self, key: str, default: str | None = None) -> str | None:
45
+ try:
46
+ import keyring
47
+ value = keyring.get_password(_SERVICE, key)
48
+ return value if value is not None else default
49
+ except Exception:
50
+ return default
51
+
52
+ def set(self, key: str, value: str, *, secret_type: SecretType | None = None) -> None:
53
+ import keyring
54
+ keyring.set_password(_SERVICE, key, value)
55
+
56
+ def delete(self, key: str) -> None:
57
+ try:
58
+ import keyring
59
+ import keyring.errors
60
+ keyring.delete_password(_SERVICE, key)
61
+ except keyring.errors.PasswordDeleteError:
62
+ pass # Key didn't exist — idempotent delete
63
+ except Exception as exc:
64
+ logger.warning("KeyringBackend: failed to delete secret %r: %s", key, exc)
65
+
66
+ def list(self) -> list[str]:
67
+ """Return all configured keys by probing ALL_SECRET_KEYS.
68
+
69
+ The keyring library does not support listing all keys for a service,
70
+ so we iterate the known key set and check which ones are set.
71
+ """
72
+ return [k for k in sorted(ALL_SECRET_KEYS) if self.get(k) is not None]