Turn your local AI conversation history into a personal retrospective: a stream of cards, then an essay. Everything is read locally. No network, no upload.
English · 简体中文
Thirteen cards lay out the facts first — when you first spoke, how many times you sat down, how many sentences the busiest day held, how much you typed versus how much it thought. Then an essay covers what the cards cannot: the empty days, the things left unsaid, the half you never see.
It is not a productivity report or a dashboard. It is something you can leaf through.
Spotify Wrapped and the annual reports Chinese apps ship every December all do the same thing: they give a person a shape for behaviour that was scattered across a year.
Agenttime does that for AI conversations. The extra bit of self-interest: it is written by the AI, in first person.
So the report contains lines like "I cut your messages wherever the gap exceeded ninety minutes" and "you never see any of these characters." The numbers are cold; the one writing them sits right beside them.
This repository is the skill. Put it in your agent's skills directory and you are done.
git clone https://github.com/hongshuxifan321/agenttime "$HOME\.dsh\skills\agenttime"git clone https://github.com/hongshuxifan321/agenttime ~/.dsh/skills/agenttimeRestart the agent; it will pick the skill up.
git clone https://github.com/hongshuxifan321/agenttime
cd agenttime
.\install.ps1The installer supports Windows PowerShell 5.1 and PowerShell 7. If it reports running scripts is disabled, run this from the repository directory:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\install.ps1This sets the execution policy for this process only; it does not change user or system policy settings. Installer flags such as -Target can be appended to the command.
git clone https://github.com/hongshuxifan321/agenttime
cd agenttime
./install.shThe script looks for skill directories that already exist on this machine — ~/.dsh/skills, ~/.claude/skills, ~/.codex/skills, ~/.config/agents/skills — and installs into every one it finds. If none exists, it uses ~/.dsh/skills.
| Flag | Effect |
|---|---|
-Target <dir> / -t <dir> |
Install into a specific directory |
-Link / -l |
Symlink instead of copying, so edits apply immediately (on Windows this needs Developer Mode or an elevated shell; otherwise it copies) |
-Force / -f |
Overwrite an existing install without asking |
Hand the repository URL to your agent and ask it to clone the repo into its own skills directory. SKILL.md tells it what to do next.
Dependency:
pip install zstandardOnly needed to read DeepSeek Harness logs. Statistics and rendering are pure standard library.
Invoke the skill from your agent, or just say "review my conversations".
Or run it by hand:
python cli.py doctor # what can be read on this machine
python cli.py scan --out facts.json # statistics
# write narrative.json (follow narrative.example.json)
python cli.py render facts.json narrative.json
python tests/test_agenttime.py # regression testsOutput defaults to ~/Desktop/agenttime_report_YYYYMMDD_HHMMSS.html. The timestamp is down to the second, so running it several times a day — or once per agent — never overwrites an earlier report. If the desktop is not writable it falls back to the current directory and prints the real path, still without overwriting anything already there.
Passing --out can replace that report, but never an input facts, narrative or template file. An explicit output failure stops without falling back to another path. Outputs are written to a temporary file before atomic publication; concurrent default outputs cannot overwrite each other.
Keep intermediates in .agenttime/ under the current directory rather than $env:TEMP: inside the DSH sandbox the session TEMP is Temp\dsh-xxxx while an elevated process gets Temp, so files go missing halfway through.
| Layer | Who | Output |
|---|---|---|
| Statistics | engine.py, pure computation, no file access |
facts.json |
| Narrative | the AI writes it — the only layer that needs judgement | narrative.json |
| Rendering | render.py + template.html |
one HTML file |
narrative.json contains no digits, only placeholders like {volume.msgs|comma} resolved against facts.json at render time. The renderer rejects handwritten digits and common Chinese quantity expressions; narrative claims still need to be checked against the facts. Missing facts stop rendering instead of being estimated or filled in. The cover, dates, lists and chrome use the same placeholder path.
The report speaks the same language as your conversations. The few fixed strings in the template default to Chinese; for English data, override them through chrome in narrative.json — see SKILL.md.
| File | Purpose |
|---|---|
SKILL.md |
skill definition, what the AI reads |
cli.py |
command line: doctor / scan / render |
engine.py |
statistics over the unified session model |
adapters/ |
one file per agent log format |
adapters/discover.py |
finds conversation stores that have no adapter yet |
render.py |
facts + narrative + template → single HTML |
template.html |
layout: cover / cards / divider / essay / footer |
narrative.example.json |
example narrative (fictional data, structure only) |
install.ps1 / install.sh |
installers |
tests/ |
regression tests, standard library only |
Only DeepSeek Harness (~/.dsh/sessions) is officially supported today.
It is the only source verified against real data. Other agent log formats differ a lot: some mix a system prompt into user messages, some encrypt the reasoning, some record nothing locally at all. Shipping an adapter nobody has validated would only create false expectations.
But agenttime does not pretend other agents do not exist.
When more than one source is readable, scan merges them into a single report. Your conversations are spread across whatever agents you used; what you want is all of it, not one of them. sources in facts.json records how many sessions each one contributed.
For a single source: python cli.py scan --source dsh.
doctor looks in two places. First, fixed paths for known agents — Claude Code's ~/.claude/projects, Codex's ~/.codex/sessions, ZCode's ~/.zcode/cli/db. These are nested several levels deep and the files are named by UUID, so a generic directory scan will never find them. Then a generic scan catches anything else whose name looks like a conversation store.
The command line prints in Chinese. Its output looks like this:
找到 1 个数据源:
dsh ~/.dsh/sessions
40 个会话 · 612 条真人消息
挡掉系统注入 481 条
还有这些地方有对话数据,但 agenttime 还读不了:
Codex ~/.codex/sessions
98 个候选文件,例如 2026/11/05/rollout-....jsonl.zst
ZCode ~/.zcode/cli/db/db.sqlite
1 个候选文件,例如 db.sqlite
On a machine without DSH this output is the whole point. doctor exits non-zero, but not with a bare "not found" — it tells you what conversation data exists, where, and then admits it cannot read it. It will not assemble a report out of something else.
The generic scan is deliberately conservative: a directory or file name must contain session / chat / conversation / history / thread, and databases additionally need a corroborating directory name. Better to miss one than to treat an audit log as a conversation.
Write one file under adapters/ implementing two functions:
def detect() -> str | None:
"""Return the data root, or None if absent."""
...
def load(root: str) -> tuple[list[Session], LoadReport]:
"""Read into the unified model. Human messages only; drop every injection."""
...Then add a line to REGISTRY in adapters/__init__.py. Statistics and rendering stay untouched.
If you support Python 3.9, put from __future__ import annotations at the top of the new file — the str | None and tuple[...] annotations above are only legal there with it.
The hard part is always the injection filter in load(). DSH has a clean field, source.kind == "user"; most agents do not, so you have to infer from isMeta, synthetic, XML tags and similar markers. Get it wrong and the top-words card fills up with system prompt text — this is the easiest thing in the project to get wrong.
- Only human input counts. DSH writes runtime context,
AGENTS.mdand the skill catalog asuser/messageevents; these are dropped by requiringdata.source.kind == "user". The injected events are few in number but huge in size: on this machine 1,288 of them carried 11.0M characters against 129k characters of real input, about 85×. Skip the filter and the word-frequency card fills up with instruction text - Timestamps come from human messages only, never from tool calls or title events
- "Thinking volume" is the character length of reasoning blocks, not the block count
- "A sitting" splits messages on gaps longer than 90 minutes, rather than using a session's first and last timestamp — sessions resume days later, and first/last arithmetic produces values like 453 hours
- Unreadable sessions and sessions with no human messages are reported in
quality, never skipped silently
- Python 3.9+ (the source carries
from __future__ import annotations, so 3.9 works) zstandard, only for DSH logs- Windows / macOS / Linux, paths expanded from
~
Everything runs locally. No network, no upload. The report is a single HTML file and you decide where it goes. Read it before you show it to anyone — it is made of your conversations.
MIT


