Use case
Counting things without losing count
How many times has this happened today? How many retries left? Counting looks trivial until two runs overlap, at which point the naive version quietly undercounts and nothing reports an error.
The problem
With only a read and a write, counting takes three steps: fetch the current value, add one, store the result. Run two of those at the same time and you get this:
run A: get_value → "7"
run B: get_value → "7"
run A: set_value("8")
run B: set_value("8")
Two increments, one counted. Both calls succeeded, both agents believe they counted, and the number is wrong. There is no error anywhere, because from the store's point of view it received two perfectly valid writes.
The gap between the read and the write is not microseconds for an agent. There is a network round trip, model inference, possibly a whole reasoning step. Seconds is an enormous window to lose a write in, and agent runs overlap more than you would expect — retries, a schedule firing alongside a chat session, or a model firing several tool calls in one turn.
The pattern
One call instead of three:
increment("cowork:daily-digest:runs")
→ { key: "cowork:daily-digest:runs", value: 8, created: false }
The store reads, adds and writes as a single atomic step under a row lock, so concurrent calls serialise and every one of them counts. Fifty at once gives you fifty.
The counter is created from zero on first use, so there is no setup call. The
amount parameter is optional and signed, which makes decrementing the
same tool with a negative number rather than a second one to learn:
increment("budget:api-calls:remaining", amount: -1)
→ { value: 449 }
Tell your agent
Each time you process a support ticket, use Junkt to
increment the key "support:tickets:handled_today".
Use increment, not get_value and set_value — several of
these can run at once and the counter has to be exact.
If the returned value goes above 50, stop and tell me
rather than continuing.
Counters are their own kind of key
A key holds either a value or a counter, and the type is locked in both directions. If
a long prompt leads the model to write "done" over your counter,
set_value refuses:
The key "support:tickets:handled_today" is a counter. Use the increment tool to change it; it cannot be overwritten with set_value.
And a key already holding a plain value cannot be incremented. Both cases leave the existing key exactly as it was, and the error message is written to be read by the model — an agent that receives it usually recovers and calls the right tool without asking you.
Counters that reset themselves
A counter created by increment can carry a TTL if you write the key with
set_value first, but the tidier approach is to put the period in the key
name:
increment("support:tickets:2026-08-15")
Yesterday's counter is still there for comparison, and
list_keys(prefix: "support:tickets:") gives you the whole history at a
glance. The cost is one key per day against your budget, so delete the old ones or
keep the window short.
Ask the agent to call time with kind: "same_day" rather than
letting it guess at the date — a model that thinks it is a different day will happily start a
fresh counter.
What people count
- Runs — how many times a scheduled job has fired, which is how you notice a schedule misbehaving.
- Budgets — decrement a starting allowance and stop at zero, so a runaway agent has a hard ceiling it cannot reason its way past.
- Retries — the input to exponential backoff. Delete on success.
- Items processed — a progress figure you can read from outside the run while it is still going.
Worth knowing
- Whole numbers only. There is no floating point counter. Count pennies rather than pounds if you need precision.
- Creating a counter counts toward your key limit the same as any other key.
- Delete to reset. There is no "set to zero" —
delete_keythen let the nextincrementrecreate it. - Every increment is recorded in your activity log with the key and the source, so you can see what has been counting and when.
Counting that survives concurrency
increment is in the free tier along with everything else. 25 keys, no card, about ten minutes to your first call.