@letta-ai/letta-code 0.24.6 → 0.24.8
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.
- package/letta.js +3314 -2662
- package/package.json +1 -1
- package/skills/configuring-your-harness/SKILL.md +242 -0
- package/skills/configuring-your-harness/references/hooks.md +261 -0
- package/skills/modifying-letta-code/SKILL.md +0 -270
- /package/skills/{modifying-letta-code → configuring-your-harness}/scripts/add_hook.py +0 -0
- /package/skills/{modifying-letta-code → configuring-your-harness}/scripts/add_permission.py +0 -0
- /package/skills/{modifying-letta-code → configuring-your-harness}/scripts/show_config.py +0 -0
package/package.json
CHANGED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: configuring-your-harness
|
|
3
|
+
description: "Configure deterministic Letta Code harness behavior, such as permission rules, lifecycle hooks, and model configuration."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Configuring Your Harness
|
|
7
|
+
|
|
8
|
+
Use this skill to configure deterministic Letta Code harness behavior, primarily permissions and lifecycle hooks. It can also help with local per-agent settings like toolset, model, context window, name, and description.
|
|
9
|
+
|
|
10
|
+
## Memory vs harness
|
|
11
|
+
|
|
12
|
+
Keep these layers separate:
|
|
13
|
+
|
|
14
|
+
| Layer | What it is | How to change it |
|
|
15
|
+
|-------|------------|------------------|
|
|
16
|
+
| **Memory** | Learned state, memfs files, conversation recall, skills | Edit `$MEMORY_DIR`, use memory tooling, create/update skill files |
|
|
17
|
+
| **Harness** | Deterministic runtime config around the agent | Edit Letta settings JSON or call the Letta API |
|
|
18
|
+
|
|
19
|
+
Edit memory when the agent should remember or learn something. Edit the harness when runtime behavior should deterministically change.
|
|
20
|
+
|
|
21
|
+
Do **not** edit harness settings for ordinary preferences like “remember I prefer concise answers.” Store those in memory. Do edit harness settings for deterministic behavior like “always ask before shell commands,” “add a hook to block unsafe edits,” or “change this agent’s toolset.”
|
|
22
|
+
|
|
23
|
+
Decision rule: if the LLM is responsible for choosing the behavior, store the instruction in memory. If the harness should enforce the behavior outside the LLM, use this skill.
|
|
24
|
+
|
|
25
|
+
Examples:
|
|
26
|
+
|
|
27
|
+
| User asks for... | Use |
|
|
28
|
+
|------------------|-----|
|
|
29
|
+
| “Auto-approve safe `git diff` commands” | Harness permission rule |
|
|
30
|
+
| “Deny all `rm -rf` shell commands” | Harness permission rule or hook |
|
|
31
|
+
| “Run a script before every Bash call” | Harness hook |
|
|
32
|
+
| “Notify me when you finish a response” | Harness hook |
|
|
33
|
+
| “Always sign commits like XYZ” | Memory, because the LLM writes commit messages |
|
|
34
|
+
| “Prefer short answers” | Memory, because the LLM controls response style |
|
|
35
|
+
| “Remember this repo’s PR checklist” | Memory or project docs, because the LLM applies it |
|
|
36
|
+
|
|
37
|
+
## Where harness changes live
|
|
38
|
+
|
|
39
|
+
### Settings JSON files
|
|
40
|
+
|
|
41
|
+
| File | Scope | Typical contents |
|
|
42
|
+
|------|-------|------------------|
|
|
43
|
+
| `~/.letta/settings.json` | User/global | Permissions, hooks, user-wide env vars, `agents[]` entries |
|
|
44
|
+
| `./.letta/settings.json` | Project/shared | Project permissions and hooks, committed with the repo |
|
|
45
|
+
| `./.letta/settings.local.json` | Project-local | Personal project overrides, gitignored |
|
|
46
|
+
|
|
47
|
+
Precedence for settings scopes is **local > project > user**, but list-like entries such as permissions and hooks are merged. For hooks, project-local hooks run first, then project hooks, then user hooks.
|
|
48
|
+
|
|
49
|
+
Use project or local scope only when the current working directory is intentionally the project root.
|
|
50
|
+
|
|
51
|
+
### Letta API fields
|
|
52
|
+
|
|
53
|
+
Name, description, model, and context window are server-side agent fields. Change them with `PATCH /v1/agents/{agent_id}`.
|
|
54
|
+
|
|
55
|
+
Required environment:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
export LETTA_API_KEY=...
|
|
59
|
+
export LETTA_AGENT_ID=...
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Example:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
curl -X PATCH "https://api.letta.com/v1/agents/$LETTA_AGENT_ID" \
|
|
66
|
+
-H "Authorization: Bearer $LETTA_API_KEY" \
|
|
67
|
+
-H "Content-Type: application/json" \
|
|
68
|
+
-d '{"name": "new-name"}'
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Load the `letta-api-client` skill for richer SDK examples.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 1. Change permissions
|
|
76
|
+
|
|
77
|
+
Permissions decide which tool calls are allowed, denied, or require approval.
|
|
78
|
+
|
|
79
|
+
### Rule syntax
|
|
80
|
+
|
|
81
|
+
- Bash prefix match: `Bash(npm install:*)`, `Bash(git:*)`, `Bash(curl:*)`
|
|
82
|
+
- File globs: `Read(src/**)`, `Edit(**/*.ts)`, `Write(*.md)`
|
|
83
|
+
- Broad rules: `*`, `Bash`, `Read` — use sparingly
|
|
84
|
+
|
|
85
|
+
### Add a permission with the helper
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
python3 <skill-dir>/scripts/add_permission.py \
|
|
89
|
+
--rule "Bash(curl:*)" \
|
|
90
|
+
--type allow \
|
|
91
|
+
--scope user
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Edit directly
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"permissions": {
|
|
99
|
+
"allow": ["Bash(npm:*)", "Read(src/**)"],
|
|
100
|
+
"deny": ["Bash(rm -rf:*)"],
|
|
101
|
+
"ask": []
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Permissions loaded from settings files are signature-checked and can update during a running session. If behavior does not update immediately, start a fresh session.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## 2. Add hooks
|
|
111
|
+
|
|
112
|
+
Hooks run shell commands or LLM prompt checks in response to Letta Code events. Use them to audit actions, inject context, enforce policy, auto-format after edits, notify on completion, or block unsafe actions.
|
|
113
|
+
|
|
114
|
+
Before adding hooks, inspect existing config to avoid duplicates or contradictory policy:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
python3 <skill-dir>/scripts/show_config.py
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Read [`references/hooks.md`](references/hooks.md) when adding, debugging, or explaining hooks. It covers scopes, merge order, events, matchers, command hooks, prompt hooks, input JSON, exit codes, direct JSON format, and practical patterns.
|
|
121
|
+
|
|
122
|
+
Quick examples:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Log every Bash tool call
|
|
126
|
+
python3 <skill-dir>/scripts/add_hook.py \
|
|
127
|
+
--event PreToolUse \
|
|
128
|
+
--matcher Bash \
|
|
129
|
+
--type command \
|
|
130
|
+
--command 'python3 ~/.letta/hooks/log-bash.py' \
|
|
131
|
+
--scope user
|
|
132
|
+
|
|
133
|
+
# Gate edits with an LLM prompt hook
|
|
134
|
+
python3 <skill-dir>/scripts/add_hook.py \
|
|
135
|
+
--event PreToolUse \
|
|
136
|
+
--matcher "Edit|Write" \
|
|
137
|
+
--type prompt \
|
|
138
|
+
--prompt 'Allow only edits under src/ unless the user explicitly requested otherwise. Input: $ARGUMENTS' \
|
|
139
|
+
--scope project
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
External edits to hook settings through scripts or direct JSON may require a fresh session because hooks are read through the settings manager cache. Hooks changed through in-app hook management APIs update in-memory settings immediately.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## 3. Change agent configuration
|
|
147
|
+
|
|
148
|
+
Agent config splits between the Letta server and local settings.
|
|
149
|
+
|
|
150
|
+
### Server-side fields (use the Letta API)
|
|
151
|
+
|
|
152
|
+
Use `PATCH /v1/agents/{agent_id}` with `$LETTA_AGENT_ID`.
|
|
153
|
+
|
|
154
|
+
**Change your model and context window:**
|
|
155
|
+
```bash
|
|
156
|
+
curl -X PATCH "https://api.letta.com/v1/agents/$LETTA_AGENT_ID" \
|
|
157
|
+
-H "Authorization: Bearer $LETTA_API_KEY" \
|
|
158
|
+
-H "Content-Type: application/json" \
|
|
159
|
+
-d '{
|
|
160
|
+
"llm_config": {
|
|
161
|
+
"model": "claude-sonnet-4.5",
|
|
162
|
+
"model_endpoint_type": "anthropic",
|
|
163
|
+
"context_window": 200000
|
|
164
|
+
}
|
|
165
|
+
}'
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Rename yourself:**
|
|
169
|
+
```bash
|
|
170
|
+
curl -X PATCH "https://api.letta.com/v1/agents/$LETTA_AGENT_ID" \
|
|
171
|
+
-H "Authorization: Bearer $LETTA_API_KEY" \
|
|
172
|
+
-H "Content-Type: application/json" \
|
|
173
|
+
-d '{"name": "draft-v2"}'
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Update your description:**
|
|
177
|
+
```bash
|
|
178
|
+
curl -X PATCH "https://api.letta.com/v1/agents/$LETTA_AGENT_ID" \
|
|
179
|
+
-H "Authorization: Bearer $LETTA_API_KEY" \
|
|
180
|
+
-H "Content-Type: application/json" \
|
|
181
|
+
-d '{"description": "..."}'
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
For Python / TypeScript SDK usage, see `docs.letta.com/api-overview/introduction` or load the `letta-api-client` skill.
|
|
185
|
+
|
|
186
|
+
### Local per-agent harness (edit `~/.letta/settings.json`)
|
|
187
|
+
|
|
188
|
+
The `agents[]` array stores per-agent harness preferences you can edit directly:
|
|
189
|
+
|
|
190
|
+
```json
|
|
191
|
+
{
|
|
192
|
+
"agents": [
|
|
193
|
+
{
|
|
194
|
+
"agentId": "agent-abc123",
|
|
195
|
+
"baseUrl": "https://api.letta.com",
|
|
196
|
+
"pinned": true,
|
|
197
|
+
"toolset": "default"
|
|
198
|
+
}
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
- **`toolset`** — which tool set to load for this agent
|
|
204
|
+
- **`pinned`** — quick-switch visibility
|
|
205
|
+
|
|
206
|
+
Find your own entry by matching `agentId === $LETTA_AGENT_ID`, then edit the fields you need.
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Quick reference
|
|
211
|
+
|
|
212
|
+
| Goal | Command/change |
|
|
213
|
+
|------|----------------|
|
|
214
|
+
| Auto-approve curl | `add_permission.py --rule "Bash(curl:*)" --type allow --scope user` |
|
|
215
|
+
| Block `rm -rf` | Add `"Bash(rm -rf:*)"` to `permissions.deny`, or add a `PreToolUse` hook |
|
|
216
|
+
| Log all Bash calls | `add_hook.py --event PreToolUse --matcher Bash --type command --command '...' --scope user` |
|
|
217
|
+
| Auto-format after edits | `add_hook.py --event PostToolUse --matcher "Edit|Write" --type command --command '...' --scope project` |
|
|
218
|
+
| Gate edits with LLM | `add_hook.py --event PreToolUse --matcher "Edit|Write" --type prompt --prompt '...' --scope user` |
|
|
219
|
+
| Notify when done | `add_hook.py --event Stop --type command --command 'say done' --scope user` |
|
|
220
|
+
| Show config | `python3 <skill-dir>/scripts/show_config.py` |
|
|
221
|
+
| Change model | `PATCH /v1/agents/$LETTA_AGENT_ID` with `llm_config.model` |
|
|
222
|
+
| Change context window | `PATCH /v1/agents/$LETTA_AGENT_ID` with `llm_config.context_window` |
|
|
223
|
+
| Rename | `PATCH /v1/agents/$LETTA_AGENT_ID` with `name` |
|
|
224
|
+
| Update description | `PATCH /v1/agents/$LETTA_AGENT_ID` with `description` |
|
|
225
|
+
| Change toolset | Edit `agents[].toolset` in `~/.letta/settings.json` |
|
|
226
|
+
|
|
227
|
+
## After making changes
|
|
228
|
+
|
|
229
|
+
- **Permissions** — file changes are signature-checked and generally hot-reload; restart if behavior does not update.
|
|
230
|
+
- **Hooks** — external file edits may require a fresh session; in-app hook management updates in-memory settings immediately.
|
|
231
|
+
- **Letta API changes** — apply server-side immediately, but current session state may not fully reflect them until restart.
|
|
232
|
+
- **Model changes** — start a fresh conversation after changing them for a clean context.
|
|
233
|
+
|
|
234
|
+
## Helper scripts in this skill
|
|
235
|
+
|
|
236
|
+
| Script | Purpose |
|
|
237
|
+
|--------|---------|
|
|
238
|
+
| `scripts/add_permission.py` | Add an allow/deny/ask rule to any scope |
|
|
239
|
+
| `scripts/add_hook.py` | Add a command or prompt hook to any event |
|
|
240
|
+
| `scripts/show_config.py` | Show merged permissions, hooks, and per-agent settings across all scopes |
|
|
241
|
+
|
|
242
|
+
All three accept `--scope user|project|local`. Run `--help` for full usage.
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# Hook configuration reference
|
|
2
|
+
|
|
3
|
+
Use this reference when adding, debugging, or explaining Letta Code hooks.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- [Before adding a hook](#before-adding-a-hook)
|
|
8
|
+
- [Scopes and merge order](#scopes-and-merge-order)
|
|
9
|
+
- [Events](#events)
|
|
10
|
+
- [Matchers](#matchers)
|
|
11
|
+
- [Command hooks](#command-hooks)
|
|
12
|
+
- [Prompt hooks](#prompt-hooks)
|
|
13
|
+
- [Direct JSON format](#direct-json-format)
|
|
14
|
+
- [Hook input fields](#hook-input-fields)
|
|
15
|
+
- [Practical patterns](#practical-patterns)
|
|
16
|
+
- [Debugging](#debugging)
|
|
17
|
+
|
|
18
|
+
## Before adding a hook
|
|
19
|
+
|
|
20
|
+
Inspect existing config first:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
python3 <skill-dir>/scripts/show_config.py
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Avoid duplicate hooks, contradictory safety policies, and broad hooks when a narrow matcher is enough.
|
|
27
|
+
|
|
28
|
+
## Scopes and merge order
|
|
29
|
+
|
|
30
|
+
| Scope | File | Use for |
|
|
31
|
+
|-------|------|---------|
|
|
32
|
+
| User | `~/.letta/settings.json` | Personal global hooks: audit logs, notifications, global safety rails |
|
|
33
|
+
| Project | `./.letta/settings.json` | Team-shared hooks committed with the repo |
|
|
34
|
+
| Local | `./.letta/settings.local.json` | Personal project overrides or experiments; should be gitignored |
|
|
35
|
+
|
|
36
|
+
Hooks from all scopes are merged. Execution order is:
|
|
37
|
+
|
|
38
|
+
1. Project-local hooks
|
|
39
|
+
2. Project hooks
|
|
40
|
+
3. User hooks
|
|
41
|
+
|
|
42
|
+
Project or local scope only works as intended when Letta Code is running from the project root.
|
|
43
|
+
|
|
44
|
+
## Events
|
|
45
|
+
|
|
46
|
+
Tool events require a `matcher`:
|
|
47
|
+
|
|
48
|
+
| Event | When it runs | Blocking behavior |
|
|
49
|
+
|-------|--------------|-------------------|
|
|
50
|
+
| `PreToolUse` | Before a tool runs | Exit 2 blocks the tool |
|
|
51
|
+
| `PostToolUse` | After a tool succeeds | Good for logging/context; do not rely on it to undo work |
|
|
52
|
+
| `PostToolUseFailure` | After a tool fails | Good for diagnostics; it cannot make the failed tool succeed |
|
|
53
|
+
| `PermissionRequest` | When an approval dialog would show | Exit 0 allows; exit 2 denies |
|
|
54
|
+
|
|
55
|
+
Simple events do not use a matcher:
|
|
56
|
+
|
|
57
|
+
| Event | When it runs |
|
|
58
|
+
|-------|--------------|
|
|
59
|
+
| `UserPromptSubmit` | User submits a normal prompt, not a slash command |
|
|
60
|
+
| `Stop` | Agent finishes a response |
|
|
61
|
+
| `SubagentStop` | Subagent completes |
|
|
62
|
+
| `PreCompact` | Before context compaction |
|
|
63
|
+
| `SessionStart` | Session starts |
|
|
64
|
+
| `SessionEnd` | Session ends |
|
|
65
|
+
| `Notification` | Notification event fires |
|
|
66
|
+
|
|
67
|
+
## Matchers
|
|
68
|
+
|
|
69
|
+
Tool-event matchers are case-sensitive regex patterns over the tool name. `*` is the special match-all value.
|
|
70
|
+
|
|
71
|
+
Common matchers:
|
|
72
|
+
|
|
73
|
+
```text
|
|
74
|
+
Bash # shell commands
|
|
75
|
+
Edit|Write # edits and writes
|
|
76
|
+
Read|Grep # reads/searches
|
|
77
|
+
* # all tools
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Prefer narrow matchers. Use `*` only for cheap logging or broad policy checks.
|
|
81
|
+
|
|
82
|
+
## Command hooks
|
|
83
|
+
|
|
84
|
+
Command hooks run a shell command. The hook input JSON is written to stdin. The command also receives:
|
|
85
|
+
|
|
86
|
+
- `LETTA_HOOK_EVENT` — event name
|
|
87
|
+
- `LETTA_WORKING_DIR` / `USER_CWD` — working directory
|
|
88
|
+
- `LETTA_AGENT_ID` / `AGENT_ID` — present when the event has an agent id
|
|
89
|
+
|
|
90
|
+
Exit codes:
|
|
91
|
+
|
|
92
|
+
- `0` — allow / success
|
|
93
|
+
- `2` — block, for blocking-capable events
|
|
94
|
+
- Any other code or timeout — hook error
|
|
95
|
+
|
|
96
|
+
Example: log every Bash invocation as one JSON line:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
mkdir -p ~/.letta/hooks
|
|
100
|
+
cat > ~/.letta/hooks/log-bash.py <<'PY'
|
|
101
|
+
import pathlib
|
|
102
|
+
import sys
|
|
103
|
+
|
|
104
|
+
path = pathlib.Path.home() / ".letta" / "bash-audit.jsonl"
|
|
105
|
+
path.parent.mkdir(exist_ok=True)
|
|
106
|
+
path.open("a").write(sys.stdin.read() + "\n")
|
|
107
|
+
PY
|
|
108
|
+
|
|
109
|
+
python3 <skill-dir>/scripts/add_hook.py \
|
|
110
|
+
--event PreToolUse \
|
|
111
|
+
--matcher Bash \
|
|
112
|
+
--type command \
|
|
113
|
+
--command 'python3 ~/.letta/hooks/log-bash.py' \
|
|
114
|
+
--scope user
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Example: block shell commands containing `rm -rf`:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
mkdir -p ~/.letta/hooks
|
|
121
|
+
cat > ~/.letta/hooks/check-bash.py <<'PY'
|
|
122
|
+
import json
|
|
123
|
+
import sys
|
|
124
|
+
|
|
125
|
+
data = json.load(sys.stdin)
|
|
126
|
+
cmd = str(data.get("tool_input", {}).get("command", ""))
|
|
127
|
+
if "rm -rf" in cmd:
|
|
128
|
+
print("rm -rf is blocked by hook", file=sys.stderr)
|
|
129
|
+
sys.exit(2)
|
|
130
|
+
PY
|
|
131
|
+
|
|
132
|
+
python3 <skill-dir>/scripts/add_hook.py \
|
|
133
|
+
--event PreToolUse \
|
|
134
|
+
--matcher Bash \
|
|
135
|
+
--type command \
|
|
136
|
+
--command 'python3 ~/.letta/hooks/check-bash.py' \
|
|
137
|
+
--scope user
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
For anything non-trivial, write a script somewhere stable and call it from the hook. This avoids brittle shell quoting:
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"type": "command",
|
|
145
|
+
"command": "python3 ~/.letta/hooks/check-bash.py",
|
|
146
|
+
"timeout": 60000
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Prompt hooks
|
|
151
|
+
|
|
152
|
+
Prompt hooks send the hook input to an LLM evaluator. Use `$ARGUMENTS` inside the prompt to insert the event JSON; if omitted, the JSON is appended automatically.
|
|
153
|
+
|
|
154
|
+
Supported prompt-hook events:
|
|
155
|
+
`PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, `UserPromptSubmit`, `Stop`, `SubagentStop`.
|
|
156
|
+
|
|
157
|
+
The evaluator must return JSON with `ok: true` or `ok: false`; when blocking, include `reason`.
|
|
158
|
+
|
|
159
|
+
Example:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
python3 <skill-dir>/scripts/add_hook.py \
|
|
163
|
+
--event PreToolUse \
|
|
164
|
+
--matcher "Edit|Write" \
|
|
165
|
+
--type prompt \
|
|
166
|
+
--prompt 'Allow only edits under src/ unless the user explicitly requested otherwise. Input: $ARGUMENTS' \
|
|
167
|
+
--scope project
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Add `--model <model-name>` only when a specific model is required.
|
|
171
|
+
|
|
172
|
+
## Direct JSON format
|
|
173
|
+
|
|
174
|
+
Tool events group hooks under matcher entries:
|
|
175
|
+
|
|
176
|
+
```json
|
|
177
|
+
{
|
|
178
|
+
"hooks": {
|
|
179
|
+
"PreToolUse": [
|
|
180
|
+
{
|
|
181
|
+
"matcher": "Bash",
|
|
182
|
+
"hooks": [
|
|
183
|
+
{
|
|
184
|
+
"type": "command",
|
|
185
|
+
"command": "python3 ~/.letta/hooks/check-bash.py",
|
|
186
|
+
"timeout": 60000
|
|
187
|
+
}
|
|
188
|
+
]
|
|
189
|
+
}
|
|
190
|
+
]
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Simple events omit matchers:
|
|
196
|
+
|
|
197
|
+
```json
|
|
198
|
+
{
|
|
199
|
+
"hooks": {
|
|
200
|
+
"Stop": [
|
|
201
|
+
{
|
|
202
|
+
"hooks": [
|
|
203
|
+
{ "type": "command", "command": "say done" }
|
|
204
|
+
]
|
|
205
|
+
}
|
|
206
|
+
]
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Disable hooks in a settings file with:
|
|
212
|
+
|
|
213
|
+
```json
|
|
214
|
+
{
|
|
215
|
+
"hooks": {
|
|
216
|
+
"disabled": true
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
A user-level `"disabled": false` explicitly keeps hooks enabled and overrides project/local `disabled: true`. Without that user-level override, project or local `disabled: true` disables hooks.
|
|
222
|
+
|
|
223
|
+
## Hook input fields
|
|
224
|
+
|
|
225
|
+
All hook inputs include `event_type` and `working_directory`. Event-specific fields commonly include:
|
|
226
|
+
|
|
227
|
+
- Tool events: `tool_name`, `tool_input`, `tool_call_id`
|
|
228
|
+
- `PostToolUse`: `tool_result`
|
|
229
|
+
- `PostToolUseFailure`: `error_message`, `error_type`
|
|
230
|
+
- `PermissionRequest`: `permission`, `session_permissions`
|
|
231
|
+
- `UserPromptSubmit`: `prompt`, `conversation_id`, `agent_id`
|
|
232
|
+
- `Stop`: `stop_reason`, `message_count`, `tool_call_count`, `assistant_message`, `user_message`
|
|
233
|
+
- `SessionStart` / `SessionEnd`: session metadata, `agent_id`, `conversation_id`
|
|
234
|
+
|
|
235
|
+
When unsure, add a temporary logging hook and inspect the JSON it writes.
|
|
236
|
+
|
|
237
|
+
## Practical patterns
|
|
238
|
+
|
|
239
|
+
- **Audit tools**: `PreToolUse` + `matcher: "*"` + append stdin to JSONL.
|
|
240
|
+
- **Safety gate**: `PreToolUse` on `Bash` or `Edit|Write`; exit 2 with a stderr reason to block.
|
|
241
|
+
- **Permission policy**: `PermissionRequest`; exit 0 for known-safe requests and exit 2 for known-dangerous ones.
|
|
242
|
+
- **Auto-format**: `PostToolUse` on `Edit|Write`; run a fast idempotent formatter.
|
|
243
|
+
- **Context injection**: `UserPromptSubmit` or `SessionStart`; stdout can be fed back as context.
|
|
244
|
+
- **Notifications**: `Stop` or `SessionEnd`; call `say`, `terminal-notifier`, Slack scripts, etc.
|
|
245
|
+
|
|
246
|
+
## Debugging
|
|
247
|
+
|
|
248
|
+
Show merged config:
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
python3 <skill-dir>/scripts/show_config.py
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Common gotchas:
|
|
255
|
+
|
|
256
|
+
- External edits to hook settings through scripts or direct JSON may require a fresh session because hooks are read through the settings manager cache.
|
|
257
|
+
- In-app hook management APIs update the in-memory settings immediately.
|
|
258
|
+
- Project/local hooks depend on starting Letta Code from the intended project root.
|
|
259
|
+
- JSON quoting inside shell one-liners is fragile; use a separate script for real logic.
|
|
260
|
+
- Long-running hooks block the agent. Keep hooks fast and set `timeout`.
|
|
261
|
+
- Prompt hooks require an agent id and LLM access.
|