arkaos 5.17.2 → 5.18.0

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 (130) hide show
  1. package/THE-ARKAOS-GUIDE.md +2 -2
  2. package/VERSION +1 -1
  3. package/arka/SKILL.md +2 -1
  4. package/arka/skills/costs/SKILL.md +16 -0
  5. package/config/claude-agents/marta-cqo.md +10 -0
  6. package/config/decisions/corpora/bash-effect.jsonl +38 -0
  7. package/config/decisions/corpora/creation-intent.jsonl +47 -0
  8. package/config/decisions/corpora/dispatch-role.jsonl +32 -0
  9. package/config/decisions/corpora/forge-complexity.jsonl +102 -0
  10. package/config/decisions/corpora/forge-departments.jsonl +34 -0
  11. package/config/decisions/corpora/heldout/learning-signal.jsonl +17 -0
  12. package/config/decisions/corpora/heldout/qg-prescreen.jsonl +20 -0
  13. package/config/decisions/corpora/heldout/slop-score.jsonl +20 -0
  14. package/config/decisions/corpora/learning-signal.jsonl +35 -0
  15. package/config/decisions/corpora/phantom-action.jsonl +34 -0
  16. package/config/decisions/corpora/qg-prescreen.jsonl +46 -0
  17. package/config/decisions/corpora/refine.jsonl +104 -0
  18. package/config/decisions/corpora/route.jsonl +33 -0
  19. package/config/decisions/corpora/skill-hints.jsonl +34 -0
  20. package/config/decisions/corpora/skill-proposer.jsonl +32 -0
  21. package/config/decisions/corpora/slop-score.jsonl +33 -0
  22. package/config/decisions/corpora/subagent-discipline.jsonl +32 -0
  23. package/config/decisions/corpora/sycophancy.jsonl +34 -0
  24. package/config/decisions/corpora/topic-drift.jsonl +34 -0
  25. package/config/decisions/corpora/ui-in-ts.jsonl +60 -0
  26. package/config/hooks/gate-manifest.json +1 -0
  27. package/config/models.yaml +10 -2
  28. package/core/decisions/__init__.py +8 -0
  29. package/core/decisions/backoff.py +137 -0
  30. package/core/decisions/cache.py +92 -0
  31. package/core/decisions/client.py +191 -0
  32. package/core/decisions/config.py +159 -0
  33. package/core/decisions/cost_effect.py +242 -0
  34. package/core/decisions/engine.py +400 -0
  35. package/core/decisions/models.py +118 -0
  36. package/core/decisions/paths.py +39 -0
  37. package/core/decisions/privacy.py +378 -0
  38. package/core/decisions/promotion.py +126 -0
  39. package/core/decisions/registry.py +23 -0
  40. package/core/decisions/replay.py +852 -0
  41. package/core/decisions/replay_report.py +413 -0
  42. package/core/decisions/shadow.py +147 -0
  43. package/core/decisions/site.py +343 -0
  44. package/core/decisions/sites/__init__.py +1 -0
  45. package/core/decisions/sites/command.py +58 -0
  46. package/core/decisions/sites/dispatch.py +161 -0
  47. package/core/decisions/sites/forge.py +230 -0
  48. package/core/decisions/sites/governance.py +361 -0
  49. package/core/decisions/sites/prompt.py +195 -0
  50. package/core/decisions/sites/quality.py +281 -0
  51. package/core/decisions/telemetry.py +335 -0
  52. package/core/decisions/telemetry_cli.py +149 -0
  53. package/core/decisions/transport.py +97 -0
  54. package/core/egress/audit.py +46 -6
  55. package/core/egress/credentials.py +1296 -0
  56. package/core/egress/policy.py +7 -4
  57. package/core/evals/verdict_labels.py +25 -5
  58. package/core/forge/budget.py +32 -0
  59. package/core/forge/complexity.py +11 -3
  60. package/core/forge/jev_sites.py +80 -0
  61. package/core/forge/orchestrator.py +76 -72
  62. package/core/governance/aggregate_guard.py +175 -77
  63. package/core/governance/carry_advisor.py +36 -19
  64. package/core/governance/evidence_checks.py +134 -55
  65. package/core/governance/fence_recovery.py +165 -0
  66. package/core/governance/harness_scanner.py +4 -0
  67. package/core/governance/jev_advisory.py +154 -0
  68. package/core/governance/leak_scanner.py +2 -2
  69. package/core/governance/literal_git.py +66 -0
  70. package/core/governance/qg_prescreen.py +518 -0
  71. package/core/governance/qg_tier.py +17 -0
  72. package/core/governance/reviewer_ledger.py +225 -46
  73. package/core/governance/skill_proposer.py +38 -13
  74. package/core/governance/slop_check.py +169 -0
  75. package/core/hooks/operator_message.py +128 -0
  76. package/core/hooks/stop.py +418 -38
  77. package/core/hooks/stop_budget.py +43 -0
  78. package/core/hooks/subagent_stop.py +39 -9
  79. package/core/hooks/ups_dispatch.py +243 -0
  80. package/core/hooks/user_prompt_submit.py +458 -106
  81. package/core/keys.py +4 -0
  82. package/core/runtime/model_router.py +38 -0
  83. package/core/runtime/pricing.py +8 -0
  84. package/core/synapse/command_menu.py +70 -0
  85. package/core/synapse/layers.py +108 -66
  86. package/core/workflow/flow_enforcer.py +71 -1
  87. package/core/workflow/frontend_gate.py +57 -1
  88. package/core/workflow/transcript_scope.py +34 -25
  89. package/departments/quality/SKILL.md +29 -2
  90. package/harness/codex/AGENTS.md +1 -1
  91. package/harness/copilot/copilot-instructions.md +1 -1
  92. package/harness/cursor/rules/arkaos.mdc +2 -2
  93. package/harness/gemini/GEMINI.md +1 -1
  94. package/harness/opencode/AGENTS.md +1 -1
  95. package/harness/opencode/agents/arka-architect-gabriel.md +1 -1
  96. package/harness/opencode/agents/arka-brand-director-valentina.md +1 -1
  97. package/harness/opencode/agents/arka-cfo-helena.md +1 -1
  98. package/harness/opencode/agents/arka-chief-of-staff-afonso.md +1 -1
  99. package/harness/opencode/agents/arka-community-strategist-beatriz.md +1 -1
  100. package/harness/opencode/agents/arka-content-strategist-rafael.md +1 -1
  101. package/harness/opencode/agents/arka-conversion-strategist-ines.md +1 -1
  102. package/harness/opencode/agents/arka-coo-sofia.md +1 -1
  103. package/harness/opencode/agents/arka-copy-director-eduardo.md +1 -1
  104. package/harness/opencode/agents/arka-cqo-marta.md +1 -1
  105. package/harness/opencode/agents/arka-cto-marco.md +1 -1
  106. package/harness/opencode/agents/arka-design-ops-lead-iris.md +1 -1
  107. package/harness/opencode/agents/arka-ecom-director-ricardo.md +1 -1
  108. package/harness/opencode/agents/arka-knowledge-director-clara.md +1 -1
  109. package/harness/opencode/agents/arka-leadership-director-rodrigo.md +1 -1
  110. package/harness/opencode/agents/arka-marketing-director-luna.md +1 -1
  111. package/harness/opencode/agents/arka-ops-lead-daniel.md +1 -1
  112. package/harness/opencode/agents/arka-pm-director-carolina.md +1 -1
  113. package/harness/opencode/agents/arka-revops-lead-vicente.md +1 -1
  114. package/harness/opencode/agents/arka-saas-strategist-tiago.md +1 -1
  115. package/harness/opencode/agents/arka-sales-director-miguel.md +1 -1
  116. package/harness/opencode/agents/arka-strategy-director-tomas.md +1 -1
  117. package/harness/opencode/agents/arka-tech-director-francisca.md +1 -1
  118. package/harness/opencode/agents/arka-tech-lead-paulo.md +1 -1
  119. package/harness/opencode/agents/arka-video-producer-simao.md +1 -1
  120. package/harness/zed/.rules +1 -1
  121. package/installer/config-seed.js +71 -1
  122. package/installer/keys.js +1 -1
  123. package/installer/update.js +29 -0
  124. package/knowledge/commands-registry.json +17 -4
  125. package/knowledge/commands-registry.json.bak +6739 -0
  126. package/knowledge/skills-manifest.json +1 -1
  127. package/package.json +1 -1
  128. package/pyproject.toml +1 -1
  129. package/scripts/synapse-bridge.py +17 -1
  130. package/scripts/tools/corpus_merge.py +402 -0
@@ -0,0 +1,159 @@
1
+ """Decisions config — the ``decisions`` block of ``~/.arkaos/config.json``.
2
+
3
+ The installer seed (``installer/config-seed.js``) writes three keys::
4
+
5
+ "decisions": {"enabled": true, "transport": "openrouter",
6
+ "redactClients": true}
7
+
8
+ Everything else has its default in code (``hookTimeoutMs``,
9
+ ``cacheTtlSeconds``, ``thresholds`` here; each site's mode in
10
+ ``Site.default_mode``). A key the operator writes overrides that
11
+ default and is never rewritten, for example::
12
+
13
+ "sites": {"topic-drift": "shadow",
14
+ "route": {"mode": "act", "minConfidence": 0.8}}
15
+
16
+ Loading never raises: a missing, oversized, corrupt or invalid file
17
+ yields the defaults. ``ARKA_BYPASS_DECISIONS=1`` turns every site off.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import json
23
+ import os
24
+ from pathlib import Path
25
+ from typing import TYPE_CHECKING, Any, Literal
26
+
27
+ from pydantic import BaseModel, ConfigDict, Field, ValidationError, field_validator
28
+
29
+ if TYPE_CHECKING:
30
+ from core.decisions.site import Site
31
+
32
+ Mode = Literal["off", "shadow", "act"]
33
+ MODES: frozenset[str] = frozenset({"off", "shadow", "act"})
34
+
35
+ # Default action thresholds by site risk. Read at decision time (never
36
+ # copied into a config instance) so a config override and this table
37
+ # stay the only two sources — pinned by test_write_threshold_is_load_bearing.
38
+ THRESHOLDS: dict[str, float] = {"read": 0.60, "write": 0.75, "destructive": 0.90}
39
+
40
+ CONFIG_PATH: Path = Path.home() / ".arkaos" / "config.json"
41
+ MAX_CONFIG_BYTES = 1_000_000
42
+ BYPASS_ENV = "ARKA_BYPASS_DECISIONS"
43
+
44
+
45
+ class SiteConfig(BaseModel):
46
+ """Per-site override: mode, action threshold, call ceiling."""
47
+
48
+ model_config = ConfigDict(populate_by_name=True, extra="ignore")
49
+
50
+ # None = "the site's own ``default_mode``" (PR5 D3): an override that
51
+ # only tunes ``timeoutMs`` or ``minConfidence`` must not flip a shadow
52
+ # site to act, as the old ``"act"`` default did.
53
+ mode: Mode | None = None
54
+ min_confidence: float | None = Field(default=None, alias="minConfidence", ge=0.0, le=1.0)
55
+ timeout_ms: int | None = Field(default=None, alias="timeoutMs", gt=0)
56
+
57
+ @field_validator("mode", mode="before")
58
+ @classmethod
59
+ def _unknown_mode_is_off(cls, value: object) -> object:
60
+ if value is None:
61
+ return None
62
+ return value if isinstance(value, str) and value in MODES else "off"
63
+
64
+
65
+ class DecisionsConfig(BaseModel):
66
+ """The whole ``decisions`` block with safe defaults."""
67
+
68
+ model_config = ConfigDict(populate_by_name=True, extra="ignore")
69
+
70
+ enabled: bool = True
71
+ transport: str = "openrouter"
72
+ redact_clients: bool = Field(default=True, alias="redactClients")
73
+ hook_timeout_ms: int = Field(default=1500, alias="hookTimeoutMs", gt=0)
74
+ cache_ttl_seconds: int = Field(default=86400, alias="cacheTtlSeconds", ge=0)
75
+ thresholds: dict[str, float] = Field(default_factory=dict)
76
+ sites: dict[str, SiteConfig] = Field(default_factory=dict)
77
+
78
+ @field_validator("sites", mode="before")
79
+ @classmethod
80
+ def _coerce_sites(cls, value: object) -> dict[str, Any]:
81
+ if not isinstance(value, dict):
82
+ return {}
83
+ out: dict[str, Any] = {}
84
+ for name, raw in value.items():
85
+ if isinstance(raw, str):
86
+ out[str(name)] = {"mode": raw}
87
+ elif isinstance(raw, dict):
88
+ out[str(name)] = raw
89
+ else:
90
+ out[str(name)] = {"mode": "off"}
91
+ return out
92
+
93
+
94
+ def load_decisions_config(path: Path | None = None) -> DecisionsConfig:
95
+ """Read the ``decisions`` block; any failure returns the defaults."""
96
+ target = path or CONFIG_PATH
97
+ try:
98
+ if target.stat().st_size > MAX_CONFIG_BYTES:
99
+ return DecisionsConfig()
100
+ data = json.loads(target.read_text(encoding="utf-8"))
101
+ except (OSError, ValueError):
102
+ return DecisionsConfig()
103
+ block = data.get("decisions") if isinstance(data, dict) else None
104
+ if not isinstance(block, dict):
105
+ return DecisionsConfig()
106
+ try:
107
+ return DecisionsConfig.model_validate(block)
108
+ except ValidationError:
109
+ return DecisionsConfig()
110
+
111
+
112
+ def bypassed() -> bool:
113
+ """Global kill-switch for diagnosis: ``ARKA_BYPASS_DECISIONS=1``."""
114
+ return os.environ.get(BYPASS_ENV, "").strip() == "1"
115
+
116
+
117
+ def configured_mode(cfg: DecisionsConfig, site: Site) -> Mode:
118
+ """The operator's ``mode`` for ``site`` when set, else ``site.default_mode``.
119
+
120
+ Ignores the kill-switch and ``enabled``: this is what the config says,
121
+ for reports; :func:`site_mode` is what a call site runs.
122
+ """
123
+ override = cfg.sites.get(site.name)
124
+ if override is None or override.mode is None:
125
+ return site.default_mode
126
+ return override.mode
127
+
128
+
129
+ def site_mode(cfg: DecisionsConfig, site: Site) -> Mode:
130
+ """Effective mode: bypass/disabled → off, else config, else site default."""
131
+ if bypassed() or not cfg.enabled:
132
+ return "off"
133
+ return configured_mode(cfg, site)
134
+
135
+
136
+ def threshold_for(cfg: DecisionsConfig, site: Site) -> float:
137
+ """Operator ``minConfidence`` > ``Site.min_confidence`` > config table > ``THRESHOLDS``.
138
+
139
+ A site's own floor (spec PR5 D3; route = 0.70) outranks the risk table,
140
+ including an operator ``thresholds`` override, because the table speaks
141
+ for a risk class and the site value for one decision point. Only the
142
+ per-site ``sites.<name>.minConfidence`` override beats it.
143
+ """
144
+ override = cfg.sites.get(site.name)
145
+ if override is not None and override.min_confidence is not None:
146
+ return override.min_confidence
147
+ if site.min_confidence is not None:
148
+ return site.min_confidence
149
+ if site.risk in cfg.thresholds:
150
+ return cfg.thresholds[site.risk]
151
+ return THRESHOLDS[site.risk]
152
+
153
+
154
+ def site_timeout_ms(cfg: DecisionsConfig, site: Site) -> int:
155
+ """Site ``timeoutMs`` override, else the site's own ceiling."""
156
+ override = cfg.sites.get(site.name)
157
+ if override is not None and override.timeout_ms is not None:
158
+ return override.timeout_ms
159
+ return site.timeout_ms
@@ -0,0 +1,242 @@
1
+ """Decisions cost and effect: what Jev costs, what it changed, what it could save.
2
+
3
+ Read-only view for ``/arka decisions`` and ``/arka status`` (spec PR5, D5).
4
+
5
+ * **Cost** is measured: the LLM cost ledger split into production
6
+ (``category=decision``) and replay-harness runs (``decision-replay``),
7
+ so evidence runs never read as production spend.
8
+ * **Effect** is counted per site from ``decisions.jsonl``.
9
+ * **Saving** is never measured today. Four sites carry a counterfactual
10
+ (plan numbering): #7 forge-complexity (runs in shadow), #10
11
+ dispatch-role (escalate-only, so it never saves by construction), #11
12
+ qg-prescreen (advisory, never removes a reviewer) and #22
13
+ subagent-discipline (advisory, no ledger row links to a turn). Only #11
14
+ has a dollar figure, and it is a CEILING labelled ``counterfactual``:
15
+ it is never added to the cost and never called a saving.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from dataclasses import dataclass, field
21
+ from datetime import datetime
22
+ from pathlib import Path
23
+ from typing import Any
24
+
25
+ from core.decisions.paths import telemetry_path
26
+ from core.decisions.telemetry import (
27
+ COST_CATEGORY,
28
+ REPLAY_COST_CATEGORY,
29
+ REPLAY_SESSION_ID,
30
+ VALID_PERIODS,
31
+ _period_cutoff,
32
+ _read_jsonl,
33
+ )
34
+
35
+ COUNTERFACTUAL = "counterfactual (ceiling)"
36
+ NO_SAVING = "—"
37
+ NO_MEASURED_SAVING = (
38
+ "No measured saving is claimable today: forge-complexity and qg-prescreen "
39
+ "do not change what runs, dispatch-role only escalates, and no ledger row "
40
+ "links a subagent to the turn that suggested it. Cost is measured; the "
41
+ "rows below are effect counts and counterfactuals."
42
+ )
43
+ REVIEWER_CATEGORIES = frozenset({"subagent:eduardo-copy", "subagent:francisca-tech"})
44
+ _TIER_ORDER = {"SHALLOW": 0, "STANDARD": 1, "DEEP": 2}
45
+
46
+ Rows = list[dict[str, Any]]
47
+
48
+
49
+ @dataclass(frozen=True)
50
+ class LedgerSplit:
51
+ """Ledger cost of Jev calls: production vs replay runs."""
52
+
53
+ production_usd: float = 0.0
54
+ production_calls: int = 0
55
+ replay_usd: float = 0.0
56
+ replay_calls: int = 0
57
+
58
+
59
+ @dataclass(frozen=True)
60
+ class Counterfactual:
61
+ """One cost site's effect count and saving status."""
62
+
63
+ number: int
64
+ site: str
65
+ effect: str
66
+ count: int
67
+ detail: dict[str, int] = field(default_factory=dict)
68
+ saving: str = NO_SAVING
69
+ ceiling_usd: float | None = None
70
+ not_measurable: str = ""
71
+
72
+
73
+ @dataclass(frozen=True)
74
+ class CostEffect:
75
+ """The whole cost-and-effect view of one period."""
76
+
77
+ period: str
78
+ ledger: LedgerSplit
79
+ counterfactuals: list[Counterfactual]
80
+ has_data: bool
81
+
82
+
83
+ @dataclass(frozen=True)
84
+ class Sources:
85
+ """Where each input lives (tests point these at tmp files)."""
86
+
87
+ decisions: Path
88
+ ledger: Path
89
+ verdicts: Path
90
+ activations: Path
91
+
92
+
93
+ def default_sources() -> Sources:
94
+ """The live paths, each honouring its own env override."""
95
+ from core.evals.verdict_labels import _labels_path
96
+ from core.governance import activation_tracker
97
+ from core.runtime.llm_cost_telemetry import _telemetry_path
98
+
99
+ return Sources(telemetry_path(), _telemetry_path(), _labels_path(),
100
+ activation_tracker.TELEMETRY_PATH)
101
+
102
+
103
+ def is_replay_row(row: dict[str, Any]) -> bool:
104
+ """A replay row: ``decision-replay``, or an older ``decision`` row of session "replay"."""
105
+ category = row.get("category")
106
+ return category == REPLAY_COST_CATEGORY or (
107
+ category == COST_CATEGORY and row.get("session_id") == REPLAY_SESSION_ID)
108
+
109
+
110
+ def ledger_split(ledger: Rows) -> LedgerSplit:
111
+ """Sum the ledger's decision rows: production vs replay runs."""
112
+ rep = [r for r in ledger if is_replay_row(r)]
113
+ prod = [r for r in ledger if r.get("category") == COST_CATEGORY and not is_replay_row(r)]
114
+ return LedgerSplit(_usd(prod), len(prod), _usd(rep), len(rep))
115
+
116
+
117
+ def _usd(rows: Rows) -> float:
118
+ return round(sum(float(r.get("estimated_cost_usd") or 0.0) for r in rows), 8)
119
+
120
+
121
+ def _site_rows(decisions: Rows, site: str) -> Rows:
122
+ return [r for r in decisions if r.get("site") == site]
123
+
124
+
125
+ def _tier(dims: object) -> str | None:
126
+ """The Forge tier of a dimensions dict; None unless it has exactly the five dimensions."""
127
+ from core.decisions.sites.forge import DIMENSIONS
128
+
129
+ if not isinstance(dims, dict) or set(dims) != set(DIMENSIONS):
130
+ return None
131
+ from core.forge.complexity import calculate_weighted_score, determine_tier
132
+ from core.forge.schema import ComplexityDimensions
133
+
134
+ try:
135
+ return determine_tier(calculate_weighted_score(ComplexityDimensions(**dims))).value.upper()
136
+ except (TypeError, ValueError):
137
+ return None
138
+
139
+
140
+ def forge_complexity(decisions: Rows) -> Counterfactual:
141
+ """#7: rows where Jev's dimensions give another Forge tier than the heuristic's."""
142
+ up = down = 0
143
+ for row in _site_rows(decisions, "forge-complexity"):
144
+ h, j = _tier(row.get("heuristic_result")), _tier(row.get("jev_result"))
145
+ if h is None or j is None or h == j:
146
+ continue
147
+ if _TIER_ORDER[j] > _TIER_ORDER[h]:
148
+ up += 1
149
+ else:
150
+ down += 1
151
+ return Counterfactual(
152
+ 7, "forge-complexity", "counterfactual: tier would differ", up + down,
153
+ {"up": up, "down": down},
154
+ not_measurable="cost of a tier: the ledger has no Forge category")
155
+
156
+
157
+ def dispatch_role(decisions: Rows) -> Counterfactual:
158
+ """#10: escalations Jev acted on; downgrades the rule blocked."""
159
+ rows = _site_rows(decisions, "dispatch-role")
160
+ escalated = sum(1 for r in rows if r.get("acted_on") == "jev"
161
+ and r.get("jev_result") != r.get("heuristic_result"))
162
+ blocked = sum(1 for r in rows if r.get("reason") == "downgrade-blocked")
163
+ return Counterfactual(
164
+ 10, "dispatch-role", "escalations acted on", escalated,
165
+ {"downgrade_blocked": blocked},
166
+ saving=f"{NO_SAVING} escalate-only: never saves by construction",
167
+ not_measurable="whether the orchestrator followed the marker (Task model has no hook)")
168
+
169
+
170
+ def _prescreen_rounds(verdicts: Rows) -> list[tuple[str, str]]:
171
+ """``(predicted, final)`` per QG round that carried a prescreen, lower case."""
172
+ out: list[tuple[str, str]] = []
173
+ for row in verdicts:
174
+ pre = row.get("prescreen")
175
+ if isinstance(pre, dict) and pre.get("verdict") in ("approved", "rejected"):
176
+ out.append((str(pre["verdict"]), str(row.get("verdict") or "").lower()))
177
+ return out
178
+
179
+
180
+ def _rejected_sessions(verdicts: Rows) -> set[str]:
181
+ return {str(r.get("session_id") or "") for r in verdicts
182
+ if isinstance(r.get("prescreen"), dict)
183
+ and r["prescreen"].get("verdict") == "rejected"
184
+ and str(r.get("verdict") or "").upper() == "REJECTED"} - {""}
185
+
186
+
187
+ def qg_prescreen(verdicts: Rows, ledger: Rows) -> Counterfactual:
188
+ """#11: prediction accuracy and the reviewer-cost ceiling of correct rejections.
189
+
190
+ The ceiling sums the reviewers' ledger cost over whole SESSIONS with a
191
+ correctly predicted rejection: the ledger has no round id, so this
192
+ over-counts by design (an upper bound, never an estimate).
193
+ """
194
+ rounds = _prescreen_rounds(verdicts)
195
+ agree = sum(1 for predicted, final in rounds if predicted == final)
196
+ sessions = _rejected_sessions(verdicts)
197
+ ceiling = _usd([r for r in ledger if r.get("category") in REVIEWER_CATEGORIES
198
+ and str(r.get("session_id") or "") in sessions])
199
+ return Counterfactual(
200
+ 11, "qg-prescreen", "rounds with a prescreen", len(rounds),
201
+ {"agree_with_final": agree, "sessions_rejected_as_predicted": len(sessions)},
202
+ saving=COUNTERFACTUAL, ceiling_usd=ceiling,
203
+ not_measurable="real saving: the prescreen never skips a reviewer")
204
+
205
+
206
+ def _isolate_value(row: dict[str, Any]) -> object:
207
+ return row.get("jev_result") if row.get("acted_on") == "jev" else row.get("heuristic_result")
208
+
209
+
210
+ def subagent_discipline(decisions: Rows, activations: Rows) -> Counterfactual:
211
+ """#22: turns told not to isolate, and how many of their sessions launched a subagent."""
212
+ no_turns = [r for r in _site_rows(decisions, "subagent-discipline")
213
+ if _isolate_value(r) is False]
214
+ sessions = {str(r.get("session_id") or "") for r in no_turns} - {""}
215
+ launched = {str(a.get("session_id") or "") for a in activations} & sessions
216
+ return Counterfactual(
217
+ 22, "subagent-discipline", "turns marked isolate=no", len(no_turns),
218
+ {"sessions": len(sessions), "sessions_with_a_subagent": len(launched)},
219
+ saving=f"{NO_SAVING} correlation per session, not attribution",
220
+ not_measurable="a subagent that was not launched leaves no trace")
221
+
222
+
223
+ def _load(sources: Sources, cutoff: datetime | None) -> dict[str, Rows]:
224
+ paths = {"decisions": sources.decisions, "ledger": sources.ledger,
225
+ "verdicts": sources.verdicts, "activations": sources.activations}
226
+ return {name: _read_jsonl(path, cutoff)[0] for name, path in paths.items()}
227
+
228
+
229
+ def cost_effect(
230
+ period: str = "today", *, sources: Sources | None = None, now: datetime | None = None
231
+ ) -> CostEffect:
232
+ """Cost, effect and counterfactuals of one period (today | week | month | all)."""
233
+ if period not in VALID_PERIODS:
234
+ raise ValueError(f"invalid period: {period!r}")
235
+ data = _load(sources or default_sources(), _period_cutoff(period, now))
236
+ split = ledger_split(data["ledger"])
237
+ rows = [forge_complexity(data["decisions"]), dispatch_role(data["decisions"]),
238
+ qg_prescreen(data["verdicts"], data["ledger"]),
239
+ subagent_discipline(data["decisions"], data["activations"])]
240
+ has_data = bool(split.production_calls or split.replay_calls or data["decisions"]
241
+ or any(r.count for r in rows))
242
+ return CostEffect(period, split, rows, has_data)