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
apsimo/gate/models.py ADDED
@@ -0,0 +1,64 @@
1
+ """Gate data models — GatePayload, GateDecision, DispatchResult."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from datetime import datetime, timezone
7
+ from enum import Enum
8
+ from typing import Optional
9
+
10
+ from apsimo.gate.communication_policy import CommunicationPolicyContextV1
11
+ from apsimo.intelligence.relationships.trust_tiers import TrustTier
12
+
13
+
14
+ class GateResultCode(str, Enum):
15
+ PASS = "pass"
16
+ BLOCK_RECIPIENT = "block_recipient"
17
+ BLOCK_PII = "block_pii"
18
+ BLOCK_CROSS_CONTEXT = "block_cross_context"
19
+ BLOCK_TRUST_TIER = "block_trust_tier"
20
+ BLOCK_INJECTION = "block_injection"
21
+ BLOCK_REVIEW = "block_review"
22
+ PENDING_DELAY = "pending_delay"
23
+
24
+
25
+ @dataclass
26
+ class GatePayload:
27
+ """The only view of a response that the gate pipeline sees.
28
+
29
+ MUST NOT contain any workspace or reasoning content.
30
+ Fields are immutable after construction.
31
+ """
32
+ response_text: str
33
+ target_contact_id: str
34
+ target_gateway: str
35
+ session_id: str
36
+ trust_tier: TrustTier
37
+ mentioned_entities: frozenset # entities from THIS session's history
38
+ turn_id: str
39
+ incoming_message_text: str # original incoming message (for Layer 5)
40
+ # Immutable host-owned purpose/disclosure constraints. Checkers and any
41
+ # restricted review model see the exact same object whose digest is bound
42
+ # into the ResponseGuard result. None preserves the legacy contract.
43
+ communication_policy: Optional[CommunicationPolicyContextV1] = None
44
+ created_at: datetime = field(default_factory=lambda: datetime.now(tz=timezone.utc))
45
+
46
+
47
+ @dataclass
48
+ class GateDecision:
49
+ """Result of gate pipeline evaluation."""
50
+ payload_turn_id: str
51
+ result_code: GateResultCode
52
+ blocked: bool
53
+ blocking_layer: Optional[int] # 1–7, None if PASS
54
+ block_reason: Optional[str] # human-readable, used in rejection
55
+ flagged_excerpt: Optional[str] # redacted excerpt for rejection feedback
56
+ layer_results: dict # per-layer detail for audit log
57
+ evaluated_at: datetime = field(default_factory=lambda: datetime.now(tz=timezone.utc))
58
+
59
+
60
+ @dataclass
61
+ class DispatchResult:
62
+ blocked: bool
63
+ reason: Optional[str] = None
64
+ gate_decision: Optional[GateDecision] = None
@@ -0,0 +1,5 @@
1
+ """PendingDispatch — re-exported from l7_delay for external consumers."""
2
+
3
+ from apsimo.gate.layers.l7_delay import PendingDispatch, PendingDispatchStore, PendingDispatchContext
4
+
5
+ __all__ = ["PendingDispatch", "PendingDispatchStore", "PendingDispatchContext"]
@@ -0,0 +1,206 @@
1
+ """ResponseGate — seven-layer response gate pipeline orchestrator."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import logging
7
+ import time
8
+ from typing import Any, Optional
9
+
10
+ # Dedicated audit logger for security-relevant gate events (SEC-13-L-03)
11
+ _audit_log = logging.getLogger("colony.gate.audit")
12
+
13
+ from apsimo.gate.models import GateDecision, GatePayload, GateResultCode, DispatchResult
14
+ from apsimo.gate.layers.l1_recipient import RecipientVerifier
15
+ from apsimo.gate.layers.l2_pii import PIIScanner
16
+ from apsimo.gate.layers.l3_cross_context import CrossContextDetector
17
+ from apsimo.gate.layers.l4_trust_tier import TrustTierChecker
18
+ from apsimo.gate.layers.l5_injection import InjectionDetector
19
+ from apsimo.gate.layers.l6_review import SecondaryReviewer
20
+ from apsimo.gate.layers.l7_delay import SendDelayGate, PendingDispatchStore
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class ResponseGate:
26
+ """Seven-layer response gate pipeline.
27
+
28
+ Usage::
29
+
30
+ gate = ResponseGate(config, session_store, audit_log)
31
+ result = await gate.evaluate(payload)
32
+ if not result.blocked:
33
+ await dispatch(payload)
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ config: Any,
39
+ session_store: Any,
40
+ audit_log: logging.Logger,
41
+ secondary_reviewer: Optional[SecondaryReviewer] = None,
42
+ dispatch_store: Optional[PendingDispatchStore] = None,
43
+ ) -> None:
44
+ """Initialize the ResponseGate pipeline.
45
+
46
+ Args:
47
+ config: Gate configuration object (GateConfig or compatible).
48
+ session_store: Session store used by L1 and L3 layers.
49
+ audit_log: Logger to record gate decisions for the audit trail.
50
+ secondary_reviewer: Optional override for L6; defaults to SecondaryReviewer(config).
51
+ dispatch_store: Optional persistent store for L7 delayed dispatches.
52
+ """
53
+ self._config = config
54
+ self._l1 = RecipientVerifier(session_store)
55
+ self._l2 = PIIScanner(config)
56
+ self._l3 = CrossContextDetector(session_store, config)
57
+ self._l4 = TrustTierChecker(config)
58
+ self._l5 = InjectionDetector(config)
59
+ self._l6 = secondary_reviewer or SecondaryReviewer(config)
60
+ self._l7 = SendDelayGate(config, dispatch_store)
61
+ self._audit = audit_log
62
+ # Only explicitly enabled review needs a client. Do not report a
63
+ # completed review when that optional dependency is unavailable.
64
+ if (getattr(config, "enable_secondary_review", False)
65
+ and getattr(self._l6, "_llm_client", None) is None):
66
+ logger.warning(
67
+ "Gate L6 secondary review is ENABLED but no review LLM is "
68
+ "configured; L6 reports review_unavailable. Inject a client or disable "
69
+ "enable_secondary_review.")
70
+
71
+ async def evaluate(self, payload: GatePayload) -> GateDecision:
72
+ """Run all gate layers. Returns decision; does NOT dispatch."""
73
+ response_hash = hashlib.sha256(payload.response_text.encode()).hexdigest()
74
+
75
+ layer_results: dict = {
76
+ "_session_id": payload.session_id,
77
+ "_contact_id": payload.target_contact_id,
78
+ "_sensitivity": self._config.sensitivity,
79
+ "_overrides_applied": [],
80
+ "_response_hash": response_hash,
81
+ }
82
+
83
+ # Check per-contact overrides
84
+ override = self._config.get_contact_override(payload.target_contact_id)
85
+ bypass_layers: list[int] = []
86
+ if override:
87
+ raw_bypass = override.get("bypass_layers", [])
88
+ layer_results["_overrides_applied"] = [f"contact:{payload.target_contact_id}"]
89
+ # Layer 1 can NEVER be bypassed
90
+ bypass_layers = [layer_id for layer_id in raw_bypass if layer_id != 1]
91
+
92
+ # Emit structured audit entry for each active bypass (SEC-13-L-03)
93
+ for layer_id in bypass_layers:
94
+ _audit_log.warning(
95
+ "gate_layer_bypassed",
96
+ extra={
97
+ "contact_id": payload.target_contact_id,
98
+ "layer_id": layer_id,
99
+ "override_source": override.get("source", "unknown"),
100
+ "message_id": payload.turn_id,
101
+ "timestamp": time.time(),
102
+ },
103
+ )
104
+
105
+ # Layers 1–5: deterministic, short-circuit on first block
106
+ deterministic_layers = [
107
+ (1, self._l1),
108
+ (2, self._l2),
109
+ (3, self._l3),
110
+ (4, self._l4),
111
+ (5, self._l5),
112
+ ]
113
+ for layer_num, checker in deterministic_layers:
114
+ if layer_num in bypass_layers:
115
+ # GATE-01: audit log every operator bypass exercise
116
+ logger.warning(
117
+ "GATE_OPERATOR_BYPASS: layer=%d session_id=%s contact_id=%s",
118
+ layer_num,
119
+ payload.session_id,
120
+ payload.target_contact_id,
121
+ )
122
+ layer_results[f"layer_{layer_num}"] = {"bypassed": True}
123
+ continue
124
+ result = await checker.check(payload)
125
+ layer_results[f"layer_{layer_num}"] = {
126
+ "blocked": result.blocked,
127
+ "code": result.code,
128
+ "reason": result.reason,
129
+ "suspicious": result.suspicious,
130
+ }
131
+ if result.blocked:
132
+ decision = GateDecision(
133
+ payload_turn_id=payload.turn_id,
134
+ result_code=GateResultCode(result.code),
135
+ blocked=True,
136
+ blocking_layer=layer_num,
137
+ block_reason=result.reason,
138
+ flagged_excerpt=result.flagged_excerpt,
139
+ layer_results=layer_results,
140
+ )
141
+ await self._audit.record(decision)
142
+ return decision
143
+
144
+ # Layer 6: explicitly enabled secondary review.
145
+ l5_result = layer_results.get("layer_5", {})
146
+ injection_suspicious = l5_result.get("suspicious", False) if isinstance(l5_result, dict) else False
147
+
148
+ if self._config.enable_secondary_review and 6 not in bypass_layers:
149
+ l6_result = await self._l6.review(
150
+ payload, injection_suspicious=injection_suspicious
151
+ )
152
+ layer_results["layer_6"] = {
153
+ "flagged": l6_result.flagged,
154
+ "category": l6_result.category,
155
+ "status": l6_result.status,
156
+ }
157
+ if l6_result.flagged:
158
+ decision = GateDecision(
159
+ payload_turn_id=payload.turn_id,
160
+ result_code=GateResultCode.BLOCK_REVIEW,
161
+ blocked=True,
162
+ blocking_layer=6,
163
+ block_reason=("secondary_review_flagged" if l6_result.status == "reviewed"
164
+ else "secondary_review_" + l6_result.status),
165
+ flagged_excerpt=None,
166
+ layer_results=layer_results,
167
+ )
168
+ await self._audit.record(decision)
169
+ return decision
170
+ else:
171
+ layer_results["layer_6"] = {"skipped": True}
172
+
173
+ # Layer 7: send delay
174
+ if 7 not in bypass_layers:
175
+ l7_result = await self._l7.hold(payload)
176
+ layer_results["layer_7"] = {
177
+ "cancelled": l7_result.cancelled,
178
+ "pending_id": l7_result.pending_id,
179
+ }
180
+ if l7_result.cancelled:
181
+ decision = GateDecision(
182
+ payload_turn_id=payload.turn_id,
183
+ result_code=GateResultCode.PENDING_DELAY,
184
+ blocked=True,
185
+ blocking_layer=7,
186
+ block_reason=l7_result.cancel_reason or "async_cancellation_during_delay",
187
+ flagged_excerpt=None,
188
+ layer_results=layer_results,
189
+ )
190
+ await self._audit.record(decision)
191
+ return decision
192
+ else:
193
+ layer_results["layer_7"] = {"bypassed": True}
194
+
195
+ # All layers passed
196
+ decision = GateDecision(
197
+ payload_turn_id=payload.turn_id,
198
+ result_code=GateResultCode.PASS,
199
+ blocked=False,
200
+ blocking_layer=None,
201
+ block_reason=None,
202
+ flagged_excerpt=None,
203
+ layer_results=layer_results,
204
+ )
205
+ await self._audit.record(decision)
206
+ return decision
@@ -0,0 +1,259 @@
1
+ """Rejection feedback loop — regenerate once when the ResponseGuard blocks.
2
+
3
+ Rebuilt ResponseGuard-native: the original loop was an orphan of the retired
4
+ 7-layer ResponseGate and depended on subsystems that no longer exist
5
+ (GatePayload sessions, MetaLearner.record_gate_rejection, an escalation
6
+ manager). The dataclasses are kept as the stable contract; the loop now runs
7
+ against ``ResponseGuard.evaluate`` and a caller-supplied ``regenerate`` hook,
8
+ and rejections persist in a small ``RejectionStore`` instead of MetaLearner.
9
+
10
+ Error contract (mirrors the guard's own):
11
+ * A guard BLOCK is never overridden by a loop-internal error — if anything
12
+ inside the loop raises after a block, the result stays blocked (fail
13
+ CLOSED on the block side).
14
+ * If the loop errors while the latest guard verdict is an allow, the allow
15
+ stands (fail OPEN on the allow side) — a loop fault must never silence a
16
+ reply the guard already cleared.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import logging
22
+ import sqlite3
23
+ import threading
24
+ import uuid
25
+ from dataclasses import dataclass, field
26
+ from datetime import datetime, timedelta, timezone
27
+ from typing import Any, Awaitable, Callable, Dict, List, Optional
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ @dataclass
33
+ class RejectionNotice:
34
+ """Structured rejection fed back to the LLM for regeneration."""
35
+ notice_id: str
36
+ turn_id: str
37
+ session_id: str
38
+ contact_display_name: str
39
+ block_reason_type: str
40
+ blocking_layer: int
41
+ redacted_excerpt: Optional[str]
42
+ trust_tier: str
43
+ retry_number: int
44
+ created_at: datetime = field(default_factory=lambda: datetime.now(tz=timezone.utc))
45
+
46
+ def to_llm_prompt_fragment(self) -> str:
47
+ excerpt_line = ""
48
+ if self.redacted_excerpt:
49
+ excerpt_line = f'\nFlagged content: "{self.redacted_excerpt}"'
50
+ return (
51
+ f"Your response to {self.contact_display_name} was blocked by the "
52
+ f"response gate.\n\n"
53
+ f"Reason: {self.block_reason_type}{excerpt_line}\n\n"
54
+ f"Please generate a new response that does not contain this content. "
55
+ f"The recipient's trust tier is {self.trust_tier}. Adjust accordingly."
56
+ )
57
+
58
+
59
+ @dataclass
60
+ class GateRejectionEvent:
61
+ """Learning signal for a gate rejection (persisted in RejectionStore)."""
62
+ event_id: str = field(default_factory=lambda: str(uuid.uuid4()))
63
+ session_id: str = ""
64
+ contact_id: str = ""
65
+ trust_tier: str = ""
66
+ blocking_layer: int = 0
67
+ block_reason: str = ""
68
+ retry_number: int = 0
69
+ eventually_succeeded: bool = False
70
+ turn_id: str = ""
71
+ timestamp: datetime = field(default_factory=lambda: datetime.now(tz=timezone.utc))
72
+
73
+
74
+ @dataclass
75
+ class FeedbackLoopResult:
76
+ passed: bool
77
+ payload: Optional[object] # the (possibly regenerated) response text
78
+ attempts: int
79
+
80
+
81
+ class RejectionStore:
82
+ """SQLite persistence for gate rejections (replaces the MetaLearner
83
+ dependency of the retired loop). Also the counting surface for the
84
+ enforce circuit breaker."""
85
+
86
+ def __init__(self, db_path: Optional[str] = None) -> None:
87
+ self._lock = threading.RLock()
88
+ self._conn = sqlite3.connect(db_path or ":memory:",
89
+ check_same_thread=False)
90
+ self._conn.row_factory = sqlite3.Row
91
+ with self._lock:
92
+ self._conn.execute(
93
+ """CREATE TABLE IF NOT EXISTS gate_rejections (
94
+ event_id TEXT PRIMARY KEY, session_id TEXT,
95
+ contact_id TEXT, trust_tier TEXT, blocking_layer INTEGER,
96
+ block_reason TEXT, retry_number INTEGER,
97
+ eventually_succeeded INTEGER, turn_id TEXT, ts TEXT
98
+ )""")
99
+ self._conn.commit()
100
+
101
+ def record(self, event: GateRejectionEvent) -> None:
102
+ with self._lock:
103
+ self._conn.execute(
104
+ """INSERT INTO gate_rejections
105
+ (event_id, session_id, contact_id, trust_tier,
106
+ blocking_layer, block_reason, retry_number,
107
+ eventually_succeeded, turn_id, ts)
108
+ VALUES (?,?,?,?,?,?,?,?,?,?)
109
+ ON CONFLICT(event_id) DO UPDATE SET
110
+ eventually_succeeded=excluded.eventually_succeeded,
111
+ retry_number=excluded.retry_number""",
112
+ (event.event_id, event.session_id, event.contact_id,
113
+ event.trust_tier, event.blocking_layer, event.block_reason,
114
+ event.retry_number, 1 if event.eventually_succeeded else 0,
115
+ event.turn_id, event.timestamp.isoformat()))
116
+ self._conn.commit()
117
+
118
+ def recent(self, limit: int = 50) -> List[Dict[str, Any]]:
119
+ with self._lock:
120
+ rows = self._conn.execute(
121
+ "SELECT * FROM gate_rejections ORDER BY ts DESC LIMIT ?",
122
+ (int(limit),)).fetchall()
123
+ return [dict(r) for r in rows]
124
+
125
+ def count_since(self, hours: float = 24.0) -> int:
126
+ cutoff = (datetime.now(tz=timezone.utc)
127
+ - timedelta(hours=hours)).isoformat()
128
+ with self._lock:
129
+ r = self._conn.execute(
130
+ "SELECT COUNT(*) FROM gate_rejections WHERE ts >= ?",
131
+ (cutoff,)).fetchone()
132
+ return int(r[0]) if r else 0
133
+
134
+ def close(self) -> None:
135
+ try:
136
+ self._conn.close()
137
+ except Exception:
138
+ pass
139
+
140
+
141
+ # regenerate(prompt_fragment, blocked_text) -> new text (or None to give up)
142
+ RegenerateFn = Callable[[str, str], Awaitable[Optional[str]]]
143
+
144
+
145
+ class RejectionFeedbackLoop:
146
+ """One regenerate-and-re-check cycle after a ResponseGuard block.
147
+
148
+ Usage::
149
+
150
+ loop = RejectionFeedbackLoop(guard, store=store, regenerate=regen)
151
+ result = await loop.run(text, initial_result=guard_result,
152
+ target_contact_id=..., ...)
153
+
154
+ ``regenerate`` is a deployment-supplied async hook; without one the loop
155
+ cannot revise, so a block simply stands.
156
+ """
157
+
158
+ def __init__(self, guard: Any, store: Optional[RejectionStore] = None,
159
+ regenerate: Optional[RegenerateFn] = None,
160
+ max_retries: int = 1) -> None:
161
+ self._guard = guard
162
+ self._store = store
163
+ self._regenerate = regenerate
164
+ self._max_retries = max(0, int(max_retries))
165
+
166
+ def _record(self, event: Optional[GateRejectionEvent]) -> None:
167
+ if event is None or self._store is None:
168
+ return
169
+ try:
170
+ self._store.record(event)
171
+ except Exception:
172
+ logger.debug("rejection store record failed", exc_info=True)
173
+
174
+ @staticmethod
175
+ def _block_reason(result: Any) -> tuple[str, Optional[str]]:
176
+ for f in getattr(result, "findings", None) or []:
177
+ if getattr(f, "severity", "") == "block":
178
+ return (getattr(f, "check", "blocked"),
179
+ getattr(f, "excerpt", None))
180
+ return ("blocked", None)
181
+
182
+ async def run(self, response_text: str, *,
183
+ initial_result: Any = None,
184
+ contact_display_name: str = "the recipient",
185
+ **eval_kwargs: Any) -> FeedbackLoopResult:
186
+ """Evaluate (or take ``initial_result``), regenerate on block, re-check.
187
+
188
+ ``eval_kwargs`` are forwarded verbatim to ``guard.evaluate`` so the
189
+ re-check runs under exactly the same identity/context as the original.
190
+ """
191
+ text = response_text
192
+ result = initial_result
193
+ attempts = 0
194
+ event: Optional[GateRejectionEvent] = None
195
+ # Tracks the LAST KNOWN guard verdict so the exception handler can
196
+ # honor the error contract (closed on block, open on allow).
197
+ blocked = bool(getattr(result, "blocked", True))
198
+ try:
199
+ if result is None:
200
+ result = await self._guard.evaluate(
201
+ response_text=text, **eval_kwargs)
202
+ attempts = 1
203
+ blocked = result.blocked
204
+
205
+ for retry in range(self._max_retries + 1):
206
+ if not result.blocked:
207
+ if event is not None:
208
+ event.eventually_succeeded = True
209
+ self._record(event)
210
+ return FeedbackLoopResult(passed=True, payload=text,
211
+ attempts=max(attempts, 1))
212
+
213
+ reason, excerpt = self._block_reason(result)
214
+ event = GateRejectionEvent(
215
+ session_id=str(eval_kwargs.get("session_id", "")),
216
+ contact_id=str(eval_kwargs.get("target_contact_id", "")),
217
+ trust_tier=str(eval_kwargs.get("trust_tier", "")),
218
+ blocking_layer=0,
219
+ block_reason=reason,
220
+ retry_number=retry,
221
+ turn_id=str(eval_kwargs.get("turn_id", "")),
222
+ )
223
+ self._record(event)
224
+
225
+ if retry >= self._max_retries or self._regenerate is None:
226
+ break
227
+
228
+ notice = RejectionNotice(
229
+ notice_id=str(uuid.uuid4()),
230
+ turn_id=str(eval_kwargs.get("turn_id", "")),
231
+ session_id=str(eval_kwargs.get("session_id", "")),
232
+ contact_display_name=contact_display_name,
233
+ block_reason_type=reason,
234
+ blocking_layer=0,
235
+ redacted_excerpt=excerpt,
236
+ trust_tier=str(eval_kwargs.get("trust_tier", "")),
237
+ retry_number=retry + 1,
238
+ )
239
+ new_text = await self._regenerate(
240
+ notice.to_llm_prompt_fragment(), text)
241
+ if not new_text or not str(new_text).strip():
242
+ break
243
+ text = str(new_text).strip()
244
+ result = await self._guard.evaluate(
245
+ response_text=text, **eval_kwargs)
246
+ attempts += 1
247
+ blocked = result.blocked
248
+
249
+ return FeedbackLoopResult(passed=False, payload=None,
250
+ attempts=max(attempts, 1))
251
+ except Exception:
252
+ logger.warning("RejectionFeedbackLoop internal error", exc_info=True)
253
+ if not blocked:
254
+ # allow verdict stands: fail open on the allow side
255
+ return FeedbackLoopResult(passed=True, payload=text,
256
+ attempts=max(attempts, 1))
257
+ # a guard BLOCK is never overridden by a loop error
258
+ return FeedbackLoopResult(passed=False, payload=None,
259
+ attempts=max(attempts, 1))