Skip to content

solvi.core.knowledge.failures

Do not repeat an action or a plan that failed recently: a hard check with an expiry and a bound.

Failure memory: do not repeat an action or a plan that failed in the last N steps (or episodes) — a built-in hard check, with an expiry and a bound.

from solvi.core.knowledge import FailureMemory
fm = FailureMemory(window=20, unit="steps", max_blocked=8, min_open=1)
fm.install(cat, plan="plan", then={"plan": "ask_person"})   # a hard check over the given fact "recent_failures"

for step in ...:
    res = system.ask({"situation": s, **fm.given(options=plans_on_offer)})   # the memory as a given fact: replays
    ok = run(res["plan"].answer)
    fm.failed(res["plan"].answer, why="the door stayed shut") if not ok else fm.succeeded(res["plan"].answer)
    fm.step()

Every agent that proposes plans with a model needs this, and writes it by hand (a field report on solvi: "the model proposed the plan that had just failed"). In benchmarks/knowledge/toy_crafting.py a proposer without memory repeated a failure in the same situation 29.3 times per 100 steps, 0.53 behind this check. Here it is data plus a pure check: given() puts the blocked plans into the decision's input, the check reads them, the trace records them, and the decision replays.

Bounded on purpose. A memory that only tightens can block everything (a learned gate fed by its own failures lowers itself episode after episode: benchmarks/knowledge/risk_dungeon.py, arm protect_unbounded). So: - expiry: a failure blocks its plan for window steps (or episodes), then the plan may be tried again; - max_blocked: at most this many plans are blocked at once (the most recent failures); - min_open: given the plans on offer (options), at least this many stay open — the oldest failures among them are let through first; - evidence reopens: succeeded(plan) removes its block at once. Plans are compared by their JSON form (a string, a tuple of actions, a dict).

FailureMemory

FailureMemory(window=10, *, unit='steps', max_blocked=8, min_open=1, store=None)

See the module docstring. window: how long a failure blocks its plan, in unit ("steps" or "episodes"); max_blocked: plans blocked at once at most; min_open: plans on offer that always stay open; store: a KnowledgeStore every failure and success is journaled into.

step

step(k=1)

The clock moves (unit "steps").

new_episode

new_episode()

A new episode (unit "episodes": the clock moves).

failed

failed(plan, *, why=None)

The plan failed now: it is blocked for window.

succeeded

succeeded(plan)

The plan worked: evidence reopens it at once.

blocked

blocked(options=None)

The plans blocked now → {key: {"failed_at", "expires_at", "why", "n"}}: failures within the window, at most max_blocked (the most recent), and — when options (the plans on offer) are given — at least min_open of them left open (the oldest failures among them are let through).

given

given(options=None)

The memory as a given fact for a decision: {"recent_failures": {"unit", "at", "blocked"}}.

check

check(plan, options=None)

A Prediction for trying plan now: "refuse" (hard) when it is blocked, else "accept".

check_function staticmethod

check_function(plan='plan', given=GIVEN, name='not_a_recent_failure')

The hard check as a catalog function of the facts plan and given (names as you choose them).

install

install(cat, *, plan='plan', given=GIVEN, then=None, name='not_a_recent_failure')

Declare the hard check in a catalog: when plan is a recent failure it is False with the reason, and the questions in then get their answers (a constant or a function of facts) — see Catalog.check. → the function.

plan_key

plan_key(plan)

A plan as the memory compares it: a string as it is, anything else as its JSON form (sorted keys).