State and history
An index means nothing on its own — it is a position in a listing.
That listing is kept in ~/.kx/state.json so the next command can resolve against it.
kx stateThat prints the listing indexes currently resolve against, along with the namespace and the kubeconfig context it was read in.
The history stack
Each kx get pushes its result onto a stack — ten entries by default, set by max_history — with a cursor marking the current one.
kx state --all # the whole stack, with positions
kx state 2 # jump to position 2
kx state back # step back one
kx state forward # step forward one
kx state drop 2 # remove position 2
kx state drop --all # clear the stack and the slots — marks are untouched
kx state drop --empty # drop the entries whose listing found nothingJumping does not re-run anything: the entry already holds the listing, so the indexes it carries resolve immediately.
The --all flag only clears what accumulates on its own, one kx get at a time.
A mark is named on purpose, not accumulated, so it takes a command that says so: kx unmark --all.
Re-running the listing you are already on refreshes that entry instead of pushing another copy of it, whether it came from kx get, kx top, a kx diag sweep or a kx tree.
Re-running kx get is how you see what changed, so without that the stack filled with one listing — five runs of kx get pods around a single kx get deploy left nine entries, eight of them the same, and kx state back could not reach the Deployments listing.
The same session now leaves three: pods, deployments, pods.
Reading an index back out
The kx state command shows what every index means.
The kx ref command prints one of them in a form another command can take:
kx ref 3 # pod/web-abc-xyz -n prod
kubectl exec $(kx ref 3) -- sh
kubectl get $(kx ref 1..3) # one line per indexThat is what keeps the index model from being limited to the verbs kx wraps: anything that takes a resource — another kubectl subcommand, stern, velero, a script of your own — can be handed one.
The --name, --namespace and --kind flags print a single field for tools that want the pieces separately, and a cluster-scoped resource comes back without -n, since there is no namespace for it to be in.
Nothing about kx ref contacts the cluster.
It reports what the index means, not what still exists, so it answers instantly — and a stale index prints the name it was assigned, leaving the command you spend it on to discover the resource is gone.
For the whole listing at once, kx state --json prints the current entry as a document, and kx state --all --json prints the stack.
Each row carries its index, kind, name and namespace — the fields kx ref would give it — and each entry carries the context it was listed in, the query that produced it, and byAgent when kx mcp saved it for an agent.
The document also names the context you are in now, so a script can tell an entry from another cluster, whose indexes kx will refuse.
Like kx ref, it never contacts the cluster, and with nothing listed yet it prints an empty document rather than failing.
A listing that found nothing is still a listing
A listing like kx get pods -n empty-namespace saves its result like any other, even though the result is nothing.
It has to: an empty listing that saved no entry would leave the previous one resolving indexes, so kx get pods -n a followed by kx get pods -n b and then kx delete 1 deleted a pod in a — a namespace and two commands away from anything on screen.
So the numbers retire when a listing finds nothing, and kx says so on the spot:
Pods · empty-namespace · none found
'kx state back' returns to Pods · prod · 14 itemsSpending an index against it explains itself the same way, at the moment it matters rather than one command earlier:
✗ The current listing is empty — Pods · empty-namespace found none.
Run 'kx state back' for the previous listing.Those entries cost a history slot each.
Running kx state drop --empty removes all of them at once, and needs no confirmation the way --all does — an entry holding nothing is not work anyone can lose.
Every entry records the context it was listed in, because a resource name means nothing without the cluster it was read from.
The kx state command names it beside the namespace; kx state --all captions the table with it, or gives it a column when the history spans more than one.
Switching contexts retires your indexes
kx get pods # in staging
kx context 2 # switch to production
kx delete 1 # refusedA staging index is never resolved against production, where the same name is a different resource. It refuses, names both contexts, and re-runs the listing here so there are usable numbers on the screen.
Switching with kx ns <index> is refused the same way, and tells you to run kx ns — a namespace listing is per-cluster too.
Switching with kx context <index> is the one exception: contexts live in kubeconfig rather than in any cluster, so switching back always works.
The namespace and context slots
Namespaces and contexts are kept in slots of their own, outside the history stack.
kx ns # list namespaces, into the namespace slot
kx ns 2 # switch to the second of those
kx contexts # list contexts, into the context slotTwo things follow.
An index into a slot keeps meaning the same entry however much you have listed since — kx ns 2 is still the second namespace you listed, not the second row of whatever is on screen.
And switching namespaces, which is frequent, never pushes real work off the ten-entry stack.
kx state --all # summarizes the slots under the history table
kx state --targets # expands them into the listings the switch commands readThe --targets flag is how you pick a number without listing again.
Operating on a namespace, rather than switching to it
To treat a namespace as a resource — describe it, label it — list it like one:
kx get ns
kx describe 2
kx label 2 team=platformThat stacks it like any other listing, and refreshes the namespace slot too, so the two spellings never disagree about what 2 means.
A narrowed listing counts, though:
kx get ns -l team=platform
kx ns 2 # the second *platform* namespaceRun kx ns to go back to indexing all of them.
The file
The file ~/.kx/state.json holds the stack, the cursor and the slots.
It is versioned: if the schema changes under an existing install, kx resets the file rather than migrating it — the cost is re-running one listing, and the alternative is migration code for a cache.
The KX_STATE variable points at a different file, for a terminal or CI job that wants its own history instead of sharing the one in ~/.kx.
Running kx --version prints the path actually in use, in case yours is somewhere else.
# terminal A
export KX_STATE=~/.kx/state-a.json
kx get pods
# terminal B
export KX_STATE=~/.kx/state-b.json
kx get deployEach terminal now resolves indexes against its own listing — kx delete 1 in B never touches what A listed.
KX_CONFIG does the same for ~/.kx/config.toml.