Storage + tools so Decide mode can learn instead of one-shot tie-breaking: log the call Brian makes, resolve it later with the outcome, recall similar past calls to ground new recommendations in his own track record. - memory: decisions table + Decision dataclass + log/resolve/get/list/recall_decisions (embedding over situation+choice; embed failure never blocks a log) - tools: log_decision / resolve_decision / recall_decisions handlers + specs - tests: 9 covering roundtrip, resolve, open-only filter, similarity rank, tool layer Data layer only — NOT wired into any mode's allow-list or the Decide card yet (the prompt/taste part is left for Brian; see docs/DECISION_LOG.md). No behavior changes until the tools are added to _DECIDE_TOOLS. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2.5 KiB
Decision log — Decide mode's learning layer
Built overnight on feat/decision-log. This is the data layer + tools only. The
prompt/mode wiring (the taste part) is left for you on purpose — no persona/card edits
were made.
The idea
Decide mode is currently a one-shot tie-breaker. The learning layer gives it memory: log the call Brian actually makes, record how it turned out, and recall similar past calls so a new recommendation leans on his own track record instead of generic advice.
Lifecycle: log (when the call is made) → resolve (later, with the outcome) → recall (next time something similar comes up).
What's built
Storage (lyra/memory.py):
decisionstable — situation, options, choice, rationale, confidence (1-5), tags, embedding (over situation+choice), outcome, outcome_rating (-1/0/+1), resolved_at.Decisiondataclass (with a.resolvedproperty).log_decision(...) -> id,resolve_decision(id, outcome, rating) -> bool,get_decision(id),list_decisions(limit, open_only),recall_decisions(query, k)(cosine over embeddings, each hit carries.score).- Embedding failures never block a log (blob just stays NULL).
Tools (lyra/tools.py) — handlers + specs, wired into dispatch:
log_decision(situation, choice, options?, rationale?, confidence?, tags?)resolve_decision(decision_id, outcome, rating?)recall_decisions(query, k?) — returns past calls with their verdicts
Tests (tests/test_decisions.py) — 9, covering roundtrip, resolve, open-only
filtering, similarity ranking, and all three tool handlers. Full suite green, ruff clean.
What's left for you (the wiring)
- Allow-list — add the three tools to
_DECIDE_TOOLSinlyra/modes.py(and decide whetherrecall_decisionsalso belongs in Study). One-liner, but it's the gate that lets her actually call them. - Decide card guidance — tell her when to use them: recall similar decisions before recommending, log once Brian commits to a call, and circle back to resolve open ones. This is the part I didn't want to touch without you (no bandaids).
- Optional surfacing — open/unresolved decisions are a natural thing for her to raise (thought loop / ping), and a small UI panel could list them. Not built.
- Optional auto-prompt to resolve — the dream loop could notice decisions that have been open a while and nudge for an outcome.
Nothing here changes behavior until step 1 — the tools exist but no mode offers them.