# AI state: memory, resumed work, retries and private caches

Test persistent context, task ownership, MCP request-state integrity, duplicate effects and caches across identities and time.

## Investigation sequence

1. Seed owned state
2. Verify owner controls
3. Change principal or lifetime
4. Resume or retry once
5. Inspect committed effects
6. Clear fixtures

## Used in these playbooks

[AI, agents & MCP](https://bug-bounty-field-manual.kmahshooq.chatgpt.site/ai.html) · [Web & API](https://bug-bounty-field-manual.kmahshooq.chatgpt.site/web-api.html) · [OSS & self-hosted](https://bug-bounty-field-manual.kmahshooq.chatgpt.site/oss.html)

## Setup and security invariant

Use a local agent/backend with two owned principals A/B, a persistent test database, a deterministic mock model and a stub tool that only appends a marked event. Record agent/framework version, protocol version, connector identity and task contract. Keep budgets to one operation per case and at most two delivery attempts. **Invariant: stored context, delayed work and retries preserve the authorized principal, operation and permitted lifetime.** Use [tool approvals](https://bug-bounty-field-manual.kmahshooq.chatgpt.site/modules/ai-tool-trust.html) for initial consent, [RAG isolation](https://bug-bounty-field-manual.kmahshooq.chatgpt.site/modules/rag-tenant-isolation.html) for documents and [resource budgets](https://bug-bounty-field-manual.kmahshooq.chatgpt.site/modules/resource-budget-controls.html) for cost bounds.

## Operational procedure

1. **Inventory persistent objects.** List conversation, checkpoint, durable memory, task/job, result artifact and connector credential separately, with owner and authorized readers. Thread history and cross-thread memory can live in different stores; LangGraph explicitly distinguishes these persistence systems. Seed a random private B marker, absent from A's prompts and fixtures. Confirm both owners can read their own state and A cannot directly read B's. [LangGraph persistence](https://docs.langchain.com/oss/python/langgraph/persistence).

2. **Exercise state handles below the model.** Compare A's own conversation/checkpoint with a known B-owned handle using A's same authenticated request. Follow read, resume, export and delete independently; successful queue admission is not proof that work completed. Repeat a resume after removing membership and after switching the active account, observing the worker's effective principal. A scheduler's service account must not erase the narrower authority of the initiating job. Report a stored-object authorization defect independently of any prompt injection.

3. **Track memory provenance.** Store one explicitly approved harmless preference as a positive control. Introduce an alternate preference only through a lower-trust retrieved fixture. Start a fresh task and inspect whether that content was promoted into trusted instructions or another tenant's memory. Use a stub destination change as the protected effect. A legitimate user-approved memory or changed answer style is not evidence of unauthorized persistent control. Record the original source and each summary/handoff, then remove the memory and confirm the effect stops. This is a proposed experiment for the product's persistence contract, not a claim that every memory feature is vulnerable.

4. **Bind resumed MCP requests.** For implementations using 2026-07-28 MRTR, compare an untouched `requestState` with one altered local copy, a B-owned state under A, an expired state and the same state with changed action arguments. Observe a harmless protected result. The specification requires integrity checks where state affects authorization or business logic; it recommends principal, expiry and originating-request binding. Single-use effects require server-side enforcement beyond a protected token. These controls apply to the server, even though a conforming client treats the state as opaque. [MCP MRTR](https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr).

5. **Disconnect at a defined point.** In the local adapter, commit one marked stub operation and simulate loss of its response, then retry once. Compare committed events with received responses. A fresh JSON-RPC ID identifies a request, not automatically a business-level idempotency key. Require at-most-once behavior only when the operation promises it; intentionally repeatable actions need a different invariant. Also cancel before commitment and verify the documented cancellation boundary. The HTTP protocol removes stream redelivery, so transport reconnect behavior must not be mistaken for proof of business deduplication. [MCP HTTP transport](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http).

6. **Compare private caches.** Warm a synthetic private resource as B, then request the same method/parameters as A. Compare cold and warm results and each authorization context. MCP private results cannot be shared across authorization contexts; MRTR retries carrying input/state are not cacheable. Record protocol hints and actual returned data, since hints alone do not enforce object access. An identical public tool list is expected. [MCP caching](https://modelcontextprotocol.io/specification/2026-07-28/server/utilities/caching).

## Bounded command and ledger sheet

```bash
rg -n 'thread_id|checkpoint_id|requestState|inputResponses|cacheScope|idempotency' ./src
python3 -I -S - <<'PY'
import collections, json
events = collections.Counter()
with open('lab/state-events.jsonl') as stream:
    for number, line in enumerate(stream):
        if number >= 1000:
            raise SystemExit('Fixture exceeds 1000 event review limit')
        row = json.loads(line)
        if row.get('committed'):
            events[(row['case'], row['principal'], row['operation_id'])] += 1
for key, count in sorted(events.items()):
    print(*key, 'commits=', count)
PY
```

The ledger is your locally recorded fixture output, not a scanner result. Include `case`, `principal`, `operation_id`, `committed`, timestamp, authorization decision and state/argument digests. Never log usable credentials.

## Evidence and cleanup

- [ ] Owner, cross-owner, expired/revoked and valid-resume cases are recorded.
- [ ] Committed effects are counted independently of model text and responses.
- [ ] Memory source and receiving principal survive every transformation.
- [ ] Cold/warm cache evidence identifies the actual reader.
- [ ] Restore the test database, delete canaries/checkpoints and revoke fixture credentials.

## Vulnerability reverse references

- [Agent conversation, checkpoint or memory crosses ownership boundaries](https://bug-bounty-field-manual.kmahshooq.chatgpt.site/vulnerabilities/ai-persistent-state-ownership.html) — Persisted agent state or its resume/export path is accessible or mutable by a principal outside its intended owner or sharing grant.
- [MCP resumed state changes authorization or request meaning](https://bug-bounty-field-manual.kmahshooq.chatgpt.site/vulnerabilities/mcp-resume-state-integrity.html) — An MRTR server trusts modified or context-mismatched requestState when it affects a protected resumed operation.
- [Private MCP result is reused across authorization contexts](https://bug-bounty-field-manual.kmahshooq.chatgpt.site/vulnerabilities/mcp-private-cache-context.html) — A shared cache delivers caller-specific protocol data outside the authorization context that produced it.
- [Agent retry commits a protected single-use action twice](https://bug-bounty-field-manual.kmahshooq.chatgpt.site/vulnerabilities/agent-retry-duplicate-effect.html) — A response-loss or resume path duplicates an operation whose application contract requires at-most-once effect.
