diff --git a/Makefile b/Makefile index 5810906..06297f4 100644 --- a/Makefile +++ b/Makefile @@ -155,11 +155,11 @@ burn-kindergarten: bootstrap ## bust providence rows < SECONDS old [SECONDS=3600 # Multi-source RAG query against the shard cluster. # Usage: make query Q="What is anarcho-capitalism?" QUERY_TOP_K ?= 8 -query: bootstrap ## ask the corpus a question [JSON=1 BURN=1]; sources picked across shards +query: bootstrap ## ask the corpus a question [JSON=1 BURN=1 REPAIR=1 REPROMPTS=N]; sources picked across shards @if [ -z "$$Q" ] && [ -z "$(Q)" ]; then \ - echo "usage: make query Q=\"your question\" [JSON=1 BURN=1]"; exit 2; \ + echo "usage: make query Q=\"your question\" [JSON=1 BURN=1 REPAIR=1 REPROMPTS=N]"; exit 2; \ fi - $(ABORIST) --shards-dir $(SHARDS_DIR) query --top-k $(QUERY_TOP_K) $(if $(JSON),--json,) $(if $(BURN),--burn,) "$(Q)" + $(ABORIST) --shards-dir $(SHARDS_DIR) query --top-k $(QUERY_TOP_K) $(if $(JSON),--json,) $(if $(BURN),--burn,) $(if $(REPAIR),--repair,) $(if $(REPROMPTS),--repair-reprompts $(REPROMPTS),) "$(Q)" query-dry: bootstrap ## like 'make query' but skip the LLM call (dry-run) [JSON=1 BURN=1] @if [ -z "$$Q" ] && [ -z "$(Q)" ]; then \ diff --git a/aborist/cli.py b/aborist/cli.py index cc25026..797691a 100644 --- a/aborist/cli.py +++ b/aborist/cli.py @@ -343,12 +343,20 @@ def _cmd_query(args: argparse.Namespace) -> int: ) single_db = None if shards_dir else args.db - # Apply per-call policy overrides (question_dedup) on top of the - # default. fidelity is a function-level kwarg, not in the policy. + # Apply per-call policy overrides (question_dedup, repair) on top of + # the default. fidelity is a function-level kwarg, not in the policy. from aborist.qa.query import DEFAULT_QUERY_POLICY call_policy = dict(DEFAULT_QUERY_POLICY) if getattr(args, "question_dedup", None): call_policy["question_dedup"] = args.question_dedup + if getattr(args, "repair", False): + # Mechanical-only repair when --repair is set; --repair-reprompts + # adds the optional re-prompt tier on top. Both default off so + # `make query` stays single-shot unless a knob is flipped. + call_policy["repair_enabled"] = True + call_policy["repair_max_reprompts"] = max( + 0, int(getattr(args, "repair_reprompts", 0)) + ) result = query( question=args.question, @@ -1057,7 +1065,15 @@ def _cmd_burn_kindergarten(args: argparse.Namespace) -> int: from aborist.store import discover_shards now = int(_time.time()) - cutoff = now - max(0, args.kindergarten_seconds) + # `kindergarten_seconds <= 0` means "no time gate — burn every live + # row" per the verb's docstring. The earlier `cutoff = now - 0` + # treated 0 as a same-second-only window, which made + # test_burn_kindergarten_zero_seconds_burns_everything timing-flaky: + # if the wall-clock second rolled over between seed and burn, + # cutoff > seed.created_at and nothing matched. + kindergarten_seconds = max(0, args.kindergarten_seconds) + no_time_gate = kindergarten_seconds == 0 + cutoff = 0 if no_time_gate else now - kindergarten_seconds shards_dir = Path(args.global_shards_dir) if args.global_shards_dir else None single_db = Path(args.db) if args.db else None paths: list[Path] = ( @@ -1074,13 +1090,21 @@ def _cmd_burn_kindergarten(args: argparse.Namespace) -> int: for sp in paths: c = connect(sp) try: - rows = c.execute( - "SELECT cache_key, created_at, audit_mode " - "FROM providence_cache " - "WHERE created_at >= ? AND falsification_state = 'live' " - "ORDER BY created_at DESC", - (cutoff,), - ).fetchall() + if no_time_gate: + rows = c.execute( + "SELECT cache_key, created_at, audit_mode " + "FROM providence_cache " + "WHERE falsification_state = 'live' " + "ORDER BY created_at DESC" + ).fetchall() + else: + rows = c.execute( + "SELECT cache_key, created_at, audit_mode " + "FROM providence_cache " + "WHERE created_at >= ? AND falsification_state = 'live' " + "ORDER BY created_at DESC", + (cutoff,), + ).fetchall() finally: c.close() for r in rows: @@ -2856,6 +2880,25 @@ def build_parser() -> argparse.ArgumentParser: "audit event." ), ) + query_cmd.add_argument( + "--repair", action="store_true", + help=( + "enable mechanical repair after first verify (off by " + "default). When the verdict is HYBRID/UNGROUNDED, applies " + "synthetic_elision split, trailing_artifact trim, and " + "no_overlap drop deterministically; persists the repaired " + "answer with a providence_repair audit event." + ), + ) + query_cmd.add_argument( + "--repair-reprompts", dest="repair_reprompts", type=int, default=0, + help=( + "max LLM re-prompt iterations after mechanical repair " + "(default 0 = no re-prompt). Each iteration sends a feedback " + "turn naming failed quotes; the model is asked to rewrite " + "using only verbatim citations. Requires --repair." + ), + ) query_cmd.set_defaults(func=_cmd_query) inspect_cmd = sub.add_parser(