Skip to content

Personal Agentic Workspace

A tool-agnostic AI collaboration workspace. Organized by function — find what you need by what you want to do.

Commands

The root Makefile is the entrypoint. make (or make help) prints every target. make help-check (part of make check) fails if a target is missing from that list.

make                                              # catalog
make dashboard                                    # http://127.0.0.1:8787
make workspace NAME="Toy" DOMAIN=personal CLASSIFICATION=low_sensitive
make open-lab ID=tree-1 SCOPE=own
make close-lab ID=tree-1
make voice AUDIO=~/clip.wav
# first time: brew install whisper.cpp && make voice-setup
make code-library                                 # no-op until categories are enabled
make code-library-search Q='tokenizer'
make sandbox IMAGE=alpine
make check                                        # drift gates + tests

Agent skills in shared/skills/ cover the same work plus PKM (AnyType). Prefer make when you are at a shell.

Getting started

Prerequisites: Python 3.11+, uv, git. Optional, only for the capabilities that need them: Calibre + Ollama (library/), Ollama (research/), Node.js/npm (AnyType MCP via npx), the AnyType desktop app (PKM).

  1. Clone and enable the drift-check hook:
    git clone git@gitlab.com:maker-nathan/tool-chest.git && cd tool-chest
    make hooks
    
  2. Configure — review tool-chest.toml and adjust paths (e.g. calibre_library) for your machine, and set any secrets in your shell profile (never in the file itself). Details: Configuration.
  3. See the catalog, generate client configs, and verify the clone is clean:
    make              # catalog of every target
    make mcp-config   # writes .mcp.json / .cursor/mcp.json from tool-chest.toml
    make check        # fails if anything has drifted (including help-check)
    
  4. Open the repo in Cursor or Claude Code. Skills and rules load automatically from .cursor/skills/ / .cursor/rules/ (symlinked from shared/) — no extra install step.
  5. Set up whichever standalone tools you want, each self-contained via uv:
  6. cd library && uv sync — e-book RAG (needs Calibre + Ollama)
  7. cd research && uv sync — Anthropic research RAG (needs Ollama)
  8. cd patterns && uv sync — architecture-patterns RAG (needs Ollama)
  9. Browse these docs as a site (optional), via Zensical (Material for MkDocs' successor — classic mkdocs' maintainer situation has stalled, and mkdocs-material never shipped support for the interim 2.0 rewrite):
    pip install -r requirements.txt
    make docs-serve   # exports tracked files to docs-build/, then serves at http://127.0.0.1:8000
    
    Config: zensical.toml, fully generated — don't hand-edit it. Nav is derived from every tracked markdown file (make docs-config-sync / make docs-config-check, part of make check) — add a new doc and it appears on next sync. make docs-build writes the static site to site/. Note: Zensical is alpha software as of this writing.

Capability map

I want to… Go to
See every command make / make help
Capture an idea, task, or person shared/skills/pkm-capture/ or knowledge/anytype-integration/
Create a project workspace make workspace NAME=… DOMAIN=… CLASSIFICATION=…
Open/close a code analysis lab make open-lab ID=… SCOPE=… · make close-lab ID=…
Open the local dashboard make dashboard → http://127.0.0.1:8787
Search cloned OSS (enabled categories) make code-library-search Q='…'
Search architecture patterns knowledge/patterns/ + patterns/ MCP
Transcribe a voice note make voice-setup once, then make voice AUDIO=/path/to.wav
Plan a container sandbox run make sandbox IMAGE=…
Pick an agent harness workflow/harnesses/ + select-harness
Delegate a task to an agent workflow/delegation/
Find or add a skill shared/skills/
Look up stored context or research knowledge/agent-wiki/
Add a long-form note or article knowledge/anytype-integration/ (AnyType PKM)
See my open commitments / tasks AnyType → Tasks database
Run the default task loop workflow/task-loop.md
Hand off or onboard a project workflow/handoff/
Bootstrap rules for a new project shared/rules/ + shared/skills/rules-bootstrap/
Review an agent's work workflow/delegation/audit-log.md
Run a security review governance/security-sandbox/
Publish or document something governance/docs-publish/
Check metrics / friction log instrumentation/
Search my e-book library semantically library/ (calibre-librarian, MCP)
Search Anthropic's published research research/ (anthropic-research, MCP)

Layout

.
├── shared/              Canonical tool-agnostic resources (skills, rules, agents)
├── knowledge/           Information stores
│   ├── agent-wiki/      Machine-readable context for agents
│   ├── anytype-integration/  AnyType PKM config and bridging
│   └── patterns/        Approved architecture patterns (markdown corpus)
├── code-library/        OSS clone catalog (lexical; category-gated)
├── workflow/            How work gets done
│   ├── task-loop.md     Default bounded task loop
│   ├── harnesses/       Agent orchestration catalog
│   ├── delegation/      Agent delegation templates and audit log
│   └── handoff/         Project handoff protocol
├── governance/          Policies, audit, security sandbox, docs/publish
├── instrumentation/     Metrics and friction logging
├── library/             calibre-librarian — local RAG over a Calibre e-book library (MCP)
├── research/            anthropic-research — local RAG archive of Anthropic's published research (MCP)
├── patterns/            RAG + MCP over knowledge/patterns/
├── integrations/        Bridges between library/research and PKM (e.g. agent-wiki logging)
├── assets/              Static assets (icon, images)
├── scripts/             Config generation and drift-check scripts (see Configuration below)
├── Makefile             Human catalog — `make` / `make help`
└── .cursor/             Cursor-specific config (references shared/ where possible)

Operating principles

  1. Single source of truth. Canonical assets live in shared/. Tool configs reference it.
  2. Capture everything, triage later. Use pkm-capture or AnyType inbox; don't let things fall through.
  3. Delegate with a paper trail. Every agent task gets a delegation template and an audit log entry.
  4. Privacy by default. Sensitive data stays local; cloud only with explicit opt-in.
  5. One capability at a time. Add skills when a real need surfaces, not speculatively.

Configuration

tool-chest.toml holds the shared structure and shipped defaults (MCP servers, clients, capabilities, the sandbox). Your own choices (the Ollama and AnyType addresses, OmniRoute, the dashboard port, the code-library source root and categories, the Calibre library, the whisper model, the research limits) live in your private data root, one file per component: <data_root>/config/<component>.toml. Edit them in the workbench (Settings) or with <component> config set; nothing in the dashboard writes tool-chest.toml. Each component publishes its settings and their defaults (<component> config schema), and workflow/config-inventory.toml lists what is shared and what is per-user. Shell environment variables still override a value for one command.

Coming from the old layout (values in tool-chest.toml, or ~/.config/research, ~/.config/library)? Run make config-migrate DRY=1 to see what would move, then make config-migrate. It keeps a backup and changes nothing it has already moved.

Secrets never go in tool-chest.toml, and the AnyType key doesn't go in your shell profile either: anything an agent runs inherits that. It lives in the macOS Keychain:

make anytype-key-store    # paste the key (hidden); checked against the local API first
make anytype-key-check    # stored? (reads no value, shows no dialog)
make anytype-key-delete

The item trusts no application, so every read shows a macOS Keychain dialog, even one by security itself. Without that, any process able to run security, a sandboxed agent included, could read the key silently. You'll see the dialog when Claude Code or Cursor starts the AnyType MCP (its launcher, scripts/anytype_mcp_launch.py, puts the key only in that server's environment), when the dashboard first writes to AnyType, and on make anytype-scope-check. The dialog calls the reader "security" in every case, so a dialog at any other moment is one to deny. Click Allow, never "Always Allow", which would trust security and reopen the gap. The dashboard's health ring never shows the dialog: it reports the key as stored but not yet read.

ANYTYPE_OPENAPI_MCP_HEADERS in the environment still works as an override, and it's how an automation hands its own key to the command it runs. If you're moving from the old setup: make anytype-key-store, delete the export from your shell profile, open a new terminal, and restart Claude Code / Cursor.

Rotating the key: issue a new one in AnyType, run make anytype-key-store (it replaces the item), revoke the old key in AnyType, and restart Claude Code / Cursor.

To make the Anytype MCP URL available when Cursor starts, source the loader in your shell profile:

eval "$(python3 /path/to/tool-chest/scripts/config_loader.py --export)"

Private-names canary

This repo is public, so a client's, employer's or person's name must never land in a tracked file. make tenancy-check fails if one does. The list of names to guard is one you write yourself, at <data_root>/canary.txt (by default ~/tool-chest-data/canary.txt): one name per line, blank lines and # comment lines ignored. Put in anyone or anything you would not want named in a public repo: clients, people, employers. make canary-init creates the file from a commented template that explains the format, if it doesn't exist (it never overwrites one). Run it on each machine: the list is per machine, like the data root.

The list stays in the data root, which the repository refuses to contain, because a list of names that must not leak would itself be the leak if it were committed. Matching ignores case and spacing, and a name only counts as a whole word (Acme doesn't match Acmeville). A failure prints the tracked file and line, never the name. With no file the check is skipped, which is what happens in CI: it guards your machine, not the pipeline.

MCP servers

Claude Code and Cursor read different files and use incompatible placeholder syntax (${NAME} vs ${env:NAME}, ${CLAUDE_PROJECT_DIR} vs ${workspaceFolder}), so they cannot share one config. Instead, both are generated from the [mcp.*] section of tool-chest.toml:

make mcp-config        # regenerate .mcp.json and .cursor/mcp.json
make mcp-check         # fail if either has drifted from tool-chest.toml
make mcp-install-user  # write the user-level clients (claude-user via
                       # `claude mcp add`, ~/.cursor/mcp.json), so they load in any repo
make mcp-smoke         # start each server, require initialize + tools/list

make mcp-check only compares config files; make mcp-smoke proves each server actually starts (a crash inside serve passes every other gate). It is slow on cold uv/npx caches and needs the Anytype app, so it is not part of make check. Skip a server explicitly with make mcp-smoke MCP_SMOKE_ARGS="--skip anytype"; CI does exactly that.

Add or change a server by editing tool-chest.toml and re-running make mcp-config — never by editing the generated files, which make mcp-check will flag. Restart the client for changes to attach.

npx servers run an exact version. The AnyType MCP server is npx = "@anyproto/anytype-mcp@2.0.1" in tool-chest.toml, so a new upstream release changes nothing until you choose it. make mcp-check fails on a range, a tag (latest) or a bare package name, and the launcher refuses to start an unpinned one. To update: check the new version (npm view @anyproto/anytype-mcp version), change the pin, run make mcp-config and make mcp-smoke, then commit. A pin fixes the version, not a hash of its contents, so you still trust the registry's copy of that version.

AnyType write gate

The scoped API key limits what an agent can read; nothing limits what it writes. scripts/hooks/anytype_write_gate.py is a Claude Code PreToolUse hook on the AnyType MCP tools that applies each space's agent_write policy from knowledge/anytype-integration/schemas/spaces.json: never is blocked with a reason, approval shows the permission prompt every time, full passes. A write whose space can't be determined, or a write tool that names no space, prompts. Read tools are untouched, and any tool not on the read list is treated as a write. The hook fails closed: an internal error becomes a prompt, never a silent allow.

make anytype-gate-install  # copy the gate to ~/.claude/hooks/tool-chest/ and register it in ~/.claude/settings.json (backs it up first)
make anytype-gate-check    # fail if the installed copy or the registration differs from the repo
make claude-sandbox-install  # also needed once: the deny_write rules that make that copy unwritable for agents

Session grants. To stop the prompts for a while, the approval prompt prints a command to run in your own terminal, e.g. make anytype-grant SPACE=work TTL=30m. Every prompt records its session in pending.json, and with no SESSION the command takes the one session prompted for that space in the last 15 minutes (it refuses, and lists them, if there are none or several; pass SESSION=<id> then). A grant covers ordinary writes to that one space in that one Claude Code session; the default is 30 minutes and requests over 2 hours are clamped to 2 hours. Deletes and type or property changes always prompt. Each write a grant allows is appended to audit.jsonl; make anytype-revoke SESSION=<id> ends a grant early. State lives in <data_root>/anytype-grants/ (owner-only files, never in the repo, expired records pruned on read). A resumed session keeps its session id, so it keeps an unexpired grant and cannot reuse an expired one; Claude Code has no "session ended" hook, so a grant ends at its TTL or on revoke. The same hook denies shell calls that run the grant or revoke command, import its module, or touch its state directory, and editor calls aimed at that directory, so an agent cannot grant itself. That is a pattern match, not a sandbox. The boundary is the install: the gate runs from a copy in ~/.claude/hooks/tool-chest/ (with a snapshot of the space registry and the resolved state directory), and the [claude_sandbox] deny_write globs stop agents writing that folder or ~/.claude/settings*.json, from Bash and from the editors. Editing the repo copy changes nothing until you re-run the install, and make anytype-gate-check fails while the two differ.

The gate also refuses shell commands that name the AnyType API port (from anytype.api_base_url, baked into the installed copy) or ANYTYPE_API_BASE_URL, so an agent can't skip the MCP tools with curl or python -c. Commit messages and grep patterns that only mention the port are left alone. This too is a pattern match, a backup behind the sandbox and the Keychain, not a boundary.

It only guards Claude Code. Cursor does not run these hooks, so keep the anytype connection off or read-only for Cursor.

Claude Code sandbox

[claude_sandbox] in tool-chest.toml turns on Claude Code's built-in Bash sandbox (macOS Seatbelt, nothing to install) with these rules:

  • it unsets credential variables (deny_env) before every sandboxed command;
  • it blocks reads of credential files and directories (deny_files);
  • it lets sandboxed commands reach only the listed hosts (allowed_domains). Commands may listen on local ports, which the test servers in make check need. On macOS that also lets them reach local services (AnyType, OmniRoute, Ollama) on this machine: they're reachable, but without their keys.

A command the sandbox refuses can be retried outside it, but that retry always asks first, as does every git push, in any permission mode.

make claude-sandbox-install    # write it into ~/.claude/settings.json (backs it up first)
make claude-sandbox-check      # in `make check`: fail if the installed copy drifted (skips without the file)
make claude-sandbox-uninstall  # roll back: remove the block and our ask rules (backs up first)

This changes every Claude Code session on the machine, not only tool-chest's, from the next session on. Rollback is make claude-sandbox-uninstall, or restore one of the ~/.claude/settings.json.bak-* copies. The whole sandbox key belongs to the installer: edit tool-chest.toml and reinstall rather than hand-editing it.

The sandbox covers Bash only. The Read and Edit tools and WebFetch follow permission rules instead, and MCP servers aren't sandboxed, since Claude Code launches them itself. Anything run from Claude that has to read OmniRoute's key store (make omniroute-*) goes through the retry prompt. ps can't run in the sandbox, so the one test that needs it prints a SKIP there and runs in full from your own terminal.

To check that the defenses hold, make security-attack drives headless Claude Code through attack probes and judges each by its effect on the machine. It calls a model, so it isn't part of make check or CI. Run it from your own terminal, not from inside a sandboxed Claude session: it has to see the real environment to detect a leak. To try a sandbox change before installing it, pass the new block with ATTACK_ARGS="--settings <file>"; scripts/claude_sandbox_install.py --print writes the current one.

Clients are a registry: each [client.<id>] names a config path and a format (claude-json, cursor-json, or the claude-cli adapter), and each server lists the clients it is connected to in clients = [...]. Project clients (path under @root/: claude-project, cursor) are generated whole and drift-checked; user clients (claude-user, cursor-user) live outside the repo and are written by make mcp-install-user. claude-user goes through Claude Code's own CLI (claude mcp add-json / remove --scope user), never a direct write to ~/.claude.json: connected servers are (re)registered, and a server removed from its clients is removed from user scope — servers tool-chest doesn't declare are never touched. ~/.cursor/mcp.json is merged (entries tool-chest doesn't declare are kept), atomically, with the previous file kept as mcp.json.bak. Adding a client whose file shape matches an existing format needs only a [client.<id>] entry; a server naming an unknown client fails make mcp-check.

A server can't be connected to both claude-project and claude-user (shadows on the client): Claude Code resolves name collisions as local > project > user, so the project entry would shadow the user one rather than fall back to it. make mcp-check fails on such a pair.

The dashboard's Home ring connects areas to clients with one toggle per registered client: it rewrites the area's servers' clients, syncs only that client, and writes tool-chest.toml last. A client shows restart pending while any of its processes started before its config last changed; it clears itself once they've all restarted, and there is nothing to dismiss.

Shared rules

.cursor/rules/ is composed from shared/rules/ according to rules-manifest.yaml (validated against shared/rules/bootstrap/manifest.schema.json):

make rules-sync    # re-render .cursor/rules/ from the manifest
make rules-check   # fail if .cursor/rules/ has drifted from the manifest

Edit rules in shared/rules/, or change which slices this project inherits in rules-manifest.yaml, then re-run make rules-sync. Never hand-edit .cursor/rules/ — the entries are symlinks, and make rules-check flags a file that has become a copy or that no manifest layer declares.

The manifest's rendered.mode is symlink here rather than copy, since the composed files are byte-identical to their sources: drift is then structurally impossible instead of merely detected. This matches .cursor/skills/, which already symlinks into shared/skills/.

make check runs every drift gate (mcp-check + rules-check + docs-config-check + tenancy-check + help-check + capabilities-check + claude-sandbox-check), and make hooks installs the pre-commit hook that runs it. Do that once per clone.

Tool setup

  • Claude Code: Skills at ~/.claude/skills/; definitions documented in shared/skills/
  • Cursor: Skills at .cursor/skills/; rules at .cursor/rules/ (both symlinked from shared/)
  • AnyType: Local instance with MCP access; config at knowledge/anytype-integration/

Phase roadmap

Phase What Status
A–B Structural reorganization + skills in shared/ done
Wave 0 Tenancy, hybrid workspaces, host labs done
Wave 1 Patterns RAG, OmniRoute toggle, harness catalog done
Wave 2 Dashboard, voice, code library, sandbox plan done
Next Dashboard UX grill; AnyType Tasks depth next

Icon Attribution & Credit: Toolbox by Muhammad Nur Auliady Pamungkas from Noun Project (CC BY 3.0)