The short version
- Agent memory and execution state are different systems. Correct recall does not remove an older queued action.
- OutcomeLock checks trusted outcome evidence immediately before execution and returns proceed, suppress, or blocked.
- A non-zero result stops the executor mechanically and leaves an auditable decision report.
- The tool does not decide whether real-world evidence is true. That trust boundary belongs to the source adapter.
An AI agent can know that a task is finished and still do it again.
That sounds contradictory until the system is split into its real parts. Memory may contain the latest outcome. The planner may even recall it correctly. But an older action can still be sitting in a queue, a retry buffer, a scheduled job, or an approval pipeline that was created before the outcome changed.
The resulting failure is not a hallucination. It is stale execution.
The dangerous failure happens after the task is complete
Imagine an agent preparing a package release. At 16:25 it queues an action to publish version 0.1.1. A separate release process finishes the publication at 16:28. PyPI now shows the wheel and source archive, but the older action remains queued.
If the executor trusts only the plan, it attempts the publication again. The same pattern appears in production deployments, invoice submissions, browser workflows, follow-up emails, data exports, support actions, and personal assistant tasks. The cost can be a harmless conflict response, or it can be a duplicate customer message, payment, deployment, or irreversible state change.
Adding more conversational memory does not close this gap. Semantic memory is useful for recall, but execution needs transactional authority: a stable identity for the work, current evidence about the outcome, and a policy that the executor cannot bypass.
Outcome reconciliation belongs between plan and act
OutcomeLock is a small local Python guard designed for that boundary. It does not generate a plan and it does not ask another model to judge whether the plan feels stale. It compares a supplied plan with structured outcome evidence and produces one of three deterministic decisions.
The distinction matters. Suppress means the system has strong evidence that an action should not run. Blocked means the system cannot safely decide. Both must stop the executor, but for different operational reasons.
Five pieces make the decision reproducible
- A stable work key identifies one logical outcome even when two plans describe it differently.
- A verdict records whether the outcome is completed, superseded, or ambiguous.
- Provenance records the source and an opaque locator instead of copying sensitive documents into the ledger.
- A source timestamp lets the guard compare when the plan was created with when the outcome was observed.
- Confidence and a deterministic content hash make policy inputs explicit and evidence imports idempotent.
Evidence stays in a local SQLite ledger by default. There is no runtime dependency, cloud account, or vector search in the decision path. The useful property is not sophistication. It is that the same inputs produce the same answer and the answer can be inspected later.
{
"schema_version": "1.0",
"observations": [
{
"work_key": "release.outcomelock.v0.1.1.publish.pypi",
"verdict": "completed",
"source": "pypi-json-api",
"locator": "https://pypi.org/pypi/outcomelock/0.1.1/json",
"summary": "PyPI already contains OutcomeLock 0.1.1.",
"observed_at": "2026-07-19T16:28:59.585878Z",
"confidence": 1.0
}
]
}A live proof using PyPI as the source of truth
The public demo uses PyPI's JSON API rather than a mocked completion flag. The API confirms that OutcomeLock 0.1.1 exists, records the upload timestamp, and lists two release files. An adapter converts that observation into the evidence format above.
The queued plan contains two actions: publish 0.1.1 again, and build the genuinely new 0.1.2 version. Running the guard produces two per-action decisions.
[SKIP] SUPPRESS release.outcomelock.v0.1.1.publish.pypi
Work is completed; evidence is newer than the plan.
[OK] PROCEED release.outcomelock.v0.1.2.build
No completion or conflict evidence exists.
EXIT_CODE=2The new build is classified as proceed, but the mixed plan exits with code 2 because it still contains stale work. That is intentional. The executor must not cherry-pick its own interpretation of a partially unsafe plan. The stale action should be removed, the plan regenerated, and the clean plan checked again before execution.
The smallest useful integration pattern
A production integration needs three enforced boundaries. Every executable action needs a work key. Every trusted source needs an adapter that records concise, non-sensitive evidence. Every planner-to-executor path must call the guard and honor its exit code.
pip install outcomelock
outcomelock import evidence.json --db evidence.db
outcomelock guard plan.json --db evidence.db --report report.html
if [ $? -ne 0 ]; then
echo "Execution blocked. Review report.html"
exit 1
fi
./run-approved-plan.shThe same contract works in shell scripts, CI, cron, browser automation, personal assistants, and coding-agent harnesses. OutcomeLock also ships a GitHub Action for guarding generated plans before later workflow steps run.
Fail closed where memory systems tend to guess
Reliable automation needs a conservative answer when evidence disagrees. A newer ambiguous observation blocks an older completion. Equal-time contradictory verdicts block. Evidence below the configured confidence threshold blocks. A missing or mistyped evidence database is an input error, not permission to proceed.
Terminal outcomes are monotonic for one work key. If an outcome genuinely reopens, it receives a new work key. That prevents an old completion record from being silently erased and keeps the history understandable.
What OutcomeLock cannot prove
The guard is deliberately narrow. It cannot prove that a source adapter told the truth. It cannot detect two different work keys that secretly refer to the same real-world task. It cannot protect a compromised SQLite file, and it cannot stop an executor that bypasses the guard.
That boundary is healthy. Source trust belongs to the integration. OutcomeLock's job is to apply the declared evidence policy consistently and expose the reason for every decision. It is not an agent framework, a memory database, or proof that the underlying work was performed correctly.
When this pattern earns its complexity
- Long-running agents whose plans can outlive the state they were based on.
- Retryable release, deployment, browser, and operations workflows with real side effects.
- Human approval systems where an action can remain pending after the user completes it elsewhere.
- Scheduled agents that wake up with cached plans.
- CI pipelines that need to reject stale or unresolved generated execution plans.
A short, single-process script may not need an outcome ledger. A workflow that can wait, retry, cross system boundaries, or create irreversible effects probably does. The decision should be based on side-effect risk, not whether the automation is marketed as AI.
OutcomeLock is open source on GitHub, available from PyPI, and now documented in the portfolio project page.

