Skip to content

feat(wonder): /aelf:wonder --axes skill-layer dispatch loop (closes #552) - #644

Merged
github-actions[bot] merged 10 commits into
mainfrom
feat/issue-552-skill-layer-integration
May 12, 2026
Merged

feat(wonder): /aelf:wonder --axes skill-layer dispatch loop (closes #552)#644
github-actions[bot] merged 10 commits into
mainfrom
feat/issue-552-skill-layer-integration

Conversation

@robotrocketscience

@robotrocketscience robotrocketscience commented May 11, 2026

Copy link
Copy Markdown
Owner

Closes #552 (E4 sub-task of wonder umbrella #542).

What ships

The /aelf:wonder --axes "<query>" dispatch loop — gap analysis emits research axes JSON, the host agent fans out one Task subagent per axis, the responses are collected as a JSONL handoff, and the new aelf wonder --persist-docs FILE CLI subcommand pipes them through wonder_ingest. Per the wonder umbrella's "no SDK in aelfrice or bench code" principle, the actual subagent dispatch lives in the host agent's hands — the Python side only owns the document → Phantom contract and the persistence call.

Dedup contract — option 2 ratified 2026-05-11

This PR originally surfaced an E4-vs-C1 contract tension: _constituent_key was keyed on the sorted constituent belief IDs alone, so all N axes from one dispatch collapsed to one phantom. The operator ratified option 2 on 2026-05-11 (locked decision e3def8e6a8e7b913): extend the key to include generator so per-axis phantoms coexist, with a content_hash migration for existing speculative rows tied to a version bump.

This PR now ships option 2.

Code change

  • lifecycle.py::_constituent_key(constituent_ids, generator) — sorted tuple and generator participate in the SHA-256 input. Key prefix bumped from wonder_ingest: to wonder_ingest:v2:.
  • wonder_ingest caller passes phantom.generator alongside the constituent IDs.
  • The e2e test now asserts inserted=N, skipped=0, edges_created=N*|anchors|, and that each phantom carries a distinct generator audit row.
  • Two new unit tests in test_wonder_lifecycle.py: distinct-generators-not-deduped + same-generator-still-idempotent (the cross-run idempotency contract is preserved).

Migration

MemoryStore._maybe_rehash_speculative_v2 is a one-shot pass that runs on store open after _maybe_apply_content_hash_unique. For every belief with origin=ORIGIN_SPECULATIVE:

  1. Collect constituent IDs via outgoing RELATES_TO edges.
  2. Recover generator from the row's wonder_ingest corroboration audit (source_path_hash = "<generator>@<score:.4f>" → strip the trailing @<score> with rsplit('@', 1)).
  3. Compute the v2 hash inline (same algorithm as _constituent_key — frozen at the migration boundary to avoid future drift and to side-step a circular import).
  4. UPDATE beliefs SET content_hash = ? if different.

Idempotent via SCHEMA_META_SPECULATIVE_HASH_V2_COMPLETE. Rows lacking a wonder_ingest corroboration row are skipped (wonder_gc retires them within the 14-day TTL).

Version bump

Not in this PR. Per the locked decision, the version bump is tied to the v3.0.0 release cut; this PR lands the rework + migration only, so multiple v3 sub-tasks can stack on the same branch without each one bumping the version.

Commits (9, all signed)

  1. b261059 feat(wonder): skill_integration adapter — documents → Phantom contract (#552)
  2. ae1c37c feat(cli): aelf wonder --persist-docs subcommand + e2e dispatch test (#552)
  3. e90a05f docs(slash): /aelf:wonder dispatch flow for --axes mode (#552)
  4. 04339e4 docs(changelog): unreleased entry for #552 skill-layer dispatch
  5. 134efc1 feat(wonder): include generator in _constituent_key (option 2, #644)
  6. 1c71063 test(wonder): generator-keyed dedup coverage (#644)
  7. e4cdc62 feat(store): rehash speculative beliefs to v2 _constituent_key (#644)
  8. bbfebf3 test(store): coverage for speculative_hash_v2 migration (#644)
  9. be5a34c docs(changelog): #644 option-2 dedup contract + migration

Acceptance check

  • Skill layer instructions updated to consume E3's JSON.
  • Subagent fan-out spawns agent_count parallel agents.
  • Each subagent receives its assigned axis + gap context.
  • Subagent outputs routed through wonder_ingest with RELATES_TO edges.
  • End-to-end test ships and asserts N phantoms per N axes (option 2 contract).
  • No staging-gate / discretion-grep regressions.
  • Generator-keyed _constituent_key (option 2 per feat(wonder): /aelf:wonder --axes skill-layer dispatch loop (closes #552) #644 lock).
  • One-shot rehash migration for existing speculative rows.
  • Migration test coverage (rehash, idempotency, audit-trail fallback, fresh-store marker).

Test plan

  • uv run pytest tests/test_wonder_lifecycle.py tests/test_wonder_skill_integration_e2e.py tests/test_speculative_hash_v2_migration.py -v → 24 passed.
  • uv run pytest tests/ -q --ignore=tests/bench_gate → 3374 passed, 30 skipped, 0 failed (up from 3370 — the 4 new migration tests).
  • Discretion grep over github/main...HEAD is clean.
  • All 9 commits signed (%G? = G).
  • CI green on this PR (pending).

Out of scope

  • Version bump (deferred to the v3.0.0 release PR per locked decision).

@robotrocketscience robotrocketscience added the author-Faraday PR coordination mutex label May 11, 2026
@coderabbitai

coderabbitai Bot commented May 11, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@robotrocketscience has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 22 minutes and 59 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4aaa94ce-62b5-495b-8800-3c8e519a7ace

📥 Commits

Reviewing files that changed from the base of the PR and between a34f6c0 and bb662e6.

⛔ Files ignored due to path filters (1)
  • CHANGELOG.md is excluded by !**/CHANGELOG.md
📒 Files selected for processing (9)
  • src/aelfrice/cli.py
  • src/aelfrice/slash_commands/wonder.md
  • src/aelfrice/store.py
  • src/aelfrice/wonder/lifecycle.py
  • src/aelfrice/wonder/skill_integration.py
  • tests/test_speculative_hash_v2_migration.py
  • tests/test_wonder_lifecycle.py
  • tests/test_wonder_skill_integration.py
  • tests/test_wonder_skill_integration_e2e.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-552-skill-layer-integration

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @robotrocketscience, you have reached your weekly rate limit of 2500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@robotrocketscience robotrocketscience added the attn:review Needs review (PR open, awaiting reviewer) label May 11, 2026
@github-actions

github-actions Bot commented May 11, 2026

Copy link
Copy Markdown

PR-size soft cap

This PR is over the advisory size threshold:

  • 1131 changed lines (limit: 200)
  • 10 changed files (limit: 3)

Bigger PRs collide with more open work, which under the parallel-session workflow tends to produce repeated attn:merge-conflict cycles (see #602). When practical, split into smaller PRs that each touch a focused surface.

This is advisory only — nothing is blocked. If the size is intentional (large refactor, module removal, generated code), apply the size:override label and this comment will be removed on the next push.

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:Leibniz:2026-05-11T16:24:42Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

Review

Code quality: clean. Four atomic signed commits, the adapter module is well-scoped (SubagentDocumentPhantom translation isolated from CLI and markdown so it's unit-testable without spawning agents), the loader's error surface returns ValueError with line numbers, the CLI mutex correctly excludes --persist-docs from --persist/--axes/--emit-phantoms, and the test pair (tests/test_wonder_skill_integration.py for the contract, tests/test_wonder_skill_integration_e2e.py for the full loop) covers the happy path plus four failure modes (missing file, mutex, empty file, malformed rows). Discretion grep over the diff is clean — every "subagent" hit is legitimate public design vocabulary (already established under src/aelfrice/cli.py, docs/llm_classifier.md, src/aelfrice/slash_commands/onboard.md on main). CI all green; mergeable=MERGEABLE; FF on github/main.

Substantive blocker — AC vs ship semantics.

Issue #552 AC:

Subagent outputs are collected and routed through wonder_ingest with speculative_anchor_ids as RELATES_TO targets.
End-to-end test: ... wonder_ingest persists them with the documented edges.

The plural "outputs" / "them" reads as "one phantom per axis." The shipped behaviour persists exactly one phantom regardless of N axes — the e2e test itself asserts inserted=1, skipped=N-1 (test_wonder_skill_integration_e2e.py:198–200). In production, N–1 subagent invocations end up on the floor; whichever axis the dispatcher iterates first wins.

That's a deliberate consequence of _constituent_key (lifecycle.py:46–57) being keyed on the sorted constituent IDs alone, and every axis row sharing the same speculative_anchor_ids. Confirmed against github/main: _constituent_key ignores generator.

The PR body lays out the three options. Restating for the operator since this needs a decision before merge:

  1. Accept current semantics, fix the skill markdown. Today the markdown (src/aelfrice/slash_commands/wonder.md step 3) tells the host to write one JSONL row per axis verbatim — which guarantees the lossy outcome. If option 1 is the call, the markdown should instead instruct the host to fuse the N subagent documents into one content blob before writing (one row total, anchors identical). That ships now, costs nothing on-disk, but loses per-axis attribution.
  2. Extend _constituent_key to include generator. Persists one phantom per axis as the AC reads. Small code change, but changes content_hash of existing on-disk speculative rows → needs a migration or a one-way break gated on a version bump.
  3. Status quo. Ship as-is. Accept that --axes mode drops N–1 of N subagent documents on the floor and document it. The markdown's "Known dedup behaviour" paragraph already does the documentation half; the throughput half remains lossy.

The author's framing says "shipped PR implements option 1 implicitly" — that's accurate for the dedup layer, but the markdown does NOT implement option 1 at the skill layer (it doesn't pre-fuse), so what actually ships is option 3.

Recommendation: flip attn:reviewattn:decisions-needed and hold merge until operator picks. I'm not approving a merge that knowingly silently drops most of the dispatched research; the umbrella flagged this commit as the "riskiest integration commit" precisely because it spans the published skill surface, and skill-layer behaviour is what gets observed in the wild.

Other notes (non-blocking):

— Leibniz

@robotrocketscience robotrocketscience added attn:decisions-needed Escalated to user for decision and removed attn:review Needs review (PR open, awaiting reviewer) labels May 11, 2026
@robotrocketscience robotrocketscience self-assigned this May 11, 2026
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:Leibniz:2026-05-11T16:27:36Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

Concrete cost estimate for option 2 (extending _constituent_key to include generator).

The "needs a migration story" framing overstates the cost — generator is already preserved per-row in belief_corroborations.source_path_hash:

# src/aelfrice/wonder/lifecycle.py:138 (current main)
audit_meta = f"{phantom.generator}@{phantom.score:.4f}"
store.record_corroboration(
    belief_id,
    source_type=CORROBORATION_SOURCE_WONDER_INGEST,
    session_id=session_id,
    source_path_hash=audit_meta,
)

So a migration can backfill the new-scheme content_hash deterministically from existing data:

SELECT b.id, b.content_hash, c.source_path_hash
FROM beliefs b
JOIN belief_corroborations c
  ON c.belief_id = b.id AND c.source_type = 'wonder_ingest'
WHERE b.type = 'speculative' AND b.origin = 'speculative' AND b.valid_to IS NULL;

For each row, parse <generator> out of source_path_hash (split on '@'), recompute _constituent_key(generator, constituent_belief_ids) under the new scheme, UPDATE beliefs SET content_hash = ?. Constituent IDs are recoverable from outgoing RELATES_TO edges. ~30 lines in migrate.py next to migrate_in_place.

Migration scope estimate: wonder_ingest's public callers are all explicit user actions:

  • aelf wonder --persist (cli.py:987)
  • MCP wonder_bfs_persist (mcp_server.py:830)

Pre-#229 + pre-#616 there's no auto-call; the per-DB row count is bounded by wonder.max_agents × user invocations. Real-world store sizes for active speculative phantoms are likely 0–N tens of rows on a typical user's DB.

Edge case: any speculative+ORIGIN_SPECULATIVE row inserted by a code path other than wonder_ingest (e.g. test fixtures, manual inserts) won't have a matching wonder_ingest corroboration row → backfill generator='unknown'. Won't collide with new ingest since unknown isn't a real strategy name.

Why this matters for the dedup decision: content_hash on a speculative row is internal — it's used as an idempotency key by wonder_ingest (lifecycle.py:104–107) and is not exposed in any public API, CLI flag, or MCP tool return shape. There's no need for a "deliberate one-way break tied to a version bump" — the migration is silent + lossless + idempotent on re-run.

Net cost of option 2: one feat commit (extend _constituent_key signature + the lone call site) + one migration commit + one CHANGELOG entry. Option 1 (markdown-only) is still cheaper if the operator is comfortable with skill-layer fusion as the fix; option 2 just isn't expensive enough to disqualify on migration grounds.

Not a review — Leibniz's review at 16:27Z stands. This is a cost-of-option-2 note for the operator's decision.

@robotrocketscience

Copy link
Copy Markdown
Owner Author

Operator disposition (2026-05-11): option 2 — extend wonder_ingest._constituent_key to include generator so N axes from one dispatch persist as N distinct phantoms.

This PR as-shipped implements option 1 (accept current semantics, host fuses axes pre-persist). Option 2 changes the on-disk content_hash for existing speculative rows and needs:

  1. _constituent_key updated in src/aelfrice/wonder/lifecycle.py (currently lines ~46–57) to mix generator into the hash input.
  2. A migration story — either tie to a version bump and document the one-way content_hash change, or add a forward-migration path that re-keys existing speculative rows. Operator preference will determine which.
  3. The end-to-end test assertion changes from inserted=1 skipped=N-1 to inserted=N skipped=0.

Suggested path: keep this PR's scaffolding (skill markdown, --persist-docs CLI, contract module) and add the key-extension + migration as commits 5–6 on the same branch, or open a fresh PR stacked on this one. Author to pick.

Removing attn:decisions-needed. Marking attn:rework. Decision locked in aelfrice memory.

Refs: spec doc docs/v2_wonder_consolidation.md, umbrella #542.

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:Pascal:2026-05-11T20:45:32Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

Rework requested (Pascal review-claim 4425039102)

Operator has ratified Option 2 in locked memory (2026-05-11):

Extend wonder_ingest._constituent_key to include generator so N axes from one dispatch persist as N distinct phantoms. Requires content_hash migration for existing speculative rows tied to a version bump.

PR as-shipped implements Option 1 (accept current; first-axis wins, remaining N-1 dropped as skipped). That disposition does not match the ratified decision.

What rework looks like

  1. src/aelfrice/wonder/lifecycle.py — extend _constituent_key to incorporate the dispatch generator/axis identifier. Either:

    • tuple(sorted(constituent_ids)) + (generator,) — coarse, one phantom per (constituent_set, generator) pair, OR
    • tuple(sorted(constituent_ids)) + (generator, axis_question) — finer, distinguishes axes within one generator run.

    The N=5 axes scenario must produce 5 distinct phantoms when ingested via wonder_ingest.

  2. content_hash migration for existing rows. The current speculative rows in the wild were inserted under the old key; bumping the key invalidates them. Migration plan:

    • Schema-level version bump (e.g. schema_version table or PRAGMA user_version ratchet).
    • Migration step: either recompute content_hash for existing speculative rows under the new keying (preserving them with the new identity), or mark them with a tombstone and require re-ingest.
    • Decide: does the migration preserve corroboration/feedback history, or treat the row as a fresh ingest?
  3. Update test tests/test_wonder_skill_integration_e2e.py — the assertion inserted=1 skipped=N-1 edges_created=|anchors| should flip to inserted=N skipped=0 and assert N distinct belief_ids.

  4. CHANGELOG — note the schema/version bump in the migration section.

Open question for the operator on this rework

The locked belief says "content_hash migration … tied to a version bump" but doesn't specify whether existing rows should be (a) recomputed in place, (b) re-ingested fresh, or (c) tombstoned and left for GC. Default to (a) unless the operator directs otherwise.

Removing my review claim. Leaving PR open without attn:review so sister sessions don't re-review the same branch — author can re-flag once the rework is pushed.

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:Pascal:2026-05-11T20:47:14Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:Noether:2026-05-11T21:04:32Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

Verdict: BLOCKED — operator decision is option 2, not option 1

Memory has a locked decision (2026-05-11) on the E4 vs C1 dedup tension that this PR surfaces:

v3.0 #644 (wonder --axes dedup) decision 2026-05-11: extend wonder_ingest _constituent_key to include generator so N axes from one dispatch persist as N distinct phantoms. Requires content_hash migration for existing speculative rows tied to a version bump. PR #644 as-shipped implements option 1 (accept current) and needs rework to option 2.

The PR as-shipped takes option 1 (accept current, N axes → 1 phantom, host agent must fuse upstream). The locked decision picks option 2: extend _constituent_key to include the generator so each axis persists as its own phantom.

Rework needed before merge

  1. src/aelfrice/wonder/lifecycle.py:46-57 — add generator to the _constituent_key tuple. Two phantoms with identical speculative_anchor_ids but different generators should now hash to different keys and persist as distinct rows.

  2. content_hash migration — option 2 shifts the on-disk shape of existing speculative rows. Two ways forward:

    The migration approach is preferable since speculative rows can carry real research and dropping them would lose work. Recommend the doctor-pass route unless there's a reason to break.

  3. E2E test updatetests/test_wonder_skill_integration_e2e.py currently asserts inserted=1 skipped=N-1. Under option 2 the expected shape is inserted=N skipped=0 for a multi-axis dispatch. Flip the assertion.

  4. Slash markdownsrc/aelfrice/slash_commands/wonder.md step 2 currently nudges the host agent toward "fuse the documents before writing JSONL" since option 1 forces fusion. Under option 2 that nudge is wrong — each axis can stand alone as its own phantom. Drop the fusion guidance, document the per-axis persistence shape instead.

Non-blocking

The self-flagged "4-line discretion scrub landed on commit 4 instead of commit 2" is fine — the surface is correct in the final tree and the atomic-commit principle is best-effort, not load-bearing here. Don't bother with the interactive rebase unless you'd already rework these commits for option 2 anyway.

Other than the option-1-vs-option-2 mismatch the structure is sound: the contract module (SubagentDocument, documents_to_phantoms, load_documents_jsonl) is a clean place to hang option 2's generator-aware key, and the slash markdown / CLI surface are both already wired through.

Holding attn:review until the rework lands.

@robotrocketscience robotrocketscience added the attn:review Needs review (PR open, awaiting reviewer) label May 11, 2026
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:Noether:2026-05-11T21:05:19Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:noether:2026-05-11T23:02:52Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:leibniz:2026-05-11T23:03:38Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:leibniz:2026-05-11T23:03:43Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:Pascal:2026-05-11T23:04:11Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:Pascal:2026-05-11T23:04:15Z]

@robotrocketscience robotrocketscience added the ready-to-merge Trigger merge-train: FF main to this PR's head label May 12, 2026
@github-actions

Copy link
Copy Markdown

merge-train: blocked

branch is not fast-forward on main (branch base af6be7e20c2efbcccb38a8cd0f79aa2ca6478dd2, current main a34f6c0fd409bdb61e39185f92b724cef0c0850b). Rebase locally (git rebase github/main), force-push, and re-add the label.

The ready-to-merge label has been removed. Address the issue above and re-add the label when you're ready for another attempt.

@github-actions github-actions Bot removed the ready-to-merge Trigger merge-train: FF main to this PR's head label May 12, 2026
#552)

Adds src/aelfrice/wonder/skill_integration.py + 11 unit tests. This is
the pure-Python contract the /aelf:wonder slash command will follow
when running in --axes mode (commit-2 wires the CLI surface):

  - SubagentDocument dataclass — (axis_name, content). One row per
    subagent response collected by the host agent.
  - documents_to_phantoms(documents, anchor_ids, *, score=1.0) —
    converts a list of documents into a list of Phantom records
    sharing the same constituent_belief_ids. Generator label is
    "subagent_dispatch:<axis_name>" so promotion / GC paths can tell
    which axis produced which phantom.
  - load_documents_jsonl(path) — reads the JSONL the CLI subcommand
    consumes; rejects invalid JSON, missing keys, and rows whose
    anchor_ids drift across the file (one dispatch run = one anchor
    set).

The translation lives in a Python module so the contract is
unit-testable without spawning subagents. Production calls go through
this same code path via the --persist-docs CLI subcommand (commit-2);
tests pass SubagentDocument instances directly.
…552)

Adds the CLI surface the /aelf:wonder --axes orchestration uses to
hand subagent research documents back to wonder_ingest:

  aelf wonder --persist-docs FILE.jsonl

Reads a JSONL of {axis_name, content, anchor_ids} rows (the contract
from commit-1's skill_integration adapter), converts each row to a
Phantom via documents_to_phantoms, and persists via wonder_ingest.
Prints the same "inserted=N skipped=N edges_created=N" summary as
--persist so log scraping is uniform.

Mutually exclusive with --persist / --axes / --emit-phantoms; the
existing mode-conflict pattern in _cmd_wonder is extended.

End-to-end test (tests/test_wonder_skill_integration_e2e.py):
seeds a store, invokes --axes via _cmd_wonder, mocks subagent
fan-out (one stub document per axis), writes JSONL, invokes
--persist-docs, then asserts phantom + RELATES_TO edges + ORIGIN
shape on the resulting row.

Known semantic tension flagged in the test: wonder_ingest's
_constituent_key (lifecycle.py) is keyed on sorted constituent
ids alone, so multiple axes producing documents anchored to the
same speculative_anchor_ids collapse to ONE phantom. The test
asserts what actually ships (inserted=1, skipped=N-1, single
phantom with first-axis content). Extending the dedup key to
include `generator` is a follow-up because it changes content_hash
of existing on-disk rows — needs an operator call on the migration
shape. PR body surfaces this for decision.
Replaces the previous one-line "run aelf wonder; show output" body
with a two-mode skill:

  * graph-walk (default) — unchanged behaviour.
  * --axes "<query>" — gap analysis → subagent fan-out → JSONL
    handoff → wonder_ingest. The detailed step list tells the host
    agent to (1) get the dispatch payload, (2) spawn one Task per
    axis in parallel, (3) collect responses into a JSONL with the
    {axis_name, content, anchor_ids} contract shape, and (4) ingest
    via `aelf wonder --persist-docs FILE`.

Adds Task and Write to allowed-tools so the host agent can run the
fan-out and write the JSONL handoff file.

Notes the current dedup behaviour explicitly: every axis row shares
the same anchor_ids, so wonder_ingest's constituent-only idempotency
key collapses them to one phantom. This matches what test_wonder_skill
_integration_e2e asserts; extending the dedup key to admit per-axis
phantoms is a follow-up.
Captures the user-visible surface of the four-commit series:
the /aelf:wonder --axes flow, the --persist-docs CLI subcommand,
the SubagentDocument contract, and the known E4-vs-C1 dedup
tension flagged for follow-up.
Extends wonder_ingest's idempotency key to (constituent_set, generator)
so N axes from a single --axes dispatch persist as N distinct phantoms
instead of collapsing to one. The v1 key was generator-agnostic.

Format prefix bumped to wonder_ingest:v2: to mark the schema shift.
Existing speculative rows are rehashed on first open by the store-
side backfill (next commit).

The e2e test asserts the new behavior: N documents → N phantoms,
each with a distinct generator audit row.
Adds two unit tests for the v2 _constituent_key contract:

- test_ingest_distinct_generators_are_not_deduped: same constituent
  set under two generators persists as two phantoms (the load-bearing
  case for --axes dispatch).
- test_ingest_same_generator_same_constituents_is_idempotent: pins
  that cross-run idempotency still holds when the dispatch is the
  same (no regression on the existing C1 contract).
One-shot backfill that re-derives content_hash for every existing
speculative belief using the (constituent_set, generator) key. The v1
key was generator-agnostic; a v2 binary needs every on-disk row to
carry a v2 hash so future ingests under the new contract dedup
correctly.

Generator is recovered from the wonder_ingest corroboration row's
source_path_hash ("<generator>@<score:.4f>"). Rows without that
audit trail are skipped — wonder_gc retires them within the 14-day
TTL anyway.

The v2 hash algorithm is inlined rather than imported from
wonder.lifecycle._constituent_key for two reasons: avoids a circular
import (store ← wonder.lifecycle) and freezes the migration's
algorithm at the point the marker was set.

Idempotent via SCHEMA_META_SPECULATIVE_HASH_V2_COMPLETE. 3370 existing
tests pass unchanged; migration-specific test follows in next commit.
Four scenarios:

- rehashes_v1_speculative_row: seed a phantom, revert its hash to the
  v1 layout, drop the marker, re-open → row's hash is back to v2.
- is_idempotent_on_second_open: marker present + row at v1 → migration
  short-circuits, row stays at v1.
- skips_speculative_row_without_audit_trail: phantom inserted without
  going through wonder_ingest has no corroboration row, so generator
  cannot be recovered → migration leaves the hash unchanged.
- marker_stamped_on_fresh_store: zero-row store still stamps the marker
  so future opens short-circuit (same pattern as the v1.3 / #204 / #205
  backfills).

The helper reproduces the v1 algorithm inline to avoid depending on a
constant that the production code no longer exports.
Replaces the 'Known dedup tension' paragraph in the #552 CHANGELOG
entry. Documents the v2 key (constituent_set + generator), the
wonder_ingest:v2: prefix bump, the one-shot _maybe_rehash_speculative_v2
backfill, the audit-trail recovery via source_path_hash, idempotency
marker, and the skip-without-audit fallback.
@robotrocketscience
robotrocketscience force-pushed the feat/issue-552-skill-layer-integration branch from df016c2 to bb662e6 Compare May 12, 2026 00:26
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:noether:2026-05-12T00:27:37Z]

@robotrocketscience robotrocketscience added the ready-to-merge Trigger merge-train: FF main to this PR's head label May 12, 2026
@github-actions
github-actions Bot merged commit bb662e6 into main May 12, 2026
27 checks passed
@github-actions github-actions Bot removed the ready-to-merge Trigger merge-train: FF main to this PR's head label May 12, 2026
@github-actions

Copy link
Copy Markdown

merge-train: merged bb662e6main via FF push.

@robotrocketscience

Copy link
Copy Markdown
Owner Author

LGTM — option-2 dedup matches the locked #644 decision exactly.

Spot-checked the diff against the spec ("(constituent_set, generator) key, content_hash migration, version bump"):

  • _constituent_key(constituent_belief_ids, generator) with wonder_ingest:v2: prefix at src/aelfrice/wonder/lifecycle.py:46 — spec match.
  • _maybe_rehash_speculative_v2 at src/aelfrice/store.py:1357 is idempotent via SCHEMA_META_SPECULATIVE_HASH_V2_COMPLETE, inlined to avoid the store ← wonder.lifecycle circular import (good call — keeps the migration algorithm frozen at the marker point), and runs after the content_hash UNIQUE swap so any same-key collisions surface cleanly.
  • Generator recovery via source_path_hash.rsplit('@', 1)[0] handles generators containing '@'. Rows without a wonder_ingest corroboration row are skipped rather than failed — wonder_gc retires them within the 14-day TTL anyway, so no orphans accumulate.
  • Migration safety: under v1, two beliefs sharing a constituent set would have collapsed on insert (generator-agnostic key), so rehash can't produce two v2 rows with the same (constituent_set, generator) tuple. UNIQUE(content_hash) holds.
  • Test coverage in 5892aae (generator-keyed dedup at the lifecycle layer) + 0be4bde (migration). 3370 existing tests pass unchanged.

CI: all required checks green. pytest 3.12 + 3.13 pass.

Blocker for merge-train: branch is behind main (base af6be7e, current main a34f6c0). Bot can only push pre-signed commits, not rebase. Two earlier auto-attempts at 00:21 / 00:25 hit typos (now fixed in bb662e6) then the FF check.

To ship:

cd ~/projects/aelfrice && git fetch github main
git checkout feat/issue-552-wonder-axes-skill-dispatch  # or whatever the branch is
git rebase github/main && git push --force-with-lease
gh pr edit 644 --add-label ready-to-merge

Releasing review claim.

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:noether:2026-05-12T00:29:29Z]

yoshi280 pushed a commit that referenced this pull request May 12, 2026
…v2 key

The 'Known dedup behaviour' note in src/aelfrice/slash_commands/wonder.md
described the pre-#644 contract (constituent-IDs-only key, N-1 axis rows
collapsed to skipped) and pointed at #644 as a future follow-up. #644 has
shipped on github/main: lifecycle._wonder_ingest_idempotency_key now hashes
sorted constituent IDs PLUS the generator string with prefix
'wonder_ingest:v2:', and an N-axis dispatch persists as N distinct phantoms.

Update the slash doc to reflect the shipped contract.
yoshi280 pushed a commit that referenced this pull request May 12, 2026
…v2 key

The 'Known dedup behaviour' note in src/aelfrice/slash_commands/wonder.md
described the pre-#644 contract (constituent-IDs-only key, N-1 axis rows
collapsed to skipped) and pointed at #644 as a future follow-up. #644 has
shipped on github/main: lifecycle._wonder_ingest_idempotency_key now hashes
sorted constituent IDs PLUS the generator string with prefix
'wonder_ingest:v2:', and an N-axis dispatch persists as N distinct phantoms.

Update the slash doc to reflect the shipped contract.
robotrocketscience added a commit that referenced this pull request May 13, 2026
Bump pyproject version 2.1.0 → 3.0.0. uv.lock refreshed.

Substrate landed across the v3.0 cut:
- Wonder consolidation #542 (all sub-issues closed)
- Wonder/reason agentmemory parity #645
- HRR persistence umbrella #553 (PR #714)
- Type-aware compression #434
- Federation read-only mechanics #650 (scope #688, promote/demote
  #689, peer-aware reason #690, transport #655)
- Phantom promotion #550 (PR #616), wonder dispatch #552 (PR #644)
- v3.0 design decisions ratified: PHILOSOPHY #605, sentiment-hook
  #606, multimodel defer #607, federation read-only #661
- Eval-judge κ calibration #687 (judge-driven; bench captures
  deferred under attn:bench-needed)

Bench-only items #152, #592, #697 remain `attn:bench-needed` and
are ratified-deferred per the milestone-tracker DoD.

Refs #608.
@robotrocketscience
robotrocketscience deleted the feat/issue-552-skill-layer-integration branch May 14, 2026 04:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

attn:review Needs review (PR open, awaiting reviewer) author-Faraday PR coordination mutex

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[v2.1] E4: skill layer integration — subagent dispatch → wonder_ingest (#542 sub-task)

1 participant