- What thts Does
- Key Concepts
- Getting Started
- Directory Organization
- Syncing
- Team Collaboration
- Profiles
- Git Worktrees
- Configuration
- Shell Completion
- Working with AI Assistants
- Compatibility with HumanLayer
thts manages developer notes (architecture decisions, TODOs, investigation logs) separately from your code repositories while making them accessible in every project.
Problems it solves:
- Notes cluttering code repos or getting lost in random files
- Context switching between projects and losing track of decisions
- Sharing team knowledge without polluting git history
- Making notes searchable by AI coding assistants
thts uses two locations that can be confusing at first:
| Thoughts Repo | Thoughts Directory | |
|---|---|---|
| Example | ~/thoughts/ |
~/src/myproject/thoughts/ |
| What it is | A real git repo | Symlinks to the thoughts repo |
| Created by | thts setup |
thts init |
| Git behavior | Normal commits, push/pull | Git-ignored, never committed |
| Contains | Your actual files | Only symlinks + hard links |
Your files live in the thoughts repo. The thoughts directory in each project is just a window into the relevant parts of that repo.
~/thoughts/ # THOUGHTS REPO - files live here
├── repos/
│ ├── myproject/
│ │ ├── {user}/
│ │ │ └── notes.md # ← actual file
│ │ └── shared/
│ │ └── architecture.md # ← actual file
│ └── another-project/
│ └── ...
└── global/
├── {user}/
│ └── snippets.md # ← actual file
└── shared/
└── team-standards.md # ← actual file
~/src/myproject/thoughts/ # THOUGHTS DIRECTORY - symlinks
├── {user}/ → ~/thoughts/repos/myproject/{user}/
├── shared/ → ~/thoughts/repos/myproject/shared/
├── global/ → ~/thoughts/global/
└── searchable/ # hard links (explained below)
When you edit ~/src/myproject/thoughts/{user}/notes.md, you're actually
editing ~/thoughts/repos/myproject/{user}/notes.md through the symlink.
This means:
- Changes appear immediately in both locations (same file)
thts synccommits changes to the thoughts repo- Your code repo never sees the files (they're git-ignored)
Many tools (including AI assistants) don't follow symlinks when searching. The
searchable/ directory contains hard links to all your thoughts files,
making them discoverable.
thoughts/searchable/
├── {user}/notes.md # hard link to actual file
├── shared/architecture.md # hard link to actual file
└── global/{user}/snippets.md # hard link to actual file
Important:
- Hard links are the same file (editing one edits both)
- Always reference files by their canonical path (e.g.,
thoughts/{user}/notes.md) - The searchable directory rebuilds on
thts sync
Run once per machine to configure your thoughts repo:
thts setupThis prompts for:
- Thoughts repo path - Where your notes live (default:
~/thoughts) - Username - Your identifier for personal notes (default:
$USER)
The thoughts repo is created as a git repo if it doesn't exist.
In any git repository:
cd ~/src/myproject
thts initThis:
- Creates the
thoughts/directory with symlinks - Adds
thoughts/to.gitignore - Installs git hooks for protection and auto-sync
- Creates the project structure in your thoughts repo
Options:
thts init --name custom-name # Override project name (default: from git remote)
thts init --profile work # Use a specific profile
thts init --force # Reinitialize existing setup
thts init --no-agents # Skip the post-init agent integration promptProfile assignment: When you run thts init, the current default profile is
explicitly assigned to the repository. This "locks in" the profile so that if
you later change your default profile, existing repositories keep their original
assignment. To see which profile a repository uses, run thts status.
# Create a note
echo "# Project Architecture" > thoughts/{user}/architecture.md
# Check status
thts status
# Sync to thoughts repo (also happens automatically on commits)
thts sync -m "Added architecture notes"The thts add command creates properly formatted thought files with templates:
thts add -t "API design decisions" # Creates in notes/ (default category)
thts add -t "Feature roadmap" --in plans # Creates in plans/
thts add -t "Sprint work" --in plans/active # Creates in plans/active/ subcategoryWhat it does:
- Creates a file named
YYYY-MM-DD-slugified-title.md - Populates it with the appropriate template from
thoughts/.templates/ - Opens the file in your editor
Scope control:
thts add -t "Team gotcha" --in notes --shared # Write to shared/
thts add -t "My todo" --in notes --personal # Write to {user}/Without flags, scope is determined by:
- The category's configured scope (e.g.,
researchdefaults toshared) - Your
defaultScopeconfig setting (defaults touser)
Sub-category scope inheritance:
Sub-categories inherit their parent category's scope unless explicitly
overridden. If a category has scope: both (allowing either shared or user),
sub-categories also inherit both and will use your defaultScope setting.
categories:
plans:
scope: both # Can be shared or user
subCategories:
active: # Inherits "both" from parent
description: "Active plans"
complete:
scope: shared # Explicitly set to shared
description: "Completed plans"To ensure sub-categories always go to a specific location, set their scope explicitly rather than relying on inheritance.
Target selection:
thts add -t "Note" --in notes # Current repo (or default global)
thts add -t "Work note" --profile work --in notes # Work profile's global dir
thts add -t "X" --repo ~/other-project --in notes # Another repo's thoughts dirTarget resolution order:
--repoflag: use that repo's thoughts directory--profileflag: use that profile's global thoughts directory- Current git repo: use current repo's thoughts directory (if thts initialized)
- Otherwise: use default profile's global directory
Content input modes:
By default, thts add opens your editor with the template. For scripted or
piped usage, you can provide content directly:
# Inline content (positional argument)
thts add -t "memory-issue" "TODO: investigate memory leak" --in notes
# From an existing file
thts add -t "imported plan" --from draft.md --in plans
# From stdin (piped) - auto-detected, no flag needed
echo "Quick note about the bug" | thts add -t "bug-note" --in notes
cat meeting-notes.txt | thts add -t "meeting-2026-01-15" --in notes
# Create file without opening editor
thts add -t "placeholder" --no-edit --in notesPiped input is automatically detected - no need to specify --stdin explicitly.
Content sources are mutually exclusive (positional content, --from, stdin).
When using any of these, the editor is skipped automatically. Use --no-edit
with the default template behavior to create a file without opening the editor.
| Location | Use For | Visibility |
|---|---|---|
thoughts/{user}/ |
Your personal project notes | Just you |
thoughts/shared/ |
Team project notes | Everyone with repo access |
thoughts/global/{user}/ |
Your cross-project notes | Just you |
thoughts/global/shared/ |
Team cross-project notes | Everyone with repo access |
thoughts/{user}/ # Personal project notes
├── todo.md # Your task list
├── investigations/
│ └── 2024-01-15-auth-bug.md # Debugging sessions
└── decisions/
└── api-design.md # Your design notes
thoughts/shared/ # Team project notes
├── architecture.md # System design
├── onboarding.md # Getting started guide
└── decisions/
└── 2024-01-10-database.md # Team decisions (ADRs)
thoughts/global/{user}/ # Your cross-project notes
├── snippets.md # Reusable code patterns
└── tools.md # Tool configurations
thoughts/global/shared/ # Team cross-project notes
├── coding-standards.md
└── review-checklist.md
Git hooks installed by thts init handle syncing:
- Pre-commit hook - Prevents accidentally committing
thoughts/to your code repo - Post-commit hook - Syncs thoughts to the thoughts repo after each commit
thts sync # Sync with auto-generated message
thts sync -m "Updated docs" # Sync with custom messageOpen your thoughts directory directly in your editor:
thts edit # Open ./thoughts/ (or default profile if not in repo)
thts edit --profile work # Open specific profile's thoughts repoEditor resolution: Uses config editor field, then $VISUAL, then
$EDITOR. Errors if none set.
Configure a default editor in ~/.config/thts/config.yaml:
editor: nvimSync does:
- Discovers other users' directories (creates symlinks for teammates)
- Rebuilds the
searchable/directory - Commits all changes to the thoughts repo
- Pulls and rebases from remote
- Pushes to remote
Control remote operations with the --mode flag or config:
thts sync --mode=full # Pull and push (default)
thts sync --mode=pull # Pull only, skip push
thts sync --mode=local # No remote operations| Mode | Pull | Push | Use Case |
|---|---|---|---|
full |
Yes | Yes | Normal operation (default) |
pull |
Yes | No | Stay updated, batch pushes for later |
local |
No | No | Offline/airplane mode, avoid auth prompts |
Set a default mode in config:
sync:
mode: localWhen push is skipped, you'll see a warning if there are unpushed commits:
! 2 commits not pushed (local mode)
Run 'thts sync --mode=full' or 'git push' in ~/thoughts to push
Customize the commit messages used when syncing thoughts using Go text/template syntax:
sync:
mode: full
commitMessage: '[{{.Profile}}] {{.Repo}} - {{.Date.Format "2006-01-02"}}'
commitMessageHook: "Auto-sync ({{.Repo}}): {{.CommitMessage}}"Available variables:
| Variable | Description |
|---|---|
{{.Date}} |
Current time (use .Format "..." for custom) |
{{.Repo}} |
Repository name |
{{.Profile}} |
Active profile name |
{{.User}} |
Your username from config |
{{.CommitMessage}} |
Triggering commit message (hook only) |
Two templates:
commitMessage- Used for manual sync (thts sync)commitMessageHook- Used for post-commit hook auto-sync- Note: the git hook lives in each repo that
thts initis run on, it triggersthts syncwith a reference to the commit message that triggered the git hook
- Note: the git hook lives in each repo that
Per-profile overrides:
profiles:
work:
thoughtsRepo: ~/work-thoughts
sync:
commitMessage: "[work] {{.Repo}} sync"
commitMessageHook: "[work] {{.CommitMessage}}"Profile settings override global settings. If not set, defaults are used:
commitMessage:sync: {{.Date.Format "2006-01-02T15:04:05Z07:00"}}commitMessageHook:sync(auto): {{.CommitMessage}}
If sync fails due to conflicts:
# thts will print instructions like:
cd ~/thoughts
git status # See conflicting files
# Fix conflicts manually
git rebase --continue
thts sync # RetryPush your thoughts repo to a private remote:
cd ~/thoughts
git remote add origin git@github.com:yourteam/thoughts.git
git push -u origin mainTeammates clone it and point their config to it.
When a teammate syncs their notes and you run thts sync, their directories
automatically appear:
thoughts/
├── {user}/ # Your notes
├── alice/ # Alice's notes (auto-discovered)
├── bob/ # Bob's notes (auto-discovered)
├── shared/ # Team notes
└── global/
This happens because sync checks for new user directories in the thoughts repo and creates symlinks for them.
Profiles let you maintain separate thoughts repos for different contexts (work vs personal, different clients).
thts profile create work --repo ~/work-thoughtscd ~/src/work-project
thts init --profile work # Uses work profile's thoughts repothts profile list # List all profiles
thts profile show work # Show profile details
thts profile delete work # Delete a profileEach profile has its own thoughts repo. When you init a project with a profile, it maps that project to use the profile's repo.
profiles:
work:
thoughtsRepo: ~/work-thoughtsRepository mappings are stored in a state file under XDG_STATE_HOME (default
~/.local/state/thts/), not in your config file.
State files are namespaced by the active config path. For example:
~/.local/state/thts/state-<sha256-of-config-realpath>.yaml
This keeps mappings separate when you switch configs with THTS_CONFIG_PATH
(for example personal vs work configs).
If you want to move a profile's central thoughts repo to a new absolute path, use this sequence to avoid surprises:
- Move the existing thoughts repo to the new location (preserves git history)
- Update
profiles.<name>.thoughtsRepoin the same config file - In each initialized project, run
thts init --refresh - Verify with
thts status - Run
thts sync
Example:
# 1) Move the central thoughts repo
mv ~/thoughts ~/notes/thoughts
# 2) Update config
thts config --edit
# profiles:
# personal:
# thoughtsRepo: ~/notes/thoughts
# 3) Refresh symlinks in each initialized project
cd ~/src/project-a && thts init --refresh
cd ~/src/project-b && thts init --refresh
# 4) Verify active config + state + profile resolution
thts status
# 5) Sync as usual
thts syncNotes:
- This works cleanly when the config path is unchanged (same
THTS_CONFIG_PATHcontext). Repo mappings remain valid because they reference profile names. thts init --refreshupdates project symlinks to point at the new central location.- If you also change the config file path, you are switching state namespaces;
run
thts initin each repository under the new config context.
thts supports git worktrees. Run thts init in each worktree where you want a
local thoughts/ directory:
git worktree add ../feature -b feature
cd ../feature
thts init --no-agents # Sets up local thoughts/ for this worktree without promptingHow it works:
- Git hooks install to the common git dir (shared across worktrees)
- Symlinks are per-worktree (each worktree has its own
thoughts/) - Repository mapping is shared by repo identity (no duplicate mapping per worktree)
- Project/profile are reused from the existing mapping when one already exists
uninit now has two scopes:
thts uninit # Remove local setup in current worktree only
thts uninit --all # Remove local setup and detach shared repo mappingUse --all only when you want to fully detach the repository from thts state.
If you don't want post-commit sync in worktrees:
thts config --edit
# Set "autoSyncInWorktrees": falsethts config # Pretty print
thts config --json # JSON outputthts config --edit # Opens in $EDITORthoughtsRepo: ~/thoughts
reposDir: repos
globalDir: global
user: "{user}"
editor: nvim
autoSyncInWorktrees: true
gitIgnore: project| Option | Description | Default |
|---|---|---|
thoughtsRepo |
Path to thoughts repo | ~/thoughts |
reposDir |
Subdirectory for project thoughts | repos |
globalDir |
Subdirectory for global thoughts | global |
user |
Your username (can't be "global") | $USER |
editor |
Editor for thts edit |
$EDITOR |
autoSyncInWorktrees |
Auto-sync on commits in worktrees | true |
gitIgnore |
Where to ignore thoughts/ |
project |
sync.mode |
Sync mode: full, pull, or local | full |
sync.commitMessage |
Template for manual sync messages | (see Commit Message section) |
sync.commitMessageHook |
Template for hook auto-sync messages | (see Commit Message section) |
Environment variables override config file settings. Useful for CI/CD, scripting, or temporary overrides without modifying config.
| Variable | Description | Overrides |
|---|---|---|
THTS_CONFIG_PATH |
Custom path to config file | Default config path |
THTS_USER |
Username for thoughts directories | user in config |
THTS_PROFILE |
Default profile to use | --profile flag |
THTS_SYNC_MODE |
Sync mode (full, pull, local) | sync.mode in config |
Resolution order (highest to lowest priority):
- CLI flag (e.g.,
--profile,--mode) - Environment variable
- Config file
- Default value
Examples:
# Use a different config file
THTS_CONFIG_PATH=~/alt-config.yaml thts status
# Override username for this session
THTS_USER=teammate thts sync
# Use work profile without --profile flag
THTS_PROFILE=work thts init
# Force local-only sync (no network)
THTS_SYNC_MODE=local thts syncScripting example:
# CI/CD: sync without remote operations
export THTS_SYNC_MODE=local
export THTS_USER=ci-bot
thts sync| Value | Behavior |
|---|---|
project |
Add to project's .gitignore |
local |
Add to .git/info/exclude |
global |
Add to ~/.config/git/ignore |
disabled |
Don't add anywhere |
Generate shell completion scripts for tab completion of commands and flags:
thts completion bash # Bash
thts completion zsh # Zsh
thts completion fish # FishFish:
thts completion fish | source
# Persist:
thts completion fish > ~/.config/fish/completions/thts.fishBash:
source <(thts completion bash)
# Persist:
thts completion bash > /etc/bash_completion.d/thtsZsh:
source <(thts completion zsh)
# Persist (ensure directory is in fpath):
thts completion zsh > "${fpath[1]}/_thts"Completions include dynamic values like profile names and agent types.
The searchable/ directory makes your thoughts discoverable by AI tools that
don't follow symlinks.
When working with AI assistants:
- Point them to search in
thoughts/searchable/for finding content - Reference files by canonical path (e.g.,
thoughts/{user}/notes.md) - Run
thts syncto update searchable directory before AI sessions
thts provides deep integration with AI coding agents to give them awareness of your thoughts directory and enable session continuity.
| Agent | Project Dir | Skills Dir | Commands Dir | Global Path |
|---|---|---|---|---|
| Claude Code | .claude/ |
skills/ |
commands/ |
~/.claude/ |
| Codex CLI | .codex/ |
skills/*/SKILL.md |
prompts/ (glob) |
~/.codex/ |
| OpenCode | .opencode/ |
skills/*/SKILL.md |
commands/ |
~/.config/opencode/ |
Key differences:
- Codex "prompts": Codex calls commands "prompts". They are global-only and
invoked as
/prompts:<name>(e.g.,/prompts:thts-handoff). - OpenCode XDG: OpenCode uses XDG for global config (
~/.config/opencode/) rather than a dot-folder in home.
thts init agents # Install for detected agents
thts init agents -i # Interactive mode
thts init agents --agents claude,codex # Specify agents
thts init agents --with-settings # Also create settings filesBy default, thts init agents installs to project directories (.claude/,
.codex/, .opencode/). You can also install globally:
thts init agents --global all # Install everything globally
thts init agents --global skills,commands # Install specific componentsGlobal paths:
| Agent | Global Path |
|---|---|
| Claude | ~/.claude/ |
| Codex | ~/.codex/ |
| OpenCode | ~/.config/opencode/ |
| Gemini | ~/.gemini/ |
When to use global:
- Skills/commands you want available in all projects
- Codex prompts (they only work globally)
- When you don't want to modify project files
Claude and Gemini hook-based integration requires:
- jq - JSON parser for hook scripts (required)
- yq - YAML parser for custom keyword configuration (optional)
Install on common systems:
# macOS
brew install jq yq
# Ubuntu/Debian
sudo apt install jq
pip install yq
# Fedora
sudo dnf install jq yqWhen you run thts init agents, you'll be asked how to activate the
integration:
| Level | Config Value | Description | Best For |
|---|---|---|---|
| Hook-based (recommended) | hook |
Uses the agent's native hook or plugin mechanism | Clean project instructions |
| Always-on (shared) | agents-content |
Adds a managed block to project instructions | Shared agent policy |
| Always-on (local) | agents-content-local |
Creates local-only instructions | Personal always-on policy |
| On-demand only | on-demand |
Skill loads instructions from the thts CLI |
Manual activation |
Hook-based integration (default) keeps project instruction files clean. Claude and Gemini:
- Injecting a minimal bootstrap (~6 lines) at session start
- Loading full instructions (~200 lines) only when keywords are detected
- Keywords include: research, plan, decision, thoughts, handoff, notes, etc.
OpenCode uses an idempotent plugin that adds the generated instructions to each
model request without adding another copy when another plugin instance already
supplied it. OpenCode local-only mode installs the same project plugin, which is
covered by the managed thts patterns in .gitignore.
The plugin caches generated instructions for the OpenCode session. Restart OpenCode after changing thts categories or instruction configuration.
Note: Codex does not support hooks and will automatically fall back to always-on mode with a warning.
The keywords that trigger full instruction loading can be customized in
~/.config/thts/config.yaml:
hooks:
keywords:
- research
- plan
- decision
- thoughts
- handoff
# Add your own keywords...Default keywords: research, plan, decision, thoughts, handoff, notes, save, document, capture, findings, learnings, gotchas, ADR, architecture, resume, wrap up, end session
Claude's session-start hook includes a plan mode directive by default, reminding
users to copy approved plans into thoughts/shared/plans/ and treat that copy
as canonical.
You can disable this behavior in ~/.config/thts/config.yaml:
hooks:
claudePlanDirective: falseFiles copied to agent directories:
skills/thts-integrate.*- Activation skill with a CLI fallbackcommands/thts-handoff.md- Create session handoff documentscommands/thts-resume.md- Resume from handoff documentsagents/thoughts-locator.md- Find documents in thoughts/agents/thoughts-analyzer.md- Extract insights from documents- Agent-specific hooks or the OpenCode
thts-integration.tsplugin in hook mode
Full integration instructions are generated by thts agent-instructions; they
are not copied as a separate file.
Note: Directory names vary by agent (see table above).
| Command | Purpose |
|---|---|
/thts-integrate |
Activate thoughts/ awareness for current task |
/thts-handoff |
Create a handoff document when ending a session |
/thts-resume <path> |
Resume work from a handoff document |
Codex note: Use /prompts:thts-handoff instead of /thts-handoff.
Handoffs preserve context across sessions:
# At end of session
/thts-handoff
# Next session (or different person)
/thts-resume thoughts/shared/handoffs/2024-01-15_10-30-00_feature-work.mdThe handoff document captures:
- Current git state (branch, commit, uncommitted changes)
- Tasks completed and in-progress
- Key learnings and gotchas
- Next steps
To remove agent integration from a project:
thts uninit agents # Interactive confirmation
thts uninit agents --force # Skip confirmation
thts uninit agents --dry-run # Preview what would be removed
thts uninit agents --global # Remove global installationThis removes:
- All thts files from agent directories (instructions, skills, commands, agents)
- Instruction file modifications
- Gitignore patterns added by init
The agent directory itself is preserved if it contains other files.
Note: Running thts uninit (or thts uninit --all) also removes agent
integration automatically, ensuring a clean teardown in the current checkout.
thts is a Go reimplementation of the thoughts subcommand from
HumanLayer's CLI (humanlayer).
| Tool | Best For |
|---|---|
thts |
Standalone binary, no runtime dependencies, Go ecosystem |
humanlayer thoughts |
Already using HumanLayer, Node.js ecosystem, additional humanlayer features |
Both tools use identical:
| Component | Location |
|---|---|
| Thoughts repo structure | ~/thoughts/repos/<project>/, ~/thoughts/global/ |
| Symlink layout | thoughts/{user}/, thoughts/shared/, thoughts/global/ |
| Searchable directory | thoughts/searchable/ with hard links |
| Git hooks | Pre-commit protection, post-commit sync |
The tools use different config files:
| Tool | Config Path | Format |
|---|---|---|
| thts | ~/.config/thts/config.yaml |
YAML |
| humanlayer | ~/.config/humanlayer/humanlayer.json |
JSON |
thts can read HumanLayer's config as a fallback, but never writes to it. This means:
- Migrating from HumanLayer to thts is seamless—existing config is read automatically
- Changes made via thts are written to thts's own config
- HumanLayer won't see config changes made by thts
Team members can use different tools on the same thoughts repo:
- Alice uses
thts(prefers Go binaries) - Bob uses
humanlayer thoughts(already has HumanLayer installed) - Both share the same thoughts repo
- Notes sync correctly regardless of which tool created them
Each team member maintains their own config file for their preferred tool.
| thts | humanlayer thoughts | Description |
|---|---|---|
thts setup |
humanlayer thoughts setup |
First-time configuration |
thts init |
humanlayer thoughts init |
Initialize in a project |
thts sync |
humanlayer thoughts sync |
Sync to thoughts repo |
thts status |
humanlayer thoughts status |
Show status |
thts add |
- | Create thought with template |
thts edit |
- | Open thoughts in editor |
thts uninit |
humanlayer thoughts uninit |
Remove from project |