briefloop 0.11.12__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 (424) hide show
  1. briefloop-0.11.12.dist-info/METADATA +473 -0
  2. briefloop-0.11.12.dist-info/RECORD +424 -0
  3. briefloop-0.11.12.dist-info/WHEEL +5 -0
  4. briefloop-0.11.12.dist-info/entry_points.txt +3 -0
  5. briefloop-0.11.12.dist-info/licenses/LICENSE +22 -0
  6. briefloop-0.11.12.dist-info/top_level.txt +1 -0
  7. multi_agent_brief/__init__.py +36 -0
  8. multi_agent_brief/analysis_blocks/__init__.py +11 -0
  9. multi_agent_brief/analysis_blocks/builder.py +206 -0
  10. multi_agent_brief/analysis_blocks/renderer.py +237 -0
  11. multi_agent_brief/analysis_blocks/schemas.py +52 -0
  12. multi_agent_brief/analysis_modules/__init__.py +8 -0
  13. multi_agent_brief/analysis_modules/base.py +87 -0
  14. multi_agent_brief/analysis_modules/market_competitor/__init__.py +68 -0
  15. multi_agent_brief/analysis_modules/market_competitor/auditor.py +290 -0
  16. multi_agent_brief/analysis_modules/market_competitor/config.py +187 -0
  17. multi_agent_brief/analysis_modules/market_competitor/event_builder.py +226 -0
  18. multi_agent_brief/analysis_modules/market_competitor/renderer.py +199 -0
  19. multi_agent_brief/analysis_modules/market_competitor/schemas.py +518 -0
  20. multi_agent_brief/analysis_modules/policy_regulatory/__init__.py +7 -0
  21. multi_agent_brief/analysis_modules/policy_regulatory/audit.py +195 -0
  22. multi_agent_brief/analysis_modules/policy_regulatory/module.py +387 -0
  23. multi_agent_brief/analysis_modules/policy_regulatory/schemas.py +129 -0
  24. multi_agent_brief/analysis_modules/registry.py +73 -0
  25. multi_agent_brief/audience/__init__.py +19 -0
  26. multi_agent_brief/audience/profiles.py +246 -0
  27. multi_agent_brief/audience_memory/__init__.py +23 -0
  28. multi_agent_brief/audience_memory/profile.py +358 -0
  29. multi_agent_brief/audit/__init__.py +36 -0
  30. multi_agent_brief/audit/case_applicability.py +181 -0
  31. multi_agent_brief/audit/deterministic.py +471 -0
  32. multi_agent_brief/audit/editorial_governance.py +404 -0
  33. multi_agent_brief/audit/final_quality.py +764 -0
  34. multi_agent_brief/audit/harness.py +242 -0
  35. multi_agent_brief/audit/interfaces.py +96 -0
  36. multi_agent_brief/audit/limitation_hygiene.py +238 -0
  37. multi_agent_brief/audit/proposal_boundary.py +34 -0
  38. multi_agent_brief/audit/redaction.py +27 -0
  39. multi_agent_brief/audit/rule_packs.py +96 -0
  40. multi_agent_brief/audit/semantic.py +435 -0
  41. multi_agent_brief/capabilities/__init__.py +22 -0
  42. multi_agent_brief/capabilities/catalog.py +215 -0
  43. multi_agent_brief/capabilities/detect.py +199 -0
  44. multi_agent_brief/capabilities/models.py +60 -0
  45. multi_agent_brief/capabilities/recommend.py +206 -0
  46. multi_agent_brief/cli/__init__.py +2 -0
  47. multi_agent_brief/cli/analysis_commands.py +140 -0
  48. multi_agent_brief/cli/approval_commands.py +107 -0
  49. multi_agent_brief/cli/audit_commands.py +59 -0
  50. multi_agent_brief/cli/capability_commands.py +548 -0
  51. multi_agent_brief/cli/claude_commands.py +159 -0
  52. multi_agent_brief/cli/competitors_commands.py +182 -0
  53. multi_agent_brief/cli/controls_commands.py +137 -0
  54. multi_agent_brief/cli/deliver_commands.py +942 -0
  55. multi_agent_brief/cli/eval_cases_commands.py +99 -0
  56. multi_agent_brief/cli/experiments_commands.py +395 -0
  57. multi_agent_brief/cli/feedback_commands.py +192 -0
  58. multi_agent_brief/cli/finalize_commands.py +153 -0
  59. multi_agent_brief/cli/gates_commands.py +174 -0
  60. multi_agent_brief/cli/hermes_commands.py +356 -0
  61. multi_agent_brief/cli/improve_commands.py +224 -0
  62. multi_agent_brief/cli/init_commands.py +564 -0
  63. multi_agent_brief/cli/init_wizard.py +1473 -0
  64. multi_agent_brief/cli/input_commands.py +237 -0
  65. multi_agent_brief/cli/main.py +288 -0
  66. multi_agent_brief/cli/onboard_commands.py +193 -0
  67. multi_agent_brief/cli/product_commands.py +1449 -0
  68. multi_agent_brief/cli/provenance_commands.py +100 -0
  69. multi_agent_brief/cli/release_commands.py +68 -0
  70. multi_agent_brief/cli/repair_commands.py +169 -0
  71. multi_agent_brief/cli/run_commands.py +292 -0
  72. multi_agent_brief/cli/runtime_commands.py +81 -0
  73. multi_agent_brief/cli/secrets_commands.py +232 -0
  74. multi_agent_brief/cli/semantic_support_commands.py +80 -0
  75. multi_agent_brief/cli/sources_commands.py +647 -0
  76. multi_agent_brief/cli/start_commands.py +8 -0
  77. multi_agent_brief/cli/state_commands.py +446 -0
  78. multi_agent_brief/cli/status_commands.py +26 -0
  79. multi_agent_brief/cli/workbuddy_commands.py +103 -0
  80. multi_agent_brief/configs/artifact_contracts.yaml +514 -0
  81. multi_agent_brief/configs/orchestrator_contract.yaml +181 -0
  82. multi_agent_brief/configs/policy_packs/default.yaml +65 -0
  83. multi_agent_brief/configs/policy_profiles/evidence_extract_default.yaml +58 -0
  84. multi_agent_brief/configs/policy_profiles/finance_default.yaml +44 -0
  85. multi_agent_brief/configs/policy_profiles/internet_default.yaml +44 -0
  86. multi_agent_brief/configs/policy_profiles/manufacturing_default.yaml +33 -0
  87. multi_agent_brief/configs/policy_profiles/solar_manufacturing_default.yaml +64 -0
  88. multi_agent_brief/configs/report_packs/evidence_extract.yaml +48 -0
  89. multi_agent_brief/configs/report_packs/management_monthly.yaml +36 -0
  90. multi_agent_brief/configs/report_packs/market_weekly.yaml +36 -0
  91. multi_agent_brief/configs/report_packs/solar_industry_periodic.yaml +50 -0
  92. multi_agent_brief/configs/report_templates/evidence_extract.yaml +55 -0
  93. multi_agent_brief/configs/report_templates/management_monthly.yaml +48 -0
  94. multi_agent_brief/configs/report_templates/market_weekly.yaml +49 -0
  95. multi_agent_brief/configs/report_templates/solar_industry_periodic.yaml +75 -0
  96. multi_agent_brief/configs/stage_specs.yaml +180 -0
  97. multi_agent_brief/contracts/__init__.py +41 -0
  98. multi_agent_brief/contracts/base.py +99 -0
  99. multi_agent_brief/contracts/errors.py +41 -0
  100. multi_agent_brief/contracts/migrations/__init__.py +5 -0
  101. multi_agent_brief/contracts/migrations/claim_v1_to_v2.py +33 -0
  102. multi_agent_brief/contracts/registry.py +159 -0
  103. multi_agent_brief/contracts/role_topology.py +25 -0
  104. multi_agent_brief/contracts/schemas/__init__.py +35 -0
  105. multi_agent_brief/contracts/schemas/analysis_pack.py +135 -0
  106. multi_agent_brief/contracts/schemas/atomic_claim_graph.py +304 -0
  107. multi_agent_brief/contracts/schemas/audit_report.py +80 -0
  108. multi_agent_brief/contracts/schemas/candidate_item.py +51 -0
  109. multi_agent_brief/contracts/schemas/claim.py +197 -0
  110. multi_agent_brief/contracts/schemas/claim_draft.py +386 -0
  111. multi_agent_brief/contracts/schemas/claim_support_matrix.py +334 -0
  112. multi_agent_brief/contracts/schemas/evidence_span_registry.py +270 -0
  113. multi_agent_brief/contracts/schemas/policy_profile.py +247 -0
  114. multi_agent_brief/contracts/schemas/report_spec.py +257 -0
  115. multi_agent_brief/contracts/schemas/semantic_assessment_report.py +480 -0
  116. multi_agent_brief/contracts/schemas/source_evidence_pack_manifest.py +233 -0
  117. multi_agent_brief/contracts/schemas/source_item.py +96 -0
  118. multi_agent_brief/contracts/semantic_assessment_status.py +12 -0
  119. multi_agent_brief/contracts/source_metadata.py +252 -0
  120. multi_agent_brief/contracts/target_contract.py +460 -0
  121. multi_agent_brief/contracts/validator.py +225 -0
  122. multi_agent_brief/controls/__init__.py +23 -0
  123. multi_agent_brief/controls/contract.py +106 -0
  124. multi_agent_brief/controls/switchboard.py +1076 -0
  125. multi_agent_brief/core/__init__.py +2 -0
  126. multi_agent_brief/core/citations.py +14 -0
  127. multi_agent_brief/core/claim_ledger.py +94 -0
  128. multi_agent_brief/core/config.py +191 -0
  129. multi_agent_brief/core/env.py +69 -0
  130. multi_agent_brief/core/previous.py +83 -0
  131. multi_agent_brief/core/schemas.py +181 -0
  132. multi_agent_brief/core/selection.py +189 -0
  133. multi_agent_brief/delivery/__init__.py +6 -0
  134. multi_agent_brief/delivery/base.py +36 -0
  135. multi_agent_brief/delivery/feishu.py +204 -0
  136. multi_agent_brief/delivery/gws.py +237 -0
  137. multi_agent_brief/evaluation_cases/__init__.py +15 -0
  138. multi_agent_brief/evaluation_cases/contract.py +429 -0
  139. multi_agent_brief/evaluation_cases/fixtures/cases/approved_guidance_materialized/workspace/config.yaml +18 -0
  140. multi_agent_brief/evaluation_cases/fixtures/cases/approved_guidance_materialized/workspace/improvement/ledger.jsonl +2 -0
  141. multi_agent_brief/evaluation_cases/fixtures/cases/approved_guidance_materialized/workspace/sources.yaml +2 -0
  142. multi_agent_brief/evaluation_cases/fixtures/cases/approved_guidance_materialized/workspace/user.md +4 -0
  143. multi_agent_brief/evaluation_cases/fixtures/cases/company_event_missing_latest_official_check/workspace/config.yaml +13 -0
  144. multi_agent_brief/evaluation_cases/fixtures/cases/company_event_missing_latest_official_check/workspace/sources.yaml +9 -0
  145. multi_agent_brief/evaluation_cases/fixtures/cases/company_event_missing_latest_official_check/workspace/user.md +4 -0
  146. multi_agent_brief/evaluation_cases/fixtures/cases/control_switchboard_selection_is_not_execution/workspace/config.yaml +12 -0
  147. multi_agent_brief/evaluation_cases/fixtures/cases/control_switchboard_selection_is_not_execution/workspace/sources.yaml +6 -0
  148. multi_agent_brief/evaluation_cases/fixtures/cases/control_switchboard_selection_is_not_execution/workspace/user.md +4 -0
  149. multi_agent_brief/evaluation_cases/fixtures/cases/feedback_triage_required/workspace/config.yaml +6 -0
  150. multi_agent_brief/evaluation_cases/fixtures/cases/feedback_triage_required/workspace/input/human_feedback.md +1 -0
  151. multi_agent_brief/evaluation_cases/fixtures/cases/feedback_triage_required/workspace/sources.yaml +2 -0
  152. multi_agent_brief/evaluation_cases/fixtures/cases/feedback_triage_required/workspace/user.md +3 -0
  153. multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/config.yaml +8 -0
  154. multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/brief.md +14 -0
  155. multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/intermediate/audit_report.json +1 -0
  156. multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/intermediate/audited_brief.md +5 -0
  157. multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/intermediate/candidate_claims.json +1 -0
  158. multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/intermediate/claim_ledger.json +1 -0
  159. multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/intermediate/screened_candidates.json +1 -0
  160. multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/sources.yaml +2 -0
  161. multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/user.md +3 -0
  162. multi_agent_brief/evaluation_cases/fixtures/cases/formal_release_missing_human_approval/workspace/config.yaml +13 -0
  163. multi_agent_brief/evaluation_cases/fixtures/cases/formal_release_missing_human_approval/workspace/sources.yaml +9 -0
  164. multi_agent_brief/evaluation_cases/fixtures/cases/formal_release_missing_human_approval/workspace/user.md +4 -0
  165. multi_agent_brief/evaluation_cases/fixtures/cases/guidance_manifestation_not_observable/workspace/config.yaml +4 -0
  166. multi_agent_brief/evaluation_cases/fixtures/cases/guidance_manifestation_not_observable/workspace/sources.yaml +2 -0
  167. multi_agent_brief/evaluation_cases/fixtures/cases/guidance_manifestation_not_observable/workspace/user.md +3 -0
  168. multi_agent_brief/evaluation_cases/fixtures/cases/media_only_legal_policy_blocks_research_review/workspace/config.yaml +13 -0
  169. multi_agent_brief/evaluation_cases/fixtures/cases/media_only_legal_policy_blocks_research_review/workspace/sources.yaml +9 -0
  170. multi_agent_brief/evaluation_cases/fixtures/cases/media_only_legal_policy_blocks_research_review/workspace/user.md +4 -0
  171. multi_agent_brief/evaluation_cases/fixtures/cases/mixed_metric_scope_support_blocker/workspace/config.yaml +13 -0
  172. multi_agent_brief/evaluation_cases/fixtures/cases/mixed_metric_scope_support_blocker/workspace/sources.yaml +9 -0
  173. multi_agent_brief/evaluation_cases/fixtures/cases/mixed_metric_scope_support_blocker/workspace/user.md +5 -0
  174. multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/config.yaml +6 -0
  175. multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/audited_brief.md +3 -0
  176. multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/candidate_claims.json +1 -0
  177. multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/claim_ledger.json +1 -0
  178. multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/feedback_issues.json +25 -0
  179. multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/repair_plan.json +27 -0
  180. multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/screened_candidates.json +1 -0
  181. multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/sources.yaml +2 -0
  182. multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/user.md +3 -0
  183. multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/config.yaml +8 -0
  184. multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/audit_report.json +1 -0
  185. multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/audited_brief.md +3 -0
  186. multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/candidate_claims.json +1 -0
  187. multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/claim_ledger.json +11 -0
  188. multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/quality_gate_report.json +22 -0
  189. multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/screened_candidates.json +1 -0
  190. multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/sources.yaml +2 -0
  191. multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/user.md +3 -0
  192. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/config.yaml +13 -0
  193. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/output/intermediate/audit_report.json +1 -0
  194. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/output/intermediate/audited_brief.md +5 -0
  195. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/output/intermediate/candidate_claims.json +1 -0
  196. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/output/intermediate/claim_ledger.json +40 -0
  197. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/output/intermediate/screened_candidates.json +1 -0
  198. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/sources.yaml +3 -0
  199. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/user.md +3 -0
  200. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/config.yaml +8 -0
  201. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/brief.md +3 -0
  202. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/intermediate/audit_report.json +1 -0
  203. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/intermediate/audited_brief.md +3 -0
  204. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/intermediate/candidate_claims.json +1 -0
  205. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/intermediate/claim_ledger.json +13 -0
  206. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/intermediate/screened_candidates.json +1 -0
  207. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/sources.yaml +2 -0
  208. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/user.md +3 -0
  209. multi_agent_brief/evaluation_cases/fixtures/cases/release_readiness_forged_event_blocker/workspace/config.yaml +13 -0
  210. multi_agent_brief/evaluation_cases/fixtures/cases/release_readiness_forged_event_blocker/workspace/output/intermediate/release_readiness_report.json +31 -0
  211. multi_agent_brief/evaluation_cases/fixtures/cases/release_readiness_forged_event_blocker/workspace/sources.yaml +9 -0
  212. multi_agent_brief/evaluation_cases/fixtures/cases/release_readiness_forged_event_blocker/workspace/user.md +4 -0
  213. multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/config.yaml +18 -0
  214. multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/improvement/ledger.jsonl +3 -0
  215. multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/improvement/memory.md +3 -0
  216. multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/output/intermediate/improvement_memory_snapshot.md +3 -0
  217. multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/sources.yaml +2 -0
  218. multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/user.md +4 -0
  219. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/config.yaml +12 -0
  220. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/input/sources/source-001.txt +1 -0
  221. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/brief.md +37 -0
  222. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/delivery/brief.md +37 -0
  223. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/atomic_claim_graph.json +17 -0
  224. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/audit_report.json +5 -0
  225. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/audited_brief.md +37 -0
  226. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/claim_ledger.json +20 -0
  227. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/evidence_span_registry.json +20 -0
  228. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/finalize_report.json +40 -0
  229. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/gates/auditor_quality_gate_report.json +42 -0
  230. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/gates/finalize_quality_gate_report.json +42 -0
  231. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/screened_candidates.json +33 -0
  232. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/source_appendix.md +12 -0
  233. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/source_appendix_trace.md +10 -0
  234. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/report_spec.yaml +24 -0
  235. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/sources.yaml +4 -0
  236. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/user.md +3 -0
  237. multi_agent_brief/evaluation_cases/fixtures/cases/source_evidence_pack_blocks_non_evidence_file/workspace/config.yaml +13 -0
  238. multi_agent_brief/evaluation_cases/fixtures/cases/source_evidence_pack_blocks_non_evidence_file/workspace/input/sources/README.md +4 -0
  239. multi_agent_brief/evaluation_cases/fixtures/cases/source_evidence_pack_blocks_non_evidence_file/workspace/output/intermediate/source_evidence_pack_manifest.json +26 -0
  240. multi_agent_brief/evaluation_cases/fixtures/cases/source_evidence_pack_blocks_non_evidence_file/workspace/sources.yaml +9 -0
  241. multi_agent_brief/evaluation_cases/fixtures/cases/source_evidence_pack_blocks_non_evidence_file/workspace/user.md +4 -0
  242. multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/config.yaml +9 -0
  243. multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/output/intermediate/audit_report.json +1 -0
  244. multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/output/intermediate/audited_brief.md +3 -0
  245. multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/output/intermediate/candidate_claims.json +1 -0
  246. multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/output/intermediate/claim_ledger.json +13 -0
  247. multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/output/intermediate/screened_candidates.json +1 -0
  248. multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/sources.yaml +2 -0
  249. multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/user.md +3 -0
  250. multi_agent_brief/evaluation_cases/fixtures/cases/third_party_price_snapshot_formal_block/workspace/config.yaml +13 -0
  251. multi_agent_brief/evaluation_cases/fixtures/cases/third_party_price_snapshot_formal_block/workspace/sources.yaml +9 -0
  252. multi_agent_brief/evaluation_cases/fixtures/cases/third_party_price_snapshot_formal_block/workspace/user.md +4 -0
  253. multi_agent_brief/evaluation_cases/fixtures/cases/trajectory_retry_budget_exhausted/workspace/config.yaml +4 -0
  254. multi_agent_brief/evaluation_cases/fixtures/cases/trajectory_retry_budget_exhausted/workspace/sources.yaml +2 -0
  255. multi_agent_brief/evaluation_cases/fixtures/cases/trajectory_retry_budget_exhausted/workspace/user.md +3 -0
  256. multi_agent_brief/evaluation_cases/fixtures/cases/unapproved_entry_not_materialized/workspace/config.yaml +18 -0
  257. multi_agent_brief/evaluation_cases/fixtures/cases/unapproved_entry_not_materialized/workspace/improvement/ledger.jsonl +1 -0
  258. multi_agent_brief/evaluation_cases/fixtures/cases/unapproved_entry_not_materialized/workspace/sources.yaml +2 -0
  259. multi_agent_brief/evaluation_cases/fixtures/cases/unapproved_entry_not_materialized/workspace/user.md +4 -0
  260. multi_agent_brief/evaluation_cases/fixtures/cases/unauthorized_institution_branding_blocks_release/workspace/config.yaml +18 -0
  261. multi_agent_brief/evaluation_cases/fixtures/cases/unauthorized_institution_branding_blocks_release/workspace/sources.yaml +9 -0
  262. multi_agent_brief/evaluation_cases/fixtures/cases/unauthorized_institution_branding_blocks_release/workspace/user.md +5 -0
  263. multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/config.yaml +8 -0
  264. multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/output/intermediate/audit_report.json +1 -0
  265. multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/output/intermediate/audited_brief.md +8 -0
  266. multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/output/intermediate/candidate_claims.json +1 -0
  267. multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/output/intermediate/claim_ledger.json +1 -0
  268. multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/output/intermediate/screened_candidates.json +1 -0
  269. multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/sources.yaml +2 -0
  270. multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/user.md +3 -0
  271. multi_agent_brief/evaluation_cases/fixtures/manifest.yaml +861 -0
  272. multi_agent_brief/evaluation_cases/fixtures.py +20 -0
  273. multi_agent_brief/evaluation_cases/runner.py +1603 -0
  274. multi_agent_brief/experiments/__init__.py +45 -0
  275. multi_agent_brief/experiments/experiment_080.py +5835 -0
  276. multi_agent_brief/experiments/schemas.py +63 -0
  277. multi_agent_brief/experiments/target_contract.py +10 -0
  278. multi_agent_brief/feedback/__init__.py +17 -0
  279. multi_agent_brief/feedback/feedback_contract.py +482 -0
  280. multi_agent_brief/feedback/feedback_state.py +898 -0
  281. multi_agent_brief/hermes/__init__.py +25 -0
  282. multi_agent_brief/hermes/adapter.py +832 -0
  283. multi_agent_brief/improvement/__init__.py +83 -0
  284. multi_agent_brief/improvement/contract.py +1114 -0
  285. multi_agent_brief/improvement/memory.py +451 -0
  286. multi_agent_brief/improvement/product_definition.py +228 -0
  287. multi_agent_brief/improvement/state.py +961 -0
  288. multi_agent_brief/inputs/__init__.py +2 -0
  289. multi_agent_brief/inputs/classifier.py +322 -0
  290. multi_agent_brief/inputs/contracts.py +59 -0
  291. multi_agent_brief/inputs/extractor.py +310 -0
  292. multi_agent_brief/install/__init__.py +2 -0
  293. multi_agent_brief/install/writer.py +97 -0
  294. multi_agent_brief/onboarding/__init__.py +11 -0
  295. multi_agent_brief/onboarding/io.py +103 -0
  296. multi_agent_brief/onboarding/mapper.py +484 -0
  297. multi_agent_brief/onboarding/schema.py +46 -0
  298. multi_agent_brief/orchestrator/__init__.py +2 -0
  299. multi_agent_brief/orchestrator/fact_layer_import.py +352 -0
  300. multi_agent_brief/orchestrator/handoff.py +2209 -0
  301. multi_agent_brief/orchestrator/role_topology.py +42 -0
  302. multi_agent_brief/orchestrator/run_archive.py +888 -0
  303. multi_agent_brief/orchestrator/run_integrity.py +346 -0
  304. multi_agent_brief/orchestrator/runtime_state/__init__.py +70 -0
  305. multi_agent_brief/orchestrator/runtime_state/_io.py +167 -0
  306. multi_agent_brief/orchestrator/runtime_state/_transactions.py +298 -0
  307. multi_agent_brief/orchestrator/runtime_state/artifact_registry.py +1825 -0
  308. multi_agent_brief/orchestrator/runtime_state/atomic_claim_graph.py +96 -0
  309. multi_agent_brief/orchestrator/runtime_state/claim_ledger_freeze.py +510 -0
  310. multi_agent_brief/orchestrator/runtime_state/claim_metadata_enrichment.py +836 -0
  311. multi_agent_brief/orchestrator/runtime_state/claim_support_matrix.py +582 -0
  312. multi_agent_brief/orchestrator/runtime_state/completion_gates.py +672 -0
  313. multi_agent_brief/orchestrator/runtime_state/contracts_loader.py +66 -0
  314. multi_agent_brief/orchestrator/runtime_state/decisions.py +239 -0
  315. multi_agent_brief/orchestrator/runtime_state/errors.py +61 -0
  316. multi_agent_brief/orchestrator/runtime_state/event_log.py +256 -0
  317. multi_agent_brief/orchestrator/runtime_state/evidence_span_registry.py +233 -0
  318. multi_agent_brief/orchestrator/runtime_state/fact_layer.py +755 -0
  319. multi_agent_brief/orchestrator/runtime_state/identity.py +90 -0
  320. multi_agent_brief/orchestrator/runtime_state/lifecycle.py +675 -0
  321. multi_agent_brief/orchestrator/runtime_state/manifest.py +82 -0
  322. multi_agent_brief/orchestrator/runtime_state/operations.py +224 -0
  323. multi_agent_brief/orchestrator/runtime_state/paths.py +38 -0
  324. multi_agent_brief/orchestrator/runtime_state/repair.py +984 -0
  325. multi_agent_brief/orchestrator/runtime_state/safety_surfaces.py +147 -0
  326. multi_agent_brief/orchestrator/runtime_state/semantic_assessment_report.py +692 -0
  327. multi_agent_brief/orchestrator/runtime_state/semantic_support_acceptance.py +683 -0
  328. multi_agent_brief/orchestrator/runtime_state/source_evidence_pack.py +131 -0
  329. multi_agent_brief/orchestrator/runtime_state/stage_completion.py +1320 -0
  330. multi_agent_brief/orchestrator/runtime_state/trajectory.py +200 -0
  331. multi_agent_brief/orchestrator/runtime_state/workflow.py +350 -0
  332. multi_agent_brief/orchestrator/source_evidence.py +43 -0
  333. multi_agent_brief/orchestrator/timing.py +410 -0
  334. multi_agent_brief/orchestrator_contract.py +96 -0
  335. multi_agent_brief/outputs/__init__.py +2 -0
  336. multi_agent_brief/outputs/atomic_reader_projection.py +288 -0
  337. multi_agent_brief/outputs/docx.py +32 -0
  338. multi_agent_brief/outputs/finalize.py +1036 -0
  339. multi_agent_brief/outputs/ib_docx.py +983 -0
  340. multi_agent_brief/outputs/naming.py +38 -0
  341. multi_agent_brief/outputs/pdf.py +20 -0
  342. multi_agent_brief/outputs/reader_final_gate.py +509 -0
  343. multi_agent_brief/outputs/reader_projection.py +397 -0
  344. multi_agent_brief/outputs/source_appendix.py +753 -0
  345. multi_agent_brief/outputs/templates/__init__.py +112 -0
  346. multi_agent_brief/product/__init__.py +49 -0
  347. multi_agent_brief/product/bundle_projection.py +686 -0
  348. multi_agent_brief/product/citation_profile.py +145 -0
  349. multi_agent_brief/product/guidance_manifestation.py +421 -0
  350. multi_agent_brief/product/materiality_selection.py +472 -0
  351. multi_agent_brief/product/policy_gate_adapter.py +116 -0
  352. multi_agent_brief/product/policy_profile.py +39 -0
  353. multi_agent_brief/product/policy_projection.py +161 -0
  354. multi_agent_brief/product/policy_registry.py +83 -0
  355. multi_agent_brief/product/policy_resolver.py +225 -0
  356. multi_agent_brief/product/quality_closeout.py +188 -0
  357. multi_agent_brief/product/quality_panel.py +2217 -0
  358. multi_agent_brief/product/release_approval.py +1034 -0
  359. multi_agent_brief/product/report_pack.py +130 -0
  360. multi_agent_brief/product/report_pack_aliases.py +68 -0
  361. multi_agent_brief/product/report_registry.py +101 -0
  362. multi_agent_brief/product/report_spec.py +181 -0
  363. multi_agent_brief/product/support_wording.py +525 -0
  364. multi_agent_brief/product/template_conformance.py +516 -0
  365. multi_agent_brief/product/template_projection.py +123 -0
  366. multi_agent_brief/product/template_registry.py +244 -0
  367. multi_agent_brief/product/template_render_plan.py +315 -0
  368. multi_agent_brief/product/template_renderer.py +224 -0
  369. multi_agent_brief/product/trajectory_regulation.py +441 -0
  370. multi_agent_brief/provenance/__init__.py +5 -0
  371. multi_agent_brief/provenance/builder.py +841 -0
  372. multi_agent_brief/provenance/contract.py +21 -0
  373. multi_agent_brief/provenance/io.py +144 -0
  374. multi_agent_brief/provenance/model.py +110 -0
  375. multi_agent_brief/provenance/references.py +43 -0
  376. multi_agent_brief/provenance/validator.py +135 -0
  377. multi_agent_brief/quality_gates/__init__.py +19 -0
  378. multi_agent_brief/quality_gates/contract.py +581 -0
  379. multi_agent_brief/quality_gates/state.py +2498 -0
  380. multi_agent_brief/repair/__init__.py +5 -0
  381. multi_agent_brief/repair/router.py +842 -0
  382. multi_agent_brief/runtime_assets.py +431 -0
  383. multi_agent_brief/sources/__init__.py +16 -0
  384. multi_agent_brief/sources/api_filings.py +236 -0
  385. multi_agent_brief/sources/api_news.py +174 -0
  386. multi_agent_brief/sources/base.py +142 -0
  387. multi_agent_brief/sources/cached_package.py +119 -0
  388. multi_agent_brief/sources/cli_provider.py +263 -0
  389. multi_agent_brief/sources/coverage.py +336 -0
  390. multi_agent_brief/sources/decider.py +756 -0
  391. multi_agent_brief/sources/doctor.py +373 -0
  392. multi_agent_brief/sources/evidence_pack.py +364 -0
  393. multi_agent_brief/sources/feishu_provider.py +326 -0
  394. multi_agent_brief/sources/filing_resolver.py +400 -0
  395. multi_agent_brief/sources/industry_packs.py +130 -0
  396. multi_agent_brief/sources/join.py +206 -0
  397. multi_agent_brief/sources/local_signal.py +81 -0
  398. multi_agent_brief/sources/local_signal_planner.py +636 -0
  399. multi_agent_brief/sources/manual.py +259 -0
  400. multi_agent_brief/sources/mcp_provider.py +309 -0
  401. multi_agent_brief/sources/mineru_provider.py +601 -0
  402. multi_agent_brief/sources/normalizer.py +95 -0
  403. multi_agent_brief/sources/opencli_provider.py +282 -0
  404. multi_agent_brief/sources/planner.py +178 -0
  405. multi_agent_brief/sources/registry.py +367 -0
  406. multi_agent_brief/sources/rss.py +190 -0
  407. multi_agent_brief/sources/search_backends/__init__.py +31 -0
  408. multi_agent_brief/sources/search_backends/base.py +51 -0
  409. multi_agent_brief/sources/search_backends/brave.py +192 -0
  410. multi_agent_brief/sources/search_backends/capabilities.py +94 -0
  411. multi_agent_brief/sources/search_backends/exa.py +180 -0
  412. multi_agent_brief/sources/search_backends/firecrawl.py +164 -0
  413. multi_agent_brief/sources/search_backends/serper.py +241 -0
  414. multi_agent_brief/sources/search_backends/tavily.py +124 -0
  415. multi_agent_brief/sources/sourcehub.py +505 -0
  416. multi_agent_brief/sources/web_search.py +267 -0
  417. multi_agent_brief/status.py +966 -0
  418. multi_agent_brief/tools/__init__.py +3 -0
  419. multi_agent_brief/tools/draft_cleanup.py +198 -0
  420. multi_agent_brief/workbuddy/__init__.py +1 -0
  421. multi_agent_brief/workbuddy/diagnose.py +569 -0
  422. multi_agent_brief/workbuddy/skill_pack.py +309 -0
  423. multi_agent_brief/workspace/__init__.py +1 -0
  424. multi_agent_brief/workspace/init_profile.py +45 -0
@@ -0,0 +1,189 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from dataclasses import dataclass, field
5
+
6
+ from multi_agent_brief.audit.deterministic import parse_date
7
+ from multi_agent_brief.core.claim_ledger import ClaimLedger
8
+ from multi_agent_brief.core.schemas import Claim, PipelineContext
9
+
10
+
11
+ @dataclass
12
+ class SelectionResult:
13
+ selected: list[Claim]
14
+ excluded: list[dict] = field(default_factory=list)
15
+ stats: dict = field(default_factory=dict)
16
+
17
+
18
+ TOPIC_CAPS = {
19
+ "policy": 24,
20
+ "compliance": 18,
21
+ "earnings": 24,
22
+ "competitor": 18,
23
+ "market": 24,
24
+ "demand": 18,
25
+ "rates": 12,
26
+ "capital": 14,
27
+ "technology": 12,
28
+ "general": 18,
29
+ }
30
+
31
+
32
+ def select_reportable_claims(
33
+ ledger: ClaimLedger,
34
+ context: PipelineContext,
35
+ previous_report_text: str = "",
36
+ ) -> SelectionResult:
37
+ scored: list[tuple[str, int, Claim]] = []
38
+ excluded: list[dict] = []
39
+
40
+ for claim in ledger:
41
+ reason = exclusion_reason(claim, context)
42
+ if reason:
43
+ claim.metadata["excluded_reason"] = reason
44
+ excluded.append({"claim_id": claim.claim_id, "reason": reason, "statement": claim.statement})
45
+ continue
46
+
47
+ topic = infer_topic(claim)
48
+ repeat = previous_report_topic_hit(claim_search_text(claim), previous_report_text)
49
+ score = novelty_score(claim, topic, repeat)
50
+ claim.metadata.update({"topic": topic, "repeat": repeat, "novelty_score": score})
51
+ scored.append((topic, score, claim))
52
+
53
+ selected: list[Claim] = []
54
+ omitted_by_topic: dict[str, int] = {}
55
+ for topic, cap in TOPIC_CAPS.items():
56
+ bucket = [item for item in scored if item[0] == topic]
57
+ bucket.sort(key=lambda item: item[1], reverse=True)
58
+ selected.extend(item[2] for item in bucket[:cap])
59
+ if len(bucket) > cap:
60
+ omitted_by_topic[topic] = len(bucket) - cap
61
+
62
+ selected.sort(key=lambda claim: int(claim.metadata.get("novelty_score", 0)), reverse=True)
63
+ selected = selected[: context.max_claims]
64
+
65
+ return SelectionResult(
66
+ selected=selected,
67
+ excluded=excluded,
68
+ stats={
69
+ "input_claims": len(ledger),
70
+ "selected_claims": len(selected),
71
+ "excluded_claims": len(excluded),
72
+ "omitted_by_topic": omitted_by_topic,
73
+ "repeat_claims": sum(1 for claim in selected if claim.metadata.get("repeat") is True),
74
+ "quiet_week": len(selected) < context.quiet_week_min_claims,
75
+ },
76
+ )
77
+
78
+
79
+ def ledger_from_selected(result: SelectionResult) -> ClaimLedger:
80
+ return ClaimLedger(result.selected)
81
+
82
+
83
+ def exclusion_reason(claim: Claim, context: PipelineContext) -> str:
84
+ if claim.claim_type == "needs_recrawl":
85
+ return "needs_recrawl"
86
+ if str(claim.metadata.get("source_tier", "")) == "T5":
87
+ return "low_confidence_source"
88
+ report_day = parse_date(context.report_date)
89
+ published_day = parse_date(str(claim.metadata.get("published_at", "")))
90
+ if report_day and published_day and context.max_source_age_days is not None:
91
+ if (report_day - published_day).days > context.max_source_age_days:
92
+ return "stale_source"
93
+ return ""
94
+
95
+
96
+ def claim_search_text(claim: Claim) -> str:
97
+ return " ".join(
98
+ str(part or "")
99
+ for part in (
100
+ claim.claim_id,
101
+ claim.statement,
102
+ claim.evidence_text,
103
+ claim.source_id,
104
+ claim.source_url,
105
+ claim.claim_type,
106
+ claim.metadata.get("source_tier", ""),
107
+ )
108
+ )
109
+
110
+
111
+ def compact_text_for_match(text: str) -> str:
112
+ return re.sub(r"[\W_]+", "", (text or "").lower(), flags=re.UNICODE)
113
+
114
+
115
+ def previous_report_topic_hit(text: str, previous_report_text: str) -> bool:
116
+ if not text or not previous_report_text:
117
+ return False
118
+ compact = compact_text_for_match(text)
119
+ previous = compact_text_for_match(previous_report_text)
120
+ if len(compact) >= 32 and (compact[:80] in previous or compact[:48] in previous):
121
+ return True
122
+ theme_groups = [
123
+ ("adcvd", "antidumping", "countervailing"),
124
+ ("feoc", "45x", "48e"),
125
+ ("uflpa", "detain", "forcedlabor"),
126
+ ("chapter11", "bankrupt", "residential"),
127
+ ("section337", "topcon", "patent"),
128
+ ]
129
+ for group in theme_groups:
130
+ hits = sum(1 for term in group if term in compact and term in previous)
131
+ if hits >= 2:
132
+ return True
133
+ return False
134
+
135
+
136
+ def infer_topic(claim: Claim) -> str:
137
+ text = claim_search_text(claim).lower()
138
+ if any(term in text for term in ("ad/cvd", "antidumping", "countervailing", "tariff", "section 337", "policy", "regulation")):
139
+ return "policy"
140
+ if any(term in text for term in ("uflpa", "cbp", "detain", "forced labor", "compliance")):
141
+ return "compliance"
142
+ if any(term in text for term in ("revenue", "eps", "ebitda", "gross margin", "earnings", "backlog", "guidance")):
143
+ return "earnings"
144
+ if any(term in text for term in ("competitor", "capacity", "expansion", "plant", "launch")):
145
+ return "competitor"
146
+ if any(term in text for term in ("price", "demand", "inventory", "market")):
147
+ return "market"
148
+ if any(term in text for term in ("installation", "generation", "ppa", "interconnection", "load growth")):
149
+ return "demand"
150
+ if any(term in text for term in ("treasury", "yield", "sofr", "fed", "rate")):
151
+ return "rates"
152
+ if any(term in text for term in ("acquisition", "investment", "fund", "capital")):
153
+ return "capital"
154
+ if any(term in text for term in ("topcon", "hjt", "bc", "technology", "efficiency")):
155
+ return "technology"
156
+ return "general"
157
+
158
+
159
+ def novelty_score(claim: Claim, topic: str, repeat: bool) -> int:
160
+ text = claim_search_text(claim).lower()
161
+ score = 10
162
+ tier = str(claim.metadata.get("source_tier", "") or "")
163
+ score += {"T1": 28, "T2": 24, "T3": 18, "T4": 14, "T5": 2}.get(tier, 8)
164
+ if claim.claim_type in {"number", "date"}:
165
+ score += 8
166
+ if claim.claim_type in {"risk", "forecast"}:
167
+ score += 6
168
+ high_signal_terms = [
169
+ "tariff",
170
+ "section 337",
171
+ "investigation",
172
+ "earnings",
173
+ "eps",
174
+ "ebitda",
175
+ "backlog",
176
+ "guidance",
177
+ "bankrupt",
178
+ "acquisition",
179
+ "capex",
180
+ "data center",
181
+ "storage",
182
+ ]
183
+ score += sum(6 for term in high_signal_terms if term in text)
184
+ if topic in {"policy", "earnings", "market"}:
185
+ score += 4
186
+ if repeat:
187
+ score -= 35
188
+ return score
189
+
@@ -0,0 +1,6 @@
1
+ """Delivery connector interfaces."""
2
+
3
+ from multi_agent_brief.delivery.base import DeliveryArtifact, DeliveryResult, DeliveryTarget
4
+
5
+ __all__ = ["DeliveryArtifact", "DeliveryResult", "DeliveryTarget"]
6
+
@@ -0,0 +1,36 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from dataclasses import dataclass, field
5
+ from typing import Any
6
+
7
+
8
+ @dataclass
9
+ class DeliveryArtifact:
10
+ path: str
11
+ title: str = ""
12
+ metadata: dict[str, Any] = field(default_factory=dict)
13
+
14
+
15
+ @dataclass
16
+ class DeliveryTarget:
17
+ channel: str
18
+ recipient: str = ""
19
+ metadata: dict[str, Any] = field(default_factory=dict)
20
+
21
+
22
+ @dataclass
23
+ class DeliveryResult:
24
+ connector_name: str
25
+ delivered: bool
26
+ message: str = ""
27
+ metadata: dict[str, Any] = field(default_factory=dict)
28
+
29
+
30
+ class DeliveryConnector(ABC):
31
+ name = "delivery-connector"
32
+
33
+ @abstractmethod
34
+ def deliver(self, artifact: DeliveryArtifact, target: DeliveryTarget) -> DeliveryResult:
35
+ raise NotImplementedError
36
+
@@ -0,0 +1,204 @@
1
+ """Feishu/Lark delivery connector using lark-cli.
2
+
3
+ Sends briefs to Feishu via:
4
+ - IM message (lark-cli im +messages-send --as bot)
5
+ - Document creation (lark-cli docs +create)
6
+ - File upload (lark-cli drive +upload)
7
+
8
+ Requires lark-cli to be installed and authenticated.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import os
14
+ import shutil
15
+ import subprocess
16
+ from pathlib import Path
17
+
18
+ from multi_agent_brief.delivery.base import DeliveryArtifact, DeliveryConnector, DeliveryResult, DeliveryTarget
19
+
20
+ # Max text length for IM messages (Feishu limit ~150k chars, conservative)
21
+ IM_MAX_LENGTH = 50000
22
+
23
+
24
+ class FeishuDeliveryConnector(DeliveryConnector):
25
+ """Deliver brief artifacts to Feishu via lark-cli.
26
+
27
+ Supported channels:
28
+ - chat: send as text message to a group or user chat
29
+ - doc: create a Feishu document with the brief content
30
+ - drive: upload the brief file to Feishu Drive
31
+
32
+ Target recipient format:
33
+ - chat: chat_id (oc_xxx) or user open_id (ou_xxx)
34
+ - doc: folder_token (optional, defaults to root)
35
+ - drive: folder_token (optional, defaults to root)
36
+ """
37
+
38
+ name = "feishu"
39
+
40
+ def deliver(self, artifact: DeliveryArtifact, target: DeliveryTarget) -> DeliveryResult:
41
+ """Deliver a brief artifact to a Feishu target.
42
+
43
+ Args:
44
+ artifact: The brief file to deliver (path + title).
45
+ target:
46
+ - channel='chat', recipient='oc_xxx' (chat_id) or 'ou_xxx' (user_id)
47
+ - channel='doc', recipient='folder_token' (optional)
48
+ - channel='drive', recipient='folder_token' (optional)
49
+
50
+ Returns:
51
+ DeliveryResult with delivery status and message.
52
+ """
53
+ # Check prerequisites
54
+ if not shutil.which("lark-cli"):
55
+ return DeliveryResult(
56
+ self.name, False,
57
+ "feishu: 'lark-cli' not found. Install: npx @larksuite/cli@latest install",
58
+ )
59
+
60
+ path = Path(artifact.path)
61
+ if not path.exists():
62
+ return DeliveryResult(self.name, False, f"feishu: artifact not found: {artifact.path}")
63
+
64
+ title = artifact.title or path.stem
65
+
66
+ channel = target.channel or "chat"
67
+
68
+ if channel == "chat":
69
+ return self._deliver_chat(path, title, target)
70
+ elif channel == "doc":
71
+ return self._deliver_doc(path, title, target)
72
+ elif channel == "drive":
73
+ return self._deliver_drive(path, title, target)
74
+ else:
75
+ return DeliveryResult(
76
+ self.name, False,
77
+ f"feishu: unknown channel '{channel}'. Supported: chat, doc, drive",
78
+ )
79
+
80
+ def _check_auth(self) -> str | None:
81
+ """Check lark-cli auth status. Returns error message or None if OK."""
82
+ try:
83
+ result = subprocess.run(
84
+ ["lark-cli", "auth", "status"],
85
+ capture_output=True, text=True, timeout=10,
86
+ )
87
+ if result.returncode != 0:
88
+ return "feishu: not authenticated. Run: lark-cli auth login --recommend"
89
+ return None
90
+ except (FileNotFoundError, subprocess.TimeoutExpired) as e:
91
+ return f"feishu: unable to check auth: {e}"
92
+
93
+ def _run_lark_cli(self, args: list[str]) -> subprocess.CompletedProcess | None:
94
+ """Run lark-cli and return result.
95
+
96
+ Disables proxy (LARK_CLI_NO_PROXY=1) so lark-cli does not route
97
+ credentials through HTTPS_PROXY. Also resolves any file path
98
+ arguments to absolute paths to avoid lark-cli's cwd-traversal guard.
99
+ """
100
+ env = {**os.environ, "LARK_CLI_NO_PROXY": "1"}
101
+ # Resolve --file paths to absolute to avoid lark-cli rejecting ".."
102
+ resolved_args: list[str] = []
103
+ skip_next = False
104
+ for i, arg in enumerate(args):
105
+ if skip_next:
106
+ resolved_args.append(arg)
107
+ skip_next = False
108
+ continue
109
+ if arg == "--file" and i + 1 < len(args):
110
+ resolved_args.append("--file")
111
+ resolved_args.append(str(Path(args[i + 1]).resolve()))
112
+ skip_next = True
113
+ else:
114
+ resolved_args.append(arg)
115
+
116
+ try:
117
+ return subprocess.run(
118
+ ["lark-cli"] + resolved_args,
119
+ capture_output=True, text=True, timeout=60, env=env,
120
+ )
121
+ except (FileNotFoundError, subprocess.TimeoutExpired):
122
+ return None
123
+
124
+ def _deliver_chat(self, path: Path, title: str, target: DeliveryTarget) -> DeliveryResult:
125
+ """Send brief content as IM message."""
126
+ auth_err = self._check_auth()
127
+ if auth_err:
128
+ return DeliveryResult(self.name, False, auth_err)
129
+
130
+ chat_id = target.recipient or target.metadata.get("chat_id", "")
131
+ if not chat_id:
132
+ return DeliveryResult(self.name, False, "feishu (chat): no chat_id provided")
133
+
134
+ # Read file content (truncate for IM limits)
135
+ try:
136
+ content = path.read_text(encoding="utf-8")[:IM_MAX_LENGTH]
137
+ except Exception as e:
138
+ return DeliveryResult(self.name, False, f"feishu (chat): failed to read artifact: {e}")
139
+
140
+ result = self._run_lark_cli([
141
+ "im", "+messages-send",
142
+ "--chat-id", chat_id,
143
+ "--as", "bot",
144
+ "--text", content,
145
+ ])
146
+ if result is None or result.returncode != 0:
147
+ stderr = (result.stderr or "").strip() if result else "command failed"
148
+ return DeliveryResult(self.name, False, f"feishu (chat): {stderr[:200]}")
149
+
150
+ return DeliveryResult(self.name, True, f"Sent to chat {chat_id}")
151
+
152
+ def _deliver_doc(self, path: Path, title: str, target: DeliveryTarget) -> DeliveryResult:
153
+ """Create a Feishu document from brief content."""
154
+ auth_err = self._check_auth()
155
+ if auth_err:
156
+ return DeliveryResult(self.name, False, auth_err)
157
+
158
+ try:
159
+ content = path.read_text(encoding="utf-8")
160
+ except Exception as e:
161
+ return DeliveryResult(self.name, False, f"feishu (doc): failed to read artifact: {e}")
162
+
163
+ # Build doc content with title
164
+ doc_content = f"<title>{title}</title>\n{content}"
165
+
166
+ result = self._run_lark_cli([
167
+ "docs", "+create",
168
+ "--api-version", "v2",
169
+ "--doc-format", "markdown",
170
+ "--content", doc_content,
171
+ ])
172
+ if result is None or result.returncode != 0:
173
+ stderr = (result.stderr or "").strip()[:200] if result else "command failed"
174
+ return DeliveryResult(self.name, False, f"feishu (doc): {stderr}")
175
+
176
+ # Try to extract the doc URL from output
177
+ try:
178
+ output = json.loads(result.stdout) if result.stdout else {}
179
+ doc_url = output.get("url", output.get("data", {}).get("url", ""))
180
+ except (json.JSONDecodeError, AttributeError):
181
+ doc_url = ""
182
+
183
+ msg = f"Doc created: {title}"
184
+ if doc_url:
185
+ msg += f" ({doc_url})"
186
+ return DeliveryResult(self.name, True, msg, metadata={"url": doc_url})
187
+
188
+ def _deliver_drive(self, path: Path, title: str, target: DeliveryTarget) -> DeliveryResult:
189
+ """Upload brief file to Feishu Drive."""
190
+ auth_err = self._check_auth()
191
+ if auth_err:
192
+ return DeliveryResult(self.name, False, auth_err)
193
+
194
+ folder_token = target.recipient or ""
195
+ args = ["drive", "+upload", "--file", str(path)]
196
+ if folder_token:
197
+ args.extend(["--folder-token", folder_token])
198
+
199
+ result = self._run_lark_cli(args)
200
+ if result is None or result.returncode != 0:
201
+ stderr = (result.stderr or "").strip()[:200] if result else "command failed"
202
+ return DeliveryResult(self.name, False, f"feishu (drive): {stderr}")
203
+
204
+ return DeliveryResult(self.name, True, f"Uploaded {path.name} to Drive")
@@ -0,0 +1,237 @@
1
+ """Gmail delivery connector using the optional gws CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import shutil
8
+ import subprocess
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ from multi_agent_brief.delivery.base import DeliveryArtifact, DeliveryConnector, DeliveryResult, DeliveryTarget
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class _GwsTimeout:
18
+ args: tuple[str, ...]
19
+
20
+
21
+ class GwsGmailDeliveryConnector(DeliveryConnector):
22
+ """Create Gmail drafts or send Gmail messages through the optional gws CLI."""
23
+
24
+ name = "gmail"
25
+
26
+ def deliver(self, artifact: DeliveryArtifact, target: DeliveryTarget) -> DeliveryResult:
27
+ if target.channel not in {"draft", "send"}:
28
+ return DeliveryResult(self.name, False, "gmail: supported channels are 'draft' and 'send'")
29
+ if not target.recipient:
30
+ return DeliveryResult(self.name, False, "gmail: --recipient is required")
31
+
32
+ path = Path(artifact.path)
33
+ if not path.exists():
34
+ return DeliveryResult(self.name, False, f"gmail: artifact not found: {artifact.path}")
35
+
36
+ if not shutil.which("gws"):
37
+ return DeliveryResult(
38
+ self.name,
39
+ False,
40
+ "gmail: 'gws' not found. Install googleworkspace/cli and run gws auth setup/login.",
41
+ )
42
+
43
+ auth_error = self._check_auth()
44
+ if auth_error:
45
+ return DeliveryResult(self.name, False, auth_error)
46
+
47
+ subject = _metadata_text(target.metadata.get("subject")) or artifact.title or path.stem
48
+ body = _metadata_text(target.metadata.get("body")) or "Please review the attached BriefLoop delivery."
49
+ attachments = _metadata_list(target.metadata.get("attachments")) or [str(path)]
50
+ attachment_paths = [Path(attachment).expanduser().resolve() for attachment in attachments]
51
+ if any(not attachment.exists() for attachment in attachment_paths):
52
+ return DeliveryResult(self.name, False, "gmail: attachment not found")
53
+ attachment_cwd = Path(os.path.commonpath([str(attachment.parent) for attachment in attachment_paths]))
54
+
55
+ args = [
56
+ "gmail",
57
+ "+send",
58
+ "--to",
59
+ target.recipient,
60
+ "--subject",
61
+ subject,
62
+ "--body",
63
+ body,
64
+ ]
65
+ if target.channel == "draft":
66
+ args.append("--draft")
67
+ for attachment in attachment_paths:
68
+ args.extend(["--attach", attachment.relative_to(attachment_cwd).as_posix()])
69
+
70
+ result = self._run_gws(args, cwd=attachment_cwd)
71
+ if isinstance(result, _GwsTimeout):
72
+ action = "draft creation" if target.channel == "draft" else "send"
73
+ inspect_target = "Gmail Drafts" if target.channel == "draft" else "Gmail Sent Mail"
74
+ return DeliveryResult(
75
+ self.name,
76
+ False,
77
+ f"gmail: gws {action} timed out after the Gmail request may have been accepted. "
78
+ f"Inspect {inspect_target} before retrying; do not retry blindly.",
79
+ metadata={
80
+ "outcome_unknown": True,
81
+ "timeout": True,
82
+ "inspect_target": inspect_target,
83
+ },
84
+ )
85
+ if result is None:
86
+ return DeliveryResult(self.name, False, "gmail: gws command failed")
87
+ if result.returncode != 0:
88
+ action = "draft creation" if target.channel == "draft" else "send"
89
+ return DeliveryResult(
90
+ self.name,
91
+ False,
92
+ f"gmail: gws {action} failed. Check gws auth, Gmail permissions, recipient, and attachment access.",
93
+ )
94
+
95
+ if target.channel == "draft":
96
+ metadata = _draft_metadata(result.stdout)
97
+ if not metadata.get("draft_id_present"):
98
+ return DeliveryResult(
99
+ self.name,
100
+ False,
101
+ "gmail: gws did not confirm Gmail draft creation. "
102
+ "Inspect Gmail Drafts before retrying; do not retry blindly.",
103
+ )
104
+ return DeliveryResult(
105
+ self.name,
106
+ True,
107
+ "Gmail draft created",
108
+ metadata=metadata,
109
+ )
110
+
111
+ metadata = _message_metadata(result.stdout)
112
+ if not metadata.get("sent_message_present"):
113
+ return DeliveryResult(
114
+ self.name,
115
+ False,
116
+ "gmail: gws did not confirm Gmail send. "
117
+ "Inspect Gmail Sent Mail before retrying; do not retry blindly.",
118
+ )
119
+ return DeliveryResult(
120
+ self.name,
121
+ True,
122
+ "Gmail message sent",
123
+ metadata=metadata,
124
+ )
125
+
126
+ def _check_auth(self) -> str | None:
127
+ result = self._run_gws(["auth", "status"], timeout=10)
128
+ if result is None or isinstance(result, _GwsTimeout):
129
+ return "gmail: unable to check gws auth. Run: gws auth setup; gws auth login"
130
+ if result.returncode != 0:
131
+ if _has_external_auth_signal():
132
+ return None
133
+ return "gmail: gws is not authenticated. Run: gws auth setup; gws auth login"
134
+ payload = _json_object_from_output(result.stdout)
135
+ if payload is None:
136
+ return None
137
+ if isinstance(payload, dict) and payload.get("auth_method") == "none" and not _has_external_auth_signal():
138
+ return "gmail: gws is not authenticated. Run: gws auth setup; gws auth login"
139
+ return None
140
+
141
+ def _run_gws(
142
+ self,
143
+ args: list[str],
144
+ *,
145
+ timeout: int = 60,
146
+ cwd: Path | None = None,
147
+ ) -> subprocess.CompletedProcess[str] | _GwsTimeout | None:
148
+ env = {**os.environ}
149
+ try:
150
+ return subprocess.run(
151
+ ["gws", *args],
152
+ capture_output=True,
153
+ text=True,
154
+ timeout=timeout,
155
+ cwd=str(cwd) if cwd is not None else None,
156
+ env=env,
157
+ )
158
+ except FileNotFoundError:
159
+ return None
160
+ except subprocess.TimeoutExpired:
161
+ return _GwsTimeout(tuple(args))
162
+
163
+
164
+ def _metadata_text(value: Any) -> str:
165
+ return value.strip() if isinstance(value, str) and value.strip() else ""
166
+
167
+
168
+ def _metadata_list(value: Any) -> list[str]:
169
+ if not isinstance(value, list):
170
+ return []
171
+ return [str(item) for item in value if str(item).strip()]
172
+
173
+
174
+ def _has_external_auth_signal() -> bool:
175
+ if any(
176
+ os.environ.get(name)
177
+ for name in (
178
+ "GOOGLE_WORKSPACE_CLI_TOKEN",
179
+ "GWS_TOKEN",
180
+ "GOOGLE_APPLICATION_CREDENTIALS",
181
+ )
182
+ ):
183
+ return True
184
+ return any(path.exists() for path in _well_known_adc_paths())
185
+
186
+
187
+ def _well_known_adc_paths() -> list[Path]:
188
+ paths: list[Path] = []
189
+ cloudsdk_config = os.environ.get("CLOUDSDK_CONFIG")
190
+ if cloudsdk_config:
191
+ paths.append(Path(cloudsdk_config).expanduser() / "application_default_credentials.json")
192
+ paths.append(Path.home() / ".config" / "gcloud" / "application_default_credentials.json")
193
+ appdata = os.environ.get("APPDATA")
194
+ if appdata:
195
+ paths.append(Path(appdata).expanduser() / "gcloud" / "application_default_credentials.json")
196
+ return paths
197
+
198
+
199
+ def _json_object_from_output(stdout: str) -> dict[str, Any] | None:
200
+ text = stdout.strip()
201
+ if not text:
202
+ return {}
203
+ try:
204
+ payload = json.loads(text)
205
+ except json.JSONDecodeError:
206
+ start = text.find("{")
207
+ end = text.rfind("}")
208
+ if start < 0 or end < start:
209
+ return None
210
+ try:
211
+ payload = json.loads(text[start : end + 1])
212
+ except json.JSONDecodeError:
213
+ return None
214
+ return payload if isinstance(payload, dict) else None
215
+
216
+
217
+ def _draft_metadata(stdout: str) -> dict[str, Any]:
218
+ payload = _json_object_from_output(stdout)
219
+ if payload is None:
220
+ return {}
221
+ data = payload.get("data") if isinstance(payload.get("data"), dict) else {}
222
+ draft_id = payload.get("id") or payload.get("draft_id") or data.get("id")
223
+ if isinstance(draft_id, str) and draft_id.strip():
224
+ return {"draft_id_present": True}
225
+ return {}
226
+
227
+
228
+ def _message_metadata(stdout: str) -> dict[str, Any]:
229
+ payload = _json_object_from_output(stdout)
230
+ if payload is None:
231
+ return {}
232
+ data = payload.get("data") if isinstance(payload.get("data"), dict) else {}
233
+ message = payload.get("message") if isinstance(payload.get("message"), dict) else {}
234
+ sent_ref = payload.get("message" + "_id") or payload.get("id") or message.get("id") or data.get("id")
235
+ if isinstance(sent_ref, str) and sent_ref.strip():
236
+ return {"sent_message_present": True}
237
+ return {}
@@ -0,0 +1,15 @@
1
+ """Public-safe evaluation case runner for MABW control surfaces."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from multi_agent_brief.evaluation_cases.runner import (
6
+ list_evaluation_cases,
7
+ run_evaluation_cases,
8
+ validate_evaluation_cases,
9
+ )
10
+
11
+ __all__ = [
12
+ "list_evaluation_cases",
13
+ "run_evaluation_cases",
14
+ "validate_evaluation_cases",
15
+ ]