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,188 @@
1
+ """DirectiveExtractor -- capture standing directives from owner messages.
2
+
3
+ Deterministic, cue-driven detection of prohibitions ("don't / stop / avoid /
4
+ leave X alone"), requirements ("always / from now on / make sure to X"), and
5
+ revocations ("you can X again / disregard that"). Owner-gated by the caller.
6
+
7
+ Only explicitly lasting clauses become standing rules. Ordinary task limits
8
+ stay in their source conversation. Pure communication-STYLE directives
9
+ ("be concise", "no emoji") are left to the PreferenceLearner and skipped here.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import re
15
+ from typing import List
16
+
17
+ from apsimo.directives.models import (
18
+ Directive, Polarity, normalize_terms,
19
+ )
20
+
21
+ # Prohibition openers -> capture the subject that follows.
22
+ _PROHIBIT_PATTERNS = [
23
+ re.compile(r"\b(?:do\s*not|don'?t|never|no\s+longer)\s+(?P<subj>.+)", re.I),
24
+ re.compile(r"\b(?:stop|avoid|quit|cease|drop|skip)\s+(?P<subj>.+)", re.I),
25
+ re.compile(r"\b(?:steer\s+clear\s+of|stay\s+away\s+from|lay\s+off|hands\s+off)\s+(?P<subj>.+)", re.I),
26
+ re.compile(r"\bleave\s+(?P<subj>.+?)\s+alone\b", re.I),
27
+ re.compile(r"\b(?:ignore|forget\s+about)\s+(?P<subj>.+)", re.I),
28
+ ]
29
+
30
+ # Revocation openers -> the owner is lifting a prior boundary.
31
+ _REVOKE_PATTERNS = [
32
+ re.compile(r"\b(?:you\s+can\s+now|go\s+ahead\s+and|actually,?\s+(?:you\s+can|go\s+ahead)|nevermind|never\s+mind|disregard(?:\s+(?:that|what\s+i\s+said))?|forget\s+what\s+i\s+said)\s*(?P<subj>.*)", re.I),
33
+ re.compile(r"\b(?:resume|unpause|restart)\s+(?P<subj>.+)", re.I),
34
+ ]
35
+
36
+ # One-command global pause (Amendment 1.5): the owner's kill switch. Any of
37
+ # these phrasings becomes an immediate GLOBAL ACT-level boundary: every
38
+ # autonomous act is refused until the owner lifts it (staged confirmation,
39
+ # like any boundary lift). Perception/reads stay open (ACT semantics).
40
+ _GLOBAL_PAUSE_PATTERNS = [
41
+ re.compile(r"\b(?:stop|pause|halt|freeze|suspend)\s+(?:(?:all|your|the|any)\s+)*"
42
+ r"(?:acting|autonomy|autonomous\s+(?:actions?|work|mode)|"
43
+ r"taking\s+actions?|doing\s+things)\b", re.I),
44
+ re.compile(r"\bstand\s+down\b", re.I),
45
+ re.compile(r"\bstop\s+acting\s+on\s+your\s+own\b", re.I),
46
+ ]
47
+
48
+
49
+ def make_global_pause_directive(raw_text: str = "",
50
+ source: str = "owner_explicit") -> Directive:
51
+ """The global ACT-level pause boundary (kill switch)."""
52
+ from apsimo.directives.models import GLOBAL_PAUSE_TERM, Level
53
+ return Directive(
54
+ subject="all autonomous actions (global pause)",
55
+ polarity=Polarity.PROHIBIT,
56
+ raw_text=raw_text or "stop acting",
57
+ # GLOBAL_PAUSE_TERM makes the guard refuse every act-capability
58
+ # action; the plain terms let "resume autonomy/acting" find and lift
59
+ # this directive through the normal staged confirmation.
60
+ match_terms=[GLOBAL_PAUSE_TERM, "autonomy", "acting", "autonomous"],
61
+ source=source, confidence=1.0, level=Level.ACT,
62
+ )
63
+
64
+ # Clause terminators: cut the subject at the first of these.
65
+ _CLAUSE_END = re.compile(r"\.(?=\s|$)|[;,!?\n]| but | and then | because | since | unless | so that ", re.I)
66
+
67
+ # Words that mark a pure communication-style directive (handled elsewhere).
68
+ _STYLE_ONLY = frozenset({
69
+ "concise", "verbose", "brief", "shorter", "longer", "emoji", "emojis",
70
+ "formal", "casual", "tone", "bullets", "bullet", "markdown", "wordy",
71
+ "replies", "reply", "responses", "response", "language", "words", "word",
72
+ # fillers that commonly accompany a pure style directive
73
+ "using", "use", "being", "sound", "sounding", "talking", "writing",
74
+ })
75
+
76
+
77
+ def _clean_subject(subj: str) -> str:
78
+ subj = _CLAUSE_END.split(subj, 1)[0].strip()
79
+ # strip a leading gerund/verb that adds no discriminating value is NOT done
80
+ # here: keeping "researching competitors" preserves intent; the guard's
81
+ # loose stem matching handles research/researching.
82
+ return subj.strip(" '\"")
83
+
84
+
85
+ def _is_style_only(subj: str) -> bool:
86
+ terms = set(normalize_terms(subj))
87
+ return bool(terms) and terms.issubset(_STYLE_ONLY)
88
+
89
+
90
+ # Durable admission requires positive lasting scope. Task instructions remain
91
+ # canonical evidence, even when they contain imperatives such as "never".
92
+ _TASK_SCOPE = re.compile(
93
+ r"\b(?:for|during) (?:this|the current) (?:task|request|phase|run|example)\b"
94
+ r"|\b(?:this is|these are) (?:an? |the )?(?:temporary|one[- ]off) (?:task |standing )?(?:instruction|requirement|rule|constraint)s?\b"
95
+ r"|(?:^|[.!?;\n]\s*)(?:temporary|one[- ]off) (?:task )?(?:instruction|requirement|rule|constraint)s?\s*[:.!?;\n]"
96
+ r"|\bnot (?:a |my )?(?:personal preference|standing (?:rule|instruction|boundary))\b"
97
+ r"|\b(?:for now|until (?:this|the) (?:task|phase|run) (?:ends|finishes|completes))\b", re.I)
98
+ _STANDING = re.compile(r"^(?:please\s+)?(?:(?:from now on|going forward|as a standing rule|as a permanent rule|permanently)[,:]?\s+|(?:always|never)\s+)", re.I)
99
+ _SENTENCES = re.compile(r"(?<=[.!?])\s+|[;\n]+")
100
+
101
+
102
+ def standing_clauses(message: str) -> List[str]:
103
+ """Bounded, exact clauses with explicit lasting intent, not inferred intent."""
104
+ text = (message or '').strip()
105
+ if not text or _TASK_SCOPE.search(text):
106
+ return []
107
+ admitted = []
108
+ for candidate in _SENTENCES.split(text):
109
+ clause = candidate.strip()
110
+ prefix = _STANDING.match(clause)
111
+ if prefix is None:
112
+ continue
113
+ # The keyword guard cannot enforce exceptions or contextual conditions.
114
+ # Keep these instructions in the conversation instead of broadening
115
+ # them into an unconditional standing prohibition.
116
+ if re.search(r'\b(?:unless|until|except|if|when|without)\b', clause, re.I):
117
+ continue
118
+ # Keep the exact rule clause, excluding explanatory or unrelated text.
119
+ ending = _CLAUSE_END.search(clause, prefix.end())
120
+ if ending:
121
+ end = ending.start() + (ending[0] in '.!?')
122
+ clause = clause[:end].strip()
123
+ if 0 < len(clause) <= 500:
124
+ if clause not in admitted:
125
+ admitted.append(clause)
126
+ return admitted
127
+
128
+
129
+ def extract_directives(message: str, *, source: str = "owner_explicit") -> List[Directive]:
130
+ """Extract explicit standing owner rules; one-off work is not a rule."""
131
+ from apsimo.directives.models import Level
132
+ text = (message or '').strip()
133
+ if not text:
134
+ return []
135
+ # The standalone pause command retains its immediate, explicit meaning.
136
+ command = re.sub(r'^please\s+', '', text.rstrip('.!?'), flags=re.I)
137
+ command = re.sub(r'\s+(?:for now|for a while)$', '', command, flags=re.I)
138
+ if any(pattern.fullmatch(command) for pattern in _GLOBAL_PAUSE_PATTERNS):
139
+ return [make_global_pause_directive(text, source=source)]
140
+ # Lifting a rule is explicit interaction with an existing rule, not new
141
+ # durable learning. Keep the existing confirmation behavior unchanged.
142
+ for pattern in _REVOKE_PATTERNS:
143
+ match = pattern.match(text)
144
+ if match and not _TASK_SCOPE.search(text):
145
+ subject = _clean_subject(match.group('subj') or '')
146
+ if subject and not _is_style_only(subject):
147
+ directive = Directive(subject=subject, polarity=Polarity.PREFER,
148
+ raw_text=text, source=source)
149
+ directive.__dict__['_revocation'] = True
150
+ return [directive]
151
+ return []
152
+ result = []
153
+ for clause in standing_clauses(text):
154
+ prefix = _STANDING.match(clause)
155
+ body = clause[prefix.end():].strip()
156
+ # Never/always themselves are the lasting imperative.
157
+ if re.search(r'\bnever\s+$', prefix[0], re.I):
158
+ body = 'never '+body
159
+ polarity, subject = Polarity.REQUIRE, body
160
+ for pattern in _PROHIBIT_PATTERNS:
161
+ match = pattern.match(body)
162
+ if match:
163
+ polarity, subject = Polarity.PROHIBIT, match.group('subj')
164
+ break
165
+ if polarity == Polarity.PROHIBIT and re.search(r'\b(?:before|after|once)\b', body, re.I):
166
+ continue
167
+ subject = re.sub(r'^always\s+', '', _clean_subject(subject), flags=re.I)
168
+ if polarity == Polarity.PROHIBIT and re.match(
169
+ r'^(?:(?:forget|fail|neglect|refuse)\s+to\b|stop\s+\w+ing\b)', subject, re.I):
170
+ continue # Negated omissions do not prohibit the underlying action.
171
+ if (not subject or len(subject) < 2 or _is_style_only(subject)
172
+ or re.match(r'^(?:(?:I|we|he|she|they|my|your|did|had|has|was|were|is|am|are)\b|have\s+(?:I|we|they)\b)', subject, re.I)
173
+ or re.match(r'^(?:(?:again|ever|really|actually|previously|once)\s+)*(?:did|do|does|have|has|had|am|is|are|was|were)\s+(?:I|we|he|she|they|you)\b', subject, re.I)
174
+ or polarity == Polarity.REQUIRE and not re.match(
175
+ r'^(?:ask|check|confirm|consult|contact|keep|leave|look|make|maintain|notify|obtain|read|remember|report|request|require|respect|review|run|save|seek|send|show|tell|test|track|use|verify|wait)\b', subject, re.I)):
176
+ continue
177
+ # Only the prohibited action determines observation versus action.
178
+ # Unrelated "read the documentation" elsewhere cannot escalate it.
179
+ perception = re.match(r'^(?:even\s+)?(?:look(?:ing)?\s+at|read(?:ing)?\b|watch(?:ing)?\b|monitor(?:ing)?\b|track(?:ing)?\b|stay\s+out\s+of|snoop|peek|observ(?:e|ing))', subject, re.I)
180
+ result.append(Directive(subject=subject, polarity=polarity, raw_text=clause,
181
+ match_terms=normalize_terms(subject), source=source,
182
+ confidence=0.9 if source == 'owner_explicit' else 0.6,
183
+ level=Level.OBSERVE if polarity == Polarity.PROHIBIT and perception else Level.ACT))
184
+ return result
185
+
186
+
187
+ def is_revocation(directive: Directive) -> bool:
188
+ return bool(directive.__dict__.get("_revocation"))
@@ -0,0 +1,364 @@
1
+ """DirectiveGuard -- enforce owner boundaries before any autonomous action.
2
+
3
+ Given a proposed action (deliver a message, execute a tool, run a directed
4
+ task, generate an initiative), the guard checks it against every active
5
+ PROHIBIT directive and returns a Verdict. Callers at each action chokepoint
6
+ (executor, delivery, generation, directed-action) consult it and REFUSE when
7
+ a boundary is violated.
8
+
9
+ Matching is deterministic and biased toward catching real violations on
10
+ SPECIFIC subjects (repo names, business names, proper nouns) while avoiding
11
+ false blocks on generic words. This hard, code-level gate is paired with
12
+ soft LLM awareness (active directives injected into reasoning context), so a
13
+ model can neither forget nor talk its way past a standing boundary.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import logging
19
+ import re
20
+ import time
21
+ from collections import deque
22
+ from dataclasses import dataclass, field
23
+ from typing import Any, Dict, List, Optional
24
+
25
+ from apsimo.directives.models import (
26
+ GLOBAL_PAUSE_TERM, Directive, Level, Polarity, normalize_terms,
27
+ )
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+ # Capability classes (tiered boundary semantics): boundaries bind ACTION, not
32
+ # judgment or perception, unless explicitly about perception. READ kinds stay
33
+ # open under an ACT-level boundary and are blocked only by an OBSERVE
34
+ # blackout. Active autonomous work (research jobs, delegation, delivery,
35
+ # execution) is an ACT capability -- "stop researching X" must stop research
36
+ # about X, while awareness/reads of X survive "don't touch X".
37
+ _READ_KINDS = frozenset({"repo_read", "read", "recall", "populate", "observe"})
38
+
39
+
40
+ class DirectiveStoreUnavailable(RuntimeError):
41
+ """The active owner-boundary set could not be read safely.
42
+
43
+ An unavailable or malformed store is not equivalent to a healthy store
44
+ with zero active prohibitions. Enforcement callers use this distinct
45
+ failure to apply their capability-specific fail-closed policy.
46
+ """
47
+
48
+
49
+ def action_capability(kind: str) -> str:
50
+ return "read" if (kind or "").lower() in _READ_KINDS else "act"
51
+
52
+
53
+ def boundary_fail_closed() -> bool:
54
+ """Whether an ERROR inside a boundary check refuses the action.
55
+
56
+ Default TRUE: a boundary is an owner-set prohibition, so an exception
57
+ while evaluating it must refuse (fail closed) rather than silently
58
+ allow the very action the owner may have forbidden. Set
59
+ COLONY_BOUNDARY_FAIL_CLOSED=false to restore the legacy allow-on-error
60
+ behavior.
61
+ """
62
+ from apsimo.util.autonomy_preset import resolve_bool
63
+ return resolve_bool("COLONY_BOUNDARY_FAIL_CLOSED", True)
64
+
65
+
66
+ @dataclass
67
+ class Action:
68
+ """A proposed autonomous action to be checked against boundaries."""
69
+
70
+ kind: str # deliver | execute_tool | directed_action | generate | research
71
+ text: str = "" # description / message / topic
72
+ target: str = "" # recipient / repo / business / subject
73
+ entity_id: str = "" # optional graph entity id
74
+ tool_name: str = ""
75
+ args: Optional[Dict[str, Any]] = None
76
+ high_risk: bool = False # outbound / mutating / directed -> fail-closed on ambiguity
77
+
78
+ def searchable_terms(self) -> List[str]:
79
+ parts = [self.text, self.target, self.tool_name]
80
+ if self.args:
81
+ for v in self.args.values():
82
+ if isinstance(v, (str, int, float)):
83
+ parts.append(str(v))
84
+ toks = set()
85
+ for p in parts:
86
+ toks.update(normalize_terms(p))
87
+ return list(toks)
88
+
89
+
90
+ @dataclass
91
+ class Verdict:
92
+ allowed: bool
93
+ violations: List[Directive] = field(default_factory=list)
94
+ reason: str = "ok"
95
+
96
+ def as_dict(self) -> Dict[str, Any]:
97
+ return {
98
+ "allowed": self.allowed,
99
+ "reason": self.reason,
100
+ "violations": [
101
+ {"id": d.id, **({} if d.evidence else {"subject": d.subject, "raw_text": d.raw_text})}
102
+ for d in self.violations
103
+ ],
104
+ }
105
+
106
+
107
+ def _common_terms() -> frozenset:
108
+ """Terms that appear in virtually every internal action text and must
109
+ never count as a match signal on their own: the product name, the agent's
110
+ own name, plus any deployment-supplied extras (COLONY_MATCH_COMMON_TERMS,
111
+ csv). Live incident 2026-07-05: a fragment boundary whose only surviving
112
+ term was "colony" matched every job that said "report to Colony"."""
113
+ import os
114
+ terms = {"colony"}
115
+ agent = os.environ.get("COLONY_AGENT_NAME", "")
116
+ for tok in re.findall(r"[a-z0-9]+", agent.lower()):
117
+ if len(tok) > 1:
118
+ terms.add(tok)
119
+ extra = os.environ.get("COLONY_MATCH_COMMON_TERMS", "")
120
+ for tok in extra.lower().split(","):
121
+ tok = tok.strip()
122
+ if tok:
123
+ terms.add(tok)
124
+ return frozenset(terms)
125
+
126
+
127
+ def _is_distinctive(term: str) -> bool:
128
+ """A term specific enough that a single match is a real signal.
129
+
130
+ The bar is deliberately high (>=8 chars, or a separator/digit shape like
131
+ repo/path/version tokens): mid-length generic words ("attempt",
132
+ "respond", "message") matching alone caused mass false blocks. Shorter
133
+ real subjects still match through the all-terms path below.
134
+ """
135
+ if term in _common_terms():
136
+ return False
137
+ return (
138
+ len(term) >= 8
139
+ or any(c in term for c in "-_/.")
140
+ or any(c.isdigit() for c in term)
141
+ )
142
+
143
+
144
+ def _terms_match(directive_terms: List[str], action_terms: List[str]) -> bool:
145
+ """True if the directive's subject matches the action's terms."""
146
+ if not directive_terms:
147
+ return False
148
+ action = set(action_terms)
149
+
150
+ def _hit(t: str) -> bool:
151
+ if t in action:
152
+ return True
153
+ # Stem/prefix containment for morphological variants only
154
+ # (research/researching, cert/certs, deploy/deployment). The shorter
155
+ # side must be a real stem: >=5 chars, or within 2 chars of the
156
+ # longer word. Without that floor, "what" counted as a variant of
157
+ # "whatsapp" and a WhatsApp boundary matched any sentence containing
158
+ # the word "what" (live false-block, 2026-07-05).
159
+ if len(t) >= 4:
160
+ for a in action:
161
+ if a == t:
162
+ return True
163
+ if len(a) >= 4 and (a.startswith(t) or t.startswith(a)):
164
+ shorter, longer = (a, t) if len(a) <= len(t) else (t, a)
165
+ if len(shorter) >= 5 or len(longer) - len(shorter) <= 2:
166
+ return True
167
+ return False
168
+
169
+ hits = [t for t in directive_terms if _hit(t)]
170
+ if not hits:
171
+ return False
172
+ # A distinctive subject term (repo/business/proper-noun-like) matching alone
173
+ # is a real signal. Generic short terms require the whole subject to match,
174
+ # and an all-terms match must include at least one substantive (len>=4,
175
+ # non-common) term so pronoun-grade fragments can never bind.
176
+ if any(_is_distinctive(t) for t in hits):
177
+ return True
178
+ common = _common_terms()
179
+ if not any(len(t) >= 4 and t not in common for t in hits):
180
+ return False
181
+ return len(hits) == len(directive_terms)
182
+
183
+
184
+ class DirectiveGuard:
185
+ """Consults the directive store and enforces PROHIBIT boundaries."""
186
+
187
+ def __init__(self, store: Any) -> None:
188
+ self._store = store
189
+ # Recent refusals, for observability / "why didn't you do X" answers.
190
+ self._recent_blocks: deque = deque(maxlen=100)
191
+ # Entity-scoped matching (2): {frozenset(alias_tokens): entity_id}. Built
192
+ # from the world model as entities are learned; empty -> keyword-only.
193
+ self._entity_index: Dict[frozenset, str] = {}
194
+
195
+ def set_entity_index(self, entities: Dict[str, List[str]]) -> None:
196
+ """Refresh the alias->entity_id index. entities = {entity_id: [names/aliases]}."""
197
+ index: Dict[frozenset, str] = {}
198
+ for eid, aliases in (entities or {}).items():
199
+ for alias in aliases:
200
+ toks = frozenset(normalize_terms(alias))
201
+ if toks:
202
+ index[toks] = eid
203
+ self._entity_index = index
204
+
205
+ def _resolve_entities(self, terms: set) -> set:
206
+ """Entity IDs whose alias tokens are all present in `terms`."""
207
+ if not self._entity_index or not terms:
208
+ return set()
209
+ return {eid for alias_toks, eid in self._entity_index.items()
210
+ if alias_toks <= terms}
211
+
212
+ def _active_prohibitions(self) -> List[Directive]:
213
+ if self._store is None:
214
+ raise DirectiveStoreUnavailable("directive store is unavailable")
215
+ try:
216
+ active = self._store.active(polarity=Polarity.PROHIBIT)
217
+ except Exception as exc:
218
+ logger.warning("DirectiveGuard: store read failed: %s", exc)
219
+ raise DirectiveStoreUnavailable(
220
+ "directive store active-boundary read failed"
221
+ ) from exc
222
+
223
+ if not isinstance(active, (list, tuple)) or any(
224
+ not isinstance(directive, Directive) for directive in active
225
+ ):
226
+ logger.warning(
227
+ "DirectiveGuard: store returned a malformed active-boundary set"
228
+ )
229
+ raise DirectiveStoreUnavailable(
230
+ "directive store returned a malformed active-boundary set"
231
+ )
232
+ return list(active)
233
+
234
+ def check(self, action: Action) -> Verdict:
235
+ """Return a Verdict. allowed=False means the action violates a boundary."""
236
+ prohibitions = self._active_prohibitions()
237
+ if not prohibitions:
238
+ return Verdict(allowed=True, reason="no_active_boundaries")
239
+
240
+ action_terms = action.searchable_terms()
241
+ action_term_set = set(action_terms)
242
+ capability = action_capability(action.kind)
243
+
244
+ # Global pause (Amendment 1.5): an active kill-switch boundary refuses
245
+ # every act-capability action immediately, no matching required.
246
+ # Reads/perception stay open (ACT semantics).
247
+ if capability != "read":
248
+ paused = [d for d in prohibitions
249
+ if GLOBAL_PAUSE_TERM in (d.match_terms or [])]
250
+ if paused:
251
+ d = paused[0]
252
+ action_summary = (action.text or action.target or action.tool_name)[:120]
253
+ logger.warning(
254
+ "DirectiveGuard GLOBAL PAUSE active [%s]: %s action (%r) "
255
+ "refused", d.id, action.kind, "source-bound action" if d.evidence else action_summary,
256
+ )
257
+ self._recent_blocks.append({
258
+ "ts": time.time(),
259
+ "action_kind": action.kind,
260
+ "capability": capability,
261
+ "action_summary": action_summary,
262
+ "directive_ids": [d.id],
263
+ "subjects": [d.subject],
264
+ "directives": [d.raw_text or d.subject],
265
+ })
266
+ return Verdict(allowed=False, violations=[d],
267
+ reason="global_pause_active")
268
+ # Entity-scoped: resolve the action's target/terms to entity IDs so a
269
+ # directive about an entity blocks actions naming it by ANY alias, not
270
+ # just a keyword hit. Keyword matching (below) stays as the fallback.
271
+ action_entities = {action.entity_id} if action.entity_id else set()
272
+ action_entities |= self._resolve_entities(action_term_set)
273
+ violations: List[Directive] = []
274
+ for d in prohibitions:
275
+ # Tiered semantics: an ACT-level boundary binds actions only;
276
+ # reads/perception stay open. Only an OBSERVE blackout binds reads.
277
+ if capability == "read" and (d.level or Level.ACT) != Level.OBSERVE:
278
+ continue
279
+ # scope by action kind if the directive restricts kinds
280
+ if d.action_kinds and action.kind not in d.action_kinds:
281
+ continue
282
+ directive_entities = set(d.entity_ids) | self._resolve_entities(set(d.match_terms))
283
+ if action_entities and directive_entities & action_entities:
284
+ violations.append(d)
285
+ continue
286
+ if _terms_match(d.match_terms, action_terms):
287
+ violations.append(d)
288
+
289
+ if violations:
290
+ subjects = "; ".join(v.id if v.evidence else v.subject for v in violations)
291
+ ids = ",".join(v.id for v in violations)
292
+ action_summary = (action.text or action.target or action.tool_name)[:120]
293
+ logged_summary = "source-bound action" if any(v.evidence for v in violations) else action_summary
294
+ logged_subjects = subjects
295
+ if capability == "read":
296
+ # OBSERVE blackout on a read: logged so introspection about the
297
+ # blindspot's existence still works ("not looking, per directive").
298
+ logger.info(
299
+ "DirectiveGuard: not looking, per directive [%s] -- %s read "
300
+ "of %r withheld (%s)",
301
+ ids, action.kind, logged_summary, logged_subjects,
302
+ )
303
+ else:
304
+ logger.warning(
305
+ "DirectiveGuard BLOCKED %s action (%r): violates boundary(ies) "
306
+ "[%s]: %s",
307
+ action.kind, logged_summary, ids, logged_subjects,
308
+ )
309
+ self._recent_blocks.append({
310
+ "ts": time.time(),
311
+ "action_kind": action.kind,
312
+ "capability": capability,
313
+ "action_summary": action_summary,
314
+ "directive_ids": [v.id for v in violations],
315
+ "subjects": [v.subject for v in violations],
316
+ "directives": [v.raw_text or v.subject for v in violations],
317
+ })
318
+ return Verdict(allowed=False, violations=violations,
319
+ reason=f"boundary_violation: {subjects}")
320
+ return Verdict(allowed=True, reason="ok")
321
+
322
+ def recent_blocks(self, limit: int = 20) -> List[Dict[str, Any]]:
323
+ """Most-recent refusals (for the 'why didn't you do X' introspection)."""
324
+ # A forgotten/corrected source must not reappear through this cache.
325
+ items = [item for item in self._recent_blocks
326
+ if self._store is not None and all(self._store.get(identifier)
327
+ for identifier in item["directive_ids"])][-limit:]
328
+ items.reverse()
329
+ return items
330
+
331
+ def obligations(self) -> List[Directive]:
332
+ """Active REQUIRE directives (standing obligations)."""
333
+ if self._store is None:
334
+ return []
335
+ try:
336
+ return self._store.active(polarity=Polarity.REQUIRE)
337
+ except Exception:
338
+ return []
339
+
340
+ def context_brief(self, limit: int = 20) -> str:
341
+ """Render active boundaries/obligations for injection into reasoning."""
342
+ if self._store is None:
343
+ return ""
344
+ try:
345
+ active = self._store.active()
346
+ except Exception:
347
+ return ""
348
+ if not active:
349
+ return ""
350
+ lines: List[str] = []
351
+ prohibits = [d for d in active if d.polarity == Polarity.PROHIBIT][:limit]
352
+ requires = [d for d in active if d.polarity == Polarity.REQUIRE][:limit]
353
+ if prohibits:
354
+ lines.append("MUST NOT (standing boundaries from the owner):")
355
+ # Several extracted clauses can reference the same complete owner
356
+ # message. Keep every selected quotation, but render it only once
357
+ # per polarity; action enforcement still consults every directive.
358
+ for text in dict.fromkeys(d.raw_text or d.subject for d in prohibits):
359
+ lines.append(f" - {text}")
360
+ if requires:
361
+ lines.append("MUST (standing obligations from the owner):")
362
+ for text in dict.fromkeys(d.raw_text or d.subject for d in requires):
363
+ lines.append(f" - {text}")
364
+ return "\n".join(lines)