Skip to content
What is kslate?

What is kslate?

kslate is a shared record for a group of agents working on the same task, with a gateway in front that checks every write. Agents read and write fields in the record. The gateway rejects a write if the writer is acting on information that has since changed, and tells the writer what changed. Every write and every rejection is stored with who made it.

What that lets you do is run several agents on one task without a leader. Nothing decides which agent runs or in what order. Each one reads the record and acts when a field it cares about has changed, and a write that names what it relied on is refused if any of that has moved. So no agent waits to be given work, and none acts on a decision another has already replaced. Why there is no leader covers how that holds.

It runs as one binary plus Postgres, on or off Kubernetes. Agents talk to it over HTTP/JSON, gRPC or MCP.

The problem

Say two agents handle one incident. A diagnoser decides what to do. A remediator does it. They share state somewhere: a database row, a document, a chat thread.

  1. The diagnoser writes the plan: “restart the api pods”.
  2. The remediator reads the plan and starts restarting pods.
  3. The diagnoser finds new evidence and changes the plan to “roll back the deployment”.
  4. The remediator finishes the restart and records “done”.

Nothing failed. Both agents did their job. The incident record now says the plan was a rollback and the action was a restart. Nobody was told. Afterwards you cannot tell from the record what the remediator was looking at when it acted.

Most agent frameworks assume one agent runs at a time, so they have no check for this. kslate adds it.

The same scenario on kslate

The record is incidents/inc-1. It has a field planned_action that the diagnoser writes and a field actions_taken that the remediator writes.

    sequenceDiagram
  participant D as diagnoser
  participant K as record inc-1
  participant R as remediator
  D->>K: put planned_action "restart pods"
  K-->>D: ok, v1
  R->>K: get planned_action
  K-->>R: "restart pods", v1
  D->>K: put planned_action "roll back", expected v1
  K-->>D: ok, v2
  R->>K: append actions_taken, based_on planned_action@1
  K-->>R: STALE_PREMISE, planned_action 1→2 by diagnoser
  R->>K: get planned_action
  K-->>R: "roll back", v2
  R->>K: append actions_taken, based_on planned_action@2
  K-->>R: ok
  

Two things are different from the plain version.

Every field has a version. When the diagnoser changes the plan it sends the version it read, 1. If someone else had changed the plan first, kslate would reject the write with STALE and the current value.

A write can say what it was based on. The remediator’s write says based_on: planned_action@1. kslate checks that inside the write. The plan is at version 2, so the write is rejected. The response says which field moved, from what version to what, and who moved it:

{
  "error": "STALE_PREMISE",
  "record": "incidents/inc-1",
  "field": "actions_taken",
  "moved": [
    { "field": "planned_action", "your_version": 1, "current_version": 2,
      "current_author": "apikey:diagnoser",
      "current_evidence": "api logs show config parse error at 14:02:11" }
  ]
}

The remediator reads the plan again, decides again, and writes with based_on: planned_action@2. That write is accepted.

Afterwards, kslate blame shows the whole sequence from the record alone:

planned_action  (versioned, evidence required)
v2  22:34:15  apikey:diagnoser      "roll back api deployment"
      evidence: api logs show config parse error at 14:02:11
v1  22:34:15  apikey:diagnoser      "restart payments-api pods"
      evidence: api pods in CrashLoopBackOff
      superseded by v2 after 0s
--  22:34:15  apikey:remediator     REJECTED STALE_PREMISE
      write: actions_taken  based_on: planned_action@1  (moved to v2 at 22:34:15)

Your first agent runs exactly this in Python, and the rejection happens in your own terminal. The same exchange on the wire runs it with two curl commands per step.

Where your code goes

Your agents call the gateway. Your model call lives inside your agent, in the function that reads the fields and decides what to write. kslate never calls a model and never sees a prompt. Build an agent shows where that function goes.

Where to go next

  • Install, then write your first agent. It shows the class it writes to, so you see where the field names and the rules come from, and it ends with the rejection above.
  • Build an agent to write your own in Python or Go.
  • The shared record for the full model: classes, records, fields, the six operations, and every check the gateway runs.
  • Field modes for the four ways a field can be written.
  • Where kslate fits for what it is not, and what your agent runtime keeps doing.
  • The incident fleet for four agents on one record, including a crash and takeover.
  • Honest limits before you put it on a real network.