Documentation
Junkt in about five minutes
Junkt is a hosted key/value store your agents reach over the Model Context Protocol. There is not much to it, which is the point — this page is most of what there is to know.
The model
Each account has exactly one flat keyspace. No buckets, no nesting, no databases to pick between. A key is a string, a value is a string, and that is the whole data model.
Keys are namespaced by convention rather than by the system. Colons are the usual
separator and list_keys can filter by prefix, so a naming scheme like
cowork:daily-digest:last_run gives you grouping without Junkt having to
implement it.
cowork:daily-digest:last_run
cowork:daily-digest:runs
cowork:inbox:cursor
deploys:staging:cooldown
Every key belongs to exactly one account and is scoped by user at the query layer. Nobody else can list, read or write your keys.
One prefix is not your agent’s to use: control: is reserved for
controls, and writes to it are refused.
Two kinds of key
A key holds either a value or a counter, and the two cannot be confused for each other:
- Values are written with
set_valueand hold any string up to 2KB. - Counters are created and changed with
incrementand hold a whole number.
The type is locked in both directions. set_value refuses to overwrite a
counter, and a key holding a plain value cannot be incremented. Both cases raise a
clear error and leave the original key untouched, so two agents using the same key for
different purposes fail loudly instead of quietly corrupting each other. The type is
reported by get_value and list_keys.
Expiry
set_value takes an optional ttl in seconds. When it elapses
the key stops being visible to get_value and list_keys, and
stops counting toward your key limit, before it is physically removed by an hourly
prune. Re-setting a key rewrites its TTL; setting it without one clears any previous
expiry.
This is what makes cooldowns and rate-limit windows a one-liner. A key with a one-hour TTL is a lock that releases itself:
set_value("deploys:staging:cooldown", "held", ttl: 3600)
Controls
Everything above flows one way: your agent writes a key, and reads it back on a later run. Controls go the other way. A control is a setting you write in the dashboard, which your agent may read and cannot change — the way to tell a running agent something without editing its prompt or its schedule.
The obvious one is a stop switch:
control:paused boolean true
"If true, stop and do nothing this run."
Your agent reads its controls with list_controls.
set_value, increment and delete_key all refuse
the control: namespace, so it cannot clear a control you have set,
including one that stops it. An agent that could switch off its own pause flag would
not be paused.
Controls are the one thing in Junkt that is typed, because they are the one thing written by a person and read by a machine — the two never meet, so the type is what holds them to the same meaning:
- boolean — a flag. On or off, and nothing in between to misread.
- string — free text, for a short standing instruction.
- number — a budget, a threshold, a batch size.
- enum — one of a fixed set you define, so the agent picks from a closed list rather than interpreting prose.
Each control also carries a description, and that is the part that matters. A flag
named paused means nothing to an agent on its own; the description is
where you say what to do about it. It is returned with the value, and it is read as an
instruction rather than as data.
Controls have their own allowance — ten on the free tier — separate from your keys, so setting one never costs you somewhere to remember something.
One thing to be clear about: Junkt cannot stop an agent by force.
A control is somewhere your agent will reliably look, which only works if you have
told it to look. That is worth having, and it is not the same as a kill switch. Tell
your agent to check list_controls before it starts, and the mechanism
works; leave that out and a paused flag sits there being ignored.
What a model cannot do for itself
An agent needs two things it has no way to produce: the current time, and a genuinely random value. Both come from tools rather than keys. Nothing is stored, there is nothing to read back, and neither costs you a key.
The time tool answers anything that turns on the real clock: what time it
is now in a given timezone, how long since a stored timestamp, whether that timestamp
was today, and how many seconds until midnight. That last one is a ttl
for a key that clears itself at the end of the day. It matters more than it sounds —
a model asked whether something ran today will otherwise guess at the date, and it
guesses badly.
random_number, random_token and random_choice
cover integers and probability gates, UUIDs and lock tokens, and picking or shuffling
a list. Ask a model for a random number and you get the same handful of favourites;
ask it to choose from a list and it leans on whichever option reads best. Neither is
fit for sampling or for splitting traffic.
Pass a seed when you want the opposite of randomness — the same input
returning the same answer every time, which is how you bucket an account or assign it
consistently between runs.
Limits
| Limit | Free tier | What happens at the edge |
|---|---|---|
| Keys per account | 25 | A write creating a 26th key is rejected. Expired keys do not count. |
| Value size | 2048 bytes | Rejected with the size and the limit, so the agent can trim. |
| Key length | 128 characters | Rejected. Any characters are allowed within that length. |
| Requests | 60 / minute | Per account, across all connectors. |
| Activity retention | 30 days | Older entries are pruned automatically. |
The activity log
Every successful write, increment and delete is recorded with the key, the time, and
whether it came from an MCP client or the web dashboard. Rejected operations leave no
trace, and reads are deliberately not logged — logging every get_value
would drown the useful entries.
Recent activity appears on your dashboard. It exists because handing an autonomous agent persistent memory is a lot easier when you can check what it did with it.
Security
- Junkt is its own OAuth 2.1 authorization and resource server, with PKCE and Dynamic Client Registration, so clients register themselves with no client IDs to copy around.
- Every token maps to exactly one account, and every query is scoped by user.
- Tokens are listed on your dashboard with the date you approved them, and revoking one takes effect on its next call.
- Accounts support passkeys and two-factor authentication.
- Stored values are never written to application logs.
Next
Connect a client if you have an account, or create one first. If you want to see the exact shape of every request and response, go straight to the tool reference.