See what happened
Your agent wrote two entries, and the gateway refused one write between them. This page reads that back, which is the thing kslate is for: answering afterwards what each agent did and what it was acting on.
You need the record from your first agent.
Who wrote a field, and on what basis
kslate blame shows one field’s whole story.
KSLATE_APIKEY=rem-token bin/kslate blame --server http://127.0.0.1:8080 \
incidents/inc-7 actions_takenactions_taken (append, evidence required)
v2 19:49:58 apikey:remediator "executed: roll back api deployment"
evidence: kubectl output
based_on: planned_action@2
identity: apikey:remediator (shared-secret)
v1 19:49:58 apikey:remediator "executed: restart payments-api pods"
evidence: kubectl rollout restart deploy/payments-api
based_on: planned_action@1
identity: apikey:remediator (shared-secret)
-- 19:49:58 apikey:remediator REJECTED STALE_PREMISE
based_on: planned_action@1Four things, none of which your agent had to record on purpose:
- who wrote it —
apikey:remediator, from the credential on the connection. A request cannot name its own author. - what evidence they cited — the string you passed. kslate checks it is present, never that it is true.
- what they were acting on —
based_on: planned_action@2. The client filled that in from the version it read. - how well the credential was bound —
shared-secretfor an API key. Identity strength covers the stronger kinds.
The based_on line answers “was this decision current when it was
made”. Entry v1 read planned_action@1, which the diagnoser replaced
while your agent was deciding. Entry v2 read planned_action@2, the
plan the record still holds.
The -- row is the write the gateway refused. It carries no version,
because the gateway stored no entry for it. It is the first attempt at
entry v2, sent with based_on: planned_action@1 after the plan had
moved.
The whole record
kslate history is every write to every field, newest first.
KSLATE_APIKEY=rem-token bin/kslate history --server http://127.0.0.1:8080 incidents/inc-7v2 19:49:58 actions_taken apikey:remediator append APPLIED
evidence: kubectl output
based_on: planned_action@2
identity: apikey:remediator (shared-secret)
-- 19:49:58 actions_taken apikey:remediator append STALE_PREMISE
evidence: kubectl output
based_on: planned_action@1
identity: apikey:remediator (shared-secret)
v2 19:49:58 planned_action apikey:diagnoser put APPLIED
evidence: api logs show config parse error at 14:02:11
identity: apikey:diagnoser (shared-secret)
v1 19:49:58 actions_taken apikey:remediator append APPLIED
evidence: kubectl rollout restart deploy/payments-api
based_on: planned_action@1
identity: apikey:remediator (shared-secret)
v1 19:49:58 planned_action apikey:diagnoser put APPLIED
evidence: api pods in CrashLoopBackOff
identity: apikey:diagnoser (shared-secret)Five writes by two identities. Nobody told either agent about the other.
The refused write is in the record with the same detail as the applied ones: who sent it, what evidence it cited, what it was based on, and which check it failed. That is how you find an agent that acted on something stale.
The field that moved
blame on planned_action shows the same rejection from the other
side.
KSLATE_APIKEY=rem-token bin/kslate blame --server http://127.0.0.1:8080 \
incidents/inc-7 planned_actionplanned_action (versioned, evidence required)
v2 19:49:58 apikey:diagnoser "roll back api deployment"
evidence: api logs show config parse error at 14:02:11
identity: apikey:diagnoser (shared-secret)
v1 19:49:58 apikey:diagnoser "restart payments-api pods"
evidence: api pods in CrashLoopBackOff
identity: apikey:diagnoser (shared-secret)
superseded by v2 after 0s
-- 19:49:58 apikey:remediator REJECTED STALE_PREMISE
write: actions_taken based_on: planned_action@1 (moved to v2 at 19:49:58)v1 was the current plan for under a second. That is the superseded by v2 after 0s line. The -- row names what that cost: an append to
actions_taken that the gateway refused, because it was based on v1
after v2 existed.
Next
- Build an agent adds one capability at a time: a second agent with its own scope, acting on current decisions, many copies sharing work, surviving a crash.
- Running on Kubernetes if that is where this is going.
- blame and history for what the audit trail does and does not tell you.
- When a field changes mid-decision
for the five errors a client raises, and for running
decideonce when it must not run twice.