agent-learning-kit 0.1.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 (642) hide show
  1. agent_learning_kit-0.1.0.dist-info/METADATA +381 -0
  2. agent_learning_kit-0.1.0.dist-info/RECORD +642 -0
  3. agent_learning_kit-0.1.0.dist-info/WHEEL +4 -0
  4. agent_learning_kit-0.1.0.dist-info/entry_points.txt +5 -0
  5. agent_learning_kit-0.1.0.dist-info/licenses/LICENSE +173 -0
  6. agent_learning_kit-0.1.0.dist-info/licenses/NOTICE +7 -0
  7. fi/__init__.py +5 -0
  8. fi/alk/__init__.py +57 -0
  9. fi/alk/_facade.py +31 -0
  10. fi/alk/_module_alias.py +68 -0
  11. fi/alk/_paths.py +14 -0
  12. fi/alk/_schema.py +522 -0
  13. fi/alk/actions.py +727 -0
  14. fi/alk/bench/__init__.py +517 -0
  15. fi/alk/bench/_codeexec.py +213 -0
  16. fi/alk/bench/_coding.py +215 -0
  17. fi/alk/bench/_docker.py +237 -0
  18. fi/alk/bench/_grader.py +286 -0
  19. fi/alk/bench/_pull.py +212 -0
  20. fi/alk/bench/_voice.py +147 -0
  21. fi/alk/capabilities.py +627 -0
  22. fi/alk/cli.py +6396 -0
  23. fi/alk/config.py +130 -0
  24. fi/alk/cua_loop.py +562 -0
  25. fi/alk/evals.py +2351 -0
  26. fi/alk/extensions.py +163 -0
  27. fi/alk/harness/ARCHITECTURE.md +231 -0
  28. fi/alk/harness/DESIGN.md +246 -0
  29. fi/alk/harness/ENVIRONMENT_CONFORMANCE.md +127 -0
  30. fi/alk/harness/HOW-IT-WORKS.md +297 -0
  31. fi/alk/harness/IMPLEMENTATION_AND_VALIDATION_STATUS.md +229 -0
  32. fi/alk/harness/README.md +417 -0
  33. fi/alk/harness/__init__.py +77 -0
  34. fi/alk/harness/__main__.py +3 -0
  35. fi/alk/harness/amend.py +312 -0
  36. fi/alk/harness/artifacts.py +319 -0
  37. fi/alk/harness/authoring_entrypoint.py +189 -0
  38. fi/alk/harness/authoring_runtime_validation.py +267 -0
  39. fi/alk/harness/backends/README.md +43 -0
  40. fi/alk/harness/backends/__init__.py +122 -0
  41. fi/alk/harness/backends/base.py +241 -0
  42. fi/alk/harness/backends/claude.py +211 -0
  43. fi/alk/harness/backends/files.py +182 -0
  44. fi/alk/harness/backends/vertex_gemini.py +457 -0
  45. fi/alk/harness/background_noise.py +95 -0
  46. fi/alk/harness/build.py +385 -0
  47. fi/alk/harness/bundle.py +593 -0
  48. fi/alk/harness/bundle_author_v2.py +1831 -0
  49. fi/alk/harness/bundle_v2.py +719 -0
  50. fi/alk/harness/call_runner.py +1440 -0
  51. fi/alk/harness/callback_http_adapter.py +111 -0
  52. fi/alk/harness/catalogue.py +287 -0
  53. fi/alk/harness/chat.py +428 -0
  54. fi/alk/harness/chat_call_runner.py +506 -0
  55. fi/alk/harness/checks.py +136 -0
  56. fi/alk/harness/cli.py +1354 -0
  57. fi/alk/harness/config.py +338 -0
  58. fi/alk/harness/contract.py +718 -0
  59. fi/alk/harness/credentials.py +674 -0
  60. fi/alk/harness/data/persona_vocabulary.json +111 -0
  61. fi/alk/harness/environment.py +99 -0
  62. fi/alk/harness/environment_plan.py +168 -0
  63. fi/alk/harness/events.py +125 -0
  64. fi/alk/harness/executor.py +304 -0
  65. fi/alk/harness/folder.py +234 -0
  66. fi/alk/harness/generated_runtime.py +815 -0
  67. fi/alk/harness/github.py +72 -0
  68. fi/alk/harness/hosted_authoring_entrypoint.py +183 -0
  69. fi/alk/harness/hosted_entrypoint.py +2402 -0
  70. fi/alk/harness/hosted_scheduler.py +2218 -0
  71. fi/alk/harness/job.py +426 -0
  72. fi/alk/harness/judge.py +184 -0
  73. fi/alk/harness/livekit_source.py +50 -0
  74. fi/alk/harness/livekit_tool_trace_bootstrap.py +71 -0
  75. fi/alk/harness/observability.py +208 -0
  76. fi/alk/harness/outbound.py +3252 -0
  77. fi/alk/harness/packaging.py +515 -0
  78. fi/alk/harness/persona_guides.py +157 -0
  79. fi/alk/harness/platform.py +692 -0
  80. fi/alk/harness/process_preflight.py +764 -0
  81. fi/alk/harness/process_runtime.py +5670 -0
  82. fi/alk/harness/prove.py +425 -0
  83. fi/alk/harness/provider_import.py +703 -0
  84. fi/alk/harness/provider_lifecycle.py +392 -0
  85. fi/alk/harness/provision.py +2896 -0
  86. fi/alk/harness/reception.py +147 -0
  87. fi/alk/harness/retell_chat_call_runner.py +373 -0
  88. fi/alk/harness/run/__init__.py +296 -0
  89. fi/alk/harness/run/alk.py +184 -0
  90. fi/alk/harness/run/call.py +162 -0
  91. fi/alk/harness/run/conversation.py +264 -0
  92. fi/alk/harness/run/data/voices_by_language_and_gender.json +693 -0
  93. fi/alk/harness/run/evidence.py +195 -0
  94. fi/alk/harness/run/grade.py +598 -0
  95. fi/alk/harness/run/live.py +297 -0
  96. fi/alk/harness/run/models.py +56 -0
  97. fi/alk/harness/run/platform_evals.py +227 -0
  98. fi/alk/harness/run/sdk_voice.py +130 -0
  99. fi/alk/harness/run/simulation.py +1209 -0
  100. fi/alk/harness/run/stage.py +91 -0
  101. fi/alk/harness/run/targets.py +508 -0
  102. fi/alk/harness/run/tools.py +601 -0
  103. fi/alk/harness/run/voice.py +340 -0
  104. fi/alk/harness/runtime.py +172 -0
  105. fi/alk/harness/sandbox_server.py +2011 -0
  106. fi/alk/harness/sandbox_worker.py +44 -0
  107. fi/alk/harness/scenario.py +1048 -0
  108. fi/alk/harness/scenario_source.py +879 -0
  109. fi/alk/harness/scenario_tools.py +1143 -0
  110. fi/alk/harness/scenarios.py +915 -0
  111. fi/alk/harness/secrets.py +168 -0
  112. fi/alk/harness/service_catalog.py +97 -0
  113. fi/alk/harness/session.py +391 -0
  114. fi/alk/harness/sessions.py +372 -0
  115. fi/alk/harness/simulator.py +76 -0
  116. fi/alk/harness/simulator_voice.py +928 -0
  117. fi/alk/harness/skills/build-environment/SKILL.md +538 -0
  118. fi/alk/harness/skills/harness.md +131 -0
  119. fi/alk/harness/skills/kinds/chat.md +48 -0
  120. fi/alk/harness/skills/kinds/voice-voicemail.md +63 -0
  121. fi/alk/harness/skills/kinds/voice.md +59 -0
  122. fi/alk/harness/skills/plan-suite/SKILL.md +103 -0
  123. fi/alk/harness/skills/provision-environment/SKILL.md +136 -0
  124. fi/alk/harness/skills/run-scenarios/SKILL.md +112 -0
  125. fi/alk/harness/skills/understand-agent/SKILL.md +251 -0
  126. fi/alk/harness/skills/write-scenarios/SKILL.md +606 -0
  127. fi/alk/harness/skills/write-scenarios/references/refusals.md +28 -0
  128. fi/alk/harness/skills/write-scenarios/references/world-api.md +92 -0
  129. fi/alk/harness/source_data_invariants.py +444 -0
  130. fi/alk/harness/source_tool_evidence.py +79 -0
  131. fi/alk/harness/sources.py +253 -0
  132. fi/alk/harness/spend.py +140 -0
  133. fi/alk/harness/tool_trace_proxy.py +104 -0
  134. fi/alk/harness/tools.py +1018 -0
  135. fi/alk/harness/understand.py +169 -0
  136. fi/alk/harness/voicemail_audio.py +74 -0
  137. fi/alk/harness/world/__init__.py +33 -0
  138. fi/alk/harness/world/errors.py +68 -0
  139. fi/alk/harness/world/expectations.py +91 -0
  140. fi/alk/harness/world/handle.py +538 -0
  141. fi/alk/harness/world/kinds.py +196 -0
  142. fi/alk/harness/world/mutate.py +186 -0
  143. fi/alk/harness/world/probe.py +413 -0
  144. fi/alk/harness/world/provision.py +511 -0
  145. fi/alk/harness/world/provisioned.py +191 -0
  146. fi/alk/harness/world/runtime.py +616 -0
  147. fi/alk/harness/world/snapshot.py +288 -0
  148. fi/alk/harness/world/stores/__init__.py +305 -0
  149. fi/alk/harness/world/stores/container.py +215 -0
  150. fi/alk/harness/world/stores/inprocess.py +346 -0
  151. fi/alk/harness/world/stores/postgres.py +481 -0
  152. fi/alk/harness/world/stores/prove.py +202 -0
  153. fi/alk/harness/world/stores/sqlite.py +245 -0
  154. fi/alk/harness/world/stores/written.py +182 -0
  155. fi/alk/harness/world/tools.py +1516 -0
  156. fi/alk/harness/world/workspace.py +144 -0
  157. fi/alk/image_loop.py +453 -0
  158. fi/alk/image_perturb.py +241 -0
  159. fi/alk/improve.py +274 -0
  160. fi/alk/live/__init__.py +154 -0
  161. fi/alk/live/_attribution.py +184 -0
  162. fi/alk/live/_capture.py +264 -0
  163. fi/alk/live/_codec.py +391 -0
  164. fi/alk/live/_contract.py +134 -0
  165. fi/alk/live/_loopback.py +316 -0
  166. fi/alk/live/_perturb.py +449 -0
  167. fi/alk/live/_runner.py +386 -0
  168. fi/alk/live/_stats.py +561 -0
  169. fi/alk/live/_transcript.py +240 -0
  170. fi/alk/live/_workers/__init__.py +9 -0
  171. fi/alk/live/_workers/a2a_worker.py +316 -0
  172. fi/alk/live/_workers/langgraph_worker.py +217 -0
  173. fi/alk/live/_workers/livekit_worker.py +207 -0
  174. fi/alk/live/_workers/mcp_loopback_server.py +46 -0
  175. fi/alk/live/_workers/mcp_worker.py +158 -0
  176. fi/alk/live/_workers/pipecat_worker.py +189 -0
  177. fi/alk/live/a2a_lane.py +138 -0
  178. fi/alk/live/langgraph_lane.py +339 -0
  179. fi/alk/live/livekit_lane.py +376 -0
  180. fi/alk/live/mcp_lane.py +172 -0
  181. fi/alk/live/pipecat_lane.py +341 -0
  182. fi/alk/live/voice_redteam.py +494 -0
  183. fi/alk/loss.py +306 -0
  184. fi/alk/optimize.py +36260 -0
  185. fi/alk/practice/__init__.py +51 -0
  186. fi/alk/practice/_assess.py +103 -0
  187. fi/alk/practice/_budget.py +81 -0
  188. fi/alk/practice/_calibrate.py +69 -0
  189. fi/alk/practice/_capstone.py +86 -0
  190. fi/alk/practice/_contract.py +91 -0
  191. fi/alk/practice/_diagnose.py +79 -0
  192. fi/alk/practice/_drill.py +196 -0
  193. fi/alk/practice/_experiment.py +720 -0
  194. fi/alk/practice/_schedule.py +102 -0
  195. fi/alk/practice/_store.py +194 -0
  196. fi/alk/practice/_trainer.py +245 -0
  197. fi/alk/practice/_update.py +125 -0
  198. fi/alk/redteam.py +2621 -0
  199. fi/alk/rewardhack.py +237 -0
  200. fi/alk/simulate.py +10351 -0
  201. fi/alk/studio/__init__.py +82 -0
  202. fi/alk/studio/_bias.py +314 -0
  203. fi/alk/studio/_calibration.py +522 -0
  204. fi/alk/studio/_coverage.py +262 -0
  205. fi/alk/studio/_download.py +665 -0
  206. fi/alk/studio/_fidelity_attack.py +114 -0
  207. fi/alk/studio/_generate.py +652 -0
  208. fi/alk/studio/_library.py +370 -0
  209. fi/alk/studio/_scan.py +134 -0
  210. fi/alk/studio/_upgrade.py +42 -0
  211. fi/alk/studio/_vendor.py +172 -0
  212. fi/alk/suite.py +4200 -0
  213. fi/alk/tasks.py +828 -0
  214. fi/alk/telemetry/__init__.py +149 -0
  215. fi/alk/telemetry/_contract.py +141 -0
  216. fi/alk/telemetry/_emit.py +182 -0
  217. fi/alk/telemetry/_ledger.py +296 -0
  218. fi/alk/telemetry/_queue.py +127 -0
  219. fi/alk/telemetry/_row.py +294 -0
  220. fi/alk/telemetry/_run.py +233 -0
  221. fi/alk/telemetry/_sync.py +193 -0
  222. fi/alk/telemetry/_url.py +119 -0
  223. fi/alk/trinity.py +49397 -0
  224. fi/alk/voice_loop.py +174 -0
  225. fi/api/__init__.py +1 -0
  226. fi/api/auth.py +137 -0
  227. fi/api/types.py +29 -0
  228. fi/cli/__init__.py +9 -0
  229. fi/cli/assertions/__init__.py +25 -0
  230. fi/cli/assertions/conditions.py +76 -0
  231. fi/cli/assertions/evaluator.py +286 -0
  232. fi/cli/assertions/exit_codes.py +20 -0
  233. fi/cli/assertions/parser.py +131 -0
  234. fi/cli/assertions/reporter.py +194 -0
  235. fi/cli/commands/__init__.py +9 -0
  236. fi/cli/commands/config.py +165 -0
  237. fi/cli/commands/export.py +208 -0
  238. fi/cli/commands/init.py +112 -0
  239. fi/cli/commands/list_cmd.py +213 -0
  240. fi/cli/commands/run.py +486 -0
  241. fi/cli/commands/validate.py +173 -0
  242. fi/cli/commands/view.py +424 -0
  243. fi/cli/config/__init__.py +6 -0
  244. fi/cli/config/defaults.py +206 -0
  245. fi/cli/config/loader.py +155 -0
  246. fi/cli/config/schema.py +174 -0
  247. fi/cli/main.py +78 -0
  248. fi/cli/output/__init__.py +6 -0
  249. fi/cli/output/formatters.py +106 -0
  250. fi/cli/output/reporters.py +46 -0
  251. fi/cli/storage/__init__.py +5 -0
  252. fi/cli/storage/run_history.py +249 -0
  253. fi/cli/utils/__init__.py +5 -0
  254. fi/cli/utils/console.py +44 -0
  255. fi/evals/__init__.py +131 -0
  256. fi/evals/autoeval/__init__.py +137 -0
  257. fi/evals/autoeval/analyzer.py +211 -0
  258. fi/evals/autoeval/config.py +244 -0
  259. fi/evals/autoeval/export.py +213 -0
  260. fi/evals/autoeval/interactive.py +283 -0
  261. fi/evals/autoeval/pipeline.py +625 -0
  262. fi/evals/autoeval/prompts.py +139 -0
  263. fi/evals/autoeval/recommender.py +242 -0
  264. fi/evals/autoeval/rules.py +589 -0
  265. fi/evals/autoeval/templates.py +299 -0
  266. fi/evals/autoeval/types.py +232 -0
  267. fi/evals/core/__init__.py +16 -0
  268. fi/evals/core/cloud_registry.py +184 -0
  269. fi/evals/core/engines.py +368 -0
  270. fi/evals/core/evaluate.py +319 -0
  271. fi/evals/core/judge_prompt.py +90 -0
  272. fi/evals/core/prompt_generator.py +83 -0
  273. fi/evals/core/registry.py +57 -0
  274. fi/evals/core/result.py +55 -0
  275. fi/evals/evaluator.py +721 -0
  276. fi/evals/execution.py +168 -0
  277. fi/evals/feedback/__init__.py +32 -0
  278. fi/evals/feedback/calibrator.py +160 -0
  279. fi/evals/feedback/collector.py +214 -0
  280. fi/evals/feedback/hooks.py +81 -0
  281. fi/evals/feedback/retriever.py +128 -0
  282. fi/evals/feedback/store.py +272 -0
  283. fi/evals/feedback/types.py +99 -0
  284. fi/evals/framework/README.md +79 -0
  285. fi/evals/framework/__init__.py +267 -0
  286. fi/evals/framework/backends/Dockerfile.eval-runner +33 -0
  287. fi/evals/framework/backends/__init__.py +99 -0
  288. fi/evals/framework/backends/_container.py +141 -0
  289. fi/evals/framework/backends/_utils.py +145 -0
  290. fi/evals/framework/backends/base.py +223 -0
  291. fi/evals/framework/backends/celery_backend.py +417 -0
  292. fi/evals/framework/backends/celery_worker.py +78 -0
  293. fi/evals/framework/backends/kubernetes_backend.py +665 -0
  294. fi/evals/framework/backends/ray_backend.py +521 -0
  295. fi/evals/framework/backends/temporal.py +350 -0
  296. fi/evals/framework/backends/temporal_worker.py +126 -0
  297. fi/evals/framework/backends/thread_pool.py +286 -0
  298. fi/evals/framework/context.py +258 -0
  299. fi/evals/framework/enrichment.py +306 -0
  300. fi/evals/framework/evals/__init__.py +68 -0
  301. fi/evals/framework/evals/agentic.py +399 -0
  302. fi/evals/framework/evals/builder.py +609 -0
  303. fi/evals/framework/evals/semantic.py +142 -0
  304. fi/evals/framework/evaluator.py +647 -0
  305. fi/evals/framework/evaluators/__init__.py +22 -0
  306. fi/evals/framework/evaluators/blocking.py +347 -0
  307. fi/evals/framework/evaluators/non_blocking.py +577 -0
  308. fi/evals/framework/propagation.py +421 -0
  309. fi/evals/framework/protocols.py +385 -0
  310. fi/evals/framework/registry.py +370 -0
  311. fi/evals/framework/resilience/__init__.py +150 -0
  312. fi/evals/framework/resilience/circuit_breaker.py +309 -0
  313. fi/evals/framework/resilience/degradation.py +355 -0
  314. fi/evals/framework/resilience/health.py +505 -0
  315. fi/evals/framework/resilience/rate_limiter.py +228 -0
  316. fi/evals/framework/resilience/retry.py +274 -0
  317. fi/evals/framework/resilience/types.py +288 -0
  318. fi/evals/framework/resilience/wrapper.py +433 -0
  319. fi/evals/framework/types.py +218 -0
  320. fi/evals/guardrails/README.md +915 -0
  321. fi/evals/guardrails/__init__.py +96 -0
  322. fi/evals/guardrails/backends/__init__.py +43 -0
  323. fi/evals/guardrails/backends/azure.py +361 -0
  324. fi/evals/guardrails/backends/base.py +88 -0
  325. fi/evals/guardrails/backends/generic_llm.py +163 -0
  326. fi/evals/guardrails/backends/granite.py +216 -0
  327. fi/evals/guardrails/backends/llamaguard.py +221 -0
  328. fi/evals/guardrails/backends/local_base.py +479 -0
  329. fi/evals/guardrails/backends/openai.py +365 -0
  330. fi/evals/guardrails/backends/qwen.py +170 -0
  331. fi/evals/guardrails/backends/shieldgemma.py +154 -0
  332. fi/evals/guardrails/backends/turing.py +235 -0
  333. fi/evals/guardrails/backends/vllm_client.py +321 -0
  334. fi/evals/guardrails/backends/wildguard.py +188 -0
  335. fi/evals/guardrails/base.py +888 -0
  336. fi/evals/guardrails/config.py +221 -0
  337. fi/evals/guardrails/discovery.py +243 -0
  338. fi/evals/guardrails/gateway.py +437 -0
  339. fi/evals/guardrails/registry.py +231 -0
  340. fi/evals/guardrails/scanners/__init__.py +127 -0
  341. fi/evals/guardrails/scanners/base.py +191 -0
  342. fi/evals/guardrails/scanners/code_injection.py +243 -0
  343. fi/evals/guardrails/scanners/eval_delegate.py +574 -0
  344. fi/evals/guardrails/scanners/invisible_chars.py +351 -0
  345. fi/evals/guardrails/scanners/jailbreak.py +412 -0
  346. fi/evals/guardrails/scanners/language.py +288 -0
  347. fi/evals/guardrails/scanners/pipeline.py +260 -0
  348. fi/evals/guardrails/scanners/regex.py +311 -0
  349. fi/evals/guardrails/scanners/secrets.py +274 -0
  350. fi/evals/guardrails/scanners/topics.py +649 -0
  351. fi/evals/guardrails/scanners/urls.py +341 -0
  352. fi/evals/guardrails/types.py +96 -0
  353. fi/evals/llm/__init__.py +3 -0
  354. fi/evals/llm/base_llm_provider.py +35 -0
  355. fi/evals/llm/providers/litellm.py +70 -0
  356. fi/evals/local/__init__.py +90 -0
  357. fi/evals/local/evaluator.py +690 -0
  358. fi/evals/local/execution_mode.py +121 -0
  359. fi/evals/local/llm.py +489 -0
  360. fi/evals/local/metrics/__init__.py +19 -0
  361. fi/evals/local/registry.py +360 -0
  362. fi/evals/manager.py +1018 -0
  363. fi/evals/manager_types.py +362 -0
  364. fi/evals/metrics/__init__.py +185 -0
  365. fi/evals/metrics/agents/__init__.py +74 -0
  366. fi/evals/metrics/agents/metrics.py +693 -0
  367. fi/evals/metrics/agents/report.py +36463 -0
  368. fi/evals/metrics/agents/types.py +160 -0
  369. fi/evals/metrics/base_llm_metric.py +111 -0
  370. fi/evals/metrics/base_metric.py +138 -0
  371. fi/evals/metrics/code_security/__init__.py +305 -0
  372. fi/evals/metrics/code_security/analyzer.py +985 -0
  373. fi/evals/metrics/code_security/benchmarks/__init__.py +73 -0
  374. fi/evals/metrics/code_security/benchmarks/builtin.py +750 -0
  375. fi/evals/metrics/code_security/benchmarks/loader.py +580 -0
  376. fi/evals/metrics/code_security/benchmarks/types.py +308 -0
  377. fi/evals/metrics/code_security/detectors/__init__.py +186 -0
  378. fi/evals/metrics/code_security/detectors/base.py +394 -0
  379. fi/evals/metrics/code_security/detectors/cryptography.py +345 -0
  380. fi/evals/metrics/code_security/detectors/injection.py +744 -0
  381. fi/evals/metrics/code_security/detectors/secrets.py +287 -0
  382. fi/evals/metrics/code_security/detectors/serialization.py +192 -0
  383. fi/evals/metrics/code_security/joint_metrics.py +588 -0
  384. fi/evals/metrics/code_security/judges/__init__.py +83 -0
  385. fi/evals/metrics/code_security/judges/base.py +238 -0
  386. fi/evals/metrics/code_security/judges/dual_judge.py +534 -0
  387. fi/evals/metrics/code_security/judges/llm_judge.py +301 -0
  388. fi/evals/metrics/code_security/judges/pattern_judge.py +515 -0
  389. fi/evals/metrics/code_security/metrics.py +388 -0
  390. fi/evals/metrics/code_security/modes/__init__.py +63 -0
  391. fi/evals/metrics/code_security/modes/adversarial.py +284 -0
  392. fi/evals/metrics/code_security/modes/autocomplete.py +198 -0
  393. fi/evals/metrics/code_security/modes/base.py +283 -0
  394. fi/evals/metrics/code_security/modes/instruct.py +253 -0
  395. fi/evals/metrics/code_security/modes/repair.py +230 -0
  396. fi/evals/metrics/code_security/reports/__init__.py +57 -0
  397. fi/evals/metrics/code_security/reports/generator.py +404 -0
  398. fi/evals/metrics/code_security/reports/leaderboard.py +509 -0
  399. fi/evals/metrics/code_security/types.py +534 -0
  400. fi/evals/metrics/function_calling/__init__.py +34 -0
  401. fi/evals/metrics/function_calling/metrics.py +573 -0
  402. fi/evals/metrics/function_calling/types.py +87 -0
  403. fi/evals/metrics/hallucination/__init__.py +54 -0
  404. fi/evals/metrics/hallucination/detector.py +149 -0
  405. fi/evals/metrics/hallucination/metrics.py +390 -0
  406. fi/evals/metrics/hallucination/nli.py +253 -0
  407. fi/evals/metrics/hallucination/sentinel.py +106 -0
  408. fi/evals/metrics/hallucination/types.py +132 -0
  409. fi/evals/metrics/heuristics/aggregation_metrics.py +85 -0
  410. fi/evals/metrics/heuristics/json_metrics.py +87 -0
  411. fi/evals/metrics/heuristics/similarity_metrics.py +375 -0
  412. fi/evals/metrics/heuristics/string_metrics.py +391 -0
  413. fi/evals/metrics/llm_as_judges/__init__.py +17 -0
  414. fi/evals/metrics/llm_as_judges/custom_judge/metric.py +112 -0
  415. fi/evals/metrics/llm_as_judges/custom_judge/prompts.py +26 -0
  416. fi/evals/metrics/llm_as_judges/types.py +48 -0
  417. fi/evals/metrics/rag/__init__.py +111 -0
  418. fi/evals/metrics/rag/advanced/__init__.py +14 -0
  419. fi/evals/metrics/rag/advanced/multi_hop.py +283 -0
  420. fi/evals/metrics/rag/advanced/source_attribution.py +344 -0
  421. fi/evals/metrics/rag/generation/__init__.py +17 -0
  422. fi/evals/metrics/rag/generation/answer_relevancy.py +176 -0
  423. fi/evals/metrics/rag/generation/context_utilization.py +245 -0
  424. fi/evals/metrics/rag/generation/faithfulness.py +241 -0
  425. fi/evals/metrics/rag/generation/groundedness.py +131 -0
  426. fi/evals/metrics/rag/rag_score.py +277 -0
  427. fi/evals/metrics/rag/retrieval/__init__.py +20 -0
  428. fi/evals/metrics/rag/retrieval/context_entity_recall.py +124 -0
  429. fi/evals/metrics/rag/retrieval/context_precision.py +158 -0
  430. fi/evals/metrics/rag/retrieval/context_recall.py +106 -0
  431. fi/evals/metrics/rag/retrieval/noise_sensitivity.py +163 -0
  432. fi/evals/metrics/rag/retrieval/ranking.py +261 -0
  433. fi/evals/metrics/rag/types.py +100 -0
  434. fi/evals/metrics/rag/utils/__init__.py +62 -0
  435. fi/evals/metrics/rag/utils/claims.py +189 -0
  436. fi/evals/metrics/rag/utils/entities.py +244 -0
  437. fi/evals/metrics/rag/utils/nli.py +92 -0
  438. fi/evals/metrics/rag/utils/similarity.py +345 -0
  439. fi/evals/metrics/structured/__init__.py +114 -0
  440. fi/evals/metrics/structured/field_completeness.py +313 -0
  441. fi/evals/metrics/structured/hierarchy_score.py +366 -0
  442. fi/evals/metrics/structured/json_validation.py +190 -0
  443. fi/evals/metrics/structured/schema_compliance.py +280 -0
  444. fi/evals/metrics/structured/structured_output_score.py +298 -0
  445. fi/evals/metrics/structured/types.py +108 -0
  446. fi/evals/metrics/structured/validators/__init__.py +30 -0
  447. fi/evals/metrics/structured/validators/base.py +189 -0
  448. fi/evals/metrics/structured/validators/json_validator.py +196 -0
  449. fi/evals/metrics/structured/validators/pydantic_validator.py +178 -0
  450. fi/evals/metrics/structured/validators/yaml_validator.py +248 -0
  451. fi/evals/otel/__init__.py +266 -0
  452. fi/evals/otel/config.py +400 -0
  453. fi/evals/otel/conventions.py +463 -0
  454. fi/evals/otel/enrichment.py +371 -0
  455. fi/evals/otel/instrumentors/__init__.py +140 -0
  456. fi/evals/otel/instrumentors/anthropic.py +517 -0
  457. fi/evals/otel/instrumentors/base.py +382 -0
  458. fi/evals/otel/instrumentors/openai.py +673 -0
  459. fi/evals/otel/processors/__init__.py +36 -0
  460. fi/evals/otel/processors/base.py +473 -0
  461. fi/evals/otel/processors/cost.py +445 -0
  462. fi/evals/otel/processors/evaluation.py +559 -0
  463. fi/evals/otel/processors/llm.py +462 -0
  464. fi/evals/otel/tracer.py +506 -0
  465. fi/evals/otel/types.py +232 -0
  466. fi/evals/otel_utils.py +23 -0
  467. fi/evals/protect.py +671 -0
  468. fi/evals/protect_input_adapter.py +154 -0
  469. fi/evals/streaming/__init__.py +88 -0
  470. fi/evals/streaming/buffer.py +213 -0
  471. fi/evals/streaming/evaluator.py +551 -0
  472. fi/evals/streaming/policy.py +307 -0
  473. fi/evals/streaming/scorers.py +368 -0
  474. fi/evals/streaming/types.py +238 -0
  475. fi/evals/templates.py +472 -0
  476. fi/evals/types.py +156 -0
  477. fi/opt/__init__.py +221 -0
  478. fi/opt/_objective_scoring.py +85 -0
  479. fi/opt/base/__init__.py +11 -0
  480. fi/opt/base/base_generator.py +33 -0
  481. fi/opt/base/base_mapper.py +26 -0
  482. fi/opt/base/base_optimizer.py +45 -0
  483. fi/opt/base/evaluator.py +211 -0
  484. fi/opt/components.py +3095 -0
  485. fi/opt/datamappers/__init__.py +3 -0
  486. fi/opt/datamappers/basic_mapper.py +40 -0
  487. fi/opt/deployment.py +1021 -0
  488. fi/opt/evidence.py +4332 -0
  489. fi/opt/generators/__init__.py +3 -0
  490. fi/opt/generators/litellm.py +66 -0
  491. fi/opt/integrations/__init__.py +23 -0
  492. fi/opt/integrations/generative_suite.py +410 -0
  493. fi/opt/integrations/simulate.py +1313 -0
  494. fi/opt/mutations.py +771 -0
  495. fi/opt/observability.py +4639 -0
  496. fi/opt/optimizer_trace.py +889 -0
  497. fi/opt/optimizers/__init__.py +80 -0
  498. fi/opt/optimizers/agent.py +331 -0
  499. fi/opt/optimizers/agent_bandit.py +392 -0
  500. fi/opt/optimizers/agent_curriculum.py +635 -0
  501. fi/opt/optimizers/agent_evolution.py +894 -0
  502. fi/opt/optimizers/agent_feedback.py +1863 -0
  503. fi/opt/optimizers/agent_pareto.py +547 -0
  504. fi/opt/optimizers/agent_social_memory.py +1113 -0
  505. fi/opt/optimizers/agent_tpe.py +321 -0
  506. fi/opt/optimizers/bayesian_search.py +449 -0
  507. fi/opt/optimizers/council.py +2075 -0
  508. fi/opt/optimizers/futureagi_replay.py +799 -0
  509. fi/opt/optimizers/gepa.py +322 -0
  510. fi/opt/optimizers/metaprompt.py +243 -0
  511. fi/opt/optimizers/promptwizard.py +417 -0
  512. fi/opt/optimizers/protegi.py +329 -0
  513. fi/opt/optimizers/random_search.py +224 -0
  514. fi/opt/research.py +518 -0
  515. fi/opt/simulation.py +260 -0
  516. fi/opt/targets.py +232 -0
  517. fi/opt/types.py +66 -0
  518. fi/opt/utils/__init__.py +4 -0
  519. fi/opt/utils/early_stopping.py +266 -0
  520. fi/opt/utils/setup_logging.py +82 -0
  521. fi/simulate/__init__.py +540 -0
  522. fi/simulate/_hashing.py +35 -0
  523. fi/simulate/_logging.py +10 -0
  524. fi/simulate/adapters.py +87 -0
  525. fi/simulate/agent/__init__.py +120 -0
  526. fi/simulate/agent/browser.py +658 -0
  527. fi/simulate/agent/definition.py +587 -0
  528. fi/simulate/agent/frameworks.py +3528 -0
  529. fi/simulate/agent/generic.py +8286 -0
  530. fi/simulate/agent/import_probe.py +227 -0
  531. fi/simulate/agent/memory.py +905 -0
  532. fi/simulate/agent/mocks.py +101 -0
  533. fi/simulate/agent/multi_agent.py +361 -0
  534. fi/simulate/agent/orchestration.py +903 -0
  535. fi/simulate/agent/realtime.py +665 -0
  536. fi/simulate/agent/wrapper.py +99 -0
  537. fi/simulate/agent/wrappers/__init__.py +18 -0
  538. fi/simulate/agent/wrappers/anthropic.py +62 -0
  539. fi/simulate/agent/wrappers/gemini.py +65 -0
  540. fi/simulate/agent/wrappers/http.py +404 -0
  541. fi/simulate/agent/wrappers/langchain.py +80 -0
  542. fi/simulate/agent/wrappers/openai.py +75 -0
  543. fi/simulate/agent/wrappers/websocket.py +326 -0
  544. fi/simulate/artifacts/__init__.py +11 -0
  545. fi/simulate/artifacts/manifest.py +62 -0
  546. fi/simulate/cli.py +20560 -0
  547. fi/simulate/endpoints/__init__.py +45 -0
  548. fi/simulate/endpoints/_http_actor.py +73 -0
  549. fi/simulate/endpoints/actor_sources.py +243 -0
  550. fi/simulate/endpoints/base.py +107 -0
  551. fi/simulate/endpoints/builtins.py +10 -0
  552. fi/simulate/endpoints/callable.py +95 -0
  553. fi/simulate/endpoints/http.py +76 -0
  554. fi/simulate/endpoints/livekit.py +138 -0
  555. fi/simulate/endpoints/originators.py +132 -0
  556. fi/simulate/endpoints/profiles.py +348 -0
  557. fi/simulate/endpoints/retell.py +633 -0
  558. fi/simulate/endpoints/vapi.py +205 -0
  559. fi/simulate/endpoints/websocket.py +76 -0
  560. fi/simulate/environment.py +33026 -0
  561. fi/simulate/environments/__init__.py +11 -0
  562. fi/simulate/environments/base.py +73 -0
  563. fi/simulate/environments/chat.py +697 -0
  564. fi/simulate/environments/voice.py +212 -0
  565. fi/simulate/evaluation/__init__.py +4 -0
  566. fi/simulate/evaluation/ai_eval.py +227 -0
  567. fi/simulate/evidence/__init__.py +35 -0
  568. fi/simulate/evidence/base.py +59 -0
  569. fi/simulate/evidence/caller_observed.py +50 -0
  570. fi/simulate/evidence/livekit_instrumentation.py +51 -0
  571. fi/simulate/evidence/livekit_room.py +50 -0
  572. fi/simulate/evidence/otel.py +49 -0
  573. fi/simulate/evidence/providers/__init__.py +24 -0
  574. fi/simulate/evidence/providers/base.py +61 -0
  575. fi/simulate/evidence/providers/retell.py +376 -0
  576. fi/simulate/evidence/providers/vapi.py +426 -0
  577. fi/simulate/hosted/__init__.py +32 -0
  578. fi/simulate/hosted/child_entrypoint.py +306 -0
  579. fi/simulate/hosted/job.py +150 -0
  580. fi/simulate/hosted/targets.py +53 -0
  581. fi/simulate/instrumentation/__init__.py +5 -0
  582. fi/simulate/instrumentation/livekit/__init__.py +122 -0
  583. fi/simulate/manifest.py +1033 -0
  584. fi/simulate/matrix_cli.py +165 -0
  585. fi/simulate/realtime/__init__.py +40 -0
  586. fi/simulate/realtime/events.py +107 -0
  587. fi/simulate/realtime/media.py +61 -0
  588. fi/simulate/realtime/session.py +91 -0
  589. fi/simulate/recording/__init__.py +5 -0
  590. fi/simulate/recording/room_recorder.py +326 -0
  591. fi/simulate/registry.py +185 -0
  592. fi/simulate/results/__init__.py +9 -0
  593. fi/simulate/results/base.py +18 -0
  594. fi/simulate/results/filesystem.py +71 -0
  595. fi/simulate/results/futureagi.py +1340 -0
  596. fi/simulate/runtime/__init__.py +85 -0
  597. fi/simulate/runtime/capabilities.py +40 -0
  598. fi/simulate/runtime/events.py +63 -0
  599. fi/simulate/runtime/failures.py +25 -0
  600. fi/simulate/runtime/ids.py +34 -0
  601. fi/simulate/runtime/plan.py +70 -0
  602. fi/simulate/runtime/planner.py +102 -0
  603. fi/simulate/runtime/report.py +174 -0
  604. fi/simulate/runtime/run.py +75 -0
  605. fi/simulate/runtime/runner.py +333 -0
  606. fi/simulate/runtime/spec.py +186 -0
  607. fi/simulate/simulation/__init__.py +30 -0
  608. fi/simulate/simulation/behavior_policy.py +425 -0
  609. fi/simulate/simulation/bridge/__init__.py +9 -0
  610. fi/simulate/simulation/bridge/audio.py +29 -0
  611. fi/simulate/simulation/bridge/connector.py +46 -0
  612. fi/simulate/simulation/bridge/livekit.py +252 -0
  613. fi/simulate/simulation/bridge/retell.py +188 -0
  614. fi/simulate/simulation/bridge/vapi.py +177 -0
  615. fi/simulate/simulation/contract.py +419 -0
  616. fi/simulate/simulation/engines/__init__.py +12 -0
  617. fi/simulate/simulation/engines/base.py +21 -0
  618. fi/simulate/simulation/engines/cloud.py +517 -0
  619. fi/simulate/simulation/engines/livekit.py +4167 -0
  620. fi/simulate/simulation/engines/local_text.py +89 -0
  621. fi/simulate/simulation/fidelity.py +374 -0
  622. fi/simulate/simulation/gemini_tts_stream.py +110 -0
  623. fi/simulate/simulation/generator.py +91 -0
  624. fi/simulate/simulation/goal_machine.py +185 -0
  625. fi/simulate/simulation/livekit_models.py +467 -0
  626. fi/simulate/simulation/matrix.py +170 -0
  627. fi/simulate/simulation/models.py +279 -0
  628. fi/simulate/simulation/runner.py +153 -0
  629. fi/simulate/simulation/synthetic.py +880 -0
  630. fi/simulate/simulation/voice_prompt.py +502 -0
  631. fi/simulate/simulator/__init__.py +55 -0
  632. fi/simulate/simulator/builtins.py +53 -0
  633. fi/simulate/suite.py +1288 -0
  634. fi/simulate/utils/routes.py +164 -0
  635. fi/simulate/voice.py +225 -0
  636. fi/simulate/voice_cli.py +182 -0
  637. fi/utils/__init__.py +1 -0
  638. fi/utils/constants.py +14 -0
  639. fi/utils/errors.py +200 -0
  640. fi/utils/executor.py +26 -0
  641. fi/utils/routes.py +119 -0
  642. fi/utils/utils.py +17 -0
@@ -0,0 +1,370 @@
1
+ """Content-addressed persona/scenario library (Phase 7, ARCH §2d).
2
+
3
+ One library root (default ``.agent-learning/library/``), no DB, no service:
4
+
5
+ personas/<slug>/<sha256-hex>.json # library content; filename IS the hash
6
+ scenarios/<slug>/<sha256-hex>.json
7
+ calibrations/<persona-hash>.json # agent-learning.persona-calibration.v1
8
+ coverage/<report-timestamp>.json # raw coverage data (index blocks)
9
+ quarantine/ # refused pulls — never loadable
10
+ index.json # agent-learning.persona-library.v1
11
+
12
+ ``save_persona`` refuses to overwrite a hash-named file with different bytes
13
+ (content addressing makes tampering loud); ``load_persona`` re-hashes and
14
+ rejects mismatches. Runtime fidelity floors live in the index as data, seeded
15
+ from the engine floor table (trinity constants pin the same values at gate
16
+ time).
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import json
22
+ import re
23
+ from datetime import datetime, timezone
24
+ from pathlib import Path
25
+ from typing import Any, Dict, List, Mapping, Optional, Union
26
+
27
+ from fi.simulate.simulation.fidelity import PERSONA_FIDELITY_FLOORS
28
+ from fi.simulate.simulation.models import Persona, Scenario
29
+
30
+ from ._upgrade import upgrade_legacy_persona
31
+
32
+ DEFAULT_LIBRARY_ROOT = ".agent-learning/library"
33
+ PERSONA_LIBRARY_KIND = "agent-learning.persona-library.v1"
34
+ PERSONA_CALIBRATION_KIND = "agent-learning.persona-calibration.v1"
35
+
36
+ _HEX_RE = re.compile(r"^[0-9a-f]{64}$")
37
+
38
+
39
+ def _now() -> str:
40
+ return datetime.now(timezone.utc).isoformat()
41
+
42
+
43
+ def _slugify(value: str) -> str:
44
+ slug = re.sub(r"[^a-z0-9]+", "-", str(value).strip().lower()).strip("-")
45
+ return slug or "unnamed"
46
+
47
+
48
+ def library_root(library: Union[str, Path, None] = None) -> Path:
49
+ return Path(library) if library is not None else Path(DEFAULT_LIBRARY_ROOT)
50
+
51
+
52
+ def ensure_library(library: Union[str, Path, None] = None) -> Path:
53
+ root = library_root(library)
54
+ for sub in ("personas", "scenarios", "calibrations", "coverage", "quarantine"):
55
+ (root / sub).mkdir(parents=True, exist_ok=True)
56
+ index_path = root / "index.json"
57
+ if not index_path.exists():
58
+ _write_index(root, {
59
+ "kind": PERSONA_LIBRARY_KIND,
60
+ "personas": [],
61
+ "scenarios": [],
62
+ # Runtime per-class floors are library-index DATA seeded from the
63
+ # engine constants (ARCH §2c); legacy is omitted on purpose.
64
+ "floors": {k: dict(v) for k, v in PERSONA_FIDELITY_FLOORS.items()},
65
+ "bias_lint": None,
66
+ "pull_receipts": [],
67
+ "representativeness_claim": "none",
68
+ })
69
+ return root
70
+
71
+
72
+ def load_index(library: Union[str, Path, None] = None) -> Dict[str, Any]:
73
+ root = ensure_library(library)
74
+ with open(root / "index.json", "r", encoding="utf-8") as handle:
75
+ return json.load(handle)
76
+
77
+
78
+ def _write_index(root: Path, index: Mapping[str, Any]) -> None:
79
+ payload = json.dumps(index, indent=2, sort_keys=True, default=str)
80
+ (root / "index.json").write_text(payload + "\n", encoding="utf-8")
81
+
82
+
83
+ def save_index(library: Union[str, Path, None], index: Mapping[str, Any]) -> None:
84
+ _write_index(ensure_library(library), index)
85
+
86
+
87
+ def _hash_hex(model: Union[Persona, Scenario]) -> str:
88
+ return model.content_hash().split(":", 1)[1]
89
+
90
+
91
+ def _dump_model(model: Union[Persona, Scenario]) -> str:
92
+ payload = model.model_dump(exclude_none=True)
93
+ return json.dumps(payload, indent=2, sort_keys=True, default=str) + "\n"
94
+
95
+
96
+ def _refuse_quarantined(path: Path) -> None:
97
+ if "quarantine" in path.parts:
98
+ raise ValueError(
99
+ f"refusing to load quarantined content: {path} "
100
+ "(quarantined artifacts are never loadable)"
101
+ )
102
+
103
+
104
+ def _write_content_addressed(directory: Path, hex_digest: str, content: str) -> Path:
105
+ directory.mkdir(parents=True, exist_ok=True)
106
+ path = directory / f"{hex_digest}.json"
107
+ if path.exists():
108
+ existing = path.read_text(encoding="utf-8")
109
+ if existing != content:
110
+ raise ValueError(
111
+ f"content-address collision: {path} already exists with "
112
+ "different bytes; refusing to overwrite (tampering is loud)"
113
+ )
114
+ return path
115
+ path.write_text(content, encoding="utf-8")
116
+ return path
117
+
118
+
119
+ def _persona_slug(persona: Persona, slug: Optional[str]) -> str:
120
+ if slug:
121
+ return _slugify(slug)
122
+ name = None
123
+ if persona.identity is not None and persona.identity.name:
124
+ name = persona.identity.name
125
+ if not name:
126
+ name = persona.persona.get("name") if isinstance(persona.persona, dict) else None
127
+ return _slugify(name or "persona")
128
+
129
+
130
+ def _upsert_entry(entries: List[Dict[str, Any]], entry: Dict[str, Any]) -> None:
131
+ for index, existing in enumerate(entries):
132
+ if existing.get("content_digest") == entry["content_digest"]:
133
+ entries[index] = {**existing, **entry}
134
+ return
135
+ entries.append(entry)
136
+
137
+
138
+ def save_persona(
139
+ persona: Persona,
140
+ *,
141
+ library: Union[str, Path, None] = None,
142
+ slug: Optional[str] = None,
143
+ admit: bool = False,
144
+ lint_result: Optional[Mapping[str, Any]] = None,
145
+ calibration_ref: Optional[str] = None,
146
+ ) -> Dict[str, Any]:
147
+ """Write a persona as content-addressed library content.
148
+
149
+ ``admit=True`` is the library-admission gate: it requires a calibrated
150
+ persona AND a current green set-level bias lint (ARCH §2f) — and refuses
151
+ loudly otherwise. Demographics-bearing personas can never be admitted
152
+ without the lint stamp (P7-D4)."""
153
+ root = ensure_library(library)
154
+ if admit:
155
+ provenance = persona.provenance
156
+ if provenance is None or not provenance.calibrated:
157
+ raise ValueError(
158
+ "admit refused: persona is not calibrated "
159
+ "(run calibrate_persona first; uncalibrated personas still "
160
+ "run at the lowest evidence class)"
161
+ )
162
+ if lint_result is None or lint_result.get("status") != "passed":
163
+ raise ValueError(
164
+ "admit refused: no current green set-level bias lint for the "
165
+ "receiving library (run bias_lint and pass the result)"
166
+ )
167
+ hex_digest = _hash_hex(persona)
168
+ slug_value = _persona_slug(persona, slug)
169
+ path = _write_content_addressed(root / "personas" / slug_value, hex_digest, _dump_model(persona))
170
+ evidence_class = (
171
+ persona.provenance.evidence_class if persona.provenance is not None else "legacy"
172
+ )
173
+ stage = "admitted" if admit else (
174
+ "interrogated" if persona.provenance is not None and persona.provenance.calibrated
175
+ else "sampled"
176
+ )
177
+ entry: Dict[str, Any] = {
178
+ "ref": str(path.relative_to(root)),
179
+ "slug": slug_value,
180
+ "content_digest": f"sha256:{hex_digest}",
181
+ "evidence_class": evidence_class,
182
+ "calibration_stage": stage,
183
+ "calibration_ref": calibration_ref or (
184
+ persona.provenance.calibration_ref if persona.provenance is not None else None
185
+ ),
186
+ "bias_lint_stamp": (
187
+ {
188
+ "status": lint_result.get("status"),
189
+ "locales_linted": list(lint_result.get("locales_linted", [])),
190
+ "stamped_at": _now(),
191
+ }
192
+ if lint_result is not None else None
193
+ ),
194
+ "locale_stamps": list(lint_result.get("locales_linted", [])) if lint_result else [],
195
+ }
196
+ index = load_index(root)
197
+ _upsert_entry(index.setdefault("personas", []), entry)
198
+ if lint_result is not None:
199
+ index["bias_lint"] = {
200
+ "status": lint_result.get("status"),
201
+ "locales_linted": list(lint_result.get("locales_linted", [])),
202
+ "stamped_at": _now(),
203
+ }
204
+ _write_index(root, index)
205
+ return {
206
+ "path": str(path),
207
+ "ref": entry["ref"],
208
+ "content_digest": entry["content_digest"],
209
+ "admitted": admit,
210
+ "evidence_class": evidence_class,
211
+ "calibration_stage": stage,
212
+ }
213
+
214
+
215
+ def load_persona(
216
+ ref: Union[str, Path],
217
+ *,
218
+ library: Union[str, Path, None] = None,
219
+ ) -> Persona:
220
+ """Load + re-hash library content; reject mismatches and quarantine.
221
+
222
+ Bare legacy rows auto-upgrade through the shim (provenance=legacy)."""
223
+ path = Path(ref)
224
+ if not path.exists():
225
+ path = library_root(library) / ref
226
+ _refuse_quarantined(path.resolve())
227
+ with open(path, "r", encoding="utf-8") as handle:
228
+ data = json.load(handle)
229
+ persona = Persona(**data)
230
+ if _HEX_RE.match(path.stem):
231
+ actual = _hash_hex(persona)
232
+ if actual != path.stem:
233
+ raise ValueError(
234
+ f"content hash mismatch for {path}: expected sha256:{path.stem}, "
235
+ f"found sha256:{actual} — artifact was tampered with or "
236
+ "hand-edited; re-pull or fork it as a NEW persona"
237
+ )
238
+ return upgrade_legacy_persona(persona)
239
+
240
+
241
+ def save_scenario(
242
+ scenario: Scenario,
243
+ *,
244
+ library: Union[str, Path, None] = None,
245
+ slug: Optional[str] = None,
246
+ ) -> Dict[str, Any]:
247
+ root = ensure_library(library)
248
+ hex_digest = _hash_hex(scenario)
249
+ slug_value = _slugify(slug or scenario.name)
250
+ path = _write_content_addressed(root / "scenarios" / slug_value, hex_digest, _dump_model(scenario))
251
+ entry = {
252
+ "ref": str(path.relative_to(root)),
253
+ "slug": slug_value,
254
+ "content_digest": f"sha256:{hex_digest}",
255
+ "kind": scenario.kind,
256
+ "version": scenario.version,
257
+ "parent_version": scenario.parent_version,
258
+ }
259
+ index = load_index(root)
260
+ _upsert_entry(index.setdefault("scenarios", []), entry)
261
+ _write_index(root, index)
262
+ return {"path": str(path), "ref": entry["ref"], "content_digest": entry["content_digest"]}
263
+
264
+
265
+ def load_scenario(
266
+ ref: Union[str, Path],
267
+ *,
268
+ library: Union[str, Path, None] = None,
269
+ ) -> Scenario:
270
+ path = Path(ref)
271
+ if not path.exists():
272
+ path = library_root(library) / ref
273
+ _refuse_quarantined(path.resolve())
274
+ with open(path, "r", encoding="utf-8") as handle:
275
+ data = json.load(handle)
276
+ scenario = Scenario(**data)
277
+ if _HEX_RE.match(path.stem):
278
+ actual = _hash_hex(scenario)
279
+ if actual != path.stem:
280
+ raise ValueError(
281
+ f"content hash mismatch for {path}: expected sha256:{path.stem}, "
282
+ f"found sha256:{actual} — artifact was tampered with"
283
+ )
284
+ return scenario
285
+
286
+
287
+ def save_calibration(
288
+ artifact: Mapping[str, Any],
289
+ persona_hash_hex: str,
290
+ *,
291
+ library: Union[str, Path, None] = None,
292
+ ) -> Path:
293
+ root = ensure_library(library)
294
+ path = root / "calibrations" / f"{persona_hash_hex}.json"
295
+ path.write_text(
296
+ json.dumps(artifact, indent=2, sort_keys=True, default=str) + "\n",
297
+ encoding="utf-8",
298
+ )
299
+ return path
300
+
301
+
302
+ def quarantine_payload(
303
+ name: str,
304
+ payload: Any,
305
+ findings: List[Dict[str, Any]],
306
+ *,
307
+ library: Union[str, Path, None] = None,
308
+ ) -> Path:
309
+ """Write a refused payload under ``quarantine/`` (never loadable)."""
310
+ root = ensure_library(library)
311
+ path = root / "quarantine" / f"{_slugify(name)}.scan.json"
312
+ path.write_text(
313
+ json.dumps(
314
+ {
315
+ "disposition": "quarantined",
316
+ "payload": payload,
317
+ "findings": findings,
318
+ "quarantined_at": _now(),
319
+ },
320
+ indent=2, sort_keys=True, default=str,
321
+ ) + "\n",
322
+ encoding="utf-8",
323
+ )
324
+ return path
325
+
326
+
327
+ def record_pull_receipt(
328
+ receipt: Mapping[str, Any],
329
+ *,
330
+ library: Union[str, Path, None] = None,
331
+ ) -> Dict[str, Any]:
332
+ """Pull receipts are provenance entries in the library index — never a
333
+ standalone artifact kind (ARCH §2g)."""
334
+ root = ensure_library(library)
335
+ index = load_index(root)
336
+ entry = {**dict(receipt), "recorded_at": _now()}
337
+ index.setdefault("pull_receipts", []).append(entry)
338
+ _write_index(root, index)
339
+ return entry
340
+
341
+
342
+ def list_library(library: Union[str, Path, None] = None) -> Dict[str, Any]:
343
+ index = load_index(library)
344
+ return {
345
+ "kind": PERSONA_LIBRARY_KIND,
346
+ "personas": list(index.get("personas", [])),
347
+ "scenarios": list(index.get("scenarios", [])),
348
+ "bias_lint": index.get("bias_lint"),
349
+ "floors": index.get("floors", {}),
350
+ "pull_receipts": list(index.get("pull_receipts", [])),
351
+ }
352
+
353
+
354
+ __all__ = [
355
+ "DEFAULT_LIBRARY_ROOT",
356
+ "PERSONA_CALIBRATION_KIND",
357
+ "PERSONA_LIBRARY_KIND",
358
+ "ensure_library",
359
+ "library_root",
360
+ "list_library",
361
+ "load_index",
362
+ "load_persona",
363
+ "load_scenario",
364
+ "quarantine_payload",
365
+ "record_pull_receipt",
366
+ "save_calibration",
367
+ "save_index",
368
+ "save_persona",
369
+ "save_scenario",
370
+ ]
fi/alk/studio/_scan.py ADDED
@@ -0,0 +1,134 @@
1
+ """Content scan for pulled artifacts (Phase 7, ARCH §2g).
2
+
3
+ Downloaded personas/scenarios are persistent content entering agent context —
4
+ a stored-injection channel (R§1 2606.04425). Every string field is scanned
5
+ before admission against rule classes aligned with the gate-enforced red-team
6
+ corpus taxonomy (``V1_REDTEAM_RESEARCH_ATTACK_TYPES`` × ``..._SURFACES``),
7
+ plus secret-marker and executable-content smells.
8
+
9
+ Two-level encoding (ARCH §4 canon): scan result tokens are ``clean`` |
10
+ ``flagged``; a flagged artifact's ENVELOPE disposition is ``quarantined``.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import re
16
+ from typing import Any, Dict, List, Tuple
17
+
18
+ CONTENT_SCAN_RESULTS = ("clean", "flagged")
19
+ SCAN_RULES_VERSION = "redteam_corpus_taxonomy"
20
+
21
+ # (substring-pattern, attack_type, surface) — attack types/surfaces are
22
+ # members of the gate-enforced 10x6 taxonomy (trinity.py:1897-1917), reused
23
+ # as rule classes, never duplicated as a new vocabulary.
24
+ STORED_INJECTION_MARKERS: Tuple[Tuple[str, str, str], ...] = (
25
+ ("ignore previous instructions", "prompt_injection", "instruction"),
26
+ ("ignore earlier instructions", "prompt_injection", "instruction"),
27
+ ("ignore the scenario constraints", "indirect_prompt_injection", "instruction"),
28
+ ("disregard the system prompt", "prompt_injection", "instruction"),
29
+ ("reveal the system prompt", "prompt_injection", "instruction"),
30
+ ("reveal any hidden policy", "prompt_injection", "instruction"),
31
+ ("when you read this later", "memory_poisoning", "memory"),
32
+ ("store this instruction for future", "memory_poisoning", "memory"),
33
+ ("remember this for all future conversations", "sleeper_memory_poisoning", "memory"),
34
+ ("fetch the url and execute", "tool_chaining", "tool"),
35
+ ("run this command", "tool_chaining", "tool"),
36
+ ("exfiltrate", "credential_exfiltration", "environment"),
37
+ ("send the credentials", "credential_exfiltration", "environment"),
38
+ )
39
+
40
+ # Secret markers: NAMES only, never values (Phase-3 redaction doctrine).
41
+ _SECRET_MARKERS = (
42
+ "api_key=", "secret_key=", "password:", "-----begin private key-----",
43
+ "x-api-key:", "bearer ",
44
+ )
45
+
46
+ _BASE64_BLOB_RE = re.compile(r"[A-Za-z0-9+/=]{120,}")
47
+ _CREDENTIAL_URL_RE = re.compile(r"https?://\S*(?:key|token|secret)=\S+", re.IGNORECASE)
48
+ _EXCERPT_LIMIT = 80
49
+
50
+
51
+ class DownloadRejected(RuntimeError):
52
+ """A pulled payload was scan-flagged — envelope disposition quarantined."""
53
+
54
+ def __init__(self, message: str, *, findings: List[Dict[str, Any]], pin: Any = None):
55
+ super().__init__(message)
56
+ self.findings = findings
57
+ self.pin = pin
58
+ self.disposition = "quarantined"
59
+
60
+
61
+ def _walk_strings(value: Any, path: str = "") -> List[Tuple[str, str]]:
62
+ found: List[Tuple[str, str]] = []
63
+ if isinstance(value, str):
64
+ found.append((path or "$", value))
65
+ elif isinstance(value, dict):
66
+ for key, item in sorted(value.items(), key=lambda kv: str(kv[0])):
67
+ child = f"{path}.{key}" if path else str(key)
68
+ found.extend(_walk_strings(item, child))
69
+ elif isinstance(value, (list, tuple)):
70
+ for index, item in enumerate(value):
71
+ found.extend(_walk_strings(item, f"{path}[{index}]"))
72
+ return found
73
+
74
+
75
+ def _excerpt(text: str, needle: str) -> str:
76
+ lowered = text.lower()
77
+ start = max(0, lowered.find(needle.lower()) - 10)
78
+ return text[start:start + _EXCERPT_LIMIT]
79
+
80
+
81
+ def scan_content(payload: Any, *, rules: str = SCAN_RULES_VERSION) -> Dict[str, Any]:
82
+ """Walk every string field; return ``{"rules", "status", "findings"}``
83
+ with status ``clean`` or ``flagged`` (ARCH §4 two-level encoding)."""
84
+ findings: List[Dict[str, Any]] = []
85
+ for field, text in _walk_strings(payload):
86
+ lowered = text.lower()
87
+ for pattern, attack_type, surface in STORED_INJECTION_MARKERS:
88
+ if pattern in lowered:
89
+ findings.append({
90
+ "field": field,
91
+ "attack_type": attack_type,
92
+ "surface": surface,
93
+ "excerpt": _excerpt(text, pattern),
94
+ "rule_source": rules,
95
+ })
96
+ for marker in _SECRET_MARKERS:
97
+ if marker in lowered:
98
+ findings.append({
99
+ "field": field,
100
+ "attack_type": "credential_exfiltration",
101
+ "surface": "environment",
102
+ "excerpt": marker, # marker NAME only, never the value
103
+ "rule_source": "secret_markers",
104
+ })
105
+ if _BASE64_BLOB_RE.search(text):
106
+ findings.append({
107
+ "field": field,
108
+ "attack_type": "tool_chaining",
109
+ "surface": "long_context",
110
+ "excerpt": "base64-like blob over length threshold",
111
+ "rule_source": "executable_content_smells",
112
+ })
113
+ if _CREDENTIAL_URL_RE.search(text):
114
+ findings.append({
115
+ "field": field,
116
+ "attack_type": "credential_exfiltration",
117
+ "surface": "retrieval",
118
+ "excerpt": "credential-bearing URL",
119
+ "rule_source": "executable_content_smells",
120
+ })
121
+ return {
122
+ "rules": rules,
123
+ "status": "flagged" if findings else "clean",
124
+ "findings": findings,
125
+ }
126
+
127
+
128
+ __all__ = [
129
+ "CONTENT_SCAN_RESULTS",
130
+ "DownloadRejected",
131
+ "SCAN_RULES_VERSION",
132
+ "STORED_INJECTION_MARKERS",
133
+ "scan_content",
134
+ ]
@@ -0,0 +1,42 @@
1
+ """Legacy embedded-dict persona auto-upgrade (Phase 7, unit 1.3)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Mapping
6
+
7
+ from fi.simulate.simulation.models import (
8
+ Persona,
9
+ PersonaIdentity,
10
+ PersonaProvenance,
11
+ )
12
+
13
+
14
+ def upgrade_legacy_persona(row: Mapping[str, Any]) -> Persona:
15
+ """Free-dict persona row -> typed Persona, provenance=legacy (PRD §4.1).
16
+
17
+ Lossless: the original dict stays in ``.persona`` untouched. Only EXACT
18
+ key matches lift into identity (name/role/language); everything else
19
+ remains free-form. No temperament/policy is invented — a legacy persona
20
+ is untyped (``is_typed == False``), runs fine, and simply cannot produce
21
+ fidelity evidence (lowest class; cannot back release claims)."""
22
+ if isinstance(row, Persona):
23
+ persona = row
24
+ else:
25
+ persona = Persona(**dict(row))
26
+ if persona.provenance is not None:
27
+ # already studio-managed (provenance deliberately set) — never
28
+ # re-touched, so content addressing stays stable across load.
29
+ return persona
30
+ embedded = dict(persona.persona)
31
+ identity = persona.identity or PersonaIdentity(
32
+ name=embedded.get("name"),
33
+ role=embedded.get("role"),
34
+ language=embedded.get("language"),
35
+ )
36
+ return persona.model_copy(update={
37
+ "identity": identity,
38
+ "provenance": PersonaProvenance(evidence_class="legacy"),
39
+ })
40
+
41
+
42
+ __all__ = ["upgrade_legacy_persona"]