{"id":47719441,"url":"https://github.com/geekmuse/chronicle","last_synced_at":"2026-04-02T19:15:28.866Z","repository":{"id":348141464,"uuid":"1196671108","full_name":"geekmuse/chronicle","owner":"geekmuse","description":"Sync AI coding agent session history (Pi, Claude Code) across machines using path canonicalization and Git-backed CRDT merge","archived":false,"fork":false,"pushed_at":"2026-03-31T00:34:00.000Z","size":1615,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-31T01:41:23.683Z","etag":null,"topics":["ai","canonicalization","claude-code","cli","developer-tools","git","pi-agent","rust","session-history","session-management","sync"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/geekmuse.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-30T23:36:19.000Z","updated_at":"2026-03-31T00:47:15.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/geekmuse/chronicle","commit_stats":null,"previous_names":["geekmuse/chronicle"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/geekmuse/chronicle","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geekmuse%2Fchronicle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geekmuse%2Fchronicle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geekmuse%2Fchronicle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geekmuse%2Fchronicle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/geekmuse","download_url":"https://codeload.github.com/geekmuse/chronicle/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geekmuse%2Fchronicle/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31314210,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","canonicalization","claude-code","cli","developer-tools","git","pi-agent","rust","session-history","session-management","sync"],"created_at":"2026-04-02T19:15:28.274Z","updated_at":"2026-04-02T19:15:28.852Z","avatar_url":"https://github.com/geekmuse.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Chronicle\n\n[![CI](https://github.com/geekmuse/chronicle/actions/workflows/ci.yml/badge.svg)](https://github.com/geekmuse/chronicle/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n\n\u003e Bidirectional sync for AI coding agent session history across machines, with path canonicalization and Git-backed storage.\n\n---\n\n\u003e [!WARNING]\n\u003e **ALPHA SOFTWARE — USE WITH CAUTION**\n\u003e\n\u003e Chronicle is alpha-quality software. It directly modifies AI agent session files on\n\u003e your machine. Bugs in the canonicalization, merge, or materialization logic could\n\u003e **corrupt or permanently delete your session history**.\n\u003e\n\u003e **Back up your existing sessions before installing or running Chronicle** (see\n\u003e [Before You Start](#before-you-start-back-up-your-existing-sessions) below).\n\u003e\n\u003e Chronicle is provided as-is, with no warranty of any kind. See [LICENSE](LICENSE).\n\n---\n\n## Overview\n\nChronicle synchronizes Pi and Claude Code session history across multiple machines\nwhere `$HOME` paths differ.\n\n**Chronicle is for you if** you use AI coding agents across multiple machines with\ndifferent home directory paths (e.g., `/Users/alice` on your Mac and `/home/alice`\non your Linux workstation) and you want session history to follow you between them\nwithout running your own sync infrastructure.\n\n**Chronicle is not for you if** you only work on one machine, your machines already\nshare identical `$HOME` layouts (in which case any file sync tool will work), or you\nalready run self-hosted sync infrastructure like Syncthing with an always-on relay\nnode — simpler tools will serve you better.\n\nIt uses a canonicalization layer to abstract away per-machine path differences and\nGit as the storage and transport backend. Session files are merged using a grow-only\nCRDT (set-union), preserving the append-only invariant of JSONL session data. See\n[Storage Backends](docs/references/002-storage-backends.md) for why Git was chosen\nover alternatives.\n\n## Features\n\n- **Cross-machine sync** — Session history follows you between machines with different `$HOME` paths\n- **Path canonicalization** — `$HOME` paths are replaced with `{{SYNC_HOME}}` tokens, with configurable canonicalization levels (paths, structured fields, freeform text)\n- **CRDT merge** — Grow-only set merge ensures no session data is ever lost, even with concurrent edits on different machines\n- **Partial materialization** — Pull only the N most recent sessions per project, while the Git repo retains complete history\n- **Agent-agnostic** — Supports Pi and Claude Code with extensible agent architecture\n- **Stateless CLI** — No daemon; a simple CLI invoked by cron on a configurable schedule\n\n---\n\n## Before You Start: Back Up Your Existing Sessions\n\nChronicle modifies session files in-place during `import` and `pull`. Take a complete\nsnapshot of your session data **before** running any Chronicle command for the first time.\n\n**Step 1 — Identify your session directories**\n\n| Agent | Default session directory |\n|-------|--------------------------|\n| Pi | `~/.pi/agent/sessions/` |\n| Claude Code | `~/.claude/projects/` |\n\n\u003e These directories may not both exist if you only use one agent.\n\n**Step 2 — Create a dated backup**\n\n```bash\n# Back up Pi sessions (skip if you don't use Pi)\ncp -r ~/.pi/agent/sessions/ ~/chronicle-backup-pi-$(date +%Y%m%d)/\n\n# Back up Claude Code sessions (skip if you don't use Claude Code)\ncp -r ~/.claude/projects/ ~/chronicle-backup-claude-$(date +%Y%m%d)/\n```\n\n**Step 3 — Verify the backup**\n\n```bash\n# Confirm the backup directories exist and are non-empty\nls -lh ~/chronicle-backup-pi-$(date +%Y%m%d)/ 2\u003e/dev/null\nls -lh ~/chronicle-backup-claude-$(date +%Y%m%d)/ 2\u003e/dev/null\n```\n\n**Step 4 — Store the backup somewhere safe**\n\nCopy the backup directories to an external drive, cloud storage, or any location\noutside `$HOME` before proceeding. Do not rely on the backup being in `$HOME` — if\nsomething goes wrong you want it clearly separated.\n\n\u003e **Keep these backups.** Do not delete them until you have been running Chronicle\n\u003e successfully across multiple machines for at least a week and have confirmed your\n\u003e session history is intact.\n\n---\n\n## Installation\n\n### Option 1: Pre-built Binaries (Recommended)\n\nWhen the GitHub Actions CI pipeline is active, pre-built binaries are attached to\neach [GitHub Release](https://github.com/geekmuse/chronicle/releases). Download\nthe binary for your platform:\n\n| Platform | Binary |\n|----------|--------|\n| Linux x86-64 | `chronicle-x86_64-unknown-linux-gnu` |\n| Linux ARM64 | `chronicle-aarch64-unknown-linux-gnu` |\n| macOS Intel | `chronicle-x86_64-apple-darwin` |\n| macOS Apple Silicon | `chronicle-aarch64-apple-darwin` |\n\n```bash\n# Example: macOS Apple Silicon\ncurl -L https://github.com/geekmuse/chronicle/releases/latest/download/chronicle-aarch64-apple-darwin \\\n  -o /usr/local/bin/chronicle\nchmod +x /usr/local/bin/chronicle\n```\n\n\u003e **Note:** If no release binaries exist yet, use Option 2 below.\n\n### Option 2: From Source\n\n```bash\n# Prerequisites: Rust stable (https://rustup.rs)\ngit clone https://github.com/geekmuse/chronicle.git\ncd chronicle\n\n# Install into ~/.cargo/bin (must be on your PATH)\ncargo install --path .\n```\n\n**After install, verify it works:**\n\n```bash\nchronicle --version\n```\n\n---\n\n## Backend Repository Setup\n\nChronicle uses a private Git repository as the sync backend — your session history\nis stored there in canonicalized form and exchanged between machines via normal\nGit push/pull.\n\n### Why it must be private\n\nSession files contain the full text of your conversations with AI coding agents,\nincluding code, file paths, and potentially sensitive details about your projects.\n**The backend repository must be private.** Never use a public repository.\n\n### Step 1 — Create a private repository\n\nCreate an empty **private** repository on GitHub, GitLab, Gitea, or any Git host\nyou control. Do not initialize it with a README, `.gitignore`, or any other files —\nChronicle will set up the repository contents itself.\n\n```\nGitHub:  https://github.com/new   → set to Private\nGitLab:  https://gitlab.com/projects/new → set Visibility to Private\n```\n\n### Step 2 — Configure SSH access\n\nChronicle uses [libgit2](https://libgit2.org/) for all Git operations. **libgit2 is\nnot `~/.ssh/config`-aware** — it ignores `IdentityFile`, `Host` blocks, and other\nssh config directives entirely. All SSH authentication goes through the SSH agent\nprotocol via `SSH_AUTH_SOCK`.\n\nThis means **your SSH key must be loaded in a running `ssh-agent`** before Chronicle\ncan push or pull.\n\n#### macOS\n\nmacOS ships a Keychain-integrated SSH agent managed by `launchd`. Use it to load\nyour key once; it will survive reboots automatically:\n\n```bash\n# Add your key to macOS Keychain (done once)\nssh-add --apple-use-keychain ~/.ssh/id_ed25519\n\n# Verify the key is loaded\nssh-add -l\n```\n\nIf `ssh-add -l` returns `The agent has no identities`, your key is not loaded. Run\nthe `--apple-use-keychain` command above.\n\n#### Linux\n\nMost desktop environments start an SSH agent automatically. If you are on a headless\nserver or your agent is not running:\n\n```bash\n# Start an agent for the current shell session\neval $(ssh-agent -s)\n\n# Add your key\nssh-add ~/.ssh/id_ed25519\n\n# For persistence across sessions, add to ~/.bashrc / ~/.zshrc:\n# if [ -z \"$SSH_AUTH_SOCK\" ]; then\n#   eval $(ssh-agent -s)\n#   ssh-add ~/.ssh/id_ed25519\n# fi\n```\n\nFor systemd-based systems you can also use `systemd --user` to run a persistent\nagent socket at `/run/user/$(id -u)/ssh-agent.socket`. Chronicle's cron entries\nfall back to this path automatically on Linux.\n\n#### Verify SSH access to your backend repo\n\n```bash\n# Test that SSH auth works before configuring Chronicle\nssh -T git@github.com       # GitHub\nssh -T git@gitlab.com       # GitLab\n```\n\n### Step 3 — Note the SSH remote URL\n\nUse the SSH remote URL (not HTTPS) for your backend repo, for example:\n\n```\ngit@github.com:yourname/chronicle-sessions.git\n```\n\nChronicle will push to this URL. Using HTTPS is technically possible but requires\ncredential helpers to be configured separately; SSH via the agent is the recommended\nand tested path.\n\n---\n\n## Quick Start\n\n```bash\n# 1. First-time setup — creates config at ~/.config/chronicle/config.toml,\n#    generates a machine name, and initializes the local mirror repo\nchronicle init\n\n# 2. Set your backend remote URL (the private repo you created above)\nchronicle config set general.remote_url git@github.com:yourname/chronicle-sessions.git\n\n# 3. Import existing session history (one-time, before first sync)\n#    This stages all current sessions into the local repo without pushing\nchronicle import\n\n# 4. Run a manual sync to push your history to the remote\nchronicle sync\n\n# 5. (Optional) Install the cron schedule for automatic background sync\nchronicle schedule install    # runs every 5 minutes by default\n```\n\n---\n\n## Usage\n\n```bash\n# First-time setup — creates config, generates machine name, inits local repo\nchronicle init\n\n# Import existing session history (one-time, before first sync)\nchronicle import\n\n# Run a single sync cycle (fetch → merge → push)\nchronicle sync\n\n# Push local commits to the remote without a full sync\nchronicle push\n\n# Pull and materialise the latest remote sessions locally\nchronicle pull\n\n# Check sync status (last sync time, pending files, remote branch)\nchronicle status\n\n# View recent sync errors\nchronicle errors\n\n# Show or change a config value\nchronicle config get canonicalization.level\nchronicle config reset canonicalization.level\n\n# Install / remove / check the cron schedule\nchronicle schedule install    # runs every 5 minutes by default\nchronicle schedule uninstall\nchronicle schedule status\n```\n\n---\n\n## Known Setup Gotchas\n\n**Project directory paths must be consistent across machines**\n\nChronicle canonicalizes your home directory (`$HOME` → `{{SYNC_HOME}}`), but it\ndoes **not** automatically handle differences in the path structure beneath it.\nIf your projects live at `~/Dev/` on one machine and `~/projects/` on another,\nChronicle will treat them as entirely separate project trees — sessions will not\nmerge, and both machines will accumulate independent histories that never converge.\n\nFor example:\n\n| Machine | Raw path | Canonical form |\n|---------|----------|----------------|\n| A | `/Users/alice/Dev/myproject` | `{{SYNC_HOME}}/Dev/myproject` |\n| B | `/home/alice/projects/myproject` | `{{SYNC_HOME}}/projects/myproject` |\n\nThese are different canonical paths. Chronicle will never merge their sessions.\n\n**The simplest fix:** use the same sub-`$HOME` path layout on every machine\n(e.g., always `~/Dev/`, always `~/code/`, etc.).\n\n**If your paths already differ:** define a custom token that maps each machine's\nprojects root to the same canonical name. In each machine's\n`~/.config/chronicle/config.toml`:\n\n```toml\n# Machine A  (~/.config/chronicle/config.toml)\n[canonicalization.tokens]\n\"{{SYNC_PROJECTS}}\" = \"/Users/alice/Dev\"\n```\n\n```toml\n# Machine B  (~/.config/chronicle/config.toml)\n[canonicalization.tokens]\n\"{{SYNC_PROJECTS}}\" = \"/home/alice/projects\"\n```\n\nWith this in place, both paths canonicalize to `{{SYNC_PROJECTS}}/myproject`\nand sessions will merge correctly. The token value is machine-local and never\nstored in the shared repository.\n\n\u003e **Note:** Custom tokens only help for sessions created *after* the token is\n\u003e configured. Pre-existing sessions already stored under mismatched paths will\n\u003e remain separate. Plan your directory layout before the first sync.\n\n---\n\n**SSH agent not available in cron**\n\nChronicle's `schedule install` command generates cron entries that automatically\ndiscover and forward `SSH_AUTH_SOCK` at runtime, so you should not need to do\nanything special. However, if `chronicle status` shows auth errors after installing\nthe schedule:\n\n- **macOS:** Make sure your key is added to Keychain (`ssh-add --apple-use-keychain`).\n  The cron entry uses a `find`-based socket discovery trick that locates the\n  Keychain agent socket even in the stripped cron environment.\n- **Linux:** The cron entry falls back to the systemd user SSH agent socket\n  (`/run/user/$(id -u)/ssh-agent.socket`). Ensure your SSH key is loaded there.\n\n**`chronicle init` fails with \"remote already exists\"**\n\nThe local mirror repo already has a remote configured. Run\n`chronicle config set general.remote_url \u003curl\u003e` instead of re-running `init`.\n\n**Large repos make the first `sync` slow**\n\nThe initial `chronicle import` can be slow on machines with many sessions (thousands\nof `.jsonl` files). This is a one-time cost. Subsequent syncs are fast because the\nstate cache (`materialize-state.json`) tracks what has already been processed.\n\n**Cron overlap / `index.lock` errors**\n\nChronicle acquires an advisory file lock (`chronicle.lock`) before each sync so\nthat overlapping cron invocations exit cleanly rather than crashing with git index\nerrors. If you see stale lock errors, check that no Chronicle process is hung, then\ndelete `\u003crepo-parent\u003e/chronicle.lock` manually.\n\n**Canonicalization level**\n\nThe `canonicalization.level` config key controls how aggressively paths are\nreplaced in session data. Changing this on an already-synced repo can cause\nre-canonicalization conflicts. Do not change the level after your first successful\nsync unless you understand the implications and are prepared to re-import.\n\n---\n\n## Documentation\n\nDetailed documentation lives in the [`docs/`](docs/) directory:\n\n| Section | Path | Description |\n|---------|------|-------------|\n| Architecture | [`docs/001-architecture.md`](docs/001-architecture.md) | System design and key decisions |\n| Development Guide | [`docs/002-development-guide.md`](docs/002-development-guide.md) | How to develop, test, and contribute |\n| Doc Standards | [`docs/003-documentation-standards.md`](docs/003-documentation-standards.md) | How docs are structured and maintained |\n| Specs | [`docs/specs/`](docs/specs/) | Feature specifications and design docs |\n| ADRs | [`docs/adrs/`](docs/adrs/) | Architecture Decision Records |\n| References | [`docs/references/`](docs/references/) | CLI reference, config reference, glossary |\n| — Encryption | [`docs/references/001-encryption.md`](docs/references/001-encryption.md) | Encryption options, tradeoffs, and setup |\n| — Storage Backends | [`docs/references/002-storage-backends.md`](docs/references/002-storage-backends.md) | Why Git, and how it compares to alternatives |\n| — Threat Model | [`docs/references/003-threat-model.md`](docs/references/003-threat-model.md) | Threat vectors, assumptions, and mitigations |\n| Tasks | [`docs/tasks/`](docs/tasks/) | Work items and implementation plans |\n| Research | [`docs/research/`](docs/research/) | Spikes, investigations, POC write-ups |\n\n## Development\n\n```bash\n# Clone the repository\ngit clone https://github.com/geekmuse/chronicle.git\ncd chronicle\n\n# Build\ncargo build\n\n# Run tests\ncargo test\n\n# Run linter\ncargo clippy -- -D warnings\n```\n\nSee [Development Guide](docs/002-development-guide.md) for full details.\n\n## Contributing\n\n1. Fork the repository\n2. Create a feature branch (`git checkout -b feat/amazing-feature`)\n3. Commit using [conventional commits](https://www.conventionalcommits.org/) (`git commit -m 'feat: add amazing feature'`)\n4. Push to the branch (`git push origin feat/amazing-feature`)\n5. Open a Pull Request\n\nPlease read [AGENTS.md](AGENTS.md) for project conventions and [docs/002-development-guide.md](docs/002-development-guide.md) for the full development workflow.\n\n## Versioning\n\nThis project uses [Semantic Versioning](https://semver.org/). See [CHANGELOG.md](CHANGELOG.md) for release history.\n\n## License\n\nMIT — see [LICENSE](LICENSE) for details.\n\n## Author\n\nBrad Campbell\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeekmuse%2Fchronicle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgeekmuse%2Fchronicle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeekmuse%2Fchronicle/lists"}