paperclip-agent 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. paperclip_agent-0.1.1/.env.example +85 -0
  2. paperclip_agent-0.1.1/.gitignore +76 -0
  3. paperclip_agent-0.1.1/PKG-INFO +289 -0
  4. paperclip_agent-0.1.1/README.md +267 -0
  5. paperclip_agent-0.1.1/paperclip/__init__.py +3 -0
  6. paperclip_agent-0.1.1/paperclip/__main__.py +40 -0
  7. paperclip_agent-0.1.1/paperclip/app.py +75 -0
  8. paperclip_agent-0.1.1/paperclip/config.py +115 -0
  9. paperclip_agent-0.1.1/paperclip/db.py +83 -0
  10. paperclip_agent-0.1.1/paperclip/gateway/__init__.py +0 -0
  11. paperclip_agent-0.1.1/paperclip/gateway/cli.py +50 -0
  12. paperclip_agent-0.1.1/paperclip/gateway/telegram.py +91 -0
  13. paperclip_agent-0.1.1/paperclip/gateway/voice.py +287 -0
  14. paperclip_agent-0.1.1/paperclip/loop/__init__.py +0 -0
  15. paperclip_agent-0.1.1/paperclip/loop/agent.py +105 -0
  16. paperclip_agent-0.1.1/paperclip/loop/errors.py +99 -0
  17. paperclip_agent-0.1.1/paperclip/loop/models.py +232 -0
  18. paperclip_agent-0.1.1/paperclip/memory/__init__.py +134 -0
  19. paperclip_agent-0.1.1/paperclip/memory/consolidation.py +65 -0
  20. paperclip_agent-0.1.1/paperclip/memory/episodic/__init__.py +0 -0
  21. paperclip_agent-0.1.1/paperclip/memory/episodic/store.py +50 -0
  22. paperclip_agent-0.1.1/paperclip/memory/procedural/__init__.py +0 -0
  23. paperclip_agent-0.1.1/paperclip/memory/procedural/installer.py +51 -0
  24. paperclip_agent-0.1.1/paperclip/memory/procedural/loader.py +73 -0
  25. paperclip_agent-0.1.1/paperclip/memory/retrieval_gate.py +40 -0
  26. paperclip_agent-0.1.1/paperclip/memory/semantic/__init__.py +0 -0
  27. paperclip_agent-0.1.1/paperclip/memory/semantic/store.py +68 -0
  28. paperclip_agent-0.1.1/paperclip/memory/semantic/supabase_store.py +42 -0
  29. paperclip_agent-0.1.1/paperclip/ops/__init__.py +0 -0
  30. paperclip_agent-0.1.1/paperclip/ops/api.py +865 -0
  31. paperclip_agent-0.1.1/paperclip/ops/brief.py +27 -0
  32. paperclip_agent-0.1.1/paperclip/ops/release_gate.py +76 -0
  33. paperclip_agent-0.1.1/paperclip/ops/tracing.py +103 -0
  34. paperclip_agent-0.1.1/paperclip/runtime/__init__.py +0 -0
  35. paperclip_agent-0.1.1/paperclip/runtime/session.py +91 -0
  36. paperclip_agent-0.1.1/paperclip/tools/__init__.py +51 -0
  37. paperclip_agent-0.1.1/paperclip/tools/apple.py +121 -0
  38. paperclip_agent-0.1.1/paperclip/tools/calendar.py +198 -0
  39. paperclip_agent-0.1.1/paperclip/tools/experimental.py +32 -0
  40. paperclip_agent-0.1.1/paperclip/tools/github.py +256 -0
  41. paperclip_agent-0.1.1/paperclip/tools/mcp_client.py +85 -0
  42. paperclip_agent-0.1.1/paperclip/tools/memory_admin.py +129 -0
  43. paperclip_agent-0.1.1/paperclip/tools/messages.py +34 -0
  44. paperclip_agent-0.1.1/paperclip/tools/notes.py +35 -0
  45. paperclip_agent-0.1.1/paperclip/tools/registry.py +41 -0
  46. paperclip_agent-0.1.1/paperclip/tools/search.py +87 -0
  47. paperclip_agent-0.1.1/pyproject.toml +42 -0
@@ -0,0 +1,85 @@
1
+ # First launch writes this file to ~/.paperclip/.env (your user home, every OS).
2
+ # A .env in the current directory fills keys that file left blank.
3
+ # Only ANTHROPIC_API_KEY is required for the default provider.
4
+ # Leave a line commented to keep the code default.
5
+
6
+ # --- Model provider ----------------------------------------------------------
7
+ # anthropic | openai | openrouter | gemini
8
+ # PAPERCLIP_PROVIDER=anthropic
9
+
10
+ # Used when PAPERCLIP_API_KEY is empty. Set the one that matches the provider.
11
+ ANTHROPIC_API_KEY=
12
+ # OPENAI_API_KEY=
13
+ # OPENROUTER_API_KEY=
14
+ # GEMINI_API_KEY=
15
+
16
+ # Optional override of the provider key above.
17
+ # PAPERCLIP_API_KEY=
18
+
19
+ # Empty model fields fall back to the provider default:
20
+ # anthropic claude-sonnet-5 / claude-haiku-4-5-20251001
21
+ # openai gpt-5.5 / gpt-4.1-mini
22
+ # openrouter nvidia/nemotron-nano-9b-v2:free / google/gemma-3-4b-it:free
23
+ # gemini gemini-3.8-flash / gemini-3.5-flash-lite
24
+ # PAPERCLIP_MODEL=
25
+ # PAPERCLIP_SMALL_MODEL=
26
+ # PAPERCLIP_BASE_URL=
27
+ # PAPERCLIP_LLM_TIMEOUT=120
28
+ # PAPERCLIP_MAX_TOKENS=2048
29
+ # PAPERCLIP_MAX_ITERATIONS=10
30
+
31
+ # Judge evals and the release gate look at ANTHROPIC_API_KEY only.
32
+
33
+ # --- Home and memory ---------------------------------------------------------
34
+ # PAPERCLIP_HOME=~/.paperclip
35
+ # PAPERCLIP_SEMANTIC_STORE=sqlite
36
+ # PAPERCLIP_RETRIEVAL_TOP_K=4
37
+ # PAPERCLIP_CONSOLIDATE_EVERY=6
38
+
39
+ # Only when PAPERCLIP_SEMANTIC_STORE=supabase. Embeddings use OPENAI_API_KEY.
40
+ # SUPABASE_URL=
41
+ # SUPABASE_SERVICE_KEY=
42
+ # OPENAI_EMBED_MODEL=text-embedding-3-small
43
+
44
+ # --- Search ------------------------------------------------------------------
45
+ # Tavily when set; otherwise DuckDuckGo. PAPERCLIP_SEARCH_API_KEY is an alias.
46
+ # TAVILY_API_KEY=
47
+ # PAPERCLIP_SEARCH_API_KEY=
48
+
49
+ # --- Telegram ----------------------------------------------------------------
50
+ # TELEGRAM_BOT_TOKEN=
51
+ # Numeric user id. Unset means anyone who finds the bot can use it.
52
+ # TELEGRAM_ALLOWED_USER=
53
+
54
+ # --- macOS -------------------------------------------------------------------
55
+ # 1, true, or yes. Calendar.app sync is separate from the AppleScript tools.
56
+ # PAPERCLIP_APPLE_CALENDAR=
57
+ # PAPERCLIP_APPLE_TOOLS=
58
+ # PAPERCLIP_APPLE_CALENDARS=Work,Home
59
+
60
+ # --- Voice -------------------------------------------------------------------
61
+ # PAPERCLIP_WHISPER_MODEL=base
62
+ # cpu avoids a missing cublas64_12.dll on machines without the CUDA 12 runtime.
63
+ # PAPERCLIP_WHISPER_DEVICE=cpu
64
+ # PAPERCLIP_WHISPER_LANG=
65
+ # Default wake phrases: paperclip paperclip,paperclippaperclip,paperclip,paper clip,paper-clip
66
+ # Set empty for push-to-talk.
67
+ # PAPERCLIP_WAKE_WORD=
68
+ # PAPERCLIP_WAKE_LANG=
69
+ # PAPERCLIP_WAKE_ACK=Yes?
70
+ # PAPERCLIP_FOLLOWUP_SECONDS=8
71
+ # PAPERCLIP_MIC_THRESHOLD=0.005
72
+ # PAPERCLIP_TTS=
73
+ # PAPERCLIP_VOICE=
74
+
75
+ # --- Dashboard and traces ----------------------------------------------------
76
+ # PAPERCLIP_DASHBOARD_PORT=7777
77
+ # PORT=7777
78
+ # PAPERCLIP_DASHBOARD_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
79
+ # PAPERCLIP_EDITOR=
80
+ # OTEL_EXPORTER_OTLP_ENDPOINT=
81
+
82
+ # --- Tools -------------------------------------------------------------------
83
+ # PAPERCLIP_EXPERIMENTAL=
84
+ # Default owner/name when github_read is called without a repo. Auth stays in gh.
85
+ # PAPERCLIP_GITHUB_REPO=
@@ -0,0 +1,76 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Virtual environments
7
+ .venv/
8
+ venv/
9
+ env/
10
+ ENV/
11
+
12
+ # uv
13
+ .python-version
14
+
15
+ # Build artifacts
16
+ build/
17
+ dist/
18
+ *.egg-info/
19
+
20
+ # Testing
21
+ .pytest_cache/
22
+ .coverage
23
+ htmlcov/
24
+
25
+ # Type checking
26
+ .mypy_cache/
27
+ .pyre/
28
+ .pytype/
29
+
30
+ # Linting
31
+ .ruff_cache/
32
+
33
+ # Jupyter
34
+ .ipynb_checkpoints/
35
+
36
+ # IDEs
37
+ .vscode/
38
+ .idea/
39
+
40
+ # OS files
41
+ .DS_Store
42
+ Thumbs.db
43
+
44
+ # Logs
45
+ *.log
46
+
47
+ # Environment variables
48
+ .env
49
+
50
+ # Generated runtime data (FAISS index + sidecar, SQLite db, etc.)
51
+ data/
52
+ *.db
53
+ *.sqlite
54
+ *.sqlite3
55
+
56
+ # Website (Next.js)
57
+ web/node_modules/
58
+ web/.next/
59
+ web/.env.local
60
+
61
+ node_modules/
62
+ .next/
63
+
64
+ # Coverage
65
+ coverage.xml
66
+
67
+ # Hatch
68
+ .hatch/
69
+
70
+ # PyInstaller
71
+ *.manifest
72
+ *.spec
73
+
74
+ # .paperclip
75
+ .paperclip
76
+ .paperclip-*
@@ -0,0 +1,289 @@
1
+ Metadata-Version: 2.5
2
+ Name: paperclip-agent
3
+ Version: 0.1.1
4
+ Summary: Local personal assistant
5
+ Requires-Python: >=3.13
6
+ Requires-Dist: anthropic>=1.7.0
7
+ Requires-Dist: deepeval>=4.3.1
8
+ Requires-Dist: faster-whisper>=1.2.1
9
+ Requires-Dist: google-api-python-client>=2.200.0
10
+ Requires-Dist: google-auth-oauthlib>=1.4.1
11
+ Requires-Dist: google-auth>=2.58.0
12
+ Requires-Dist: kokoro>=0.9.4
13
+ Requires-Dist: mcp[cli]>=2.2.0
14
+ Requires-Dist: notion-client>=3.1.0
15
+ Requires-Dist: numpy>=2.5.3
16
+ Requires-Dist: openai>=3.16.2
17
+ Requires-Dist: pytest>=9.1.1
18
+ Requires-Dist: python-dotenv>=1.2.3
19
+ Requires-Dist: python-telegram-bot>=22.8
20
+ Requires-Dist: sounddevice>=0.5.6
21
+ Description-Content-Type: text/markdown
22
+
23
+ # Paperclip
24
+
25
+ Paperclip is a local personal assistant. The terminal, a microphone, and Telegram all reach the same agent. What it remembers stays in a folder on that machine (`PAPERCLIP_HOME`, default `~/.paperclip`). Nothing is copied to a hosted account.
26
+
27
+ One turn is: decide whether to search memory, inject matching skills, call the model with tools until it replies, then log the exchange and sometimes file what it learned.
28
+
29
+ ## Requirements
30
+
31
+ - Python 3.13 or newer
32
+ - An API key for one model provider. The default is Anthropic (`ANTHROPIC_API_KEY`).
33
+
34
+ Voice also needs a microphone. The packages for speech (`faster-whisper`, `sounddevice`, `numpy`, `kokoro`) are project dependencies.
35
+
36
+ ## Install
37
+
38
+ [uv](https://docs.astral.sh/uv/) installs the published package. The PyPI name is `paperclip-agent`. The command is `paperclip`.
39
+
40
+ ```bash
41
+ uv tool install paperclip-agent
42
+ paperclip
43
+ ```
44
+
45
+ The first launch creates `~/.paperclip/.env` (your user home on every OS, `%USERPROFILE%\.paperclip` on Windows). Put your key on `ANTHROPIC_API_KEY` in that file and run `paperclip` again. That is the only line the default setup needs. The same folder holds memory, wherever you start the command.
46
+
47
+ From a checkout of this repo, dependencies come from `pyproject.toml`:
48
+
49
+ ```bash
50
+ uv sync
51
+ uv run paperclip
52
+ ```
53
+
54
+ That creates `.venv` and installs this package in editable mode. A `.env` in the repo fills keys the home file left blank. Set `PAPERCLIP_HOME` to keep memory somewhere else (this repo's old default was a `.paperclip` folder next to the code).
55
+
56
+ Run every Python command with `uv run`. A bare `python` on PATH is a different interpreter and does not have the packages from `pyproject.toml`. `python -m paperclip` is the same program. The help text says `paperclip`.
57
+
58
+ The banner shows where memory lives and which model is answering. Empty lines are ignored. Leave with `/quit`, `/exit`, or Ctrl-D. Closing the terminal does not delete memory.
59
+
60
+ ## Commands
61
+
62
+ | Command | What it does |
63
+ | --- | --- |
64
+ | `uv run paperclip` | Terminal chat |
65
+ | `uv run paperclip voice` | Microphone in, speech out |
66
+ | `uv run paperclip telegram` | Telegram long-polling (laptop stays the host) |
67
+ | `uv run paperclip api` | Local JSON API for the cockpit (`127.0.0.1`, default port `7777`) |
68
+ | `uv run paperclip brief` | One morning-brief turn, saved under the outbox |
69
+ | `uv run paperclip skill install <url>` | Download a `SKILL.md` into `{PAPERCLIP_HOME}/skills/` |
70
+
71
+ On Unix, the `Makefile` wraps the same entry points (`make run`, `make voice`, `make telegram`, `make brief`, `make api`, `make eval`, `make eval-judge`, `make gate`). It uses `.venv/bin/python` when `uv sync` has created that interpreter.
72
+
73
+ ## Model providers
74
+
75
+ Paperclip speaks one dialect (Anthropic Messages). Other providers go through an OpenAI-compatible adapter. Set `PAPERCLIP_PROVIDER` and the matching key. `PAPERCLIP_API_KEY` overrides the provider key when set. Empty `PAPERCLIP_MODEL` and `PAPERCLIP_SMALL_MODEL` fall back to the defaults below.
76
+
77
+ | `PAPERCLIP_PROVIDER` | Key | Default model | Small model (gate and consolidation) |
78
+ | --- | --- | --- | --- |
79
+ | `anthropic` (default) | `ANTHROPIC_API_KEY` | `claude-sonnet-5` | `claude-haiku-4-5-20251001` |
80
+ | `openai` | `OPENAI_API_KEY` | `gpt-5.5` | `gpt-4.1-mini` |
81
+ | `openrouter` | `OPENROUTER_API_KEY` | `nvidia/nemotron-nano-9b-v2:free` | `google/gemma-3-4b-it:free` |
82
+ | `gemini` | `GEMINI_API_KEY` | `gemini-3.8-flash` | `gemini-3.5-flash-lite` |
83
+
84
+ `PAPERCLIP_BASE_URL` overrides the provider URL. `PAPERCLIP_LLM_TIMEOUT` defaults to 120 seconds. `PAPERCLIP_MAX_TOKENS` defaults to 2048. `PAPERCLIP_MAX_ITERATIONS` defaults to 10 (tool rounds per turn).
85
+
86
+ Judge evals and the release gate look at `ANTHROPIC_API_KEY` only.
87
+
88
+ ## How a turn works
89
+
90
+ ```
91
+ user message
92
+ → retrieval gate (small model: search memory this turn?)
93
+ → matching skills (SKILL.md)
94
+ → agent loop: reason → tool calls → observe, until a reply or the iteration cap
95
+ → chat log + in-memory history
96
+ → consolidation every N exchanges (facts + a dated episode)
97
+ → rewrite MEMORY.md
98
+ ```
99
+
100
+ A failed model call is not stored as the assistant’s answer. The reply comes back, and the prompt returns.
101
+
102
+ The persona is `{PAPERCLIP_HOME}/SOUL.md`. If the file is missing, a default is written. Editing it changes the next turn. The system prompt also includes the local clock so relative times (“tomorrow at 3”) can be resolved.
103
+
104
+ ## Memory
105
+
106
+ Three stores, all local:
107
+
108
+ | Kind | Question | Where |
109
+ | --- | --- | --- |
110
+ | Semantic | What do I know? | `facts` in `state.db` (FTS5). Optional Supabase vectors. |
111
+ | Episodic | What happened, when? | `episodes` in `state.db` |
112
+ | Procedural | How should I act? | `SKILL.md` under repo `skills/` and `{home}/skills/` |
113
+
114
+ Default search is SQLite FTS5 (BM25). No embeddings unless `PAPERCLIP_SEMANTIC_STORE=supabase`, which also needs `SUPABASE_URL`, `SUPABASE_SERVICE_KEY`, and `OPENAI_API_KEY`.
115
+
116
+ A small model decides whether this turn needs a search (`PAPERCLIP_RETRIEVAL_TOP_K`, default 4). After `PAPERCLIP_CONSOLIDATE_EVERY` exchanges (default 6), another small-model pass files facts and an episode, then rewrites `{home}/MEMORY.md`. You can also ask it to remember something on purpose (`save_note` / `manage_memory`).
117
+
118
+ `MEMORY.md` is a readable export of SQL. The database is the source of truth: `{PAPERCLIP_HOME}/state.db`.
119
+
120
+ ```bash
121
+ sqlite3 ~/.paperclip/state.db ".tables"
122
+ ```
123
+
124
+ ## Tools
125
+
126
+ You do not pick a tool by name. Ask in the conversation, and the model calls the one that fits.
127
+
128
+ | Tool | What it does |
129
+ | --- | --- |
130
+ | `create_event`, `list_events` | Local calendar rows in `state.db` |
131
+ | `save_note` | A fact you asked it to keep |
132
+ | `send_message` | A text file in `{home}/outbox`. It does not send email or chat. |
133
+ | `search_web` | Tavily when `TAVILY_API_KEY` (or `PAPERCLIP_SEARCH_API_KEY`) is set; otherwise DuckDuckGo |
134
+ | `github_read` | Read-only GitHub via the `gh` CLI (issues, pull requests, files). It will not merge, comment, close, or push. |
135
+ | `manage_memory`, `update_soul`, `create_skill` | Edit facts, `SOUL.md`, and skills |
136
+
137
+ Opt-in:
138
+
139
+ - `PAPERCLIP_GITHUB_REPO=owner/name` is the default repo when `github_read` is called without one. Public repos need no extra auth. Private repos need `gh auth login`.
140
+ - `PAPERCLIP_APPLE_CALENDAR=1` also tries to put `create_event` on Calendar.app (macOS).
141
+ - `PAPERCLIP_APPLE_TOOLS=1` registers AppleScript tools (Calendar and Mail). Useful for the morning brief.
142
+ - `PAPERCLIP_EXPERIMENTAL=1` registers stub tools.
143
+ - `{PAPERCLIP_HOME}/mcp.json` starts MCP servers and exposes their tools. If the `mcp` package is missing, Paperclip prints an install hint and continues.
144
+
145
+ Morning brief:
146
+
147
+ ```bash
148
+ uv run paperclip brief
149
+ ```
150
+
151
+ The file is `{PAPERCLIP_HOME}/outbox/brief-YYYY-MM-DD.txt`.
152
+
153
+ ## Voice
154
+
155
+ ```bash
156
+ uv run paperclip voice
157
+ ```
158
+
159
+ Speech is transcribed on the machine (faster-whisper). By default it waits for a wake phrase (`paperclip`, `paper clip`, `paperclip paperclip`, and a few variants), answers `Yes?` (`PAPERCLIP_WAKE_ACK`), then listens. After a reply it stays open for `PAPERCLIP_FOLLOWUP_SECONDS` (default 8) so you can continue without the phrase.
160
+
161
+ Set `PAPERCLIP_WAKE_WORD` empty for push-to-talk: Enter starts recording, Enter stops it. Clips shorter than a quarter of a second are ignored.
162
+
163
+ `PAPERCLIP_WHISPER_MODEL` defaults to `base`. `PAPERCLIP_WHISPER_DEVICE=cpu` avoids a missing CUDA runtime. `PAPERCLIP_TTS` and `PAPERCLIP_VOICE` pick how replies are spoken. On macOS, speech uses `say` unless `PAPERCLIP_TTS` is set.
164
+
165
+ The conversation is stored the same way as terminal chat (`session_id` `voice`).
166
+
167
+ ## Telegram
168
+
169
+ The assistant still runs on your computer. The process calls Telegram, so you do not need a public address.
170
+
171
+ 1. In Telegram, open @BotFather and send `/newbot`. Copy the token.
172
+ 2. Set `TELEGRAM_BOT_TOKEN` in `.env`.
173
+ 3. Set `TELEGRAM_ALLOWED_USER` to your numeric Telegram id (from @userinfobot). If this is empty, anyone who finds the bot can talk to your assistant.
174
+
175
+ ```bash
176
+ uv run paperclip telegram
177
+ ```
178
+
179
+ Only plain messages are answered. Bot commands are ignored. If the allowlist is set, other people get a short refusal. The laptop prints the message and the reply; you read the reply in Telegram.
180
+
181
+ If the API is running and `TELEGRAM_BOT_TOKEN` is set, the poller also starts in the background.
182
+
183
+ ## API
184
+
185
+ The API and the UI are separate.
186
+
187
+ ```bash
188
+ uv run paperclip api
189
+ ```
190
+
191
+ That binds a stdlib HTTP server to `127.0.0.1`. Port is `PAPERCLIP_DASHBOARD_PORT`, else `PORT`, else `7777`. If the port is busy, the next ports up to +9 are tried. Startup prints `Paperclip API -> http://localhost:<port>`.
192
+
193
+ The cockpit (turns, memory, tools, database, evals, spend, settings, chat) is the Next.js app in `dashboard/`:
194
+
195
+ ```bash
196
+ cd dashboard
197
+ bun run dev
198
+ ```
199
+
200
+ It calls `NEXT_PUBLIC_PAPERCLIP_API` (default `http://127.0.0.1:7777`). The browser origin must be in `PAPERCLIP_DASHBOARD_ORIGINS` (default includes `localhost:3000` and `:3001`). `/api/settings` can write API keys, and `/api/reveal` opens files on the host, so CORS is an allowlist.
201
+
202
+ Chat streaming is `POST /api/chat/stream` (SSE). Call it directly; a Next.js rewrite can buffer the stream.
203
+
204
+ Traces always append to `{home}/traces/YYYY-MM-DD.jsonl`. Token use is appended to `{home}/usage.jsonl`. Set `OTEL_EXPORTER_OTLP_ENDPOINT` to also export OpenTelemetry spans (the `opentelemetry` packages are optional).
205
+
206
+ `web/` is the user-facing docs site, separate from the cockpit.
207
+
208
+ ## Home directory
209
+
210
+ `ensure_home()` creates `home`, `home/traces`, and `home/outbox`. A running install typically looks like this:
211
+
212
+ ```
213
+ ~/.paperclip/
214
+ .env # API keys. Created on first launch.
215
+ state.db # facts, episodes, chat_log, calendar
216
+ MEMORY.md # export of memory
217
+ SOUL.md # persona
218
+ skills/ # skills installed on this machine
219
+ mcp.json # optional MCP servers
220
+ outbox/ # send_message drafts and briefs
221
+ traces/ # daily JSONL
222
+ usage.jsonl # token ledger
223
+ eval_report.json # latest release-gate verdict
224
+ eval_runs.jsonl # gate history
225
+ ```
226
+
227
+ `PAPERCLIP_HOME` changes the path. A `.paperclip` folder inside this repo is gitignored.
228
+
229
+ ## Skills
230
+
231
+ A skill is a `SKILL.md` with YAML frontmatter (`name`, `description`) and instructions. The loader keeps a skill when the user message shares at least two words (3+ letters) with the name and description. Copy `skills/TEMPLATE.md` into `skills/<name>/SKILL.md` (repo) or `{home}/skills/<name>/SKILL.md` (this machine).
232
+
233
+ ```bash
234
+ uv run paperclip skill install <url>
235
+ uv run python scripts/validate_skills.py
236
+ ```
237
+
238
+ ## Tests
239
+
240
+ Deterministic tests use a scripted model and do not need an API key. Judge tests score real replies with DeepEval and run only when `ANTHROPIC_API_KEY` is set.
241
+
242
+ ```bash
243
+ uv run python -m pytest -q evals/deterministic
244
+ uv run python -m pytest -q evals/judge
245
+ uv run python -m paperclip.ops.release_gate
246
+ ```
247
+
248
+ The release gate requires deterministic tests to pass. If the Anthropic key is missing, the judge is skipped and the process still exits 0. A passing run prints `GATE OPEN` and writes `{home}/eval_report.json`.
249
+
250
+ `uv run python scripts/demo_seed.py` backs up `PAPERCLIP_HOME` and writes a small curated demo state.
251
+
252
+ ## Layout
253
+
254
+ ```
255
+ paperclip/ # the agent
256
+ app.py # one running agent; gateways call respond()
257
+ config.py # Settings from the environment
258
+ db.py # state.db schema
259
+ __main__.py # CLI entry
260
+ gateway/ # cli, voice, telegram
261
+ loop/ # providers and the tool loop
262
+ memory/ # facts, episodes, skills, gate, consolidation
263
+ runtime/ # SOUL.md and in-memory history
264
+ tools/ # functions the model can call
265
+ ops/ # JSON API, traces, brief, release gate
266
+ evals/ # deterministic tests and LLM judge
267
+ dashboard/ # Next.js cockpit
268
+ web/ # user docs site
269
+ docs/ # engineering reference (canonical for behavior)
270
+ scripts/ # demo seed and skill check
271
+ skills/ # repo skills
272
+ ```
273
+
274
+ ## Environment
275
+
276
+ `.env.example` lists every variable. The ones you usually touch:
277
+
278
+ | Variable | Default | Role |
279
+ | --- | --- | --- |
280
+ | `ANTHROPIC_API_KEY` | empty | Required for the default provider |
281
+ | `PAPERCLIP_PROVIDER` | `anthropic` | `anthropic`, `openai`, `openrouter`, `gemini` |
282
+ | `PAPERCLIP_HOME` | `~/.paperclip` | Memory, traces, outbox, skills |
283
+ | `PAPERCLIP_MODEL` | provider default | Main loop model |
284
+ | `PAPERCLIP_SMALL_MODEL` | provider default | Retrieval gate and consolidation |
285
+ | `TAVILY_API_KEY` | empty | Web search. Unset uses DuckDuckGo |
286
+ | `TELEGRAM_BOT_TOKEN` | empty | Telegram gateway |
287
+ | `TELEGRAM_ALLOWED_USER` | empty | Numeric user id. Unset means anyone |
288
+ | `PAPERCLIP_GITHUB_REPO` | empty | Default `owner/name` for `github_read` |
289
+ | `PAPERCLIP_DASHBOARD_PORT` | `7777` | Dashboard API port |