FreshCtx 如何在 AI 智能體派發前阻止過期計劃
Stale Plan Protection in an AI Agent: How FreshCtx Catches Context Drift Before Dispatch
FreshCtx 是一個 Python 函式庫,會在規劃前為相關上下文欄位建立指紋,並在派發前重新驗證;指紋變更便阻止派發。測試中,狀態由可用變為不可用會阻止派發;只更新無關欄位 last_modified_by,指紋則不變,派發仍可通過。StareBrain 在派發 Intent.ACTION_INSERT 前檢查指紋,之後由系統日曆開啟預填內容,再由用戶手動儲存。
The Problem
An AI agent builds a plan from the context it sees at planning time. By the time the user confirms and the action dispatches, that context may have changed.
A slot that was available is now taken. A contact that existed has been removed. A setting that was on is now off.
If the agent doesn’t revalidate before dispatch, it executes a plan built on stale information — and the user confirmed something that no longer reflects reality.
FreshCtx
FreshCtx is a Python library built to catch this. The core idea: fingerprint the relevant fields of a context record before planning, then revalidate that fingerprint immediately before invocation. If the fingerprint has changed, block the dispatch.
`import hashlib
import json
from pathlib import Path
def fingerprint(slot_path: str) -> str:
record = json.loads(Path(slot_path).read_text())
# Only fingerprint fields that matter for this action
relevant = {"slot_id": record["slot_id"], "status": record["status"]}
return hashlib.sha256(
json.dumps(relevant, sort_keys=True).encode()
).hexdigest()[:16]`
The key design decision: field-level fingerprinting, not record-level. You don’t want a dispatch blocked because an unrelated field changed — a scheduler tag updated, a last-modified timestamp rotated. You want it blocked when the fields your action depends on have changed.
Three-Path Comparison
We ran a fixture with three evidence files against the same base:
`# v1 — base state
v1 = {
"slot_id": "slot-42",
"time": "2026-10-23T10:00:00Z",
"status": "available",
"last_modified_by": "scheduler-v1"
}
# v2 — status changed (available → unavailable)
v2 = {
"slot_id": "slot-42",
"time": "2026-10-23T10:00:00Z",
"status": "unavailable",
"last_modified_by": "scheduler-v1"
}
# v3 — unrelated field changed (scheduler-v1 → scheduler-v2)
v3 = {
"slot_id": "slot-42",
"time": "2026-10-23T10:00:00Z",
"status": "available",
"last_modified_by": "scheduler-v2"
}`
Results:
Comparison | Fingerprint match | Outcome
v1 → v2 | ed3a5680 vs 94c87157 | BLOCKS
v1 → v3 | ed3a5680 vs ed3a5680 | PASSES
Unprotected | — | EXECUTES regardless
v1 and v3 produce identical fingerprints because last_modified_by is not in the relevant field set. The dispatch only blocks when status changes — exactly the field the booking action depends on.
Wiring It Into the Agent
In StareBrain, the calendar action uses Intent.ACTION_INSERT — the system calendar opens pre-filled and the user saves manually. FreshCtx’s boundary sits before the intent is dispatched, not before a write, because StareBrain never makes the calendar write itself.
`// Before dispatching the intent
val currentFingerprint = freshCtx.fingerprint(slotPath)
if (currentFingerprint != plan.capturedFingerprint) {
return DispatchResult.StalePlan(
reason = "slot status changed since plan was built",
capturedAt = plan.capturedAt,
detectedAt = Instant.now()
)
}
// Fingerprint matches — safe to dispatch
dispatchCalendarIntent(plan)`
The capturedFingerprint is stored on the plan at planning time. At dispatch time, FreshCtx recomputes it and compares. If they match, proceed. If they don’t, return StalePlan — the agent surfaces this to the user before anything is dispatched.
Why This Matters
Without stale plan protection, the agent is confident about information it has no business being confident about. The user confirmed a plan. The plan was built on a snapshot. The snapshot is now wrong.
FreshCtx makes the boundary explicit: the plan is valid as of the moment it was built, and that validity is checked again at the last possible moment before dispatch. Everything between planning and dispatch is treated as a window where the world could have moved.
Series so far:
- Part 1: Two-axis state model + DENIED_UNRESOLVED
- Part 2: Context propagation + confidence thresholds
- Part 3: Late confirmation race + terminal state check
- Part 4: Provider capability tiers
- Part 5: Stale plan protection with FreshCtx ← today
來源:DEV Community · dev.to