react-tool-agent

A bare-bones tool-using agent on pugmark, and the hero example of the bunch. The whole agent is two match blocks over the session’s append-only log of JSON events:

  1. Replay: walk every prior event and fold it into a conversation history (Anthropic message format).
  2. Decide: inspect the tail event and append one new event.

Crash anywhere and re-running picks up where it left off. Pugmark feeds the full session log on every invocation, so the handler is stateless across runs. Fork mid-run and push a different event to steer the agent down a different branch. Full source on GitHub.

Prerequisites

brew install firetiger-oss/tap/pugmark
pip install pugmark anthropic
export ANTHROPIC_API_KEY=sk-ant-...

Run the hero loop

pugmark switch file:///tmp/pug-react       # point pugmark at this example's bucket
pugmark run --start -- python agent.py

The handler seeds a demo prompt ("What is 17 * 23, then halve it?") on the first invocation when the log is empty; subsequent invocations replay and advance one step. pugmark run loops until the agent calls pugmark.pause(). You should see a sequence like:

user_message โ†’ tool_call(calc, "17 * 23") โ†’ tool_result(391)
            โ†’ tool_call(calc, "391 / 2") โ†’ tool_result(195.5)
            โ†’ assistant_message("โ€ฆ195.5") โ†’ pause

Crash + resume

Each step is its own checkpoint. Kill the agent mid-run (Ctrl-C, kill -9, network drop) and re-running picks up at the next missing step. The log is the agent’s only state, so resuming is just “replay then continue”.

pugmark run -- python agent.py     # interrupt anywhere
pugmark run -- python agent.py     # resumes

Fork mid-run with a substituted event

Branch a session at any snapshot and push a different event to see how the agent reacts. Here we replace the calculator’s first answer with a wrong value and watch the model recover:

pugmark fork -s <session>:3
pugmark push <<<'{"kind":"tool_result","id":"<id from log>","content":"42"}'
pugmark run -- python agent.py
pugmark log -A        # branching tree

The agent’s downstream messages now depend on 42 instead of 391. Same code, different reality, every event recorded.

What it shows

Pugmark primitiveWhere in the code
pugmark.read(), walks the log in ordermain() outer loop
obj.decode_json(), every entry is JSONlast_event = obj.decode_json()
Structural pattern matching on event shapefold_into_messages, event_from_response, main()
pugmark.write({...}), appends one new eventevery terminal in main()
pugmark.pause(reason) to break the run loopterminal states

The whole agent is ~80 lines of Python plus the Anthropic SDK. Everything else (durability, fork/replay, deploy targets) is the pugmark substrate underneath. The exact same agent.py runs as an AWS Lambda function; see lambda-deploy.