Tools
Agent Postmortem
OpenClaw plugin: when an agent turn returns nothing, detect it, ask the agent to check what actually landed, and complete what's missing
Install
npm install &&
README
# agent-postmortem
An OpenClaw plugin for the failure where your agent goes quiet.
A run does some work, then returns nothing. You see *"Agent couldn't generate a
response. Note: some tool actions may have already been executed — please verify
before retrying."* Files may be half-written. Nothing tells you which half.
This plugin notices, waits until the provider can answer, and asks the agent to
check the actual state and report back — completing whatever is genuinely
missing. By the time you look, the session says what landed and what didn't.
## Why
Born from running OpenClaw on modest self-hosted hardware. A smaller local model
working a long task will occasionally choke, and you return to a session parked
in a vague error state. The path of least resistance is to type "what happened?"
and let the model explain itself.
The insight: a smaller model is usually perfectly capable of diagnosing its own
failure once prompted, and often lands on the cause. This just does the prompting
for you.
## Behavior
```
run produces no reply → wait until the provider is ready → probe → diagnosis
```
- **Arms on any failure.** No error-string enumeration. It arms unless the run
ended in a clean reply or a deliberate abort, so timeouts, cooldowns, empty
turns, and future error variants are caught by construction.
- **The probe is the readiness check.** While the provider is cooled, OpenClaw
rejects the attempt in ~20ms without contacting it, so retrying is cheap and
can't extend the cooldown. When ready, the same attempt succeeds and delivers.
One act, no separate health check.
- **Your return cancels it.** If you come back and run anything successfully
first, the pending diagnosis is dropped silently. It only fires into a session
you walked away from.
- **It survives its own flakiness.** On a model prone to empty turns, the probe
itself can come back empty. The retry loop rides that out — observed in
testing: probe attempt 1 returned nothing, attempt 2 delivered.
- **Bounded.** One pending per session; gives up after `freshnessMinutes` or
`maxAttempts`.
## What a delivered post-mortem looks like
> Nothing completed because the previous turn failed before any writes occurred.
> I have now added both tasks to `todo/tasks.md`, regenerated the Eisenhower
> matrix, and added coffee and sugar to your priority shopping list.
Note it both **diagnosed and recovered**. That is deliberate — see the prompt
note under Config.
## Classifying failure: what actually works
This is the part worth reading if you're writing anything against `agent_end`.
The obvious approach is `event.success`, which the hook type advertises:
```ts
type PluginHookAgentEndEvent = {
runId?: string; messages: unknown[];
success: boolean; error?: string; durationMs?: number;
};
```
**It does not work.** Measured on OpenClaw 2026.7.1-2:
| Case | msgs | assistant textLen | `event.success` | `event.error` | `stopReason` |
|---|---|---|---|---|---|
| Clean reply | 2 | 5 | `true` | `""` | `"stop"` |
| Incomplete turn | 8 | 0 | `true` | `""` | `"stop"` |
| Died mid-work | 6 | 0 | `true` | `""` | `"stop"` |
The runtime reports `success: true` for runs the user watched fail, and leaves
`error` empty. The only field that tracks reality is **whether the final
assistant message carried any text.**
So `classify()` uses:
1. `abort` if the terminal mentions abort/takeover/cancel — genuine interrupts
surface as `stopReason: "aborted"`.
2. `success` if the assistant message has text and no error.
3. `success` if the reply went out through the messaging tool (`deliveredViaTool`),
which legitimately leaves an empty assistant turn.
4. `failure` otherwise — no text, nothing delivered.
An earlier revision mapped `stopReason === "stop"` with no text to `abort`, on
the theory it meant an escape or `/stop`. That is exactly the incomplete-turn
signature, so the plugin silently ignored the single most common real failure.
Removed.
## Install
```bash
./setup.sh
```
Builds, installs, enables, grants the hook permission, and restarts the gateway.
Needs `openclaw` and `npm` on `PATH`.
<details>
<summary>Manual install</summary>
```bash
npm install && npm run build # emits dist/index.js
openclaw plugins install ./agent-postmortem --force
openclaw config set plugins.entries.agent-postmortem.enabled true
openclaw config set plugins.entries.agent-postmortem.hooks.allowConversationAccess true
openclaw config validate
openclaw gateway restart
```
</details>
## Config
`plugins.entries.agent-postmortem.config`:
| Key | Default | Meaning |
|-----|---------|---------|
| `prompt` | see below | The message sent into the session. |
| `appendReason` | `true` | Append the captured reason to the prompt. |
| `freshnessMinutes` | `15` | Give up if not delivered within this window. |
| `pollSeconds` | `15` | Retry cadence. Flat — OpenClaw's cooldown gating handles backoff. |
| `maxAttempts` | `60` | Hard cap (freshness usually ends it first). |
| `debug` | `false` | Log per-run classification. Turn on when calibrating a new model. |
**On the default prompt.** It used to be `"What happened?"`. The model reads that
as *"report on the task"* and will cheerfully redo the work — harmless when
nothing landed, a duplicate-write risk when a run died halfway. The default now
asks it to check current file state first and complete only what is genuinely
missing:
> Your previous turn ended without sending a reply. Check the current state of
> any files you were working on, then tell me in 2-3 sentences what actually
> completed, what did not, and anything left inconsistent. Complete only what is
> genuinely missing — do not redo work that already landed.
**On `appendReason`.** The raw terminal is useless to paste at a model — the
dominant failure reports `stopReason: "stop"` with no message, which produced
*"(An earlier run failed: stop.)"*. `describeReason()` now renders that signature
as *"the turn ended without producing a reply (tool calls may have partly run)"*.
## How it works
Listens on `agent_end`. Classifies each run, and on failure arms a per-session
timer that invokes `openclaw agent --session-key <key> --message <prompt>`.
Delivery is confirmed from the probe's own `agent_end` — identified by the last
user message matching the configured prompt — which both proves the provider
recovered and clears pending state.
## Known limitations
- **In-memory state.** A pending post-mortem is lost if the gateway restarts
between failure and recovery. A real cooldown doesn't restart the gateway, so
this mostly affects manual stop/start testing.
- **In-flight race.** If your successful run races an already-executing probe,
that probe may still deliver right after you. Cancel stops future probes, not
in-flight ones. Rare and harmless.
- **It may act, not just report.** Because the prompt asks it to complete what's
missing, the agent can perform work *after* reporting the failure to you. That
is usually what you want, but it means a session can change state while you are
away. Set `prompt` to something purely diagnostic if you'd rather it only
looked.
- **Delivery via CLI.** The probe shells out to `openclaw`. An in-process
delivery is a possible refinement once the parameter shape is pinned down.
## License
MIT
tools
Comments
Sign in to leave a comment