Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ installable release; see the roadmap in [README.md](README.md).

- **Read-only federation — transport mechanics** ([#655](https://github.com/robotrocketscience/aelfrice/issues/655)). Per the #661 ratification of Option B (read-only federation, sole-writer per scope), aelfrice now opens peer DBs read-only via SQLite `file:...?mode=ro&immutable=1` URI handles and surfaces their FTS5 hits alongside local results. New `src/aelfrice/federation.py` provides `load_peer_deps()` (parses `knowledge_deps.json` at the repo root, with `~`/absolute/relative path resolution and missing-file tolerance), `open_peer_connection(path)`, and `ForeignBeliefError(belief_id, owning_scope)`. `MemoryStore` gains `peer_deps()`, `peer_health()`, `find_foreign_owner(belief_id)`, `assert_local_ownership(belief_id)`, and `search_peer_beliefs(query, limit)` — peer handles are opened lazily and closed on `close()`. `apply_feedback`, `promote`, `devalidate`, `unlock`, and `_cmd_delete` now raise `ForeignBeliefError` (a `ValueError` subclass) when invoked on a foreign id; MCP `tool_feedback` and `tool_unlock` produce dedicated `feedback.foreign_belief` / `unlock.foreign_belief` kinds carrying `owning_scope`. `aelf search` annotates peer hits as `[scope:<name>] <id>: <content>`. `aelf health` gains a `federation peers:` section listing each peer with reachability + scope_id prefix; JSON mode adds a `federation` object. Missing peer files warn rather than crash. Discovery uses `AELFRICE_KNOWLEDGE_DEPS` env override or `<git-toplevel>/knowledge_deps.json`. The `scope` field on beliefs and `aelf promote --to-scope` verb (umbrella #650 sub-tasks) are explicitly out of scope here — #655 lands the transport substrate only. `aelf reason` is not yet peer-aware (graph walks require local edges); tracked as follow-up under #650. Twelve new tests cover the two-scope smoke, foreign-id rejection on each mutation entry point, missing-peer tolerance, peer-scope-id surfacing, alien-SQLite-file resilience, and local-wins-over-peer-collision.

- **`/aelf:wonder --axes` skill-layer dispatch loop** ([#552](https://github.com/robotrocketscience/aelfrice/issues/552)). The published `/aelf:wonder` slash command grows a second mode: when invoked with `--axes "<query>"`, the host agent runs the existing `aelf wonder --axes` CLI (which emits the `{gap_analysis, research_axes, agent_count, speculative_anchor_ids}` JSON), spawns one Task subagent per axis in parallel (each receives that axis's name / description / search_hints / gap_context + the originating query), collects each subagent's research document into a JSONL file with `{axis_name, content, anchor_ids}` rows, and hands the file to a new `aelf wonder --persist-docs FILE` subcommand. The CLI subcommand reads the JSONL, converts each row to a `Phantom` via the new `src/aelfrice/wonder/skill_integration.py` adapter (`SubagentDocument` / `documents_to_phantoms` / `load_documents_jsonl`), and persists via the existing `wonder_ingest`. End-to-end test in `tests/test_wonder_skill_integration_e2e.py` exercises the full flow with a deterministic mock-subagent fixture — no actual subagent spawn required to assert the contract. Sub-task **E4** of the #542 wonder umbrella. **Dedup contract — option 2 (#644).** `wonder_ingest` now keys idempotency on the sorted constituent belief IDs **and** the generator string, so an N-axis `--axes` dispatch over a shared anchor set persists as N distinct phantoms (one per axis) instead of collapsing to the first. Key prefix bumped from `wonder_ingest:` to `wonder_ingest:v2:`. Existing pre-#644 speculative rows are rehashed on first open by `MemoryStore._maybe_rehash_speculative_v2`, which recovers each row's generator from the `wonder_ingest` corroboration audit (`source_path_hash = "<generator>@<score:.4f>"`). The rehash is idempotent via a `schema_meta` marker; rows lacking a wonder_ingest corroboration row are skipped (wonder_gc retires them within the 14-day TTL). The cross-run idempotency contract is preserved — re-running the same dispatch is still a no-op. No discretion-grep regressions; no SDK introduced into aelfrice or bench code (the subagent dispatch lives in the host agent's hands, mediated by a CLI handoff).

- **Type-aware compression A2 recall@k bench gate** ([#434](https://github.com/robotrocketscience/aelfrice/issues/434)). New `tests/bench_gate/test_compression_a2_recall.py` reads `tests/corpus/v2_0/compression_a2_recall/*.jsonl` (lab-side, gitignored on public CI per the directory-of-origin rule) and asserts strict positive `mean_recall@k(use_type_aware_compression=ON) > mean_recall@k(=OFF)` per spec § A2. Distinct from the upstream-invariant gate at `test_compression_uplift.py` — that gate measures "compression reduces total tokens"; this gate measures the recall@k uplift that the `use_type_aware_compression` flip-default decision rides on. New `tests/retrieve_uplift_runner.py::run_compression_a2_uplift` is the corpus-side driver, alongside `run_clustering_uplift` / `run_doc_linker_uplift` / `run_query_strategy_uplift`. Public CI continues to skip when `AELFRICE_CORPUS_ROOT` is unset; gate clears lab-side smoke against the 7-row v0_1 fixture (delta +0.321, runner verdict `strict_a2_pass=True`). Flip-default for `use_type_aware_compression=ON` still requires A4 (rebuilder continuation-fidelity), a separate bench gate against the rebuild_logs corpus — not addressed here.

- **Synthetic `hot_start` fixture for the context-rebuilder eval harness** ([#592](https://github.com/robotrocketscience/aelfrice/issues/592)). A new 14-turn fixture under `benchmarks/context-rebuilder/fixtures/synthetic/hot_start_debugging_session_001.{jsonl,meta.json}` covers the post-compact hot-start scenario from the #587 AC. Pre-fork turns 0..7 establish specific working state (failing test name `test_ingest_jsonl_idempotent`, file `src/aelfrice/ingest.py`, function `_dedup_key`); the fork at turn 8 simulates a `/clear`-induced compact; post-fork user prompts at indices 8, 10, 12 ask "where were we?", "which file/function?", and "what was the verification step?". The new fixture uses the same substring-match convention as `debugging_session_001.meta.json` (eval_turns hold user-role indices, `expected` is the user prompt text). `task_type="hot_start"` is now a first-class bucket in `sweep_thresholds` / `sweep_budgets` summaries, segmenting hot-start fidelity from the cold-start (`debug`) calibration so the AC can be read off the `hot_start` row directly. The calibration verdict itself (≥80% hot-start fidelity per #587) requires the host-agent eval-replay flow (#600) — the fixture lands here; the run is operator-driven and tracked on #592 until it produces a `replay_responses.jsonl` and a sweep report.
Expand Down
91 changes: 91 additions & 0 deletions src/aelfrice/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,63 @@ def _cmd_wonder_gc(args: argparse.Namespace, out: object) -> int:
return 0


def _cmd_wonder_persist_docs(args: argparse.Namespace, out: object) -> int:
"""Ingest subagent research documents from a JSONL file (#552).

Skill-layer integration entry point. After the host agent has
collected one document per axis from a `/aelf:wonder --axes`
dispatch, it writes them to a JSONL file with rows
``{axis_name, content, anchor_ids}`` and invokes this subcommand
to convert + persist.

Prints ``inserted=N skipped=N edges_created=N`` on success;
mirrors the ``--persist`` summary so log-scraping is uniform.
"""
from pathlib import Path

from aelfrice.wonder.lifecycle import wonder_ingest
from aelfrice.wonder.skill_integration import (
documents_to_phantoms,
load_documents_jsonl,
)

path = Path(args.persist_docs)
if not path.exists():
print(
f"aelf wonder: --persist-docs file not found: {path}",
file=out, # type: ignore[arg-type]
)
return 2

try:
documents, anchor_ids = load_documents_jsonl(path)
except ValueError as e:
print(f"aelf wonder: {e}", file=out) # type: ignore[arg-type]
return 2

if not documents:
print(
"aelf wonder: --persist-docs file contains no documents; nothing to ingest",
file=out, # type: ignore[arg-type]
)
return 0

phantoms = documents_to_phantoms(documents, anchor_ids)

store = _open_store()
try:
result = wonder_ingest(store, phantoms)
finally:
store.close()

print(
f"wonder persist-docs: inserted={result.inserted} "
f"skipped={result.skipped} edges_created={result.edges_created}",
file=out, # type: ignore[arg-type]
)
return 0


def _cmd_wonder(args: argparse.Namespace, out: object) -> int:
"""Surface consolidation candidates and (optionally) emit phantoms.

Expand All @@ -1095,6 +1152,11 @@ def _cmd_wonder(args: argparse.Namespace, out: object) -> int:
axes JSON for research-agent dispatch. Mutually exclusive with
``--persist``. If both forms are passed, ``--axes`` wins.

``--persist-docs FILE`` (#552) reads subagent research documents
from a JSONL file and persists them via ``wonder_ingest``. Used by
the ``/aelf:wonder --axes`` skill-layer orchestration; mutually
exclusive with ``--persist``, ``--axes``, and ``--emit-phantoms``.

The graph-walk path packs its computed values into a
:class:`aelfrice.wonder.result.WonderResult` (#656); ``--json``
emits ``dataclasses.asdict(result)``. Human-readable stdout is
Expand All @@ -1111,6 +1173,22 @@ def _cmd_wonder(args: argparse.Namespace, out: object) -> int:
args.axes = query

persist = getattr(args, "persist", False)
persist_docs = getattr(args, "persist_docs", None)

if persist_docs and (
persist
or getattr(args, "axes", None)
or getattr(args, "emit_phantoms", False)
):
print(
"aelf wonder: --persist-docs cannot be combined with "
"--persist / --axes / --emit-phantoms",
file=out, # type: ignore[arg-type]
)
return 2

if persist_docs:
return _cmd_wonder_persist_docs(args, out)

if persist and getattr(args, "axes", None):
print(
Expand Down Expand Up @@ -4399,6 +4477,19 @@ def build_parser(*, show_advanced: bool = False) -> argparse.ArgumentParser:
"Mutually exclusive with --emit-phantoms and --axes."
),
)
p_wonder.add_argument(
"--persist-docs", metavar="FILE", default=None, dest="persist_docs",
help=(
"skill-layer integration (#552): read subagent research "
"documents from a JSONL FILE (one row per axis: "
"{axis_name, content, anchor_ids}), convert to Phantoms, "
"and persist via wonder_ingest. Used by the "
"/aelf:wonder --axes flow after the host has collected "
"subagent responses. Skips graph-walk; ignores --seed / "
"--top. Mutually exclusive with --persist, --emit-phantoms, "
"--axes."
),
)
# gc mode (#549): soft-delete stale speculative beliefs. Promoted from
# a nested sub-subcommand to a flag in #645 so a positional QUERY can
# be added to `aelf wonder` without colliding with `wonder gc`.
Expand Down
31 changes: 28 additions & 3 deletions src/aelfrice/slash_commands/wonder.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,40 @@ description: Surface consolidation candidates and phantom-belief suggestions ove
argument-hint: Optional query (e.g. "about X as it relates to Y") or graph-walk flags (--top 5, --emit-phantoms, --seed <id>, --gc, --persist)
allowed-tools:
- Bash
- Task
- Write
---
<objective>
Two modes, picked from the arguments:

1. **No query → graph-walk consolidation.** Walk the belief graph from a deterministically-picked seed (or `--seed <id>`) and surface ranked consolidation candidates with suggested actions. Use `--emit-phantoms` to print Phantom JSON for offline review or `--persist` to write them to the store via `wonder_ingest`.
2. **With a positional query axes / research flow.** Run gap analysis against the query, generate research axes, and emit a dispatch-payload JSON suitable for the skill layer's research-agent fan-out (see `slash_commands/aelf:wonder` agentmemory-parity flow, #645). The query may carry agent-count shorthand: `quick N-agent`, `deep N-agent`, or bare `N-agent` (e.g. `aelf wonder "quick 2-agent wonder about indentation"` → `agent_count=2`, query `"about indentation"`).
2. **With a positional query (or `--axes "<query>"`) → axes-spawn-ingest research flow.** Run gap analysis against the query, generate research axes, fan out one subagent per axis to produce research documents, then hand the documents back through `wonder_ingest` so each subagent's research lands as a speculative phantom belief anchored to the gap-surface seeds. This is the wonder consolidation dispatch loop (#542 E4 / #552 / #645). The query may carry agent-count shorthand: `quick N-agent`, `deep N-agent`, or bare `N-agent` (e.g. `aelf wonder "quick 2-agent wonder about indentation"` → `agent_count=2`, query `"about indentation"`).
</objective>

<process>
Run: `uv run aelf wonder $ARGUMENTS`
Display the output verbatim. Do not add commentary.
**If `$ARGUMENTS` does NOT contain `--axes`:**

Run: `uv run aelf wonder $ARGUMENTS`. Display the output verbatim. Do not add commentary.

**If `$ARGUMENTS` contains `--axes "<query>"`:**

1. **Get the dispatch payload.** Run `uv run aelf wonder $ARGUMENTS`. Stdout is JSON of shape `{gap_analysis, research_axes, agent_count, speculative_anchor_ids}`. Parse it. If `research_axes` is empty, print the payload and stop — there is nothing to dispatch.

2. **Fan out one subagent per axis.** For each axis in `research_axes`, spawn a Task subagent in parallel (send a single assistant message containing one Task tool use per axis). Each subagent's prompt should include:

* The originating user query (`gap_analysis.query`).
* The axis `name`, `description`, `search_hints`, and `gap_context`.
* Instruction: produce a focused research document (a few paragraphs) that summarises what the subagent found about that axis. Plain text. No need for the subagent to commit code or write files.

3. **Collect responses into a JSONL file.** When all subagents have returned, write `/tmp/aelf-wonder-dispatch-<unix-ts>.jsonl`. One row per axis, shape:

```json
{"axis_name": "<axis.name>", "content": "<subagent response>", "anchor_ids": [<speculative_anchor_ids verbatim from step 1>]}
```

The `anchor_ids` array is identical across all rows — it is the gap-analysis seed set.

4. **Persist.** Run `uv run aelf wonder --persist-docs /tmp/aelf-wonder-dispatch-<unix-ts>.jsonl`. Display the resulting `inserted=N skipped=N edges_created=N` summary verbatim.

**Known dedup behaviour:** `wonder_ingest` keys idempotency on the sorted constituent belief IDs alone. Since every axis row shares the same `speculative_anchor_ids`, only the first row's document persists as a phantom; subsequent rows count as `skipped`. This is current C1 contract behaviour; extending the dedup key to include `generator` so per-axis phantoms can coexist is tracked as a follow-up to #552.
</process>
Loading
Loading