gotta
I built gotta because terminal work dies in scrollback, browser tabs, and compacted model context. The first failure is not retrieval. Usually I found the document. The failure is that the document, the path that found it, and the reason it mattered all vanish at different speeds.
I use it mostly at work. The usual starting point is one Slack or Jira link and perhaps a sentence of context. From there an agent can follow the links, materialize the ticket, discussion, document, or build it finds, and keep moving until the work is near a pull request. I have used it less outside work.
A typical evidence path might begin with a Jira issue that points to a Slack thread, which names a repository and a failed build. Gotta stores each result once, gives it a handle, and can reopen only the relevant section later. Its transcript search and leads view point to the next likely source without making the agent fetch and read the whole chain again. The session becomes the agent’s workspace across those systems without a separate MCP setup for each one.
The name is the command grammar. gotta read, gotta search, gotta todo append, and gotta oops read as sentences about what needs to happen, whether the next step is finding evidence or recording a mistake.
Early on, gotta ate its own oops: after a task ran, the friction it had recorded became the input for polishing the task. A daily run’s exhaust was the next day’s to-do list for the run itself. That loop moved into Spice with me.
The terminal is already a key
The tool is session-rooted rather than request-rooted. A request is ephemeral. A session can reopen, extend, hand off, and rehydrate after compaction without reconstructing the work from memory. That only helps if the next invocation can find the same session without requiring me to cd into a magic directory first.
The context resolver prefers an explicit override or a stable host identifier, such as CODEX_THREAD_ID or TERM_SESSION_ID. It falls back to a hash of terminal facts when necessary. That fingerprint changes if enough of the environment changes; a real identifier is better when the host supplies one.
The binding holds a relative root symlink to the session and a JSON sidecar saying who made the binding, when, and why. Resolution follows the symlink. The JSON explains it. A shell can follow the pointer without parsing a schema, and the binding survives moving the data root.
Bytes once, names and events as links
gotta read does more than print a remote result. It materializes the bytes into a content-addressed store:
content/<sha256>/
data
meta.json
names/<preferred-name> -> ../data
logs/<timestamp> -> ../names/<preferred-name>The filesystem ledger writes data once per digest. A human name is a symlink to those bytes. Each fetch event is another timestamped symlink to the name used for that fetch, so the tree records both when the artifact arrived and what it was called at the time. Fetching identical bytes again adds provenance without manufacturing a second object.
Two locators fall out of the layout: artifact:<name>@<digest12> for a human handle and content:<sha256> for exact identity. manifest.jsonl indexes the events for fast views, but it is not the authority. If the manifest is lost, the digest directories, metadata, aliases, and event links still describe the store. I wanted a cache that could explain itself by being walked.
The timeline, graph, and leads views derive from that evidence web. Gotta was built by agents for agents, and those views are theirs. I rarely run them myself. I step in when a view is not revealing something I thought it could, or when I catch an agent complaining about the tool.
An attached pipe is not input
Agent harnesses regularly launch a command with stdin attached even when no bytes are coming. The usual test, not sys.stdin.isatty(), then answers the wrong question. A bare gotta oops should show the friction ledger; piped prose should append to it. Treating an empty inherited pipe as prose turns a read into an empty write.
The stdin check looks for readable bytes without waiting. If the descriptor cannot support the check, it does not infer a write. The command needs evidence of content, not merely a pipe attached to it.
Bound the answer, keep the evidence
Retrieval tools have an easy failure mode with models: return everything and consume the context that was supposed to reason about it. Gotta bounds interactive output by line count and byte size. Its cutoff code allows a small flex band to find a clean stopping point without splitting a UTF-8 character.
JSON cannot be clipped like prose and remain JSON. Oversized JSON becomes a small envelope with a preview and a follow command. The full bytes are still in the artifact store; only the conversational view is bounded.
Stdout stays the requested payload. A compact machine receipt goes to stderr when something non-obvious happened: truncation facts, artifact and content locators, and a shell-quoted command to reopen the result or rerun with full output. The output can be small without making the operation forgetful.
Reading the public README with gotta read and --head 4 returns four lines on stdout. Its receipt includes artifact:README.txt@2bc5c32d9c42. The same session can then run:
gotta read artifact:README.txt@2bc5c32d9c42 --section InstallationThat returns the installation section from the saved document. The first four lines do not contain that section, but the initial read keeps the whole document. Remote providers still need their own access configured; keeping evidence does not supply permission to retrieve it.
Record the seam
gotta oops records what went wrong, how to reproduce it, and any workaround in state/oops.jsonl. Later invocations can reopen the same ledger and mark the problem resolved. The friction has somewhere to live after the agent that hit it has forgotten the conversation.
Gotta is Python and Apache-2.0, published on PyPI: uv tool install gotta.