Did that export actually stick around after the tool call? Developers keep collapsing three stores into one sentence. I keep hearing the same five claims.
Each claim treats prompt text like a Unix process. That mix causes fake greens in review threads. It also causes missing secrets in child processes.
Want a better mental model for this mess? Measure the store before you trust the chat.
Three stores, one sloppy verb
People still say the agent remembers everything important. Where does that memory actually live, though?
Here are the only stores I care about:
- Prompt tokens are text the model can attend to now.
- Process environment is
environfor one OS process. - Filesystem bytes are whatever
statcan still find.
Those three stores do not update together at all. Why would they ever update in lockstep? A model turn is not an execve call.
A tool call may start a fresh shell. Your laptop is not that remote process. I do not argue this from product folklore. I argue from probes you can run.
Myth 1: Later commands inherit my export
This first myth sounds like Unix 101. It is Unix 101 inside one process tree. That export command changes only the current shell.
The next agent command may be a new shell. Did anyone tell you they were the same PID?
Run this block as one command first.
export FOO=probe-1
echo "same-shell FOO=$FOO pid=$$"
Then run this as a separate command.
echo "later-shell FOO=${FOO-<unset>} pid=$$"
Compare the PIDs from both command blocks. Then compare the FOO values without asking chat. Do not ask the model to recall FOO. Ask the kernel through those echo lines.
Inheritance is only a process tree fact. Chat agreement is not the same as inheritance.
Myth 2: Telling the model sets the runtime
I see this leak in incident writeups. Someone pastes a dummy token into chat. Then a client reads os.environ and gets nothing.
Are you shocked by that empty print? You should not be shocked at all. The prompt is not a putenv call.
The model can quote your stand-in value. The child process still never received it. Spoken memory and process memory can disagree.
Try a harmless stand-in value like FOO. Keep real secrets out of the prompt.
python3 - <<'PY'
import os
print("FOO_in_process=", os.environ.get("FOO", "<unset>"))
PY
Then ask the model what FOO is. Those two answers can diverge quite hard. Which answer ships to production, the quote or environ?
Spoken values live in tokens only. Runtime values live in environ or files.
Myth 3: A prior cd pins the next cwd
Working directory is process state too. cd dies with the shell that ran it. I still catch myself trusting a previous cd src.
Then tests run under /tmp instead. Or they run under $HOME. Or they run in a sandbox root.
Relative imports lie after a lost cd. Relative config paths lie as well. You want both layers, not a story.
pwd
python3 -c "import os; print('py_cwd', os.getcwd())"
Run that pair before path-sensitive work. Did pwd and getcwd match? Did they match the repo you meant?
cwd is per process, every time. Narration about folders is not chdir.
Myth 4: A path in chat means bytes on disk
The model can print a path. It can pretty-print YAML too. Pretty-print is not open(2).
Did the bytes hit disk? Did they hit the disk you think? Did they survive the next turn?
I use a boring stamp file for this.
stamp="$PWD/.state-probe"
date -u +"%Y-%m-%dT%H:%M:%SZ" > "$stamp"
stat "$stamp"
wc -c "$stamp"
cat "$stamp"
On a later turn, stat the same path again. Missing file means the prompt was the store. Present file means the filesystem held it.
Do not guess a vendor persistence policy. Measure that host on that day. A path in chat is a claim. stat is the evidence.
Myth 5: A scratch box is my laptop profile
This myth is social, not technical. People copy bashrc assumptions onto a scratch host. Then PATH differs.
Then python3 differs. Then git identity differs. None of that requires a conspiracy.
I am not claiming any vendor image layout here. I am saying your laptop profile is local folklore. Dump facts from the box you actually have.
echo "user=$(id -un) uid=$(id -u)"
echo "home=$HOME"
echo "shell=$SHELL"
echo "pwd=$(pwd)"
uname -a
command -v python3 || true
command -v git || true
git config --show-origin --get user.email || true
env | sort | sed -n '1,40p'
Read that dump before you trust a toolchain claim. Do not read the model's autobiography of the host. A scratch server is a different machine. Prove sameness with commands, not vibes.
Artifact: a two-turn persistence probe
I want one script and two invocations. I want a table, not vibes. Save this as probe_state.sh.
Treat it as a labeled example. Run it yourself on your host. Do not treat my paste as a vendor certificate.
#!/usr/bin/env bash
# probe_state.sh — labeled example, run it yourself
set -euo pipefail
mode="${1:-now}"
probe_dir="${PROBE_DIR:-$PWD/.probe-state}"
mkdir -p "$probe_dir"
stamp_file="$probe_dir/stamp.txt"
env_file="$probe_dir/last-env.txt"
echo "=== probe mode=$mode pid=$$ ppid=$PPID ==="
echo "pwd=$(pwd)"
echo "user=$(id -un)"
echo "FOO=${FOO-<unset>}"
python3 - <<'PY'
import os, sys
print("py_exe", sys.executable)
print("py_cwd", os.getcwd())
print("py_FOO", os.environ.get("FOO", "<unset>"))
PY
case "$mode" in
write)
date -u +"%Y-%m-%dT%H:%M:%SZ" > "$stamp_file"
export FOO="written-in-write-mode"
echo "FOO=$FOO" > "$env_file"
echo "wrote $stamp_file"
echo "exported FOO in this shell only"
env | sort > "$probe_dir/env-write.txt"
;;
read)
echo "stamp_exists=$([ -f "$stamp_file" ] && echo yes || echo no)"
[ -f "$stamp_file" ] && echo "stamp_body=$(cat "$stamp_file")"
echo "env_file_exists=$([ -f "$env_file" ] && echo yes || echo no)"
[ -f "$env_file" ] && echo "env_file_body=$(cat "$env_file")"
env | sort > "$probe_dir/env-read.txt"
;;
*)
echo "usage: probe_state.sh write|read" >&2
exit 2
;;
esac
Turn A, one command
chmod +x probe_state.sh
FOO=from-wrapper ./probe_state.sh write
Turn B, a later command
./probe_state.sh read
Now fill this table with your output. Hope is not a column.
| Claim | Store you meant | Command that falsifies it | What I saw |
|---|---|---|---|
| FOO survived the next turn | process env |
echo ${FOO-<unset>} in turn B |
|
| FOO was only spoken | prompt tokens | ask the model, then ignore it | |
| stamp survived the next turn | filesystem | stat .probe-state/stamp.txt |
|
| cwd survived the next turn | process cwd |
pwd in turn B |
|
| laptop profile applied here | host identity | id; uname; command -v python3 |
Empty cells mean you did not measure. Empty cells are not greens.
A labeled miss, not a war story
This walkthrough is a constructed example. It is not a customer report. It is not a timing study.
Turn one prints FOO=from-wrapper. The model later says FOO is still set. Turn two pytest cannot see FOO at all.
You blame flaky tests for an hour. The read probe would have shown <unset> immediately. Which store did you actually write?
You wrote a wrapper environment for one PID. You also wrote a sentence into the prompt. You wrote a stamp file if disk worked. Those are three different successes.
Pin state on purpose
Hoping across turns is the bug. Wrapping one process is the fix. If you need FOO and a cwd, pin both in one script.
#!/usr/bin/env bash
# pin_and_run.sh — labeled example
set -euo pipefail
cd "${REPO_ROOT:?set REPO_ROOT}"
# Dummy value only. Do not place real secrets here.
export FOO="${FOO:-unset-on-purpose}"
exec "$@"
Then run work through that wrapper only.
REPO_ROOT="$PWD" ./pin_and_run.sh ./probe_state.sh read
The wrapper is the persistence story. The chat log is not. The model's summary is not. exec keeps one process tree honest.
A workflow that stays honest
I use the same sequence every time state matters.
- Name the store in one short phrase.
- Write a probe that reads only that store.
- Run the probe in the same channel as the work.
- Paste the probe output next to the claim.
- Refuse to continue if they disagree.
Need isolation from your laptop files? Use a scratch host for the probe. Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode provides free model access and a free server option, so I treat that server as an untrusted lab bench for the two-turn script.
I do not treat it as CI. I do not treat it as a password manager. I do not assume files live forever. I re-run ./probe_state.sh read instead.
That is the whole trick. Separate the stores. Then measure.
What this does not prove
This probe does not pin a model. This probe does not pin an image. This probe does not measure latency.
This probe does not replace your CI logs. This probe does not tell you vendor quotas. I am not publishing timings here.
I am not publishing hardware here either. Those numbers go stale fast. A passing read-turn only describes that host, that moment.
Copying the table into Slack is still not a release gate. Green chat text is still not environ.
Who should skip this
Skip this if you already wrap every agent command. One shell script. One explicit cd. One export list. You do not need the pep talk.
Skip this if your agent cannot run shell. The artifact is Unix. Browser-only chat cannot stat.
Skip this if you need secret handling. Do not put real tokens in prompts. Do not write production secrets onto a scratch disk. Use your platform's secret store.
You also need bash and python3 on the host. Missing either tool, the probe stops. Install them first, or skip the python lines.
The corrected sentence
Stop saying the agent remembered. Say which store changed.
Did the prompt echo FOO? That is tokens. Did python3 print FOO? That is environ. Did stat find the stamp? That is disk.
Three memories. One sloppy verb. Pick the memory on purpose.
Run the probe on the host that will execute the work. Your laptop remains guilty until stat disagrees.
Top comments (0)