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 orfc -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_SKIPis 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.