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
3 changes: 2 additions & 1 deletion docs/COMMANDS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Commands

Twenty-eight CLI subcommands. The retrieval/feedback ones are also exposed as MCP tools (see [MCP](MCP.md)) and slash commands (see [SLASH_COMMANDS](SLASH_COMMANDS.md)). Lifecycle commands (`setup`, `doctor`, `migrate`, `upgrade`, `uninstall`, etc.) are CLI-only.
Twenty-nine CLI subcommands. The retrieval/feedback ones are also exposed as MCP tools (see [MCP](MCP.md)) and slash commands (see [SLASH_COMMANDS](SLASH_COMMANDS.md)). Lifecycle commands (`setup`, `doctor`, `migrate`, `upgrade`, `uninstall`, etc.) are CLI-only.

```
aelf <subcommand> [args] [options]
Expand All @@ -22,6 +22,7 @@ DB resolves from `$AELFRICE_DB`, then `<git-common-dir>/aelfrice/memory.db` when
| `demote <belief_id>` | Drop a user lock (one tier per call: lock first, then user_validated). Delegates to `unlock` for the lock-drop path so an audit row is always written. |
| `promote <belief_id> [--source user_validated]` | Promote an `agent_inferred` belief to `user_validated`. Alias of `validate`; identical semantics and flags. |
| `validate <belief_id> [--source user_validated]` | Promote an `agent_inferred` belief to a user-validated origin (v1.2+). |
| `confirm <belief_id> [--source S] [--note TEXT]` | Explicit user affirmation: α += 1.0 with source `user_confirmed` (default). Writes to `feedback_history`. Note is printed on success but not persisted. Distinct from `lock` (no freeze) and from implicit retrieval feedback (`used` signal). MCP sibling: `aelf_confirm` (#390). |
| `feedback <belief_id> <used\|harmful> [--source S]` | `used` ⇒ α += 1; `harmful` ⇒ β += 1. Harmful feedback through outbound `CONTRADICTS` threads to user-locks bumps their demotion_pressure; ≥5 ⇒ auto-demote. |
| `resolve` | Sweep unresolved `CONTRADICTS` threads. Picks a winner per precedence (`user_stated > user_corrected > document_recent`) and inserts a `SUPERSEDES` thread. Idempotent. |
| `reason <query> [--seed-id ID]... [--k N] [--depth N] [--budget N] [--fanout N] [--json]` | (v2.0+, #389) Surface a reasoning chain over the belief graph. Default seeds: top-3 BM25 hits over `<query>`; `--seed-id` overrides (repeatable). Walks `expand_bfs` with terminal-tight defaults (depth=2, budget=10, fanout=8). Default output is an indented hop tree with edge-type breadcrumbs and path-scores; `--json` for tooling. Read-only over the graph. |
Expand Down
113 changes: 113 additions & 0 deletions docs/feature-aelf-confirm-cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Feature spec: `aelf confirm` CLI (#441)

**Status:** implementation spec
**Issue:** #441
**MCP sibling:** `aelf_confirm` / `tool_confirm` — shipped in #390
**`tool_confirm` location:** `src/aelfrice/mcp_server.py:432-476`

---

## Purpose

Explicit user affirmation of an existing belief. `aelf confirm <belief-id>` tells
the system "I've checked this belief and it is correct." The signal is stronger
than the implicit "got used" signal emitted by retrieval hooks, because it carries
the user's intent rather than being inferred from continuation behaviour.

---

## Contract

```
aelf confirm <belief-id> [--source SRC] [--note TEXT]
```

| Argument | Required | Default | Notes |
|---|---|---|---|
| `belief_id` | yes | — | The ID of the belief to affirm. |
| `--source` | no | `user_confirmed` | Written to `feedback_history.source`. Override to tag automated scripts. |
| `--note` | no | `""` | Free-text annotation; appears in stdout on success but is **not persisted** to the store. |

Exit codes:

- `0` — applied successfully.
- `1` — unknown belief ID, or store write error.

---

## Output (stdout on success)

```
confirmed <belief-id>: alpha 1.000->2.000, mean 0.500->0.667
```

Fields:

- `prior_alpha -> new_alpha` — raw Beta parameter before and after the update.
- `mean` — `new_alpha / (new_alpha + new_beta)` rounded to 3 d.p., so the
posterior direction is immediately readable without mental arithmetic.

On unknown belief, writes to **stderr** and exits 1:

```
confirm error: unknown belief: <belief-id>
```

---

## Semantics — how `confirm` differs from related verbs

### vs. `aelf feedback <id> used`

`feedback used` is the implicit signal emitted when retrieval hooks observe a
belief was retrieved and then the continuation referenced it. `confirm` is an
*explicit* user affirmation carrying `source="user_confirmed"`, which is
distinguishable in `feedback_history` queries and in the `aelf status` counts.
Same Beta-Bernoulli mechanic (`α += 1.0`); different intent and source label.

### vs. `aelf lock <statement>`

`lock` freezes the belief as user-asserted ground truth (`lock_level=user`),
giving it maximum retrieval priority and protecting it from demotion pressure
until `aelf unlock` is called. `confirm` applies one unit of positive feedback
to the Beta posterior without freezing the belief. Use `lock` when you want the
belief treated as canonical; use `confirm` when you want to nudge the posterior
without the commitment of a ground-truth freeze.

---

## Storage layer

`confirm` calls `apply_feedback(store, belief_id=..., valence=1.0, source=...)`,
which writes one row to **`feedback_history`**. This matches the MCP sibling
`tool_confirm` exactly.

### Reconciliation with issue #441 body

Issue #441 acceptance criterion #2 reads: *"Writes a row to the
`belief_corroborations` table (#190)."* This is incorrect / out of date.

The shipped `tool_confirm` (MCP, #390) writes to `feedback_history` via
`apply_feedback`, not to `belief_corroborations`. The `belief_corroborations`
table tracks *duplicate re-ingests* of the same content-hash from different
sources — a structural dedup concern, not a user-affirmation signal. The CLI
implementation follows the MCP — `feedback_history` only.

---

## Implementation notes

- `_cmd_confirm` in `src/aelfrice/cli.py` mirrors `_cmd_unlock` in structure.
- Calls `tool_confirm` imported from `aelfrice.mcp_server`; the wrapper is the
business logic. No refactor of `mcp_server.py` beyond the import.
- Argparse subparser registered visible (listed in `--help`) as a user-facing
verb, consistent with `unlock` and `promote`.
- Slash command `/aelf:confirm` mirrors `src/aelfrice/slash_commands/unlock.md`.

---

## Provenance refs

- #441 — this issue (CLI port)
- #390 — shipped MCP `aelf_confirm` / `tool_confirm`
- #190 — `belief_corroborations` table (out of scope for confirm)
56 changes: 56 additions & 0 deletions src/aelfrice/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
lock <statement> insert (or upgrade) a user-locked belief
locked [--pressured] list locked beliefs
demote <id> manually demote a lock to none
confirm <id> [--source S] explicitly affirm a belief (bumps Beta-Bernoulli alpha)
feedback <id> <used|harmful> apply one Bayesian feedback event
stats summary of belief / lock / history counts
health structural auditor (orphan threads, FTS5 sync, locked contradictions)
Expand Down Expand Up @@ -1178,6 +1179,40 @@ def _cmd_unlock(args: argparse.Namespace, out: object) -> int:
return 0


def _cmd_confirm(args: argparse.Namespace, out: object) -> int:
"""Explicit user affirmation of a belief. Bumps Beta-Bernoulli alpha by 1.0."""
from aelfrice.mcp_server import tool_confirm

store = _open_store()
try:
result = tool_confirm(
store,
belief_id=args.belief_id,
source=args.source,
note=getattr(args, "note", "") or "",
)
finally:
store.close()

if result.get("kind") == "confirm.unknown_belief":
print(f"confirm error: {result['error']}", file=sys.stderr)
return 1

prior_alpha: float = result["prior_alpha"]
new_alpha: float = result["new_alpha"]
new_beta: float = result["new_beta"]
posterior_mean = new_alpha / (new_alpha + new_beta)
msg = (
f"confirmed {args.belief_id}: "
f"alpha {prior_alpha:.3f}->{new_alpha:.3f}, "
f"mean {posterior_mean:.3f}"
)
if result.get("note"):
msg += f" [{result['note']}]"
print(msg, file=out) # type: ignore[arg-type]
return 0


def _cmd_promote(args: argparse.Namespace, out: object) -> int:
"""Promote agent_inferred -> user_validated. Alias of `aelf validate`."""
return _cmd_validate(args, out)
Expand Down Expand Up @@ -3274,6 +3309,27 @@ def build_parser(*, show_advanced: bool = False) -> argparse.ArgumentParser:
p_unlock.add_argument("belief_id", help="id of the belief to unlock")
p_unlock.set_defaults(func=_cmd_unlock)

# Explicit user affirmation — bumps Beta-Bernoulli alpha without freezing.
p_confirm = sub.add_parser(
"confirm",
help="affirm a belief: bumps posterior toward truth without locking it",
)
p_confirm.add_argument("belief_id", help="id of the belief to affirm")
p_confirm.add_argument(
"--source",
default="user_confirmed",
help=(
"source label written to feedback_history. "
"Defaults to 'user_confirmed'."
),
)
p_confirm.add_argument(
"--note",
default="",
help="optional free-text annotation (printed on success, not persisted)",
)
p_confirm.set_defaults(func=_cmd_confirm)

# promote: user-facing alias of validate. Same handler, same flags.
p_promote = sub.add_parser(
"promote",
Expand Down
21 changes: 21 additions & 0 deletions src/aelfrice/slash_commands/confirm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
name: aelf:confirm
description: Affirm an existing belief — bumps the Beta-Bernoulli posterior toward truth without freezing it (use aelf:lock for ground-truth freeze).
argument-hint: The belief ID to confirm
allowed-tools:
- Bash
---
<objective>
Explicitly affirm a belief: apply one unit of positive feedback (α += 1.0)
via the `user_confirmed` source. The belief's posterior mean moves toward 1
without being locked. Distinct from `aelf:lock`, which freezes the belief as
ground-truth; use `confirm` when you want to nudge the posterior without the
commitment of a lock. Not persisted to `belief_corroborations` — that table
tracks duplicate re-ingests; `confirm` is an explicit user signal written to
`feedback_history`.
</objective>

<process>
Run: `uv run aelf confirm "$ARGUMENTS"`
Display the output verbatim. Do not add commentary.
</process>
Loading
Loading