DEV Community

Quinn Li
Quinn Li

Posted on

Letter to 14:07-Me: Replay the Tool Trace, Not the Chat

Dear 14:07-Me,

You will waste a day on one agent loop.
The chat log will look complete and polite.
The remote tools will not match that story.

This letter is a postmortem template.
It is not a victory lap.
Treat every command below as a labeled example.

The scene you walk into

A ticket asks for a small API helper.
You paste the spec into a coding agent.
You let it call tools against a shared box.

By 18:00 the helper still flakes.
You reread the chat instead of the wire.
That is the first expensive habit.

Tool calling is not a conversation.
It is HTTP with a model in the middle.
If the envelope is missing, the day is gone.

The three mistakes that cost the day

Mistake 1: The tool schema kept moving

You edited the function description mid-loop.
The model then called a field you had renamed.
Retries looked like model noise. They were schema drift.

A renamed property is not a smarter prompt.
It is a broken contract with yesterday's calls.
Hash the schema, or you will debug ghosts.

Mistake 2: Mutations had no idempotency key

The agent posted the same resource twice.
Your debug run became production-shaped side effects.
You spent hours cleaning duplicate rows, not prompts.

A second POST is not extra evidence.
It is a second write with a new identity.
Without a key, replay is vandalism.

Mistake 3: You debugged prose, not envelopes

The model summarized a 200 as success.
The body failed your contract on id.
Chat text cannot replay. A JSONL file can.

English is a lossy codec for tool I/O.
Status, hash, and body survive. Summaries do not.
Close the thread until the file checks out.

What this letter will give you

A pinned schema file.
A one-line tool envelope.
A replay command that needs no model.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

I mention MonkeyCode only as the remote runner.
The project is open source.
Operator notes list free model access and a free server option.
Those notes do not define quotas, hardware, or uptime.
Remove the product name. The method still holds.

Pin the schema before any loop

Do this on your laptop.
Do not start the agent yet.

  1. Write the tool list to tools.schema.json.
  2. Hash the file. Store the hash in the ticket.
  3. Refuse any run whose hash does not match.

Example schema, labeled as a sample, not a live API:

{
  "name": "create_report",
  "method": "POST",
  "path": "/v1/reports",
  "required": ["title", "idempotency_key"],
  "properties": {
    "title": { "type": "string", "minLength": 1, "maxLength": 120 },
    "idempotency_key": { "type": "string", "pattern": "^[a-f0-9-]{36}$" }
  }
}
Enter fullscreen mode Exit fullscreen mode

Check the hash with a boring command.

sha256sum tools.schema.json > tools.schema.sha256
cat tools.schema.sha256
Enter fullscreen mode Exit fullscreen mode

If the agent rewrites the schema, the hash breaks.
You stop. You do not "just retry".
Mid-loop schema edits are how 14:07 becomes 18:00.

Keep a second copy outside the agent workspace.
Agents rewrite nearby files when stuck.
Your source of truth should not sit in that blast radius.

Wrap every tool call in one envelope

Chat is not a protocol.
Your envelope is.

Example envelope for one mutating call:

{
  "ts": "2026-09-23T14:07:00Z",
  "schema_sha256": "REPLACE_WITH_HASH",
  "tool": "create_report",
  "idempotency_key": "11111111-1111-4111-8111-111111111111",
  "request": { "title": "daily-trace" },
  "response": {
    "status": 201,
    "body": { "id": "rpt_01" }
  }
}
Enter fullscreen mode Exit fullscreen mode

Rules you will keep:

  1. One envelope per tool call. No bundles.
  2. Mutating tools always send idempotency_key.
  3. Responses store raw status and raw body.
  4. Append-only JSONL. Never edit a past line.

Name the file trace.jsonl.
One object per line. No pretty-print across lines.
Pretty JSON is for humans. JSONL is for replay.

Redact secrets before the line is written.
Authorization headers do not belong in traces.
If a token appears, delete the file and rotate it.

Numbered workflow for the next run

Follow these steps in order.
Skip none of them.

  1. Freeze tools.schema.json and print its hash.
  2. Copy the schema hash into the ticket comment.
  3. Create an empty trace.jsonl on the remote box.
  4. Start the agent only after both files exist.
  5. Reject any tool call missing the envelope fields.
  6. Replay the JSONL with a local checker.
  7. Open the chat only if the replay fails.

Proposed checker (unexecuted sample):

# replay_trace.py — sample harness, not production code
import json, sys, hashlib, pathlib

REQUIRED = ("ts", "schema_sha256", "tool", "idempotency_key", "request", "response")

def load_schema_hash(path):
    data = pathlib.Path(path).read_bytes()
    return hashlib.sha256(data).hexdigest()

def main(trace_path, schema_path):
    expected = load_schema_hash(schema_path)
    seen_keys = set()
    errors = []
    with open(trace_path) as fh:
        for i, line in enumerate(fh, 1):
            line = line.strip()
            if not line:
                continue
            row = json.loads(line)
            missing = [k for k in REQUIRED if k not in row]
            if missing:
                errors.append(f"line {i}: missing {missing}")
                continue
            if row["schema_sha256"] != expected:
                errors.append(f"line {i}: schema hash drift")
            key = (row["tool"], row["idempotency_key"])
            if key in seen_keys:
                errors.append(f"line {i}: duplicate idempotency key")
            seen_keys.add(key)
            status = row["response"].get("status")
            if not isinstance(status, int):
                errors.append(f"line {i}: status is not an int")
            body = row["response"].get("body") or {}
            if row["tool"].startswith("create") and status not in (200, 201):
                errors.append(f"line {i}: unexpected status {status}")
            if row["tool"].startswith("create") and status in (200, 201):
                if not isinstance(body.get("id"), str) or not body["id"]:
                    errors.append(f"line {i}: create returned no id")
    if errors:
        print("\n".join(errors))
        sys.exit(1)
    print(f"ok {len(seen_keys)} unique calls")

if __name__ == "__main__":
    main(sys.argv[1], sys.argv[2])
Enter fullscreen mode Exit fullscreen mode

Run it like this:

python replay_trace.py trace.jsonl tools.schema.json
Enter fullscreen mode Exit fullscreen mode

If this exits non-zero, do not prompt again.
Fix the envelope. Then rerun the checker.
The model cannot patch a missing id field with nicer prose.

Add a hard stop around the loop itself.
A shell wrapper is enough for a lab box.

# labeled example: stop after 20 envelopes
if [ "$(wc -l < trace.jsonl)" -ge 20 ]; then
  echo "trace cap hit" >&2
  exit 2
fi
Enter fullscreen mode Exit fullscreen mode

Twenty lines is arbitrary on purpose.
Pick a cap before the agent starts.
Do not negotiate the cap with the model.

Decision table for the afternoon

Signal You assumed Check instead Next action
Chat says "created" Resource exists status plus body id Replay JSONL
Second retry "fails" Model is flaky Duplicate idempotency_key Inspect store, not prompt
Field missing in body Prompt too weak Schema hash changed Restore tools.schema.json
429 from the API Need a bigger model Loop has no backoff cap Stop the loop
Free server feels slow Hardware is the bug Trace has N identical POSTs Deduplicate keys
201 with empty id Serializer bug later Envelope body is already wrong Fail replay, do not continue

Read the table before you change the prompt.
Most of those rows are I/O bugs.
Prompt edits do not restore a hash or a key.

Reconstruct the burned day in order

Here is the same afternoon as a timeline.
Use it when you start to reread the chat.

  1. 14:07 — schema file is still a draft in the prompt.
  2. 14:31 — first POST succeeds. No envelope is written.
  3. 15:10 — you rename title to name in the tool text.
  4. 15:44 — retries hit the old field. Errors look random.
  5. 16:20 — you rerun the same create. Duplicates appear.
  6. 17:35 — the model says the resource exists. The id does not.
  7. 18:02 — you still have no JSONL. The day is spent.

Each hour had a cheaper check.
None of those checks required a larger model.
They required a file the agent could not narrate away.

How the free server fits, without magic

A coding agent on your laptop mixes two risks.
Tool side effects. Untrusted generated commands.

A separate free server keeps the laptop quieter.
It does not make the trace optional.
It does not make the schema frozen.

Copy only the schema, the checker, and the empty trace.
Do not copy your laptop credentials.
Do not mount your home directory into that box.

# labeled example: ship the protocol, not your machine
scp tools.schema.json replay_trace.py box:~/run/
ssh box 'touch ~/run/trace.jsonl && wc -l ~/run/trace.jsonl'
Enter fullscreen mode Exit fullscreen mode

If you try MonkeyCode's free models on that server, keep the same envelope.
Same hash. Same JSONL. Same replay.
The vendor is not the protocol.

Do not paste secrets into the prompt or the trace.
Redact tokens before you copy files off the box.
A free server is still a shared disk with logs.

Limitations

This method does not prove business correctness.
A 201 can still store a wrong title.
Replay only proves the envelope was consistent.

JSONL is not an audit system.
Anyone with disk access can rewrite it.
Sign the file if you need a stronger claim.

# labeled example: detach a copy you can compare later
cp trace.jsonl "trace-$(date -u +%Y%m%dT%H%M%SZ).jsonl"
sha256sum trace-*.jsonl
Enter fullscreen mode Exit fullscreen mode

Free model access can change without notice.
A free server is not a compliance boundary.
Do not put regulated data on it.

Idempotency keys need server support.
If the API ignores the key, duplicates remain.
Test that path with two identical envelopes.

The checker above does not call the live API.
It only reads what you recorded.
A silent tool that never writes JSONL will look like success.

Clock stamps in the envelope are metadata.
They do not freeze remote state.
Do not treat ts as proof the resource still exists.

Who should not use this approach

Skip this if you cannot write a schema file.
Skip this if the API has no replayable HTTP surface.
Skip this for production incident response under a clock.

Do not use a shared free server for customer PII.
Do not use it as your only backup.
Do not treat chat summaries as proof.

Skip this if your tools are purely local side-effect storms.
File deletes and package publishes need stronger isolation.
An envelope does not replace a sandbox.

Skip this if nobody will read trace.jsonl on failure.
Unused protocol files become another prompt toy.
The checker only works if you stop when it fails.

Close the letter

14:07-Me, stop rereading the dialogue.
Hash the schema. Append the envelope. Replay the file.
That sequence is the whole day, recovered.

If you later try the free server path, take the checker with you.
Leave the chat closed until replay_trace.py prints ok.

Top comments (0)