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,202 @@
1
+ """Render distill / digest / discovery agent prompts from a FeedSpec.
2
+
3
+ The structure is distilled from a battle-tested single-feed deployment:
4
+ actionable framing ("what changes because of this"), hard link requirements,
5
+ tiered promotion rules for human-shared links, a privacy block, and a
6
+ learning-loop hook that records source promotions so the registry evolves.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from .spec import FeedSpec
12
+
13
+
14
+ def _archive_hint(spec: FeedSpec, suffix: str = "") -> str:
15
+ tail = f"-{suffix}" if suffix else "-HHMM"
16
+ return f"{spec.briefs_dir}/YYYY-MM-DD{tail}.md"
17
+
18
+
19
+ def _privacy_block(spec: FeedSpec) -> str:
20
+ lines = ["PRIVACY (hard rules for anything you post):"]
21
+ for item in spec.privacy.get("forbidden", []):
22
+ lines.append(f"- Never mention {item}.")
23
+ aliases = spec.privacy.get("aliases") or []
24
+ if aliases:
25
+ lines.append("- Reference hardware/platforms by public product name only "
26
+ f"({', '.join(aliases)}).")
27
+ note = spec.privacy.get("note")
28
+ if note:
29
+ lines.append(f"- {note}")
30
+ return "\n".join(lines)
31
+
32
+
33
+ def _registry_block(spec: FeedSpec) -> str:
34
+ if not spec.registry:
35
+ return ""
36
+ by_tier: dict[str, list[str]] = {"P0": [], "P1": [], "P2": []}
37
+ for e in spec.registry:
38
+ label = e.get("handle") or e.get("name")
39
+ cat = e.get("category", "")
40
+ by_tier.setdefault(e.get("weight", "P1"), []).append(
41
+ f"{label}" + (f" ({cat})" if cat else ""))
42
+ lines = ["SOURCE REGISTRY (tiered; used by the promotion rules):"]
43
+ tier_meaning = {"P0": "always include, even a bare link",
44
+ "P1": "include; promote to P0 when a curator flags it",
45
+ "P2": "background only"}
46
+ for tier in ("P0", "P1", "P2"):
47
+ if by_tier.get(tier):
48
+ lines.append(f"- {tier} ({tier_meaning[tier]}): " + ", ".join(by_tier[tier]))
49
+ return "\n".join(lines)
50
+
51
+
52
+ def _promotion_block(spec: FeedSpec) -> str:
53
+ return """PROMOTION RULES for human-shared links (apply in order, first match wins):
54
+ 1. Link's domain/author matches a P0 source AND content is a new SOTA / reproducible recipe / breakthrough -> P0
55
+ 2. Link's domain/author matches a P0 source -> P0
56
+ 3. Link's domain/author matches a P1 source AND a curator flagged it important/!!!/P0 -> P0
57
+ 4. Link's domain/author matches a P1 source -> P1
58
+ 5. Link's domain/author matches a P2 source -> P2
59
+ 6. A curator flagged it important/!!! -> P0
60
+ 7. A curator provided any context note -> P1
61
+ 8. Bare link from an unknown source -> P2
62
+
63
+ Treat every human message as a link share and PRESERVE their context notes verbatim:
64
+ text before a URL is their note about it; text after is an annotation (e.g. "and the
65
+ first reply" means follow that reply too). Never summarize their notes away - they are
66
+ first-class signal."""
67
+
68
+
69
+ def _community_block(spec: FeedSpec) -> str:
70
+ read_back = spec.destination.get("read_back", "")
71
+ if spec.audience != "group" or not read_back:
72
+ return ""
73
+ return f"""Read the destination group's recent messages from human participants since the last
74
+ brief. Use: {read_back}
75
+
76
+ {_promotion_block(spec)}
77
+
78
+ COMMUNITY INPUT section: every link humans posted in this window, in order, each with
79
+ its priority and a one-line framing answering "{spec.brief['framing']}".
80
+
81
+ ABSOLUTE RULES for the group:
82
+ - The group is a one-way broadcast channel for you: read for intel, post briefs, nothing else.
83
+ - Do NOT react to, reply to, or engage with any message in the group, ever.
84
+ - If a participant asks you a direct question there, do not answer it in the group."""
85
+
86
+
87
+ def _delivery_block(spec: FeedSpec, what: str = "brief") -> str:
88
+ kind = spec.destination.get("kind")
89
+ if kind == "command":
90
+ return (f"Post the {what} to its destination by piping it to:\n"
91
+ f" {spec.destination['send_command']}")
92
+ if kind == "deliver":
93
+ return (f"Your final response IS the {what}; the scheduler delivers it automatically. "
94
+ "Do not send it yourself with any messaging tool.")
95
+ return (f"Do NOT post the {what} anywhere; this feed is archive-only. "
96
+ "Your final response should be the brief text.")
97
+
98
+
99
+ def _context_files_block(spec: FeedSpec) -> str:
100
+ files = spec.brief.get("context_files") or []
101
+ if not files:
102
+ return ""
103
+ lines = ["Also read these context files if they exist and fold relevant movement into the brief:"]
104
+ for f in files:
105
+ lines.append(f"- {f.get('label', 'context')}: {f['path']}")
106
+ return "\n".join(lines)
107
+
108
+
109
+ def _learning_block(spec: FeedSpec) -> str:
110
+ if not spec.learning_loop:
111
+ return ""
112
+ return f"""LEARNING LOOP HOOK: after archiving, whenever you promoted a link from an unknown
113
+ source to P0/P1 on content weight alone, append one line to {spec.deltas_log}:
114
+ TIMESTAMP new_source=domain-or-handle (reason) promoted_to=P1 link=...
115
+ This is how the source registry evolves. Skip the log when nothing was promoted."""
116
+
117
+
118
+ def render_distill(spec: FeedSpec) -> str:
119
+ parts = [
120
+ f"You are the {spec.title} distillation agent.",
121
+ f"TOPIC CHARTER: {spec.topic}",
122
+ f"""Your job each run:
123
+ 1. Read the feed queue at {spec.queue_path}
124
+ 2. Distill the top items into a brief. Plain text, no markdown. Structure:
125
+ - Header line with feed title and date/time (local timezone)
126
+ - HIGH PRIORITY section (P0 and P1 items), each with: title, 1-2 sentence summary,
127
+ a "why it matters" line, benchmark numbers if any, and SOURCE LINK
128
+ - STANDARD section (P2 items): one line each with link, cap at the top
129
+ {spec.brief['standard_cap']} and drop the rest silently
130
+ 3. ALWAYS include a source link for every item so readers can click through.
131
+
132
+ OUTPUT FRAMING: for every HIGH PRIORITY item the "why it matters" line MUST answer:
133
+ "{spec.brief['framing']}"
134
+ Not "what does this article say" - the framing has to be actionable.""",
135
+ ]
136
+ for block in (_context_files_block(spec), _community_block(spec),
137
+ _privacy_block(spec), _registry_block(spec)):
138
+ if block:
139
+ parts.append(block)
140
+ parts.append(_delivery_block(spec))
141
+ parts.append(f"Archive the brief at {_archive_hint(spec)}")
142
+ lb = _learning_block(spec)
143
+ if lb:
144
+ parts.append(lb)
145
+ extra = spec.brief.get("extra_instructions")
146
+ if extra:
147
+ parts.append(extra)
148
+ parts.append('If the queue is empty and there is genuinely nothing to report, '
149
+ 'respond with exactly "[SILENT]" and post nothing.')
150
+ return "\n\n".join(parts)
151
+
152
+
153
+ def render_digest(spec: FeedSpec) -> str:
154
+ parts = [
155
+ f"You are the {spec.title} daily digest agent.",
156
+ f"TOPIC CHARTER: {spec.topic}",
157
+ f"""Your job: produce ONE daily rollup of the last 24h.
158
+ 1. Read the feed queue at {spec.queue_path}
159
+ 2. Read the briefs archived in the last 24h under {spec.briefs_dir}/
160
+ 3. Synthesize: the day's 3-7 most consequential items (dedup against what briefs
161
+ already said - lead with what CHANGED over the day, not a re-list), each with a
162
+ "why it matters" line answering "{spec.brief['framing']}" and a SOURCE LINK.
163
+ 4. Close with a 2-3 sentence trend read: where is this topic moving?""",
164
+ ]
165
+ for block in (_context_files_block(spec), _community_block(spec), _privacy_block(spec)):
166
+ if block:
167
+ parts.append(block)
168
+ parts.append(_delivery_block(spec, what="digest"))
169
+ parts.append(f"Archive the digest at {_archive_hint(spec, 'digest')}")
170
+ parts.append('If nothing meaningful happened, respond with exactly "[SILENT]".')
171
+ return "\n\n".join(parts)
172
+
173
+
174
+ def render_discovery(spec: FeedSpec) -> str:
175
+ return f"""You are the source-discovery agent for the {spec.title}.
176
+
177
+ TOPIC CHARTER: {spec.topic}
178
+
179
+ Your job: find NEW media sources worth adding to this feed, qualify them yourself,
180
+ and propose only the best. Search broadly with web search and VARY your angles each
181
+ run: niche experts on X/social platforms, technical blogs and newsletters, active
182
+ GitHub authors/orgs, forums and communities, benchmark/evaluation sites, academic
183
+ groups publishing with code.
184
+
185
+ QUALIFY each candidate before proposing: original signal (not aggregation)?
186
+ Track record of real numbers/working code? Active at least weekly? Not already
187
+ covered by the registry? High signal-to-noise?
188
+
189
+ FILTER HARD: zero good proposals beats one bad one. Diminishing returns over time
190
+ are expected - be honest when a sweep comes up empty.
191
+
192
+ For each qualified source propose: name + link, 2-3 recent high-signal examples with
193
+ direct links, why it fits this feed's topic charter, and how to monitor it (account
194
+ watch, RSS URL, forum URL, keyword filter). The operator applies accepted proposals
195
+ to the feed's spec file (registry + sources sections).
196
+
197
+ {_privacy_block(spec)}
198
+
199
+ {_registry_block(spec) or 'SOURCE REGISTRY: (none yet - everything qualified is new)'}
200
+
201
+ Your final response is the proposal report (or a one-line "no qualified sources this
202
+ sweep"); the scheduler delivers it."""
@@ -0,0 +1,18 @@
1
+ """Response Gate — deterministic 7-layer response pipeline."""
2
+
3
+ from apsimo.gate.models import GatePayload, GateDecision, GateResultCode, DispatchResult
4
+ from apsimo.gate.config import GateConfig
5
+ from apsimo.gate.pipeline import ResponseGate
6
+ from apsimo.gate.audit import GateAuditLog, GateAuditRecord, InMemoryAuditLog
7
+
8
+ __all__ = [
9
+ "GatePayload",
10
+ "GateDecision",
11
+ "GateResultCode",
12
+ "DispatchResult",
13
+ "GateConfig",
14
+ "ResponseGate",
15
+ "GateAuditLog",
16
+ "GateAuditRecord",
17
+ "InMemoryAuditLog",
18
+ ]
apsimo/gate/audit.py ADDED
@@ -0,0 +1,61 @@
1
+ """GateAuditLog — append-only audit log for all gate decisions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import uuid
7
+ from dataclasses import dataclass, field
8
+ from datetime import datetime, timezone
9
+ from typing import Optional, Protocol
10
+
11
+ from apsimo.gate.models import GateDecision
12
+
13
+
14
+ @dataclass
15
+ class GateAuditRecord:
16
+ record_id: str
17
+ turn_id: str
18
+ session_id: str
19
+ contact_id: str
20
+ result_code: str
21
+ blocked: bool
22
+ blocking_layer: Optional[int]
23
+ block_reason: Optional[str]
24
+ layer_results: dict
25
+ sensitivity_level: str
26
+ overrides_applied: list
27
+ evaluated_at: datetime
28
+ response_text_hash: str # SHA-256 of response_text (not plaintext)
29
+
30
+
31
+ class GateAuditLog(Protocol):
32
+ """Append-only audit log interface."""
33
+
34
+ async def record(self, decision: GateDecision) -> None:
35
+ """Write a gate decision to the audit log before dispatch."""
36
+ ...
37
+
38
+
39
+ class InMemoryAuditLog:
40
+ """In-memory audit log for testing."""
41
+
42
+ def __init__(self) -> None:
43
+ self.records: list[GateAuditRecord] = []
44
+
45
+ async def record(self, decision: GateDecision) -> None:
46
+ rec = GateAuditRecord(
47
+ record_id=str(uuid.uuid4()),
48
+ turn_id=decision.payload_turn_id,
49
+ session_id=decision.layer_results.get("_session_id", ""),
50
+ contact_id=decision.layer_results.get("_contact_id", ""),
51
+ result_code=decision.result_code.value,
52
+ blocked=decision.blocked,
53
+ blocking_layer=decision.blocking_layer,
54
+ block_reason=decision.block_reason,
55
+ layer_results=decision.layer_results,
56
+ sensitivity_level=decision.layer_results.get("_sensitivity", "standard"),
57
+ overrides_applied=decision.layer_results.get("_overrides_applied", []),
58
+ evaluated_at=decision.evaluated_at,
59
+ response_text_hash=decision.layer_results.get("_response_hash", ""),
60
+ )
61
+ self.records.append(rec)
@@ -0,0 +1,166 @@
1
+ """Versioned communication constraints for one ResponseGuard evaluation.
2
+
3
+ The embedding host remains the authority source. This contract only binds a
4
+ candidate response to the exact target, route, grant, and semantic policy that
5
+ the host says constrain it. It deliberately cannot grant execution/control
6
+ authority or select private owner context.
7
+
8
+ ``policy_digest`` identifies the host's complete immutable policy record. The
9
+ separate ``context_digest`` covers the exact, reduced record Colony received
10
+ and made visible to guard/model inputs, so callers can detect substitution or
11
+ field-level tampering without Colony needing the host's private policy store.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import hashlib
17
+ import json
18
+ import re
19
+ from typing import Any, Dict, Literal
20
+
21
+ from pydantic import BaseModel, ConfigDict, Field, field_validator
22
+
23
+
24
+ COMMUNICATION_POLICY_CONTEXT_SCHEMA = "CommunicationPolicyContextV1"
25
+ COMMUNICATION_POLICY_CONTEXT_VERSION = 1
26
+ COMMUNICATION_DISCLOSURE_CLASSES = frozenset(
27
+ {"none", "public", "contact_scoped", "owner_explicit"}
28
+ )
29
+ MAX_COMMUNICATION_PURPOSE_CHARS = 1_024
30
+ MAX_COMMUNICATION_DISCLOSURE_STATEMENT_CHARS = 2_048
31
+
32
+ _IDENTIFIER_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:/-]{7,255}$")
33
+ _DIGEST_RE = re.compile(r"^[a-f0-9]{64}$")
34
+
35
+
36
+ def _canonical_json(value: object) -> str:
37
+ """Return the one stable JSON representation used by ``context_digest``."""
38
+
39
+ return json.dumps(
40
+ value,
41
+ ensure_ascii=False,
42
+ allow_nan=False,
43
+ separators=(",", ":"),
44
+ sort_keys=True,
45
+ )
46
+
47
+
48
+ class CommunicationPolicyContextV1(BaseModel):
49
+ """Exact, immutable communication-policy input for ResponseGuard.
50
+
51
+ Every field is required, including ``schema``, ``version``, and the three
52
+ literal-false capability statements. That makes a partial policy invalid
53
+ instead of allowing defaults to silently widen or reinterpret it.
54
+ """
55
+
56
+ model_config = ConfigDict(extra="forbid", frozen=True, strict=True)
57
+
58
+ schema_name: Literal["CommunicationPolicyContextV1"] = Field(
59
+ alias="schema", serialization_alias="schema"
60
+ )
61
+ version: Literal[1]
62
+ target_contact_id: str = Field(min_length=1, max_length=128)
63
+ route_id: str = Field(min_length=8, max_length=256)
64
+ grant_id: str = Field(min_length=8, max_length=256)
65
+ grant_digest: str = Field(min_length=64, max_length=64)
66
+ policy_id: str = Field(min_length=8, max_length=256)
67
+ policy_digest: str = Field(min_length=64, max_length=64)
68
+ purpose: str = Field(min_length=1, max_length=MAX_COMMUNICATION_PURPOSE_CHARS)
69
+ disclosure_class: Literal[
70
+ "none", "public", "contact_scoped", "owner_explicit"
71
+ ]
72
+ disclosure_statement: str = Field(
73
+ min_length=1,
74
+ max_length=MAX_COMMUNICATION_DISCLOSURE_STATEMENT_CHARS,
75
+ )
76
+ grants_execution_authority: Literal[False]
77
+ grants_control_authority: Literal[False]
78
+ selects_owner_private_context: Literal[False]
79
+
80
+ @field_validator(
81
+ "target_contact_id",
82
+ "purpose",
83
+ "disclosure_statement",
84
+ )
85
+ @classmethod
86
+ def _canonical_text(cls, value: str) -> str:
87
+ if value != value.strip():
88
+ raise ValueError("communication policy text must be canonical")
89
+ if any(ord(character) < 0x20 or ord(character) == 0x7F for character in value):
90
+ raise ValueError("communication policy text contains control characters")
91
+ return value
92
+
93
+ @field_validator("target_contact_id")
94
+ @classmethod
95
+ def _exact_target(cls, value: str) -> str:
96
+ if (
97
+ value.lower() in {"all", "any", "everyone"}
98
+ or any(character in value for character in ("*", "?", "[", "]"))
99
+ ):
100
+ raise ValueError("communication policy target must be exact")
101
+ return value
102
+
103
+ @field_validator("route_id", "grant_id", "policy_id")
104
+ @classmethod
105
+ def _identifier(cls, value: str) -> str:
106
+ if not _IDENTIFIER_RE.fullmatch(value):
107
+ raise ValueError("communication policy identifier is invalid")
108
+ return value
109
+
110
+ @field_validator("grant_digest", "policy_digest")
111
+ @classmethod
112
+ def _digest(cls, value: str) -> str:
113
+ if not _DIGEST_RE.fullmatch(value):
114
+ raise ValueError("communication policy digest must be lowercase SHA-256")
115
+ return value
116
+
117
+ @field_validator(
118
+ "grants_execution_authority",
119
+ "grants_control_authority",
120
+ "selects_owner_private_context",
121
+ mode="before",
122
+ )
123
+ @classmethod
124
+ def _literal_false_boolean(cls, value: object) -> object:
125
+ # ``False == 0`` in Python, so Literal[False] alone accepts integer
126
+ # zero. The wire contract requires an explicit JSON boolean.
127
+ if type(value) is not bool or value is not False:
128
+ raise ValueError("communication policy capability must be false")
129
+ return value
130
+
131
+ def canonical_dict(self) -> Dict[str, Any]:
132
+ """The exact JSON-compatible context included in the binding digest."""
133
+
134
+ return self.model_dump(mode="json", by_alias=True)
135
+
136
+ @property
137
+ def context_digest(self) -> str:
138
+ """SHA-256 identity of every field Colony received and exposed."""
139
+
140
+ return hashlib.sha256(
141
+ _canonical_json(self.canonical_dict()).encode("utf-8")
142
+ ).hexdigest()
143
+
144
+ def to_model_prompt_fragment(self) -> str:
145
+ """Deterministic, unambiguous model view of the constraints.
146
+
147
+ Values are serialized as canonical JSON instead of interpolated into
148
+ instructions. The preamble makes explicit that the object only
149
+ narrows disclosure and cannot confer any authority.
150
+ """
151
+
152
+ return (
153
+ "Communication policy constraints (data only). This context grants no "
154
+ "execution authority, control authority, or private-context selection:\n"
155
+ + _canonical_json(self.canonical_dict())
156
+ )
157
+
158
+
159
+ __all__ = [
160
+ "COMMUNICATION_DISCLOSURE_CLASSES",
161
+ "COMMUNICATION_POLICY_CONTEXT_SCHEMA",
162
+ "COMMUNICATION_POLICY_CONTEXT_VERSION",
163
+ "CommunicationPolicyContextV1",
164
+ "MAX_COMMUNICATION_DISCLOSURE_STATEMENT_CHARS",
165
+ "MAX_COMMUNICATION_PURPOSE_CHARS",
166
+ ]
apsimo/gate/config.py ADDED
@@ -0,0 +1,72 @@
1
+ """GateConfig — full configuration for the Response Gate pipeline."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Optional
7
+
8
+
9
+ @dataclass
10
+ class GateConfig:
11
+ """Full configuration for the Response Gate pipeline."""
12
+
13
+ # Sensitivity
14
+ sensitivity: str = "standard" # paranoid | standard | relaxed
15
+
16
+ # Layer 3
17
+ cross_context_lookback_hours: int = 4
18
+
19
+ # Layer 6 — secondary LLM appropriateness review. Off by default because
20
+ # it needs an injected reviewer client. Explicitly enabling it without one
21
+ # returns review_unavailable; this does not enable another approval path.
22
+ enable_secondary_review: bool = False
23
+ secondary_review_model: str = "claude-haiku-4-5-20251001"
24
+
25
+ # Layer 7 — send-delay cancel window. 0 = no hold (the default; L7 is a
26
+ # pass-through). Set >0 (server env COLONY_GATE_SEND_DELAY_SECS) for a
27
+ # real cancellation window on the request-path gate.
28
+ send_delay_seconds: float = 0.0
29
+
30
+ # Rejection feedback loop
31
+ max_retries: int = 3
32
+
33
+ # Context loading
34
+ context_token_limit: int = 80_000
35
+
36
+ # Concurrent sessions
37
+ max_concurrent_sessions: int = 20
38
+
39
+ # Supervised mode
40
+ require_send_approval: bool = False
41
+
42
+ # Owner-shareable patterns (bypass Layer 2)
43
+ owner_shareable_patterns: list = field(default_factory=list)
44
+
45
+ # Per-contact overrides
46
+ contact_overrides: list = field(default_factory=list)
47
+
48
+ # Custom PII patterns
49
+ custom_pii_patterns: list = field(default_factory=list)
50
+
51
+ # Injection ruleset
52
+ injection_ruleset_path: str = "colony/gate/rulesets/injection_v1.yaml"
53
+ ruleset_hot_reload: bool = False
54
+
55
+ # Audit
56
+ audit_retention_days: int = 365
57
+
58
+ # Workspace
59
+ log_full_deliberation: bool = False
60
+ workspace_memory_ttl_days: int = 90
61
+
62
+ def is_owner_shareable(self, value: str, pattern_name: str) -> bool:
63
+ for item in self.owner_shareable_patterns:
64
+ if item.get("pattern_name") == pattern_name and item.get("value") == value:
65
+ return True
66
+ return False
67
+
68
+ def get_contact_override(self, contact_id: str) -> Optional[dict]:
69
+ for override in self.contact_overrides:
70
+ if override.get("contact_id") == contact_id:
71
+ return override
72
+ return None