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,206 @@
1
+ """Directive / boundary data model.
2
+
3
+ A Directive is a durable standing instruction from the owner, captured from
4
+ conversation or set explicitly. It has a polarity:
5
+
6
+ * PROHIBIT -- "don't / avoid / ignore / stop / leave alone X". A binding
7
+ boundary: autonomous actions that match it are REFUSED.
8
+ * REQUIRE -- "always / make sure to / from now on do X". A standing
9
+ obligation surfaced to the reasoner and the initiative layer.
10
+ * PREFER -- a soft preference (context only, never blocks).
11
+
12
+ Directives are deployment-agnostic: the subject text and match terms come
13
+ from the owner's own words / config, never hardcoded here.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import re
19
+ import json
20
+ import time
21
+ import uuid
22
+ from dataclasses import dataclass, field
23
+ from enum import Enum
24
+ from typing import Any, Dict, List, Optional
25
+
26
+
27
+ class Polarity(str, Enum):
28
+ PROHIBIT = "prohibit"
29
+ REQUIRE = "require"
30
+ PREFER = "prefer"
31
+
32
+
33
+ # Marker term for the one-command global pause (Amendment 1.5). A PROHIBIT
34
+ # directive carrying this term refuses EVERY act-capability action while
35
+ # active. It never matches by keyword (leading underscores keep it out of
36
+ # normalize_terms output), so only the explicit kill switch can set it.
37
+ GLOBAL_PAUSE_TERM = "__all_autonomy__"
38
+
39
+
40
+ class Level(str, Enum):
41
+ """How deep a PROHIBIT boundary cuts.
42
+
43
+ Boundaries bind ACTION, not judgment or perception, except when the
44
+ boundary is explicitly about perception (privacy). Ignorance-based safety
45
+ is brittle: perception and learning stay maximally broad while action is
46
+ gated.
47
+
48
+ ACT (default): blocks mutations, delegated tasks, and autonomous outbound
49
+ actions about the subject. Reads, awareness, reflection, and answering
50
+ the owner's direct questions remain OPEN.
51
+ OBSERVE: full blackout -- blocks reads/perception too. Chosen only when
52
+ the owner uses explicit perception language ("don't look at / don't
53
+ read / stay out of / don't monitor").
54
+ """
55
+ ACT = "act"
56
+ OBSERVE = "observe"
57
+
58
+
59
+ def default_level() -> "Level":
60
+ """Config knob: what an unqualified prohibition means (default act)."""
61
+ import os
62
+ raw = os.environ.get("COLONY_BOUNDARY_DEFAULT_LEVEL", "act").strip().lower()
63
+ try:
64
+ return Level(raw)
65
+ except ValueError:
66
+ return Level.ACT
67
+
68
+
69
+ # Explicit perception language -> OBSERVE (full blackout).
70
+ PERCEPTION_RE = re.compile(
71
+ r"\b(?:look(?:ing)?\s+at|read(?:ing)?(?!-only\b)|watch(?:ing)?|monitor(?:ing)?|"
72
+ r"track(?:ing)?|stay\s+out\s+of|snoop|peek|observe|observing|"
73
+ r"even\s+look)\b", re.IGNORECASE,
74
+ )
75
+
76
+
77
+ def level_from_text(text: Optional[str]) -> "Level":
78
+ """Derive the boundary level from the owner's phrasing."""
79
+ if text and PERCEPTION_RE.search(str(text)):
80
+ return Level.OBSERVE
81
+ return default_level()
82
+
83
+
84
+ class DirectiveStatus(str, Enum):
85
+ ACTIVE = "active"
86
+ REVOKED = "revoked"
87
+ EXPIRED = "expired"
88
+ SUPERSEDED = "superseded"
89
+
90
+
91
+ # Generic tokens that carry no discriminating meaning for subject matching.
92
+ _STOPWORDS = frozenset({
93
+ "the", "a", "an", "to", "of", "on", "in", "for", "with", "about", "at",
94
+ "my", "your", "me", "you", "it", "that", "this", "any", "some", "and",
95
+ "or", "please", "just", "really", "anymore", "again", "do", "not", "dont",
96
+ "don", "ever", "stop", "avoid", "ignore", "never", "leave", "alone",
97
+ "touch", "is", "are", "be", "when", "if", "should", "would", "can",
98
+ # generic filler verbs / quantifiers that carry no subject meaning
99
+ "track", "tracking", "anything", "everything", "something", "stuff",
100
+ "thing", "things", "worry", "worrying", "bother", "bothering", "mention",
101
+ "mentioning", "care", "dealing", "deal", "regarding", "worried",
102
+ })
103
+
104
+ _WORD = re.compile(r"[a-z0-9][a-z0-9_+.\-/]*")
105
+
106
+
107
+ def normalize_terms(text: Optional[str]) -> List[str]:
108
+ """Lowercase significant tokens from a subject/target string."""
109
+ if not text:
110
+ return []
111
+ toks = _WORD.findall(str(text).lower())
112
+ return [t for t in toks if t not in _STOPWORDS and len(t) > 1]
113
+
114
+
115
+ @dataclass
116
+ class Directive:
117
+ """A standing owner directive / boundary."""
118
+
119
+ subject: str # human-readable subject ("repo colony-web")
120
+ polarity: Polarity = Polarity.PROHIBIT
121
+ raw_text: str = "" # the owner's original words
122
+ match_terms: List[str] = field(default_factory=list) # normalized subject tokens
123
+ entity_ids: List[str] = field(default_factory=list) # optional graph entity ids
124
+ action_kinds: List[str] = field(default_factory=list) # limit to kinds; empty = all
125
+ source: str = "owner_explicit" # owner_explicit | inferred | config
126
+ confidence: float = 0.9
127
+ status: DirectiveStatus = DirectiveStatus.ACTIVE
128
+ level: Optional[Level] = None # act | observe (PROHIBIT only; None -> derive)
129
+ id: str = field(default_factory=lambda: f"dir-{uuid.uuid4().hex[:12]}")
130
+ created_at: float = field(default_factory=time.time)
131
+ updated_at: float = field(default_factory=time.time)
132
+ expires_at: Optional[float] = None
133
+ evidence: Optional[Dict[str, Any]] = None
134
+
135
+ def __post_init__(self) -> None:
136
+ if isinstance(self.polarity, str):
137
+ self.polarity = Polarity(self.polarity)
138
+ if isinstance(self.status, str):
139
+ self.status = DirectiveStatus(self.status)
140
+ if isinstance(self.level, str):
141
+ self.level = Level(self.level)
142
+ if self.level is None:
143
+ # Conservative migration/default: derive from phrasing; only
144
+ # perception-explicit wording escalates to a full blackout.
145
+ self.level = level_from_text(self.raw_text or self.subject)
146
+ if not self.match_terms:
147
+ self.match_terms = normalize_terms(self.subject)
148
+
149
+ def is_active(self, now: Optional[float] = None) -> bool:
150
+ if self.status != DirectiveStatus.ACTIVE:
151
+ return False
152
+ if self.expires_at is not None and (now or time.time()) > self.expires_at:
153
+ return False
154
+ return True
155
+
156
+ def to_row(self) -> Dict[str, Any]:
157
+ row = {
158
+ "id": self.id,
159
+ "subject": self.subject,
160
+ "polarity": self.polarity.value,
161
+ "raw_text": self.raw_text,
162
+ "match_terms": " ".join(self.match_terms),
163
+ "entity_ids": " ".join(self.entity_ids),
164
+ "action_kinds": " ".join(self.action_kinds),
165
+ "source": self.source,
166
+ "confidence": self.confidence,
167
+ "status": self.status.value,
168
+ "level": self.level.value if self.level else "act",
169
+ "created_at": self.created_at,
170
+ "updated_at": self.updated_at,
171
+ "expires_at": self.expires_at,
172
+ }
173
+
174
+ if self.evidence is not None:
175
+ # Existing metadata column, no second prose copy or schema. Older
176
+ # readers see an empty subject rather than resurrecting erased text.
177
+ row.update(subject='', raw_text='', match_terms='',
178
+ source='canonical-directive-v1:'+json.dumps({**self.evidence, 'method': self.source}, sort_keys=True, separators=(',', ':')))
179
+ return row
180
+
181
+ @classmethod
182
+ def from_row(cls, row: Dict[str, Any]) -> "Directive":
183
+ def _split(v: Any) -> List[str]:
184
+ return [t for t in str(v or "").split(" ") if t]
185
+ source = row.get('source', 'owner_explicit') or 'owner_explicit'
186
+ evidence = None
187
+ if source.startswith('canonical-directive-v1:'):
188
+ evidence = json.loads(source.removeprefix('canonical-directive-v1:'))
189
+ source = evidence.get('method', 'owner_explicit')
190
+ return cls(
191
+ evidence=evidence,
192
+ id=row["id"],
193
+ subject=row["subject"],
194
+ polarity=Polarity(row["polarity"]),
195
+ raw_text=row.get("raw_text", "") or "",
196
+ match_terms=_split(row.get("match_terms")),
197
+ entity_ids=_split(row.get("entity_ids")),
198
+ action_kinds=_split(row.get("action_kinds")),
199
+ source=source,
200
+ confidence=float(row.get("confidence", 0.9) or 0.9),
201
+ status=DirectiveStatus(row.get("status", "active")),
202
+ level=(Level(row["level"]) if row.get("level") else None),
203
+ created_at=float(row.get("created_at") or time.time()),
204
+ updated_at=float(row.get("updated_at") or time.time()),
205
+ expires_at=(float(row["expires_at"]) if row.get("expires_at") not in (None, "") else None),
206
+ )
@@ -0,0 +1,372 @@
1
+ """DirectiveManager -- capture, store, revoke, and enforce owner boundaries.
2
+
3
+ The single entry point the rest of Colony uses: the turn pipeline calls
4
+ ``capture_from_message`` on owner messages; the executor / delivery /
5
+ generation chokepoints call ``guard.check(action)``; the context assembler
6
+ calls ``guard.context_brief()``.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+ import re
13
+ import time
14
+ from dataclasses import dataclass, field
15
+ from typing import Any, List, Optional
16
+
17
+ from apsimo.directives.extractor import extract_directives, is_revocation
18
+ from apsimo.directives.guard import DirectiveGuard, Action, Verdict
19
+ from apsimo.directives.models import (
20
+ Directive, Level, Polarity, normalize_terms,
21
+ )
22
+ from apsimo.directives.store import DirectiveStore
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ # Affirmations that confirm a pending boundary lift. Deliberately short/explicit.
27
+ _AFFIRM = re.compile(
28
+ r"^\s*(?:yes|yep|yeah|yup|confirm(?:ed)?|do it|go ahead|correct|affirmative|"
29
+ r"sure|ok(?:ay)?|please do|resume it|lift it)\b", re.IGNORECASE,
30
+ )
31
+ # How long a pending confirmation stays valid (owner must confirm promptly).
32
+ _PENDING_TTL_SECS = 900.0
33
+
34
+ # Subjects that start with an unresolved anaphor or a dangling conjunction are
35
+ # sentence FRAGMENTS ("that and wipe it from colony", "or manage this job",
36
+ # "attempt them"): the real referent was lost at extraction, so storing them
37
+ # creates boundaries that fuzzy-match everything and nothing the owner meant.
38
+ # Found live 2026-07-05: a store poisoned with these plus test canaries blocked
39
+ # every routine job once the worker governor went live.
40
+ _ANAPHORA_LEAD = re.compile(
41
+ r"^(?:that|this|it|them|those|these|him|her|or|and|but|so|then|there|"
42
+ r"y|x)\b", re.IGNORECASE,
43
+ )
44
+
45
+
46
+ @dataclass
47
+ class CaptureResult:
48
+ captured: List[Directive] = field(default_factory=list)
49
+ revoked: List[Directive] = field(default_factory=list)
50
+ ack: Optional[str] = None # confirmation echo for the owner
51
+ needs_confirmation: Optional[str] = None # a lift awaiting explicit confirmation
52
+
53
+ def any(self) -> bool:
54
+ return bool(self.captured or self.revoked or self.ack or self.needs_confirmation)
55
+
56
+
57
+ class DirectiveManager:
58
+ def __init__(self, store: DirectiveStore) -> None:
59
+ self.store = store
60
+ self.guard = DirectiveGuard(store)
61
+ # Pending boundary lift awaiting explicit owner confirmation (1c).
62
+ self._pending_lift: Optional[dict] = None
63
+ # One-shot acknowledgment to echo back to the owner (1a).
64
+ self._last_ack: Optional[str] = None
65
+ self._ack_ids: List[str] = []
66
+
67
+ # -- capture -------------------------------------------------------
68
+ def capture_from_message(self, message: str, *, source: str = "owner_explicit",
69
+ source_id: str = "", contact_id: str = "") -> CaptureResult:
70
+ """Extract directives from an OWNER message and persist them.
71
+
72
+ Asymmetric friction (1c): SETTING a boundary is one-turn easy; LIFTING
73
+ one is staged as a pending confirmation and only applied when the owner
74
+ explicitly confirms on a subsequent message. Caller MUST gate to owner.
75
+ """
76
+ result = CaptureResult()
77
+ text = (message or "").strip()
78
+
79
+ # 0) Machine-origin text never sets or lifts a boundary: skill/system/
80
+ # interrupted-turn residue that reaches this path with the owner's
81
+ # contact id (cron review prompts, tool echoes) must not become
82
+ # standing directives. (Live incident 2026-07-05: the executor's own
83
+ # refusal text "Do not attempt them" was re-captured as a boundary.)
84
+ try:
85
+ from apsimo.delivery.reachout_policy import is_system_origin
86
+ if is_system_origin(text):
87
+ return result
88
+ except ImportError:
89
+ pass
90
+
91
+ # 1) If a lift is pending, ONLY an explicit affirmation on the immediate
92
+ # next message confirms it; anything else clears it (fail-safe: a
93
+ # prohibition is never lifted by an attribution error or stray text).
94
+ if self.store.ledger is not None and not source_id:
95
+ return result
96
+ if self._pending_lift is not None and not self._pending_lift_current():
97
+ self._pending_lift = None
98
+ if self._pending_lift is not None:
99
+ expired = time.time() - self._pending_lift["ts"] > _PENDING_TTL_SECS
100
+ if not expired and _AFFIRM.match(text):
101
+ if self.store.ledger is not None:
102
+ from .evidence import bind
103
+ if bind(self.store.ledger, source_id=source_id, contact_id=contact_id,
104
+ message=text, clause=text) is None:
105
+ return result
106
+ revoked = self._apply_revocation_ids(self._pending_lift["ids"])
107
+ subj = self._pending_lift["subject"]
108
+ self._pending_lift = None
109
+ result.revoked = revoked
110
+ result.ack = f"Confirmed. I will resume {subj}."
111
+ self._last_ack = result.ack
112
+ self._ack_ids = [d.id for d in revoked]
113
+ return result
114
+ # not confirmed -> the boundary stays; drop the pending lift
115
+ self._pending_lift = None
116
+
117
+ found = extract_directives(text, source=source)
118
+
119
+ # 2) A revocation stages a pending confirmation (never lifts immediately).
120
+ revocations = [d for d in found if is_revocation(d)]
121
+ if revocations:
122
+ rev = revocations[0]
123
+ terms = set(rev.match_terms or normalize_terms(rev.subject))
124
+ matches = [d for d in self.store.active(polarity=Polarity.PROHIBIT)
125
+ if set(d.match_terms) & terms]
126
+ if matches:
127
+ evidence = None
128
+ if self.store.ledger is not None:
129
+ from .evidence import bind
130
+ evidence = bind(self.store.ledger, source_id=source_id, contact_id=contact_id,
131
+ message=text, clause=text)
132
+ if evidence is None:
133
+ return result
134
+ self._pending_lift = {
135
+ "subject": rev.subject, "ids": [d.id for d in matches],
136
+ "ts": time.time(), "evidence": evidence,
137
+ }
138
+ subs = "; ".join(d.raw_text or d.subject for d in matches)
139
+ result.needs_confirmation = (
140
+ f"You asked me to resume: {rev.subject}. That lifts a boundary "
141
+ f"you set ({subs}). Confirm and I will resume it; otherwise it stays in place."
142
+ )
143
+ return result
144
+
145
+ # 3) New prohibitions / requirements are stored immediately -- after
146
+ # the quality gates: fragments are refused and duplicates never pile up.
147
+ for d in found:
148
+ if not self._subject_acceptable(d):
149
+ logger.info("Directive capture skipped: degenerate subject")
150
+ continue
151
+ if self._duplicate_active(d):
152
+ logger.debug("Directive capture skipped: duplicate active rule")
153
+ continue
154
+ if source_id:
155
+ from .evidence import bind
156
+ d.evidence = bind(self.store.ledger, source_id=source_id, contact_id=contact_id,
157
+ message=text, clause=d.raw_text)
158
+ if d.evidence is None:
159
+ continue
160
+ self.store.add(d)
161
+ result.captured.append(d)
162
+ if result.captured:
163
+ parts = []
164
+ for d in result.captured:
165
+ # The echo STATES the interpretation (tiered semantics) so the
166
+ # owner can correct it in one turn.
167
+ from apsimo.directives.models import GLOBAL_PAUSE_TERM
168
+ if GLOBAL_PAUSE_TERM in (d.match_terms or []):
169
+ parts.append(
170
+ "GLOBAL PAUSE engaged: I will take no autonomous "
171
+ "actions until you tell me to resume (say 'resume "
172
+ "autonomy')")
173
+ elif d.polarity != Polarity.PROHIBIT:
174
+ parts.append(f"I will make sure to {d.subject}")
175
+ elif d.level == Level.OBSERVE:
176
+ parts.append(
177
+ f"full blackout on {d.subject}: I will not act on it "
178
+ "or look at it")
179
+ else:
180
+ parts.append(
181
+ f"I will stop acting on {d.subject}. I will still keep "
182
+ "awareness of it; say 'don't even look at it' for a "
183
+ "full blackout")
184
+ result.ack = "Noted: " + "; ".join(parts) + "."
185
+ self._last_ack = result.ack
186
+ self._ack_ids = [d.id for d in result.captured]
187
+ return result
188
+
189
+ # -- capture quality gates ------------------------------------------
190
+ _PRONOUNS = frozenset({"it", "that", "this", "them", "those", "these",
191
+ "him", "her", "they", "he", "she"})
192
+
193
+ def _subject_acceptable(self, d: Directive) -> bool:
194
+ """A storable subject names something concrete: no unresolved
195
+ anaphora fragments, and at least one substantive term survives
196
+ normalization."""
197
+ subj = (d.subject or "").strip()
198
+ if not subj:
199
+ return False
200
+ if _ANAPHORA_LEAD.match(subj):
201
+ return False
202
+ raw_tokens = re.findall(r"[a-z]+", subj.lower())
203
+ terms = d.match_terms or normalize_terms(subj)
204
+ substantive = [t for t in terms
205
+ if len(t) >= 4 and t not in self._PRONOUNS]
206
+ if not substantive:
207
+ return False
208
+ # A bare pronoun object with a thin subject ("attempt them",
209
+ # "resend it") is an unresolved reference: the thing the owner meant
210
+ # never made it into the subject.
211
+ if any(t in self._PRONOUNS for t in raw_tokens) and len(substantive) < 2:
212
+ return False
213
+ return True
214
+
215
+ def _duplicate_active(self, d: Directive) -> bool:
216
+ """An active directive with the same polarity and identical
217
+ normalized terms already covers this capture."""
218
+ key = set(d.match_terms or normalize_terms(d.subject))
219
+ if not key:
220
+ return False
221
+ try:
222
+ for e in self.store.active(polarity=d.polarity):
223
+ if set(e.match_terms or []) == key:
224
+ return True
225
+ except Exception:
226
+ pass
227
+ return False
228
+
229
+ def consume_ack(self) -> Optional[str]:
230
+ """Return and clear the one-shot acknowledgment (echoed once)."""
231
+ ack, self._last_ack = self._last_ack, None
232
+ identifiers, self._ack_ids = self._ack_ids, []
233
+ if any(self.store.get(identifier) is None for identifier in identifiers):
234
+ return None
235
+ return ack
236
+
237
+ def _pending_lift_current(self) -> bool:
238
+ pending = self._pending_lift
239
+ if (pending is None or time.time() - pending["ts"] > _PENDING_TTL_SECS
240
+ or not any(self.store.get(identifier) for identifier in pending["ids"])):
241
+ return False
242
+ if self.store.ledger is not None:
243
+ from .evidence import _message
244
+ return bool(pending.get("evidence") and _message(self.store.ledger, pending["evidence"]) is not None)
245
+ return True
246
+
247
+ def pending_confirmation(self) -> Optional[str]:
248
+ """The current pending boundary-lift prompt, if any (for context echo)."""
249
+ if self._pending_lift is None:
250
+ return None
251
+ if not self._pending_lift_current():
252
+ self._pending_lift = None
253
+ return None
254
+ subj = self._pending_lift["subject"]
255
+ return f"AWAITING CONFIRMATION: the owner asked to resume {subj}; do not resume until they confirm."
256
+
257
+ def _apply_revocation_ids(self, ids: List[str]) -> List[Directive]:
258
+ revoked = []
259
+ for did in ids:
260
+ d = self.store.get(did)
261
+ if d and self.store.revoke(did):
262
+ revoked.append(d)
263
+ logger.info("Directive revoked by owner (confirmed): id=%s", did)
264
+ return revoked
265
+
266
+ def add_explicit(self, subject: str, polarity: str = "prohibit",
267
+ raw_text: str = "", source: str = "owner_explicit",
268
+ entity_ids: Optional[List[str]] = None) -> Directive:
269
+ d = Directive(
270
+ subject=subject, polarity=Polarity(polarity), raw_text=raw_text or subject,
271
+ source=source, entity_ids=entity_ids or [],
272
+ )
273
+ return self.store.add(d)
274
+
275
+ # -- boundary-respecting critical flag (high-severity path) ---------
276
+ def set_delivery_router(self, router: Any) -> None:
277
+ """Async callable(payload)->bool routing through the guarded reach-out
278
+ path. Used ONLY for the once-per-boundary critical flag."""
279
+ self._delivery_router = router
280
+
281
+ async def flag_critical(self, subject: str, finding: str,
282
+ severity: float = 0.9) -> dict:
283
+ """Under an ACT boundary, reflection may surface something critical
284
+ (security vulnerability, data loss, financial risk). Correct behavior:
285
+ an internal note plus AT MOST ONE flag surfaced to the owner through
286
+ the guarded reach-out path, clearly marked as boundary-respecting.
287
+ Never silent action; never silent omission of a critical fact.
288
+ """
289
+ import os
290
+ try:
291
+ threshold = float(os.environ.get("COLONY_BOUNDARY_FLAG_MIN_SEVERITY", "0.85"))
292
+ except (TypeError, ValueError):
293
+ threshold = 0.85
294
+ logger.info("boundary-concern noted (severity=%.2f): %s -- %s",
295
+ severity, subject, finding[:160])
296
+ if severity < threshold:
297
+ return {"flagged": False, "reason": "below_threshold",
298
+ "noted_internally": True}
299
+
300
+ # Which active prohibition covers this subject?
301
+ terms = set(normalize_terms(subject))
302
+ match = None
303
+ for d in self.store.active(polarity=Polarity.PROHIBIT):
304
+ if set(d.match_terms) & terms:
305
+ match = d
306
+ break
307
+ if match is None:
308
+ return {"flagged": False, "reason": "no_matching_boundary",
309
+ "noted_internally": True}
310
+ if self.store.has_flag(match.id):
311
+ return {"flagged": False, "reason": "already_flagged_once",
312
+ "noted_internally": True}
313
+ if not self.store.record_flag(match.id, finding, severity):
314
+ return {"flagged": False, "reason": "already_flagged_once",
315
+ "noted_internally": True}
316
+
317
+ delivered = False
318
+ router = getattr(self, "_delivery_router", None)
319
+ if router is not None:
320
+ try:
321
+ from apsimo.proposals import Proposal, proposal_to_payload
322
+ prop = Proposal(
323
+ title=f"Boundary-respecting flag: {subject[:60]}",
324
+ finding=(f"You asked me to leave {match.subject} alone, and I have. "
325
+ f"But you should know: {finding}"),
326
+ why_it_helps="a critical fact should never be silently omitted",
327
+ suggested_action="Tell me whether to keep hands off or investigate.",
328
+ source=f"boundary-flag:{match.id}",
329
+ initiative_type="proposal",
330
+ confidence=min(1.0, max(0.0, severity)),
331
+ )
332
+ delivered = bool(await router(proposal_to_payload(prop)))
333
+ except Exception:
334
+ logger.debug("boundary flag delivery failed", exc_info=True)
335
+ logger.warning(
336
+ "BOUNDARY-RESPECTING CRITICAL FLAG surfaced once for [%s] %r: %s",
337
+ match.id, match.subject, finding[:200],
338
+ )
339
+ return {"flagged": True, "directive_id": match.id, "delivered": delivered}
340
+
341
+ # -- entity-scoped matching (2) ------------------------------------
342
+ def set_entity_index(self, entities: dict) -> None:
343
+ """Provide {entity_id: [names/aliases]} so boundaries resolve by entity."""
344
+ self.guard.set_entity_index(entities)
345
+
346
+ async def refresh_entity_index(self, world_store: Any, limit: int = 500) -> int:
347
+ """Load entities + aliases from the world model into the guard index."""
348
+ if world_store is None:
349
+ return 0
350
+ try:
351
+ ents = await world_store.find_entities(query="", limit=limit)
352
+ except Exception:
353
+ return 0
354
+ mapping = {}
355
+ for e in ents or []:
356
+ eid = getattr(e, "id", None) or (e.get("id") if isinstance(e, dict) else None)
357
+ name = getattr(e, "name", None) or (e.get("name") if isinstance(e, dict) else None)
358
+ aliases = getattr(e, "aliases", None) or (e.get("aliases") if isinstance(e, dict) else []) or []
359
+ if eid and name:
360
+ mapping[eid] = [name] + list(aliases)
361
+ self.guard.set_entity_index(mapping)
362
+ return len(mapping)
363
+
364
+ # -- enforce -------------------------------------------------------
365
+ def check(self, action: Action) -> Verdict:
366
+ return self.guard.check(action)
367
+
368
+ def context_brief(self) -> str:
369
+ return self.guard.context_brief()
370
+
371
+ def active(self) -> List[Directive]:
372
+ return self.store.active()