{"id":"agents-history","repo":"panrafal/paseo-plugins","url":"https://github.com/panrafal/paseo-plugins/tree/main/agents-history","name":"agents-history","description":"Sidebar history of every workspace and agent, including archived ones, with filters and ranked search over on-disk conversation transcripts.","categories":["monitoring","productivity"],"platforms":[],"caveats":["Reads on-disk provider transcripts; writes only its own search index under plugin-data"],"images":["https://raw.githubusercontent.com/panrafal/paseo-plugins/004c6a80c6aa8e76bb563694c3eee5c2c0b0466f/agents-history/images/agents-history.png"],"themes":[],"health":{"manifestValid":true,"hasReadme":true,"hasLicense":true,"hasTests":false,"hasTypecheckScript":true,"updatedRecently":true},"scannedAt":"2026-10-15T12:29:07.772Z","addedAt":"2026-09-10T11:29:03+03:00","path":"agents-history","version":"0.1.0","security":{"status":"failed","blockingFindings":1,"advisoryFindings":0,"scannedAt":"2026-10-15T12:14:24.405Z","commit":"004c6a80c6aa8e76bb563694c3eee5c2c0b0466f"},"author":"panrafal","license":"MIT","paseoVersionRequirement":">=0.7.2-0","descriptionNodes":[{"type":"text","text":"Sidebar history of every workspace and agent, including archived ones, with filters and ranked search over on-disk conversation transcripts."}],"caveatNodes":[[{"type":"text","text":"Reads on-disk provider transcripts; writes only its own search index under plugin-data"}]],"manifest":{"id":"agents-history","requirements":{"paseo":">=0.7.2-0"}},"repoMeta":{"stars":6,"defaultBranch":"main","pushedAt":"2026-10-15T04:33:35Z"},"owner":{"login":"panrafal","avatarUrl":"https://avatars.githubusercontent.com/u/1268900?v=4"},"installNotesHtml":"<pre><code class=\"language-bash\">paseo plugin add panrafal/paseo-plugins:agents-history\n</code></pre>\n<p>From a checkout:</p>\n<pre><code class=\"language-bash\">npm install            # from the monorepo root\nnpm run typecheck\npaseo plugin install /absolute/path/to/paseo-plugins/agents-history\npaseo plugin ls        # expect \"running\"\n</code></pre>\n<p>After editing:</p>\n<pre><code class=\"language-bash\">npm run typecheck\npaseo plugin reload agents-history\npaseo plugin logs agents-history   # \"search index at …\" and \"index sync\" lines once the surface is opened\n</code></pre>","limitationsNotesHtml":"<ul>\n<li>Only providers that keep a transcript file on disk are searchable (see the table above).</li>\n<li>Conversation files that have been deleted, or a Claude session directory Paseo cannot map to\nthe agent's directory, make that agent \"not searchable\"; the census still lists it.</li>\n<li>The index holds what people and agents said. Tool input and output, injected context, and\nprovider bookkeeping are not indexed; use <strong>Regex</strong> to grep those.</li>\n<li>Ranked search folds case and stems words with the English porter stemmer; it does not match\ninside words (<code>beat</code> does not find <code>heartbeat</code>) and cannot take a regular expression.</li>\n<li>A transcript's last line is indexed once its newline is written; a message still being\nstreamed appears after the next sync.</li>\n<li><code>grep -i</code> folds case according to the daemon's locale. A grep hit inside a very long line (a\nlarge tool result) shows a snippet cut from the first megabyte of that line. <code>grep</code> returns\nthe first hits in file order, and a query that also appears in a record's fields (a branch\nname, a directory, a session id) matches every line of that transcript; such hits are labelled\n<code>meta</code> and shown only when nothing said in the conversation matched.</li>\n<li>A grep-backed search needs <code>grep</code> on the daemon's <code>PATH</code>. Windows ships none, so when <code>PATH</code>\nhas no <code>grep</code> the one bundled with Git for Windows is used, looked up under <code>%ProgramFiles%</code>,\n<code>%ProgramFiles(x86)%</code>, <code>%LOCALAPPDATA%</code>, <code>scoop</code>, and <code>C:\\msys64</code>. On a host with neither,\nRanked search still …</li>\n</ul>","readmeText":"# agents-history\n\nPaseo plugin that adds an **Agents history** entry to the app sidebar. It lists every workspace\nthe selected daemon has ever had, archived ones included, with the agents that ran in each, and\nsearches what was said in those conversations through a ranked full-text index of the providers'\ntranscript files on the daemon host (falling back to `grep -E` over the raw transcripts for\nregular expressions, and wherever the index is not available yet — see **Limitations**).\n\nPaseo's own History screen searches agent titles and hides archived workspaces; this surface is\nfor finding the workspace where something was discussed weeks ago.\n\n![Agents history](./images/agents-history.png)\n\n## What is listed\n\nEvery workspace in the daemon's registry, newest activity first, grouped into **Active** and\n**Archived** when the archived filter is set to all. Each card carries:\n\n1. **Head** — the workspace title (its name when it has no title), an amber `archived` badge\n   when the workspace or its project is archived, and how long ago anything in it last moved.\n   Below, each with a small icon: project, branch, worktree name, agent count, and labels. Pressing the head opens\n   the workspace; for an archived one Paseo shows its recovery screen and offers to unarchive.\n2. **Agent rows** — one per agent, archived ones included: a status glyph (a spinner while\n   running), the title, an `archived` badge, the last activity, and under it, each with a small\n   icon, provider, model, and last status.\n   Pressing an active row opens the agent. Pressing an archived row asks whether to restore it.\n   **Restore and open** restores its provider session, then opens that agent; **Cancel** leaves\n   it archived. A failed restore shows an error and keeps the dialog open for retry.\n\nAgents whose workspace record no longer exists are grouped under a \"Workspace record missing\"\ncard so they stay reachable.\n\nThe header sums what is showing: how many workspaces and agents, and, with filters on, out of how\nmany.\n\n## Search\n\nTyping in the search box does two things:\n\n- **Immediately**, the list narrows to workspaces whose name, title, branch, directory, project,\n  labels, or agent titles contain the text.\n- **After a short pause** (or on Enter), the daemon searches every conversation of the agents that\n  pass the other filters and the list is reordered: workspaces with the best match first, and\n  inside each, the best-matching agents first. Under a matching agent up to three lines appear:\n  who said it (`you` or `agent`), the text around the match, and the match itself in bold.\n  \"+ N more matching lines\" means more exist than are shown.\n\n**Ranked** (the default) answers from the search index. Every word must occur, in any order and\nany form (`schedule` finds `scheduled`, `heartbeat` finds `heartbeats`); the last word may be a\nprefix so results fill in while typing. When no conversation has every word, any word is accepted\nand the note under the filters says so. Names count more than what was said: a hit in an agent\ntitle, workspace name, branch, project, label, directory segment, model, or provider is weighted\nabove hits in the conversation text, and such agents show \"matches workspace name, branch\" above\ntheir snippets. Letter case is ignored, so **Aa** is muted.\n\n**Regex** treats the text as an extended regular expression and runs `grep -E` over the raw\ntranscript files instead; **Aa** then makes it case-sensitive. Grep also answers when the index\nhas not been built yet (the note says so) or when the daemon's Node.js has no SQLite with FTS5.\nGrep hits are listed in file order, not ranked, and also cover tool output and provider\nbookkeeping (`tool` and `meta` lines), which the index leaves out.\n\nA line under the filters says how many conversations matched across how many agents, how many\nagents could not be searched or are not indexed yet, and whether the search was cut short. An\ninvalid regular expression is reported there as well. A second line shows the state of the index.\n\n## Filters\n\n- **Archived** — all, active only, or archived only. A workspace counts as archived when it or\n  its project is.\n- **Provider** — pick any number of providers, with agent counts; a workspace stays listed when\n  at least one of its agents matches, and only those agents are shown.\n- **Project** — pick any number of projects, with workspace counts.\n- **Label** — pick any number of workspace labels, with workspace counts; a workspace needs at\n  least one of them.\n- **Period** — last 24 hours, 7, 30, or 90 days, or any time, on the latest activity of the\n  workspace or any of its agents.\n\nOn a desktop-sized window the dropdowns open as popovers under their trigger; on a phone they\nopen in Paseo's sheet.\n\nThe search text and filter choices are kept while you navigate around the app, so opening a\nworkspace you found and coming back shows the same results; they reset when the app restarts.\n\n## Where the history comes from\n\nPaseo does not store conversations itself; it replays the provider's own transcript. The plugin\nsearches those files directly:\n\n| Provider          | File                                                                                                  |\n| ----------------- | ----------------------------------------------------------------------------------------------------- |\n| Claude Code       | `~/.claude/projects/<encoded cwd>/<session id>.jsonl` plus the subagent files under `<session id>/subagents/` (`CLAUDE_CONFIG_DIR` respected) |\n| Codex             | `~/.codex/sessions/<date>/rollout-*-<thread id>.jsonl`, or `~/.codex/archived_sessions/` once the agent is archived (`CODEX_HOME` respected) |\n| OMP, Pi           | the session file the daemon recorded as the agent's native handle                                     |\n| Cursor, Kilo, Copilot, other ACP providers | nothing on disk; these agents are listed but say \"keeps no conversation file to search\" during a search |\n\n## How it works\n\n- The plugin SDK lists only live workspaces, so the server entry reads the daemon's own records:\n  `$PASEO_HOME/projects/workspaces.json`, `projects.json`, and one JSON file per agent under\n  `$PASEO_HOME/agents`. Files are re-parsed only when their size or modification time changes;\n  the client re-reads the census every 30 seconds. Agent records also hold provider credentials;\n  the server never returns or logs those fields.\n- Typing is debounced by 400 ms; a newer search cancels the one still running; at most two run\n  at once; a grep search is cut short after 20 seconds.\n- Everything is scoped to the host you are viewing; switching hosts reloads the census.\n\n### Search index\n\n- The index is one SQLite file, `$PASEO_HOME/plugin-data/agents-history/search.sqlite`, using\n  Node's built-in `node:sqlite` and its FTS5 extension (present in Node 24 and in Node 22.16 or\n  later). Nothing is installed. It holds one row per user or assistant message (`chunks`) and one\n  row of metadata per agent (`agent_meta`: title, workspace, project, branch, labels, paths,\n  model, provider), both with the porter stemmer.\n- It is built the first time the surface is opened and brought up to date every time the surface\n  refreshes its census (every 30 seconds while open) or a ranked search finds it stale, at most\n  once per 15 seconds. Nothing runs while the surface is closed, and nothing watches the files.\n  A schema bump — a change to how a line is parsed — deletes the file and rebuilds it from the\n  transcripts on the next open, because a file already consumed to its end would otherwise never\n  be read again.\n- Transcripts are append-only, so each file remembers how many bytes were consumed and a later\n  sync reads only what was added; a file that shrank or was replaced is re-read from the start.\n  Work happens in small batches, each its own transaction, with the event loop released in\n  between, so searches answer while indexing runs and a plugin reload stops it within a batch.\n  Files, agents, and messages that disappear are pruned.\n- A search runs two FTS5 queries: one over the messages (every word required, prefix on the last\n  one; relaxed to any word when nothing matched) and one over the agent metadata with per-column\n  weights (title 6, workspace 5, project, branch, and labels 4, path 2, model and provider 1.5).\n  An agent's score is five times its metadata score plus its best message score plus a share of\n  its next four. Snippets come from FTS5's `snippet()`.\n- A Node.js without `node:sqlite` or without FTS5 leaves every search on grep; the note under the\n  filters says so.\n\n## Limitations\n\n- Only providers that keep a transcript file on disk are searchable (see the table above).\n- Conversation files that have been deleted, or a Claude session directory Paseo cannot map to\n  the agent's directory, make that agent \"not searchable\"; the census still lists it.\n- The index holds what people and agents said. Tool input and output, injected context, and\n  provider bookkeeping are not indexed; use **Regex** to grep those.\n- Ranked search folds case and stems words with the English porter stemmer; it does not match\n  inside words (`beat` does not find `heartbeat`) and cannot take a regular expression.\n- A transcript's last line is indexed once its newline is written; a message still being\n  streamed appears after the next sync.\n- `grep -i` folds case according to the daemon's locale. A grep hit inside a very long line (a\n  large tool result) shows a snippet cut from the first megabyte of that line. `grep` returns\n  the first hits in file order, and a query that also appears in a record's fields (a branch\n  name, a directory, a session id) matches every line of that transcript; such hits are labelled\n  `meta` and shown only when nothing said in the conversation matched.\n- A grep-backed search needs `grep` on the daemon's `PATH`. Windows ships none, so when `PATH`\n  has no `grep` the one bundled with Git for Windows is used, looked up under `%ProgramFiles%`,\n  `%ProgramFiles(x86)%`, `%LOCALAPPDATA%`, `scoop`, and `C:\\msys64`. On a host with neither,\n  Ranked search still works once the index is built, but the first search after a fresh install\n  fails with a message saying which executable is missing. Set `AGENTS_HISTORY_GREP` to a grep\n  binary to override the search.\n- Grep is handed one command line per chunk of transcript files. Windows rejects a command line\n  over 32,767 characters, so the chunk budget is smaller there and a single agent holding more\n  files than one command line can carry is split across chunks.\n- The plugin never archives, unarchives, or deletes anything. Its only writes are its own index\n  file, which can be deleted at any time and is rebuilt on the next open.\n- Browsing and searching write only the plugin's index, which can be deleted and rebuilt.\n  Confirming **Restore and open** uses the daemon-local Paseo CLI (`agent reload`) to unarchive\n  the selected agent and restore its provider session. This may also recover its owning workspace.\n  The CLI must be available as `paseo` or through `PASEO_CLI_PATH`.\n\n## Install\n\n```bash\npaseo plugin add panrafal/paseo-plugins:agents-history\n```\n\nFrom a checkout:\n\n```bash\nnpm install            # from the monorepo root\nnpm run typecheck\npaseo plugin install /absolute/path/to/paseo-plugins/agents-history\npaseo plugin ls        # expect \"running\"\n```\n\nAfter editing:\n\n```bash\nnpm run typecheck\npaseo plugin reload agents-history\npaseo plugin logs agents-history   # \"search index at …\" and \"index sync\" lines once the surface is opened\n```\n"}