Skip to content

agenthist: your shell learns from your agent's commands

The problem

A coding agent runs the build, the deploy, the query that finally worked, in its own subshell. Your history never sees it, so next week Ctrl-R and autosuggest know nothing about it.

Appending to ~/.zsh_history looks like one line of code, but zsh's file is not plain text. Bytes 0x83 to 0xa2 are escaped ("metafied"), and those bytes sit inside ordinary UTF-8: Ã is c3 83. Write echo NÃO raw and zsh reads back a broken byte and a lower-case o. Newlines inside a command become backslash-newline, a final backslash needs a space after it, and zsh locks the file while it writes.

The method

Write exactly what zsh would write, under zsh's own lock, in one append:

def zsh_encode(cmd):
    out = bytearray()
    for b in cmd.encode("utf-8", "surrogateescape"):
        if b == 0x0A:
            out += b"\\\n"                     # newline -> backslash-newline
        elif 0x83 <= b <= 0xA2:
            out += bytes((0x83, b ^ 0x20))     # zsh's Meta escape
        else:
            out.append(b)
    if out.endswith(b"\\"):
        out += b" "                            # or it reads as "continued"
    return bytes(out)

line = b": %d:0;" % time.time() + zsh_encode(cmd) + b"\n"

The lock: make FILE.LOCK with O_EXCL (wait up to 2 s; older than 10 s is abandoned, zsh's rule), take an fcntl lock on the file, then one O_APPEND write. bash gets #<epoch> and the command; atuin gets history start and history end with a session id made for it.

It never creates a history file for a shell you do not use, skips a repeat of the last entry, and always exits 0 so the agent's command never fails.

Sample

{"hooks": {"PostToolUse": [{"matcher": "Bash",
  "hooks": [{"type": "command", "command": "/path/to/agenthist"}]}]}}
agenthist -c 'make release'        # agents without JSON hooks
AGENTHIST_SKIP='TOKEN=|password'   # regex: never keep these
AGENTHIST_ATUIN=0                  # leave atuin out

Limits

  • A running zsh sees the lines when it next reads the file (SHARE_HISTORY: next prompt; otherwise a new shell or fc -R).
  • The duration and exit status are not given to a post-command hook: 0 is kept.
  • bash before 5 may read a multi-line command back as separate lines.
  • AGENTHIST_SKIP is a filter you write; it does not find secrets for you.

Code

methods/agenthist/ in the methods repository: the script, a README and a 19-check test that compares the bytes with zsh's own writer for eight tricky commands, has real zsh and bash read them back, and runs 20 writers at once.