Skip to content

issue-697: Causal context-vector patch on #537 adapters#525

Open
superkaiba wants to merge 22 commits into
mainfrom
issue-697
Open

issue-697: Causal context-vector patch on #537 adapters#525
superkaiba wants to merge 22 commits into
mainfrom
issue-697

Conversation

@superkaiba

Copy link
Copy Markdown
Owner

Closes task #697.

Causal decomposition: does finetuning a behavior into a context shift the context vector (input) or the context→behavior mapping (function)?

Substrate: #537's already-trained behavior×context LoRA adapters (no new training).

Plan: tasks/planning/697/plans/plan.md (v2 after one revise round)

superkaiba and others added 22 commits June 28, 2026 00:11
…h framing)

Resolve a contradiction the #694 r2 Codex code-reviewer surfaced as a
Style/Consistency note: SKILL.md's Step 10 analysis branch (~line 6037-6038)
said a finding-less kind:analysis task 'self-skips cleanly inside the agent'
after reaching the Step 10b/10c/10c-bis batch, while the routing entry
condition two paragraphs later (~line 6045-6047) said the same finding-less
task 'enters NONE of the Step 10b/10c/10c-bis batch — proceed straight to
Step 10d (unchanged)'.

Pick the defense-in-depth framing (plan option (b)): the outer `## Results`
entry condition is PRIMARY (finding-less analysis never enters this branch
and routes straight to Step 10d); the in-agent skip is reframed as a
redundant defense-in-depth BACKSTOP ("even if a finding-less analysis task
somehow reached this branch it would self-skip cleanly inside the agent"),
preserving the related-work-finder agent's documented self-protection
without contradicting the entry condition. Location 2 unchanged.

Effective routing behavior is preserved — pure prose alignment.
Six TDD invariants (plan §TDD / Gate C1): self-patch identity (read+gen),
non-identity KV-cache propagation, patch-slot audit, patch-at-position
isolation, f_CV math (both poolings), no-effect-cell sentinel. Stub module
raises NotImplementedError so tests collect + fail red.
The layer-output overwrite REPLACES layer_module.forward (not a forward
hook) so the patched residual is what Transformers' hidden-states recorder
captures into hidden_states[L+1] — a forward hook fires after the recorder
wrapper, so it propagates downstream but is invisible to
output_hidden_states (the T4 / read-test invariant).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…y + analysis

- issue697_dispatch.py: vendor/canary/sweep/analyze phases; PASS_UNIFIED
  smoke=sweep-with-one-cell; --n-gpus 4 default (ft-7b 4x A100-80); per-phase
  cell list all derives from the same --cells subset; [phase=...] + sentinel.
- issue697_cell.py: per-(B,C,seed) cross-model CV patch read (capture +
  P-down/P-up + 4 controls on the 14x20 panel), per-cell slot audit, persists
  .pt (both poolings for marker/fact) + _E.json, per-cell HF upload.
- issue697_canary.py: Gate C1 (cv_patch correctness) + Gate C2 (inherited
  #651 Gate 7a rsLoRA parity); smoke skips C2.
- issue697_analysis.py: persona-clustered bootstrap f_CV + hero 2x4 grid.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…paper_palette_role

- issue697_cell.py: marker arm's behavioral E DV is now the judge-free
  four-float-style TF slot read (log P(※) at the post-response slot under
  unpatched/P-up/P-down), NOT 4x free generation — the marker DV is a
  teacher-forced log-prob per the #537/#651 recipe + marker-leakage rule.
  Removes 32 marker cells from the free-gen cost surface.
- issue697_analysis.py: paper_palette_role(role) returns a single hex string
  per role (was mis-called as a dict).

The residual behavioral-E free-gen cost (em/syc/fact over the full panel) is
surfaced via epm:compute-deviation for orchestrator descope; the v-space
PRIMARY f_CV pipeline is in budget (16 GPU-h / 4h wall).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ge, wire full_span + use_cache, close concerns

- cv_patch: make_cv_patch_hook accepts per-position distinct replacements
  (dict[int,Tensor] / position-aligned 2-D) — full_span scope (concern #3);
  context_span_positions helper; param rename replacement_vec -> replacements.
- cell.py: full_span v-condition (FT c+ at every [0..patch_pos] position);
  E-gen descope to source-anchor + 4 closest bystanders (compute-deviation v2);
  per-behavior decode knobs (em sample temp=1.0 n=5 / syc+fact greedy max=512);
  --use-cache threaded; raw_completions split to canonical prefix + verify;
  _E_metadata.json under analysis_tensors/.
- canary.py: writes canary_decision.json (use_cache_production_default);
  marginal-parity safety net flips to uncached.
- dispatch.py: reads use_cache decision + threads --use-cache; device-count
  preflight assert (item 5c); analyze phase runs vendored Sonnet judge.
- judge.py (NEW) + vendored i537_judging.py: off-pod CPU judge, Sonnet 4.5 for
  em/syc/fact (supersedes #537 Haiku pin), produces {cell}_judged.json.
- tests: T7/T7b multi-position replacements, T8 use_cache=False threading,
  T9 per-behavior decode knobs (18/18 green).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e decision and gate_7a vendor

issue697_canary.py + issue697_dispatch.py run as SCRIPTS (uv run python
scripts/<name>.py) where sys.path[0]=scripts/ and the `scripts.` package
import idiom does not resolve. Robustly import the vendored #651 gate_7a
(script-mode/package-mode try/except) and inline the canary use_cache
decision path constant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…dge raw-text poller

The vendored i537_judging.submit_judge_batch_raw carried a hand-rolled
messages.batches.create + unbounded while-True poll, which workflow_lint
--check-batch-judge-client FAILs (the deadline-less poller forbidden by
#658/#663). Add a sanctioned RAW-TEXT sibling submit_and_poll_batch_raw to
eval/batch_judge.py (same #663 shard + expires_at deadline bound as
_submit_and_poll_batch, but collects verdict text verbatim instead of
parsing to a score dict — the #537 pools have their own downstream parsers)
and delegate to it. Pin with 3 regression tests (raw text verbatim, error
sentinel mapping, deadline bound) using the existing _DeadlineSubmitClient.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…pace/P-down verdict)

Closes the four binding code-review BLOCKERs from the round-2 reconciler verdict,
all 0-GPU analyze-side fixes (no retrain, no sweep re-run):

- full-span-not-consumed-by-analyze: _per_question_f_cv now reads
  conditions["full_span"], analyze() aggregates its persona-clustered CI +
  the last-token-vs-full-span scope delta into f_cv_summary.json, and the hero
  v-space row plots the full_span comparison point (plan control #4).
- e-subset-uses-euclidean-not-cosine: select_e_subset ranks bystanders by
  cosine distance 1-cos(c_p, c_anchor) (raw pairwise per persona-distance-metrics.md;
  descope marker v2 mandates closest-by-#651-cosine), docstrings updated, and the
  chosen metric is persisted in the per-cell manifest (e_subset_metric).
- e-space-analysis-not-wired: analyze() loads {cell}_judged.json (em/syc/fact)
  + the marker _E_metadata.json TF marker-logp records, computes
  f_CV^E = (E_Pup - E0)/(E+ - E0) and its P-down analog, populates
  f_cv_e_ci / f_cv_e_down_ci + the hero E-row (placeholder only when judged
  data is genuinely absent).
- pdown-verdict-crosscheck-not-wired: analyze() aggregates f_cv_down, bootstraps
  its CI (persona-clustered), and _verdict emits patch-inconsistent when the
  P-up and P-down CIs disagree (plan §6.3).

Adds 4 regression pins to tests/test_cv_patch.py (one per concern_id), all
fail pre-fix / pass post-fix. Full cv_patch suite 22/22; lint + format clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ose marker-e-space-mean-of-ratios

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…arker + upload resilience

- §4.0 cv_patch.patched_read gains read_layer (Option B): patch hook at
  patch_layer (L=10), read v at hidden_states[read_layer+1] (L=14) so the patch
  propagates 4 attention layers to the response-slot read (read_layer=None keeps
  the legacy same-layer behavior for the canary self-patch null). Cell threads
  patch_layer (L=10) through _build_conditions/_patched_reads + asserts
  patch_layer < read_layer (the v3 read-inert class); donor captured at L_patch;
  capture set {7,10,14,21}; --patch-layer arg (default 10).
- §6.1 _marker_logp_at_slot returns the four-float storage contract
  {log_p, z_marker, z_eos, logZ} from the same forward (im_end id 151645);
  marker E records persist all four per condition.
- §4.2 _upload_cell_artifacts: per-EXPECTED-file HfApi.file_exists (not a
  ~64K-file list_repo_files) + _hf_retry exponential backoff on transient
  5xx/timeouts (HfHubHTTPError from huggingface_hub.utils); fail-loud on 4xx /
  budget exhaustion / missing file. tests/test_issue697_cell_upload.py pins it.
- §4.4 R_base cache read: _load_rbase_cache (canonical HF -> local -> inline-gen
  fallback) replaces per-cell R_base regeneration; --rbase-cache-dir arg.
- §11 cosmetics: cell --max-new-tokens default 1024.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…gate + continue-on-fail + #537 ctx-gating

- §4.5 issue697_rbase.py: vLLM-batched greedy R_base over the 280-prompt panel,
  cached + uploaded to HF. spawn multiproc method (#628 fork-poison),
  use_tqdm=False (#613), chunked at EPM_VLLM_GREEDY_CHUNK_SIZE (#664) with
  per-chunk INFO logs; EngineCore reaped before the HF parity load (coexistence);
  vLLM-vs-HF greedy parity gate with HF-greedy fallback on material divergence.
- §4.3 phase_sweep continue-on-cell-fail: budget ceil(N/16)=4; per-cell
  epm:cell-failed sentinel + terminal epm:cell-failed-summary + sweep_coverage.json
  (failed_tuples for analyze check 11b); raise above budget.
- §7.1 inert-read gate: issue697_analysis.assert_not_read_inert (v5 abs(pdn)<1e-4
  threshold — the v4 abs(pdn-1.0) was non-functional). phase_inert_read_assert
  (7.1a) MUST detect inertness on the v3-salvaged marker_sp_swe_seed42.pt before
  the sweep; assert_smoke_cell_not_inert (7.1b) requires the new smoke cell's .pt
  be NON-inert before a production sweep dispatches.
- §6.5 #537 ctx-gating: load_537_ctx_gating + cell_ctx_gating_status join
  eval_results/issue_537 per_row_breadth; analyze restricts the behavioral-E
  mapping-changed verdict to #537-context-gated cells (WARN-not-fail when absent).
- §Salvage: --seed-42-only -> the 64-cell production grid (4 beh x 16 ctx x seed
  42); the 4 salvaged marker cells are RE-RUN (v3 v is read-inert), not skipped.
- analysis: four-float marker back-compat read (_marker_logp_value reads the new
  dict or the salvaged single-float). 'all' phase order =
  vendor -> inert_read_assert -> rbase_prep -> sweep (canary C2/C1.2 REUSED).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…off-pod HF hydration, use_cache=False default

B1 (smoke-non-inert-gate-not-enforced): add phase_smoke (one real-GPU cell ->
§7.1b non-inert assert -> smoke_697b_pass.json + HF upload) BEFORE sweep in the
'all' ladder; phase_sweep now REFUSES the wave loop on len(cells)>1 until a
smoke-pass artifact exists with matching git SHA + read layer + non_inert=true
(_assert_smoke_pass_for_sweep). A 64-cell A100 sweep can no longer start without
a verified non-inert L=10/L=14 read. Test: tests/test_issue697_dispatch_smoke_gate.py.

B2 (analysis-hf-artifact-hydration-missing): phase_analyze hydrates per-cell .pt
+ _E_metadata + raw_completions from HF (_hydrate_analyze_artifacts, canonical
list_repo_files+hf_hub_download, idempotent resume-skip, fail-loud when no .pt on
HF) before the glob, so off-pod analyze on a fresh VM works. Test:
tests/test_issue697_analysis_hydration.py.

B3 (production-use-cache-default-true): flip --use-cache + _read_use_cache_decision
defaults to False; fetch the 7B canary decision from HF (gates/ then salvaged),
REJECT a non-7B (0.5B-smoke) provenance for the 7B sweep, delete the local 0.5B
canary_decision.json. KV caching drops the patch (parity Δ=0.25) -> uncached
default protects p_up/p_down E. Test: tests/test_issue697_use_cache_decision.py.

Minor: other_ctx donor rotates per persona (idx+1)%N (no self-reference).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… data only)

Single-seed (seed=42) read against the canonical issue697_cv_patch/analysis_tensors/ HF
prefix at primary layer L=14, slot pooling for fact/marker, persona-clustered bootstrap
over 4480 = 280 x 16 cells.

Headline (n=16 contexts, seed 42, P-up arm):
- fact   f_CV = 0.0007 [0.0006, 0.0009] (n=4480); full_span upper bound 0.096; null floor 0.0009;
                 verdict = patch-inconsistent (P-up/P-down CIs disjoint at sub-0.001 level);
- marker f_CV = 0.0498 [0.0454, 0.0548] (n=4480); full_span upper bound 0.174; null floor 0.079;
                 f_CV^E = 0.00033 [-0.0017, 0.0022] over 16 cells; verdict = mapping-changed.

Both behaviors say the L=14 context vector does NOT carry the finetuning shift; the
context-to-behavior mapping M does. For marker, last-token f_CV (0.050) is BELOW the
random-CV null floor (0.079), so the patch literally does less than noise. The full_span
upper bound puts the context-vector-mediated share at <= 17% (marker) / <= 10% (fact);
83-90% lives in M. The behavioral DV agrees for marker (f_CV^E ~= 0); fact's f_CV^E awaits
the off-pod Sonnet judge pass.

Caveats: single seed (seed=1042 leg unrun); em is 8/16, sycophancy is 0/16 (the JOB-3
re-dispatch closes both gaps); no F4 #537-ctx-gating restriction applied (no file yet).

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
…weeps)

The original FLEX_START sweep is going to be force-deleted by the 24h fence
with 40/64 cells complete on HF canonical and 24 still to run (8 em + 16
sycophancy). Without a skip-if-exists guard, ANY resume launch (orchestrator
re-drive, autonomous-session GCP→RunPod failover, or a manual rerun) would
re-run all 64 cells from scratch — wasting ~33h of GPU time.

This adds the same skip-if-exists pattern siblings use (issue664_dispatch's
_classify_cell_hub_state + _cell_done_anywhere): a single list_repo_files
call resolves which cells already have both canonical artifacts on HF
(issue697_cv_patch/analysis_tensors/{cell_id}.pt + _E_metadata.json), and
_filter_cells_already_on_hub drops them from the dispatched list. Default
ON; --no-skip-on-hub forces the full sweep.

A failed listing call (the 504 the paginated tree endpoint occasionally
throws on huge repos) is fail-soft → proceed with the full subset, since
the per-cell create_commit is itself idempotent (re-running a complete
cell rewrites the same files).

Verified via --dry-run --seed-42-only: 40/64 cells correctly skipped,
24 cells (8 em + 16 syc) dispatched. The dispatched 24 matches the
ground truth: 8 missing em (default, binst_em, fmt_json, fmt_code,
reph_polite, reph_imp, reph_casual, icl_k8) + 16 sycophancy.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
…ur-condition logp

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…result repro

The patch/ tree's *_judged.json files (Claude-Sonnet-4.5-judged per-cell EM
and taught-fact behavioral reads) were uncommitted. Commit the small JSONs
(22 MB total) so the clean-result footer can SHA-pin them; the 9.5 GB .pt
activation tensors stay on the HF data repo per upload policy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@superkaiba
superkaiba marked this pull request as ready for review June 30, 2026 08:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant