Saturday at 2 p.m. you promised a working agent demo.
The live model call hangs for forty painful seconds.
Your guest watches a spinner instead of a result.
You do not have a product problem today.
You have a demo problem mixed with network risk.
The model is not the bottleneck you think.
This log cuts the live path on purpose.
You freeze one tool schema and one fixture.
Then you replay the call from disk every time.
The scene you are actually in
You want a tiny weekend agent that answers.
It should call one tool and print one result.
That sentence hides three unstable moving systems.
The model may rename arguments without warning you.
The third-party API may rate-limit your cafe demo.
Your laptop may drop Wi-Fi at the worst moment.
A live demo that needs all three will flake.
Flaky demos teach nothing and burn the hour.
You need a path that fails on your terms.
Why live tool calling flakes on Saturday
Tool calling looks simple in clean architecture slides.
The model emits a function name plus arguments.
Your code then executes that call against something.
Each arrow can fail in a different way.
Wrong key names break your handler immediately.
Wrong types pass JSON and fail one layer later.
A weekend guest cannot debug that stack with you.
They came to see one result, not your traceback.
So you freeze the arrow that leaves the model.
What you keep this weekend
You keep one tool with a frozen JSON schema.
You keep one local handler with no HTTP client.
You keep one fixture file checked into git.
The CLI must print a result from that fixture.
The handler must reject any live URL it sees.
That is the whole working demo this Saturday.
What you cut without apology
You skip the chat UI this weekend completely.
You skip streaming tokens and fake typing indicators.
You skip OAuth, retries, and multi-tool routing logic.
You also skip one live call during the demo.
Live calls belong in a later gated command.
The Saturday demo must run with Wi-Fi off.
The artifact: four small files
Create a folder named replay_demo on disk.
Put four files in it with boring names.
You should read every file in one sitting.
-
schema.jsonholds the frozen tool contract. -
fixture.jsonholds one captured tool call. -
handler.pyholds local logic and no HTTP. -
replay.pyholds the only demo command.
Skip frameworks, agent SDKs, and extra config files.
Guests can follow the files in numbered order.
Step 1: Freeze the tool schema first
Do not ask a model to invent this contract.
You write the schema by hand on purpose.
The model may fill values later, never keys.
{
"name": "lookup_order",
"description": "Return a local order summary",
"parameters": {
"type": "object",
"additionalProperties": false,
"required": ["order_id"],
"properties": {
"order_id": {
"type": "string",
"pattern": "^ORD-[0-9]{6}$"
}
}
}
}
Notice there is no url field present.
There is no api_key field either here.
The contract cannot request the public network.
Step 2: Write the fixture by hand
A fixture is one captured call, not a dump.
Use one object and one allowed order_id value.
You should be able to diff it during review.
{
"tool": "lookup_order",
"arguments": {
"order_id": "ORD-100042"
},
"source": "disk"
}
The "source": "disk" field is your gate.
Replay code must refuse any other source value.
That stops a future you from wiring live calls.
Step 3: Build a handler that cannot fetch
The handler maps order_id to a local dict.
It never imports urllib, httpx, or requests.
If a URL sneaks in, it raises immediately now.
# handler.py
from __future__ import annotations
ALLOWED_PREFIX = "ORD-"
ORDERS = {
"ORD-100042": {"status": "shipped", "item": "USB cable"},
}
def lookup_order(order_id: str) -> dict:
if "://" in order_id:
raise ValueError("live urls are not allowed")
if not order_id.startswith(ALLOWED_PREFIX):
raise ValueError("order_id failed the local prefix")
row = ORDERS.get(order_id)
if row is None:
raise KeyError("unknown order_id in local map")
return {"order_id": order_id, **row}
This file is the demo core, not a toy.
Guests can read it in about thirty seconds.
You can run it with the Wi-Fi fully killed.
Step 4: Replay from disk, not from a model
The replay command loads schema and fixture together.
It validates keys against the frozen schema first.
Then it calls the local handler exactly once.
# replay.py
from __future__ import annotations
import json
import sys
from pathlib import Path
from handler import lookup_order
ROOT = Path(__file__).parent
def load_json(name: str) -> dict:
return json.loads((ROOT / name).read_text())
def validate(schema: dict, fixture: dict) -> None:
if fixture.get("source") != "disk":
raise SystemExit("refusing a non-disk fixture")
if fixture.get("tool") != schema.get("name"):
raise SystemExit("fixture tool does not match schema")
args = fixture.get("arguments") or {}
required = schema["parameters"]["required"]
for key in required:
if key not in args:
raise SystemExit(f"missing argument: {key}")
extra = set(args) - set(schema["parameters"]["properties"])
if extra:
raise SystemExit(f"unknown arguments: {sorted(extra)}")
def main() -> None:
schema = load_json("schema.json")
fixture = load_json("fixture.json")
validate(schema, fixture)
result = lookup_order(fixture["arguments"]["order_id"])
json.dump(result, sys.stdout, indent=2)
sys.stdout.write("\n")
if __name__ == "__main__":
main()
Run it like a normal weekend Python script.
cd replay_demo
python3 replay.py
Expected output stays tiny, boring, and stable.
{
"order_id": "ORD-100042",
"status": "shipped",
"item": "USB cable"
}
If that print works, the demo already works.
You do not need a model for Saturday guests.
You need a contract that you can actually trust.
Step 5: Add one test that kills live paths
Do not test the model this weekend at all.
Test the fence around URLs and source flags.
The replay must reject a live source value.
# test_replay.py
import json
import tempfile
import unittest
from pathlib import Path
import handler
import replay
class ReplayFenceTests(unittest.TestCase):
def test_handler_rejects_urls(self):
with self.assertRaises(ValueError) as ctx:
handler.lookup_order("https://example.invalid/ORD-100042")
self.assertIn("live urls", str(ctx.exception))
def test_replay_rejects_live_source(self):
with tempfile.TemporaryDirectory() as raw:
root = Path(raw)
(root / "schema.json").write_text(Path("schema.json").read_text())
live = {
"tool": "lookup_order",
"arguments": {"order_id": "ORD-100042"},
"source": "live-model",
}
(root / "fixture.json").write_text(json.dumps(live))
old = replay.ROOT
replay.ROOT = root
try:
with self.assertRaises(SystemExit) as ctx:
replay.main()
self.assertIn("non-disk", str(ctx.exception))
finally:
replay.ROOT = old
if __name__ == "__main__":
unittest.main()
Run the tests before you invite any guest.
python3 test_replay.py
Green tests mean the demo cannot wander online.
That is the gate, not a screenshot or vibe.
Broken fences get fixed before any live capture.
Step 6: Prove it with the network off
Turn on airplane mode before the guest arrives.
Run the same replay command one more time.
Confirm the JSON still prints without a network.
python3 replay.py
python3 test_replay.py
If either command waits on DNS, you failed.
Find the import that talks to the network.
Delete that import. Do not patch around it.
This is the working demo you actually show.
Schema, fixture, handler, replay, and two commands.
No spinner. No API key. No surprise bill.
Step 7: Optional live capture after replay is green
Only now do you consider a live model call.
The live path writes a new capture file.
It must not overwrite fixture.json inside git.
A safe layout looks like the tree below.
replay_demo/
schema.json
fixture.json # committed, disk only
live_capture.json # gitignored, optional
handler.py
replay.py
test_replay.py
Put live_capture.json into .gitignore immediately.
Your Saturday demo still reads fixture.json only.
Monday experiments can read a capture file instead.
Keep the capture command optional and clearly later.
# proposal only: never run this during the demo
python3 capture.py --out live_capture.json
The demo script never imports capture.py at all.
That split is the point of this weekend build.
Guests should not even see the capture file.
When you want a hosted model for capture, keep it gated.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
MonkeyCode's free model access and free server option can host that capture step.
They do not replace the disk fixture for Saturday.
Use them after replay, tests, and airplane mode all pass.
If you try that path, reuse the same schema.
Refuse extra keys and refuse URL-shaped arguments.
Write the capture beside the fixture, never over it.
A decision table you can print
Use this table before you add any live call.
| Question | Disk replay | Live model capture |
|---|---|---|
| Network required? | No | Yes |
| Schema can drift? | No, file is frozen | Yes, unless you validate |
| Demo-safe on Saturday? | Yes | No |
| Needs a model call? | No | Yes |
| Good for guests? | Yes | Only as a backup |
| Writes git-tracked files? | Fixture only | Capture file, gitignored |
If two answers land in the live column, stop.
Return to the fixture and cut scope again.
The weekend is for a demo, not a platform.
Limitations, said plainly
This workflow will not train a serious agent.
It will not measure model quality or latency.
It will not replace tests against a real API.
The local ORDERS map is a stub dataset.
The order_id prefix check is a fence only.
You still need auth before any customer traffic.
Schema freeze also has a real product cost.
You will reject useful new fields on purpose.
That is correct for a two-hour demo, not production.
Who should not use this approach
Do not use this if you grade model accuracy.
Do not use this if the live contract is the deliverable.
Do not use this if reviewers must watch live tool calls.
Do not use this for anything that can spend money.
A replay handler should never charge a card.
A fixture should never contain secrets or personal data.
If your weekend goal is a pretty chat skin, skip this.
Build the UI another weekend after the contract holds.
Pretty UIs hide broken tool calls very well.
What you skipped, listed in the open
You skipped a planner that picks among many tools.
You skipped conversation memory and automatic retries.
You skipped tracing, metrics, and prompt versioning work.
You skipped a hosted queue and a worker pool.
You skipped fine-tuning and eval dashboard screens.
You skipped every SDK that demanded an API key first.
Those cuts are the build, not leftover chores.
A weekend demo that does one frozen call is honest.
A weekend demo that almost does ten things is not.
Close the laptop on a green replay
Turn the Wi-Fi off and run python3 replay.py.
If JSON prints, you have a real demo.
If it fails, you have a local bug to fix.
Invite the guest after that print already works.
Walk four files with one command and no spinner.
Ship the disk replay as the only guest path.
Leave live capture for a quieter Monday night.
Top comments (0)