{"slug": "stale-plan-protection-in-an-ai-agent-how-freshctx-catches-context-drift-before", "title": "Stale Plan Protection in an AI Agent: How FreshCtx Catches Context Drift Before Dispatch", "summary": "A developer built FreshCtx, a Python library that fingerprints the relevant fields of a context record at planning time and revalidates that fingerprint immediately before an AI agent dispatches an action, blocking the dispatch if the fields have changed. In a three-fixture test, a status change from available to unavailable altered the fingerprint and blocked dispatch, while an unrelated last_modified_by change produced an identical fingerprint and passed. The library is integrated into the StareBrain calendar agent, where it sits before the Intent.ACTION_INSERT dispatch and returns a StalePlan result to surface to the user.", "body_md": "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.\n\nA slot that was available is now taken. A contact that existed has been removed. A setting that was on is now off.\n\nIf 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.\n\nFreshCtx 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.\n\n``` python\n`import hashlib\nimport json\nfrom pathlib import Path\n\ndef fingerprint(slot_path: str) -> str:\n    record = json.loads(Path(slot_path).read_text())\n    # Only fingerprint fields that matter for this action\n    relevant = {\"slot_id\": record[\"slot_id\"], \"status\": record[\"status\"]}\n    return hashlib.sha256(\n        json.dumps(relevant, sort_keys=True).encode()\n    ).hexdigest()[:16]`\n```\n\nThe 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.\n\nWe ran a fixture with three evidence files against the same base:\n\n```\n`# v1 — base state\nv1 = {\n    \"slot_id\": \"slot-42\",\n    \"time\": \"2026-10-23T10:00:00Z\",\n    \"status\": \"available\",\n    \"last_modified_by\": \"scheduler-v1\"\n}\n\n# v2 — status changed (available → unavailable)\nv2 = {\n    \"slot_id\": \"slot-42\",\n    \"time\": \"2026-10-23T10:00:00Z\",\n    \"status\": \"unavailable\",\n    \"last_modified_by\": \"scheduler-v1\"\n}\n\n# v3 — unrelated field changed (scheduler-v1 → scheduler-v2)\nv3 = {\n    \"slot_id\": \"slot-42\",\n    \"time\": \"2026-10-23T10:00:00Z\",\n    \"status\": \"available\",\n    \"last_modified_by\": \"scheduler-v2\"\n}`\n```\n\nResults:\n\nComparison   |   Fingerprint match    |   Outcome\n\nv1 → v2        |   ed3a5680 vs 94c87157 |   BLOCKS\n\nv1 → v3        |   ed3a5680 vs ed3a5680 |   PASSES\n\nUnprotected  |   —                  |  EXECUTES regardless\n\nv1 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.\n\nIn 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.\n\n```\n`// Before dispatching the intent\nval currentFingerprint = freshCtx.fingerprint(slotPath)\n\nif (currentFingerprint != plan.capturedFingerprint) {\n    return DispatchResult.StalePlan(\n        reason = \"slot status changed since plan was built\",\n        capturedAt = plan.capturedAt,\n        detectedAt = Instant.now()\n    )\n}\n\n// Fingerprint matches — safe to dispatch\ndispatchCalendarIntent(plan)`\n```\n\nThe `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.\n\nWithout 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.\n\nFreshCtx 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.\n\nSeries so far:", "url": "https://wpnews.pro/news/stale-plan-protection-in-an-ai-agent-how-freshctx-catches-context-drift-before", "canonical_source": "https://dev.to/starebrain/stale-plan-protection-in-an-ai-agent-how-freshctx-catches-context-drift-before-dispatch-2kb0", "published_at": "2026-10-26 03:39:19+00:00", "updated_at": "2026-10-26 03:42:45.634847+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "ai-safety"], "entities": ["FreshCtx", "StareBrain", "Python"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/stale-plan-protection-in-an-ai-agent-how-freshctx-catches-context-drift-before", "markdown": "https://wpnews.pro/news/stale-plan-protection-in-an-ai-agent-how-freshctx-catches-context-drift-before.md", "text": "https://wpnews.pro/news/stale-plan-protection-in-an-ai-agent-how-freshctx-catches-context-drift-before.txt", "jsonld": "https://wpnews.pro/news/stale-plan-protection-in-an-ai-agent-how-freshctx-catches-context-drift-before.jsonld"}}