TL;DR: An AI conversation has state: the whole history of what was said. A CLI tool exits between invocations, so by default that state is gone and every run starts cold. go-tool-base’s chat package can persist a conversation. Save a snapshot, store it, optionally encrypted, restore it next run and carry on. The snapshot is deliberate about what it carries: the messages, yes; the API token, never.
A CLI forgets everything
A long-running service keeps its state in memory for as long as it runs. A CLI tool does not get that luxury. It starts, does one thing, exits. The next invocation is a brand-new process with no memory of the last one.
For most commands that is exactly right, and you would not want it any other way. But an AI conversation is a different kind of thing, because a conversation is its history. The model’s next answer depends on everything said so far. Run an AI command, exit, run it again, and you have started a fresh conversation with someone who has never met you. For an interactive assistant, or any AI workflow that unfolds across several invocations, that is the wrong behaviour. The user expects to pick up where they left off.
Save and restore
The chat package handles this through a PersistentChatClient interface. Like streaming, it is an optional capability discovered with a type assertion, sitting beside the four-method core rather than bloating it. A client that supports persistence also satisfies this interface:
if pc, ok := client.(chat.PersistentChatClient); ok {
snapshot, err := pc.Save()
// store the snapshot somewhere
}
A snapshot is a serialisable value that captures the conversation. You store it. Next run, you load it, Restore it onto a fresh client, re-register your tools, and call Chat again. “Where were we?” works, because the model is handed back the whole history.
A snapshot is opinionated about what it carries
The interesting part is what a snapshot does and does not contain, because that is a series of deliberate decisions.
It carries the messages, the system prompt, the model name, and tool metadata, the names, descriptions and parameter schemas of the tools that were registered.
It does not carry tool handlers. Handlers are code, not data; you cannot serialise a function meaningfully, so after a restore you re-register them with SetTools. The snapshot remembers that a tool called read_file existed and what its shape was; it does not try to remember the Go function behind it.
And it does not carry API tokens. This is the one to dwell on. A snapshot is a file. A file gets synced, backed up, copied between machines, attached to a support ticket. A snapshot that carried the API key would be a credential leak the moment it left the laptop it was made on. So the snapshot never contains a token, at all. On restore, the client picks the credential up again the ordinary way, from the environment or the keychain. The conversation and the secret are kept in separate places on purpose, and only one of them is in the file.
Encrypted at rest, if you want it
The package ships a FileStore that writes snapshots as JSON files, with 0600 permissions in a 0700 directory, and it can encrypt them. Pass WithEncryption a 32-byte key and snapshots are written with AES-256-GCM.
That option exists because a conversation can hold sensitive content even when it holds no credential. The log a user pasted in for analysis, the source file they asked the model to review, the internal details in their questions: none of that is an API key, and all of it might be something you would not want sitting in plain JSON in a backup. Encryption at rest covers it.
The FileStore is also careful about the snapshot identifiers it is handed. An ID has to be a canonical UUID, and the resolved file path is checked to lie inside the store directory, so a snapshot ID arriving from an untrusted source, a CLI flag or a request payload, cannot be bent into a path-traversal that reads or writes somewhere it should not. Persisting conversations adds a small filesystem surface, and the store treats it as one.
The short version
A CLI tool forgets everything between invocations, which is correct for most commands and wrong for an AI conversation, because a conversation is its history.
go-tool-base’s chat package lets you persist one. PersistentChatClient saves a snapshot you can store and restore later, picking the conversation back up where it ended. The snapshot is deliberate about its contents: messages, system prompt and tool metadata yes; tool handlers no, because they are code you re-register; API tokens never, because a snapshot is a file and a file travels. The built-in FileStore can encrypt snapshots at rest with AES-256-GCM and validates snapshot IDs against path traversal. Resumable conversations, without the conversation file becoming a place secrets leak from.