@desplega.ai/agent-swarm 1.92.0 → 1.92.2
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/README.md +1 -1
- package/openapi.json +276 -3
- package/package.json +6 -6
- package/plugin/skills/pages/SKILL.md +5 -2
- package/src/be/db.ts +416 -20
- package/src/be/memory/boot-reembed.ts +85 -0
- package/src/be/memory/constants.ts +44 -2
- package/src/be/memory/providers/openai-embedding.ts +15 -5
- package/src/be/memory/providers/sqlite-store.ts +325 -76
- package/src/be/memory/reranker.ts +35 -17
- package/src/be/memory/types.ts +43 -0
- package/src/be/migrations/084_script_run_journal_duration.sql +5 -0
- package/src/be/migrations/085_script_runs_kind.sql +9 -0
- package/src/be/migrations/086_pages_default_authed.sql +64 -0
- package/src/be/migrations/087_skill_files.sql +19 -0
- package/src/be/modelsdev-cache.json +5622 -2543
- package/src/be/seed-scripts/catalog/boot-triage.ts +221 -0
- package/src/be/seed-scripts/catalog/catalog-report.ts +457 -0
- package/src/be/seed-scripts/catalog/compound-insights.ts +465 -0
- package/src/be/seed-scripts/catalog/gh-pr-snapshot.ts +1 -1
- package/src/be/seed-scripts/catalog/memory-eval.ts +1059 -0
- package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +34 -439
- package/src/be/seed-scripts/catalog/schedule-health.ts +78 -2
- package/src/be/seed-scripts/catalog/task-failure-audit.ts +48 -1
- package/src/be/seed-scripts/index.ts +32 -4
- package/src/be/seed-skills/index.ts +0 -7
- package/src/be/skill-sync.ts +91 -7
- package/src/commands/runner.ts +6 -2
- package/src/heartbeat/templates.ts +20 -16
- package/src/http/index.ts +50 -7
- package/src/http/mcp-user.ts +23 -0
- package/src/http/mcp.ts +58 -0
- package/src/http/memory.ts +62 -0
- package/src/http/pages.ts +1 -1
- package/src/http/script-runs.ts +2 -0
- package/src/http/scripts.ts +39 -2
- package/src/http/skills.ts +225 -0
- package/src/providers/claude-adapter.ts +56 -24
- package/src/script-workflows/workflow-ctx.ts +7 -3
- package/src/scripts-runtime/sdk-allowlist.ts +1 -0
- package/src/scripts-runtime/swarm-sdk.ts +13 -0
- package/src/scripts-runtime/types/stdlib.d.ts +1 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
- package/src/server.ts +2 -0
- package/src/tasks/worker-follow-up.ts +12 -0
- package/src/tests/claude-adapter-binary.test.ts +135 -81
- package/src/tests/create-page-tool.test.ts +19 -2
- package/src/tests/heartbeat-checklist.test.ts +36 -0
- package/src/tests/mcp-transport-gc.test.ts +58 -0
- package/src/tests/memory-e2e.test.ts +6 -6
- package/src/tests/memory-health-endpoint.test.ts +78 -0
- package/src/tests/memory-rater-e2e.test.ts +4 -5
- package/src/tests/memory-reranker.test.ts +135 -124
- package/src/tests/memory-store.test.ts +221 -1
- package/src/tests/memory.test.ts +13 -12
- package/src/tests/pages-http.test.ts +20 -2
- package/src/tests/pages-storage.test.ts +26 -0
- package/src/tests/scripts-mcp-e2e.test.ts +53 -0
- package/src/tests/seed-scripts.test.ts +328 -3
- package/src/tests/skill-files-http.test.ts +171 -0
- package/src/tests/skill-files.test.ts +162 -0
- package/src/tests/skill-get-file-tool.test.ts +110 -0
- package/src/tests/skill-sync.test.ts +125 -6
- package/src/tests/task-cascade-fail.test.ts +304 -0
- package/src/tools/create-page.ts +2 -2
- package/src/tools/skills/index.ts +1 -0
- package/src/tools/skills/skill-get-file.ts +80 -0
- package/src/tools/tool-config.ts +2 -1
- package/src/types.ts +20 -0
- package/src/utils/internal-ai/complete-structured.ts +2 -2
- package/templates/schedules/daily-blocker-digest/content.md +68 -54
- package/templates/schedules/daily-compounding-reflection/content.md +4 -4
- package/templates/schedules/daily-hn-briefing/content.md +5 -5
- package/templates/schedules/daily-workflow-health-audit/content.md +6 -6
- package/templates/schedules/gtm-weekly-review/content.md +9 -9
- package/templates/schedules/weekly-dependabot-triage/content.md +24 -20
- package/templates/skills/agentmail-sending/content.md +6 -7
- package/templates/skills/desloppify/content.md +8 -9
- package/templates/skills/jira-interaction/content.md +25 -33
- package/templates/skills/kapso-whatsapp/content.md +29 -30
- package/templates/skills/linear-interaction/content.md +8 -9
- package/templates/skills/profile-corruption-escalation/content.md +44 -85
- package/templates/skills/sprite-cli/content.md +4 -5
- package/templates/skills/turso-interaction/content.md +14 -17
- package/templates/skills/workflow-iterate/content.md +38 -391
- package/templates/skills/x-api-interactions/content.md +4 -6
- package/templates/workflows/llm-safe-release-context/config.json +13 -0
- package/templates/workflows/llm-safe-release-context/content.md +69 -0
- package/templates/skills/scheduled-task-resilience/config.json +0 -14
- package/templates/skills/scheduled-task-resilience/content.md +0 -95
|
@@ -5,15 +5,15 @@ The swarm has Jira OAuth connected but **no inbound sync** (unlike Linear). Ever
|
|
|
5
5
|
## TL;DR — minimum knowledge
|
|
6
6
|
|
|
7
7
|
1. Pull the access token from `oauth_tokens` (provider = `jira`).
|
|
8
|
-
2. Hit `https://api.atlassian.com/ex/jira/<CLOUD_ID>/rest/api/3/...` —
|
|
8
|
+
2. Hit `https://api.atlassian.com/ex/jira/<CLOUD_ID>/rest/api/3/...` — not your site hostname directly. 3LO bearer tokens only work via the `api.atlassian.com` proxy.
|
|
9
9
|
3. Bodies for descriptions/comments must be in **ADF** (Atlassian Document Format), not plain text or markdown.
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Deployment constants
|
|
12
12
|
|
|
13
|
-
- Site:
|
|
14
|
-
- Cloud ID:
|
|
15
|
-
- Default project:
|
|
16
|
-
- Scopes on the stored token:
|
|
13
|
+
- Site: `<your-site>.atlassian.net`
|
|
14
|
+
- Cloud ID: `<cloud-id>`
|
|
15
|
+
- Default project: `<PROJECT>`
|
|
16
|
+
- Scopes on the stored token: confirm from `oauth_tokens.scope` before write operations
|
|
17
17
|
|
|
18
18
|
If the cloudId ever changes, rediscover it:
|
|
19
19
|
```bash
|
|
@@ -33,7 +33,7 @@ SELECT accessToken, expiresAt, scope FROM oauth_tokens WHERE provider = 'jira';
|
|
|
33
33
|
**Always check `expiresAt` first.** Atlassian access tokens are short-lived (~1h). If expired, do NOT keep retrying — report it. Re-auth path:
|
|
34
34
|
|
|
35
35
|
```
|
|
36
|
-
|
|
36
|
+
<SWARM_API_BASE_URL>/api/trackers/jira/authorize
|
|
37
37
|
```
|
|
38
38
|
(User may need to remove the app and re-auth.)
|
|
39
39
|
|
|
@@ -65,7 +65,7 @@ curl -s -H "Authorization: Bearer $TOKEN" -H "Accept: application/json" \
|
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
67
|
curl -s -H "Authorization: Bearer $TOKEN" -H "Accept: application/json" \
|
|
68
|
-
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/project
|
|
68
|
+
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/project/$PROJECT_KEY" \
|
|
69
69
|
| jq '{key, name, lead: .lead.displayName, issueTypes: [.issueTypes[] | {id, name, subtask}]}'
|
|
70
70
|
```
|
|
71
71
|
|
|
@@ -76,7 +76,7 @@ Use the **`/search/jql`** endpoint (the older `/search` is deprecated for cloud)
|
|
|
76
76
|
```bash
|
|
77
77
|
curl -s -G \
|
|
78
78
|
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json" \
|
|
79
|
-
--data-urlencode 'jql=project =
|
|
79
|
+
--data-urlencode 'jql=project = <PROJECT> AND statusCategory != Done' \
|
|
80
80
|
--data-urlencode 'fields=summary,status,assignee,priority' \
|
|
81
81
|
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/search/jql" \
|
|
82
82
|
| jq '[.issues[] | {key, summary: .fields.summary, status: .fields.status.name, assignee: .fields.assignee.displayName}]'
|
|
@@ -92,7 +92,7 @@ curl -s -X POST \
|
|
|
92
92
|
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/issue" \
|
|
93
93
|
-d '{
|
|
94
94
|
"fields": {
|
|
95
|
-
"project": { "key": "
|
|
95
|
+
"project": { "key": "<PROJECT>" },
|
|
96
96
|
"summary": "Short title",
|
|
97
97
|
"issuetype": { "name": "Task" },
|
|
98
98
|
"description": {
|
|
@@ -106,9 +106,9 @@ curl -s -X POST \
|
|
|
106
106
|
}'
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
-
Returns `{ id, key, self }` on success (HTTP 201). The `key` (e.g.
|
|
109
|
+
Returns `{ id, key, self }` on success (HTTP 201). The `key` (e.g. `<PROJECT>-7`) is what humans use; URL is `https://<your-site>.atlassian.net/browse/<KEY>`.
|
|
110
110
|
|
|
111
|
-
Available issue types
|
|
111
|
+
Available issue types are project-specific; list the project before creating issues.
|
|
112
112
|
|
|
113
113
|
### 5. Transition issue status (e.g. → Done)
|
|
114
114
|
|
|
@@ -120,23 +120,15 @@ curl -s -H "Authorization: Bearer $TOKEN" -H "Accept: application/json" \
|
|
|
120
120
|
| jq '.transitions[] | {id, name, to: .to.name}'
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
| Transition ID | Target state |
|
|
126
|
-
|---|---|
|
|
127
|
-
| `11` | To Do |
|
|
128
|
-
| `21` | In Progress |
|
|
129
|
-
| `31` | In Review |
|
|
130
|
-
| `41` | Backlog |
|
|
131
|
-
| `51` | **Done** |
|
|
123
|
+
Transition IDs are project-specific. Do not copy IDs between Jira projects; discover them for the issue you are about to update.
|
|
132
124
|
|
|
133
125
|
Transition (returns HTTP 204 on success, no body):
|
|
134
126
|
|
|
135
127
|
```bash
|
|
136
128
|
curl -s -X POST \
|
|
137
129
|
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \
|
|
138
|
-
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/issue
|
|
139
|
-
-d '{"transition":{"id":"
|
|
130
|
+
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/issue/<KEY>/transitions" \
|
|
131
|
+
-d '{"transition":{"id":"<TRANSITION_ID>"}}'
|
|
140
132
|
```
|
|
141
133
|
|
|
142
134
|
### 6. Comment on an issue
|
|
@@ -146,7 +138,7 @@ ADF body again:
|
|
|
146
138
|
```bash
|
|
147
139
|
curl -s -X POST \
|
|
148
140
|
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \
|
|
149
|
-
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/issue
|
|
141
|
+
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/issue/<KEY>/comment" \
|
|
150
142
|
-d '{
|
|
151
143
|
"body": {
|
|
152
144
|
"type": "doc", "version": 1,
|
|
@@ -161,7 +153,7 @@ Atlassian Cloud uses **accountId**, not username. Find one via:
|
|
|
161
153
|
|
|
162
154
|
```bash
|
|
163
155
|
curl -s -G -H "Authorization: Bearer $TOKEN" -H "Accept: application/json" \
|
|
164
|
-
--data-urlencode 'query
|
|
156
|
+
--data-urlencode 'query=<name-or-email>' \
|
|
165
157
|
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/user/search" \
|
|
166
158
|
| jq '.[] | {accountId, displayName, emailAddress}'
|
|
167
159
|
```
|
|
@@ -170,7 +162,7 @@ Assign:
|
|
|
170
162
|
```bash
|
|
171
163
|
curl -s -X PUT \
|
|
172
164
|
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \
|
|
173
|
-
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/issue
|
|
165
|
+
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/issue/<KEY>/assignee" \
|
|
174
166
|
-d '{"accountId":"<ACCOUNT_ID>"}'
|
|
175
167
|
```
|
|
176
168
|
|
|
@@ -181,7 +173,7 @@ To unassign: `{"accountId": null}`.
|
|
|
181
173
|
```bash
|
|
182
174
|
curl -s -X PUT \
|
|
183
175
|
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \
|
|
184
|
-
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/issue
|
|
176
|
+
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/issue/<KEY>" \
|
|
185
177
|
-d '{ "fields": { "summary": "New summary", "labels": ["swarm","auto"] } }'
|
|
186
178
|
```
|
|
187
179
|
|
|
@@ -204,7 +196,7 @@ If you need rich content, build it in a script — don't try to write deep ADF i
|
|
|
204
196
|
## Operational rules
|
|
205
197
|
|
|
206
198
|
- **Token-expiry first.** Always check `expiresAt`. Don't loop on 401s.
|
|
207
|
-
- **Use the proxy.** All authenticated calls go through `api.atlassian.com/ex/jira/<cloudId>/...`. Hitting
|
|
199
|
+
- **Use the proxy.** All authenticated calls go through `api.atlassian.com/ex/jira/<cloudId>/...`. Hitting `<your-site>.atlassian.net/rest/api/3/...` with a 3LO bearer token will fail.
|
|
208
200
|
- **Discover transitions per issue** before transitioning — different projects/workflows have different IDs.
|
|
209
201
|
- **Use `/search/jql`**, not the legacy `/search` (which is deprecated and may be removed).
|
|
210
202
|
- **ADF is mandatory** for `description`, `comment`, and rich text fields. Plain strings will be rejected.
|
|
@@ -226,21 +218,22 @@ If you need rich content, build it in a script — don't try to write deep ADF i
|
|
|
226
218
|
|
|
227
219
|
```bash
|
|
228
220
|
TOKEN=$(db-query "SELECT accessToken FROM oauth_tokens WHERE provider='jira'")
|
|
229
|
-
CLOUD_ID="
|
|
221
|
+
CLOUD_ID="<cloud-id>"
|
|
222
|
+
PROJECT_KEY="<PROJECT>"
|
|
230
223
|
|
|
231
224
|
# 1. List open issues in KAN
|
|
232
225
|
curl -s -G -H "Authorization: Bearer $TOKEN" -H "Accept: application/json" \
|
|
233
|
-
--data-urlencode
|
|
226
|
+
--data-urlencode "jql=project = $PROJECT_KEY AND statusCategory != Done" \
|
|
234
227
|
--data-urlencode 'fields=summary,status' \
|
|
235
228
|
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/search/jql" \
|
|
236
229
|
| jq -r '.issues[].key' > /tmp/keys.txt
|
|
237
230
|
|
|
238
|
-
# 2. Transition each to Done
|
|
231
|
+
# 2. Transition each to Done using the transition ID discovered for this workflow
|
|
239
232
|
for KEY in $(cat /tmp/keys.txt); do
|
|
240
233
|
curl -s -X POST \
|
|
241
234
|
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \
|
|
242
235
|
"https://api.atlassian.com/ex/jira/$CLOUD_ID/rest/api/3/issue/$KEY/transitions" \
|
|
243
|
-
-d '{"transition":{"id":"
|
|
236
|
+
-d '{"transition":{"id":"<DONE_TRANSITION_ID>"}}'
|
|
244
237
|
sleep 0.3
|
|
245
238
|
done
|
|
246
239
|
```
|
|
@@ -249,4 +242,3 @@ done
|
|
|
249
242
|
|
|
250
243
|
- The MCP tracker tools (`tracker-link-task`, `tracker-sync-status`, etc.) are designed for two-way sync mappings. Jira tracker support exists at the schema level but is not currently wired up to inbound webhooks. Until it is, all Jira interaction must go through this skill.
|
|
251
244
|
- If/when inbound Jira webhooks land, this skill should add a "When to transition" section mirroring the Linear one.
|
|
252
|
-
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Kapso WhatsApp
|
|
2
2
|
|
|
3
|
-
Kapso (https://kapso.ai) is
|
|
3
|
+
Kapso (https://kapso.ai) is a WhatsApp platform vendor for the Meta Cloud API. A swarm can provision one or more phone numbers and use either native inbound handlers or workflows to dispatch a task per inbound message.
|
|
4
4
|
|
|
5
5
|
## When to use MCP tools vs this skill's REST recipes
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Some deployments expose **thin MCP-tool wrappers for the common case only**:
|
|
8
8
|
|
|
9
9
|
| Tool | Use for |
|
|
10
10
|
|---|---|
|
|
@@ -32,7 +32,7 @@ Swarm config keys (resolve with `get-config key:<NAME> includeSecrets:true` —
|
|
|
32
32
|
|---|---|
|
|
33
33
|
| `KAPSO_API_BASE_URL` | `https://api.kapso.ai` (host only, no `/platform/v1`) |
|
|
34
34
|
| `KAPSO_API_KEY` | API key (`X-API-Key` header) |
|
|
35
|
-
| `KAPSO_PHONE_NUMBER_ID` |
|
|
35
|
+
| `KAPSO_PHONE_NUMBER_ID` | Provisioned number's Meta ID |
|
|
36
36
|
| `KAPSO_WEBHOOK_HMAC_SECRET` | Shared HMAC secret. Kapso signs every webhook request with `X-Webhook-Signature: <hex>` |
|
|
37
37
|
|
|
38
38
|
The Kapso CLI is NOT installed in worker containers. Use direct HTTP or clone the `gokapso/agent-skills` repo for fallback scripts.
|
|
@@ -46,7 +46,7 @@ The Meta Cloud API is proxied at `$KAPSO_API_BASE_URL/meta/whatsapp/v24.0/...` (
|
|
|
46
46
|
|
|
47
47
|
## Inbound webhook payload (v2)
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
Your inbound workflow receives `whatsapp.message.*` and `whatsapp.conversation.*` events at `POST <SWARM_API_BASE_URL>/api/webhooks/<workflow-id>`.
|
|
50
50
|
|
|
51
51
|
Shape (top-level keys):
|
|
52
52
|
|
|
@@ -54,7 +54,7 @@ Shape (top-level keys):
|
|
|
54
54
|
{
|
|
55
55
|
"message": {
|
|
56
56
|
"id": "wamid.HBgL...", // Meta message id (WAMID)
|
|
57
|
-
"from": "
|
|
57
|
+
"from": "15550100000", // dummy E.164 without +
|
|
58
58
|
"from_user_id": "ES.26772...", // Meta-internal user id
|
|
59
59
|
"timestamp": "1779281573", // unix seconds (string)
|
|
60
60
|
"type": "text", // text | image | audio | video | document | sticker | location | contacts | reaction | ...
|
|
@@ -71,9 +71,9 @@ Shape (top-level keys):
|
|
|
71
71
|
},
|
|
72
72
|
"conversation": {
|
|
73
73
|
"id": "bd7e888e-...",
|
|
74
|
-
"phone_number": "
|
|
75
|
-
"phone_number_id": "
|
|
76
|
-
"contact_name": "
|
|
74
|
+
"phone_number": "15550100000",
|
|
75
|
+
"phone_number_id": "<phone-number-id>",
|
|
76
|
+
"contact_name": "Example Contact",
|
|
77
77
|
"status": "active",
|
|
78
78
|
"last_active_at": "...",
|
|
79
79
|
"created_at": "...",
|
|
@@ -86,7 +86,7 @@ Shape (top-level keys):
|
|
|
86
86
|
}
|
|
87
87
|
},
|
|
88
88
|
"is_new_conversation": false,
|
|
89
|
-
"phone_number_id": "
|
|
89
|
+
"phone_number_id": "<phone-number-id>"
|
|
90
90
|
}
|
|
91
91
|
```
|
|
92
92
|
|
|
@@ -143,7 +143,7 @@ NB: verify the exact proxy path against a real media message — the swarm has o
|
|
|
143
143
|
|
|
144
144
|
Two paths in order:
|
|
145
145
|
|
|
146
|
-
1. By name: `resolve-user name:"<contact_name>"` (fuzzy substring match). Returns the canonical profile if there's one. Useful for known team members
|
|
146
|
+
1. By name: `resolve-user name:"<contact_name>"` (fuzzy substring match). Returns the canonical profile if there's one. Useful for known team members.
|
|
147
147
|
2. If no match — the contact is unknown. Lead can run `manage-user create name:"<contact_name>" notes:"WhatsApp +<phone>"` to register them. Workers should NOT create users autonomously; ask Lead.
|
|
148
148
|
|
|
149
149
|
Always quote the phone number in `manage-user notes` so future lookups by phone work (until we add a `phone` column to the user registry).
|
|
@@ -158,7 +158,7 @@ API_KEY=$(get-config KAPSO_API_KEY)
|
|
|
158
158
|
|
|
159
159
|
# List conversations for our number
|
|
160
160
|
curl -s -H "X-API-Key: $API_KEY" \
|
|
161
|
-
"$API_BASE/platform/v1/whatsapp/conversations?phone_number_id
|
|
161
|
+
"$API_BASE/platform/v1/whatsapp/conversations?phone_number_id=$KAPSO_PHONE_NUMBER_ID&status=active" | jq
|
|
162
162
|
|
|
163
163
|
# Get a single conversation
|
|
164
164
|
curl -s -H "X-API-Key: $API_KEY" \
|
|
@@ -183,8 +183,8 @@ Per WhatsApp policy, free-form text is only allowed within 24h of the last inbou
|
|
|
183
183
|
**Common case shortcut:** call the `send-whatsapp-message` MCP tool — it wraps exactly this REST call. The recipe below is the canonical reference and the fallback when you need fields the tool doesn't expose.
|
|
184
184
|
|
|
185
185
|
```bash
|
|
186
|
-
TO="
|
|
187
|
-
TEXT="Hi
|
|
186
|
+
TO="15550100000"
|
|
187
|
+
TEXT="Hi there"
|
|
188
188
|
|
|
189
189
|
curl -s -X POST -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
|
|
190
190
|
-d "{
|
|
@@ -194,7 +194,7 @@ curl -s -X POST -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
|
|
|
194
194
|
\"type\": \"text\",
|
|
195
195
|
\"text\": { \"preview_url\": false, \"body\": \"$TEXT\" }
|
|
196
196
|
}" \
|
|
197
|
-
"$API_BASE/meta/whatsapp/v24.0/
|
|
197
|
+
"$API_BASE/meta/whatsapp/v24.0/$KAPSO_PHONE_NUMBER_ID/messages" | jq
|
|
198
198
|
```
|
|
199
199
|
|
|
200
200
|
Returns `{ "messages": [{ "id": "wamid..." }] }` on success. Log the wamid.
|
|
@@ -218,7 +218,7 @@ Prefer quote-replies when answering a specific question — it keeps long conver
|
|
|
218
218
|
|
|
219
219
|
## Sending media (image, document, audio, video)
|
|
220
220
|
|
|
221
|
-
Two-step pipeline through Kapso's Meta proxy: **upload, then send by id**. Sending by `id` is more reliable than `link`
|
|
221
|
+
Two-step pipeline through Kapso's Meta proxy: **upload, then send by id**. Sending by `id` is more reliable than `link` because it does not require the media to be hosted at a public URL.
|
|
222
222
|
|
|
223
223
|
### 1. Upload
|
|
224
224
|
|
|
@@ -227,7 +227,7 @@ curl -s -X POST -H "X-API-Key: $API_KEY" \
|
|
|
227
227
|
-F "messaging_product=whatsapp" \
|
|
228
228
|
-F "type=<mime>" \
|
|
229
229
|
-F "file=@/path/to/file.ext;type=<mime>" \
|
|
230
|
-
"$API_BASE/meta/whatsapp/v24.0/
|
|
230
|
+
"$API_BASE/meta/whatsapp/v24.0/$KAPSO_PHONE_NUMBER_ID/media"
|
|
231
231
|
# → {"id":"<media-id>"}
|
|
232
232
|
```
|
|
233
233
|
|
|
@@ -242,7 +242,7 @@ curl -s -X POST -H "X-API-Key: $API_KEY" \
|
|
|
242
242
|
|
|
243
243
|
Quote-reply works on media too — add `"context": { "message_id": "<wamid>" }` at the top level.
|
|
244
244
|
|
|
245
|
-
### Wide images: pad to ~square, send as image
|
|
245
|
+
### Wide images: pad to ~square, send as image
|
|
246
246
|
|
|
247
247
|
WhatsApp scales `type:image` to bubble width + recompresses, so a wide 1200×630 social card renders as a tiny shrunken strip. **The fix is NOT `type:document`** — a `.png` sent as a document shows a plain file card with NO inline preview (must tap+download). Bad UX both ways.
|
|
248
248
|
|
|
@@ -280,7 +280,7 @@ curl -s -X POST -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
|
|
|
280
280
|
"message_id": "<inbound_wamid>",
|
|
281
281
|
"typing_indicator": { "type": "text" }
|
|
282
282
|
}' \
|
|
283
|
-
"$API_BASE/meta/whatsapp/v24.0/
|
|
283
|
+
"$API_BASE/meta/whatsapp/v24.0/$KAPSO_PHONE_NUMBER_ID/messages"
|
|
284
284
|
# → {"success":true}
|
|
285
285
|
```
|
|
286
286
|
|
|
@@ -297,7 +297,7 @@ curl -s -X POST -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
|
|
|
297
297
|
"type": "reaction",
|
|
298
298
|
"reaction": { "message_id": "<wamid>", "emoji": "👀" }
|
|
299
299
|
}' \
|
|
300
|
-
"$API_BASE/meta/whatsapp/v24.0/
|
|
300
|
+
"$API_BASE/meta/whatsapp/v24.0/$KAPSO_PHONE_NUMBER_ID/messages"
|
|
301
301
|
```
|
|
302
302
|
|
|
303
303
|
A user can have only ONE reaction per message — sending a new emoji REPLACES the previous one (no explicit remove needed). Send `"emoji": ""` to clear a reaction entirely.
|
|
@@ -310,21 +310,21 @@ If `send-whatsapp-message` returns `sessionWindowExpired: true`, fall through to
|
|
|
310
310
|
curl -s -X POST -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
|
|
311
311
|
-d '{
|
|
312
312
|
"messaging_product": "whatsapp",
|
|
313
|
-
"to": "
|
|
313
|
+
"to": "15550100000",
|
|
314
314
|
"type": "template",
|
|
315
315
|
"template": {
|
|
316
316
|
"name": "<template_name>",
|
|
317
317
|
"language": { "code": "en_US" }
|
|
318
318
|
}
|
|
319
319
|
}' \
|
|
320
|
-
"$API_BASE/meta/whatsapp/v24.0/
|
|
320
|
+
"$API_BASE/meta/whatsapp/v24.0/$KAPSO_PHONE_NUMBER_ID/messages"
|
|
321
321
|
```
|
|
322
322
|
|
|
323
|
-
List approved templates first: `GET $API_BASE/platform/v1/whatsapp/templates?phone_number_id
|
|
323
|
+
List approved templates first: `GET $API_BASE/platform/v1/whatsapp/templates?phone_number_id=$KAPSO_PHONE_NUMBER_ID`.
|
|
324
324
|
|
|
325
325
|
## Webhook signature verification
|
|
326
326
|
|
|
327
|
-
Every Kapso webhook delivery includes `X-Webhook-Signature: <hex>` (HMAC-SHA256 of the raw body using `KAPSO_WEBHOOK_HMAC_SECRET`).
|
|
327
|
+
Every Kapso webhook delivery includes `X-Webhook-Signature: <hex>` (HMAC-SHA256 of the raw body using `KAPSO_WEBHOOK_HMAC_SECRET`). Native handlers and workflow webhook triggers should verify the signature automatically; if you configure a custom trigger, set its HMAC header to `X-Webhook-Signature` and resolve the secret from swarm config.
|
|
328
328
|
|
|
329
329
|
To verify manually:
|
|
330
330
|
|
|
@@ -337,17 +337,17 @@ echo -n "$RAW_BODY" | openssl dgst -sha256 -hmac "$HMAC_SECRET" -hex | awk '{pri
|
|
|
337
337
|
|
|
338
338
|
- Same language as the inbound message (Spanish/English/Catalan — match what they wrote).
|
|
339
339
|
- Brief. WhatsApp is not Slack — 1-3 short messages max.
|
|
340
|
-
- Identify yourself if it's a first interaction in the conversation: "Hi! This is the
|
|
340
|
+
- Identify yourself if it's a first interaction in the conversation: "Hi! This is the agent handling this WhatsApp inbox."
|
|
341
341
|
- Quote-reply (`context.message_id`) when answering a specific question.
|
|
342
342
|
- If you can't help (no skill for the request, out of scope) — say so and either escalate to Lead or ask the human to use Slack instead.
|
|
343
343
|
- Always log the outbound wamid in your task output so it's traceable.
|
|
344
344
|
|
|
345
345
|
## Where this fits in the swarm
|
|
346
346
|
|
|
347
|
-
Two inbound paths exist
|
|
347
|
+
Two common inbound paths exist:
|
|
348
348
|
|
|
349
|
-
- **Native handler** (`/api/integrations/kapso/webhook
|
|
350
|
-
- **Workflow
|
|
349
|
+
- **Native handler** (`/api/integrations/kapso/webhook`) — fires for any phone number registered via `register-kapso-number`. Verifies HMAC, dedupes by message id, reads the routing mapping, and either dispatches an inbound-message task or delegates to a workflow trigger.
|
|
350
|
+
- **Workflow trigger** (`<workflow-id>`) — useful when you want custom routing, batching, triage, or approval steps before a reply is sent. A typical pipeline marks the message read, debounces rapid-fire bursts, routes to an agent task, and optionally sends a final reaction or status update.
|
|
351
351
|
|
|
352
352
|
**Debounce / batching:** the demo workflow's `debounce` node waits ~8s after each message and only the LAST message of a burst proceeds to the agent task — so a user firing 3 quick messages produces ONE task, not three. The agent is told the `batchSize` and should read trailing history and answer the whole burst in one reply. When >1 messages are collapsed, the user sees a "🧵 Got your N messages" note.
|
|
353
353
|
|
|
@@ -357,13 +357,12 @@ HMAC verification is enforced (signed mode) on both paths.
|
|
|
357
357
|
|
|
358
358
|
## Common gotchas
|
|
359
359
|
|
|
360
|
-
- Phone numbers from Kapso are E.164 **without `+`** (e.g. `
|
|
360
|
+
- Phone numbers from Kapso are E.164 **without `+`** (e.g. `15550100000`). Add `+` when displaying to humans, drop it when calling the API.
|
|
361
361
|
- `message.text.body` is only present for `type:"text"`. For other types read `message.<type>` (see the table above) or `message.kapso.content` for a text representation.
|
|
362
362
|
- Outbound status events (`delivered`, `read`) are NOT a customer interaction — skip them. Filter by `message.kapso.direction == "inbound"`.
|
|
363
363
|
- Real inbound messages commonly arrive with `status: "delivered"` (delivered to us). Do NOT skip on status — only `direction` signals inbound vs outbound.
|
|
364
364
|
- Kapso sometimes sends test payloads with `"test": true` and `wamid.TEST_*` ids. Don't reply to test payloads — just complete the task with a note.
|
|
365
|
-
- The provisioned phone number id
|
|
365
|
+
- The provisioned phone number id is your sender number, not the recipient's. The recipient is in `message.from` / `conversation.phone_number`.
|
|
366
366
|
- The message-list endpoint is `/platform/v1/whatsapp/messages?conversation_id=X` — the conversation-scoped `/conversations/<id>/messages` path 404s.
|
|
367
367
|
- **Wide images shrink — pad them, don't send as document.** `type:image` scales to bubble width; a wide social card becomes a strip. Sending it as `type:document` removes the preview entirely. Fix: pad onto a ~1:1/4:5 canvas and send as `type:image`. See "Sending media".
|
|
368
368
|
- **MCP tools cover text-only.** `send-whatsapp-message` and `reply-whatsapp-message` are deliberately thin — templates / media / reactions / typing / mark-as-read are NOT in the tool surface. For those, use the REST recipes in this skill directly.
|
|
369
|
-
|
|
@@ -7,7 +7,7 @@ db-query: SELECT accessToken, expiresAt FROM oauth_tokens WHERE provider = 'line
|
|
|
7
7
|
```
|
|
8
8
|
If `expiresAt` is in the past, do NOT attempt the API calls — just report the needed update in your task output.
|
|
9
9
|
|
|
10
|
-
To authorize
|
|
10
|
+
To re-authorize, use your swarm API base URL with `/api/trackers/linear/authorize` (potentially needing to remove the app and re-auth). Only mention this if you can confirm the token is expired or not present.
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
@@ -22,7 +22,7 @@ The available MCP tracker tools (`tracker-link-task`, `tracker-link-epic`, `trac
|
|
|
22
22
|
|
|
23
23
|
## When to Transition (Timing)
|
|
24
24
|
|
|
25
|
-
- **
|
|
25
|
+
- **Direct-to-main work:** Transition the Linear ticket to **Done** the moment the worker reports ship (commits on `main`). Do NOT wait for review, test-run, or merge when there is no PR to wait for. Waiting causes blocker digests to flag RESOLVED-STALE tickets.
|
|
26
26
|
- **Standard PR workflow:** Transition to **In Review** on PR open, **Done** after merge. If the ticket is still "In Progress" 30 min after the PR merges, you're late.
|
|
27
27
|
- **Blocked:** If a ticket is stuck on a dependency, add a comment linking the blocker — don't leave it silent.
|
|
28
28
|
|
|
@@ -54,9 +54,9 @@ curl -s -X POST https://api.linear.app/graphql \
|
|
|
54
54
|
-d '{"query": "<GRAPHQL_QUERY>"}'
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
## Agent Interaction API — `action` vs `thought`
|
|
57
|
+
## Agent Interaction API — `action` vs `thought`
|
|
58
58
|
|
|
59
|
-
Linear's **Agent Interaction API** (different from issue mutations above) supports two activity payload kinds.
|
|
59
|
+
Linear's **Agent Interaction API** (different from issue mutations above) supports two activity payload kinds. Use this section whenever emitting `agentActivityCreate` mutations.
|
|
60
60
|
|
|
61
61
|
| Activity kind | When to use | `parameter` field |
|
|
62
62
|
|---|---|---|
|
|
@@ -76,7 +76,7 @@ Linear's **Agent Interaction API** (different from issue mutations above) suppor
|
|
|
76
76
|
| Branch create / merge / delete | `action` | branch name |
|
|
77
77
|
| PR open / review / merge | `action` | PR URL |
|
|
78
78
|
|
|
79
|
-
**Why this trips people:** "action" reads naturally as "every tool call IS an action…". But Linear uses `action` to mean *parameterized operation Linear can index/route on*, not *task-progress-narration*. Narration is `thought`.
|
|
79
|
+
**Why this trips people:** "action" reads naturally as "every tool call IS an action…". But Linear uses `action` to mean *parameterized operation Linear can index/route on*, not *task-progress-narration*. Narration is `thought`.
|
|
80
80
|
|
|
81
81
|
## Common Operations
|
|
82
82
|
|
|
@@ -120,8 +120,8 @@ mutation {
|
|
|
120
120
|
}
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
-
**Known state IDs
|
|
124
|
-
-
|
|
123
|
+
**Known state IDs:**
|
|
124
|
+
- Store your team's common state UUIDs in local notes or swarm config; do not hardcode another team's IDs into this template.
|
|
125
125
|
|
|
126
126
|
### 2. Add a Comment to an Issue
|
|
127
127
|
|
|
@@ -213,7 +213,7 @@ curl -s -X POST https://api.linear.app/graphql \
|
|
|
213
213
|
## Important Notes
|
|
214
214
|
|
|
215
215
|
- **Always update Linear when completing Linear-sourced tasks.** The user expects the ticket to reflect the swarm's work. Marking only the swarm task as complete is insufficient. Do not complete only the swarm task — failing to update Linear breaks the sync and wastes resources.
|
|
216
|
-
- **Transition timing:** see the "When to Transition" section above.
|
|
216
|
+
- **Transition timing:** see the "When to Transition" section above. Direct-to-main work transitions on ship, not on merge.
|
|
217
217
|
- **Token expiry:** Check `expiresAt` before making calls. If expired, notify the user — they need to re-authorize.
|
|
218
218
|
- **Rate limits:** Linear has rate limits. For bulk operations, add small delays between calls.
|
|
219
219
|
- **Issue identifiers vs UUIDs:** The human-readable identifier (e.g., "DES-12") works for queries but the `issueUpdate` mutation requires the actual UUID. Always fetch the UUID first via a query.
|
|
@@ -227,4 +227,3 @@ Common errors:
|
|
|
227
227
|
- `Entity not found` → Wrong issue ID/identifier
|
|
228
228
|
- `"parameter must not be empty"` (or similar on Agent Interaction API) → You sent an `action` activity without a `parameter` — convert to `thought` or fill in a real noun. See "Agent Interaction API — action vs thought" above.
|
|
229
229
|
- Rate limited → Back off and retry after delay
|
|
230
|
-
|
|
@@ -1,105 +1,64 @@
|
|
|
1
1
|
# Profile Corruption Escalation
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use this skill when an agent's persisted profile (`SOUL.md`, `IDENTITY.md`, or equivalent DB fields) appears to be overwritten with placeholder, fixture, truncated, or otherwise invalid content.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- `src/tools/update-profile.ts:231` — gated the file write on `requestInfo.agentId === process.env.AGENT_ID`
|
|
7
|
-
- `src/hooks/hook.ts:359` — raised `IDENTITY_FILE_MIN_LENGTH` from 100 → 500 (defense in depth)
|
|
5
|
+
## When to Use
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
- An agent profile contains sentinel text such as "Test Worker", "Updated by Myself", repeated padding, or another canned payload.
|
|
8
|
+
- The profile is much shorter than the expected baseline.
|
|
9
|
+
- A recently restored profile was overwritten again.
|
|
10
|
+
- A hook, seeder, migration, test fixture, or profile-update tool may be writing to the wrong agent.
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
## Triage Loop
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
1. Capture the corrupted payload, agent ID, and `lastUpdatedAt` timestamp before changing anything.
|
|
15
|
+
2. Search memory and recent task history for the same sentinel string or same agent.
|
|
16
|
+
3. Grep the source repo for exact sentinel strings. Stable placeholder text usually points to a fixture, seed path, or test helper faster than manual restoration does.
|
|
17
|
+
4. Inspect code paths that can write profile files or profile DB fields: profile-update tools, startup hooks, stop/session hooks, seeders, migrations, and tests that set agent identity env vars.
|
|
18
|
+
5. If you find the writer, fix or escalate the code path before restoring the profile.
|
|
19
|
+
6. Restore only after evidence is captured, and only if restoration will not destroy useful debugging evidence.
|
|
14
20
|
|
|
15
|
-
|
|
21
|
+
## Escalation Threshold
|
|
16
22
|
|
|
17
|
-
|
|
23
|
+
Escalate instead of repeatedly restoring when any of these are true:
|
|
18
24
|
|
|
19
|
-
|
|
25
|
+
- The same profile is corrupted more than once in a short period.
|
|
26
|
+
- The sentinel text appears to come from code that knows schema limits or exact file paths.
|
|
27
|
+
- You cannot identify the writer quickly.
|
|
28
|
+
- A previous fix should already have eliminated this corruption family.
|
|
20
29
|
|
|
21
|
-
|
|
30
|
+
## Escalation Package
|
|
22
31
|
|
|
23
|
-
|
|
24
|
-
grep -rn '"Test Worker"' /workspace/repos/agent-swarm/src
|
|
25
|
-
grep -rn '"Updated by Myself"' /workspace/repos/agent-swarm/src
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
If you find the writer in <5 minutes, fix it (or open a PR) instead of restoring. That's the 1000× higher-value outcome.
|
|
32
|
+
Post a concise report to the operator channel or issue tracker with:
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
- Agent name and ID.
|
|
35
|
+
- Fresh write timestamp.
|
|
36
|
+
- Exact sentinel strings to search for.
|
|
37
|
+
- Whether you already grepped the repo and what matched.
|
|
38
|
+
- Suspected writer classes: hook, seeder, migration, test fixture, startup script, or profile-update tool.
|
|
39
|
+
- Whether you restored the profile or left it corrupted as evidence.
|
|
40
|
+
- Link to the task, log, or memory entry containing the captured payload.
|
|
31
41
|
|
|
32
|
-
##
|
|
42
|
+
## Report Template
|
|
33
43
|
|
|
34
|
-
|
|
44
|
+
```text
|
|
45
|
+
Profile corruption detected
|
|
35
46
|
|
|
36
|
-
|
|
47
|
+
Agent: <agent-name> (<agent-id>)
|
|
48
|
+
Fresh write at: <timestamp>
|
|
49
|
+
Sentinel strings:
|
|
50
|
+
- "<literal-1>"
|
|
51
|
+
- "<literal-2>"
|
|
37
52
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
- **1st–12th occurrence**: Restore the profile (surgical update-profile with the agent's best-known-good SOUL/IDENTITY from your memory). Write a new `{agent}-{N}th-profile-corruption-{YYYY-MM-DD}` memory recording the state + escalation status.
|
|
43
|
-
- **13th+ occurrence**: **STOP RESTORING.** Rebuild fatigue has proven the restore doesn't stick — the bug lives in code you don't control. Escalate instead.
|
|
44
|
-
|
|
45
|
-
If you can't determine N from memory, treat anything ≥ 2 prior corruptions in < 7 days as "escalate now."
|
|
46
|
-
|
|
47
|
-
**Post PR #374 (Apr 2026):** any new sentinel-payload corruption of any agent should be treated as recurrence 1 of a NEW bug — investigate fresh code paths, do not assume it's the same `update-profile.ts:231` issue.
|
|
48
|
-
|
|
49
|
-
## Escalation package (what to post)
|
|
50
|
-
|
|
51
|
-
Use `slack-post` to the ops channel. Include ALL of:
|
|
52
|
-
|
|
53
|
-
1. **Sentinel payload strings as grep targets** — exact literals to search for in the source repo. Example: `"Test Worker"`, `"Updated by Myself"`, `xxxxx...` patterns. **State whether you already grepped (and what the result was).**
|
|
54
|
-
2. **Proof of fresh write** — `lastUpdatedAt` timestamp from `get-swarm`, newer than the previous restore. Without this it could be stale state, not new corruption.
|
|
55
|
-
3. **Corruption tally** — "N occurrences in ~M weeks, half-life now <24h."
|
|
56
|
-
4. **Investigation leads** — where to look:
|
|
57
|
-
- Grep the `desplega-ai/agent-swarm` repo for the sentinel strings.
|
|
58
|
-
- Check for scheduled tasks named `validate profile`, `test update-profile`, etc.
|
|
59
|
-
- Check seed/migration scripts that run on container restart.
|
|
60
|
-
- Audit `PostToolUse` hook content-validation (flagged in prior memories as "not shipped").
|
|
61
|
-
- **Post PR #374:** Audit any other code path that writes `/workspace/SOUL.md` or `/workspace/IDENTITY.md` — the original bug was at `src/tools/update-profile.ts:231`; look for siblings.
|
|
62
|
-
5. **What you did NOT do** — explicit "I did not perform the Nth restore. Profile is left corrupted in DB as evidence."
|
|
63
|
-
|
|
64
|
-
## Template
|
|
65
|
-
|
|
66
|
-
```
|
|
67
|
-
:rotating_light: Profile Corruption — {N}th recurrence — escalation trigger fired
|
|
68
|
-
|
|
69
|
-
Agent: {name} ({id})
|
|
70
|
-
Fresh write at: {lastUpdatedAt}
|
|
71
|
-
Tally: {N} corruptions in ~{M} weeks, half-life {<hours>}h.
|
|
72
|
-
|
|
73
|
-
Sentinel grep targets (expect to find in source):
|
|
74
|
-
- "Test Worker"
|
|
75
|
-
- "Updated by Myself"
|
|
76
|
-
- Long "xxxxx…" padding literals
|
|
77
|
-
|
|
78
|
-
I already grepped: {summary of grep result, e.g. "no hits in src/ post-#374 — this is a NEW code path"}.
|
|
79
|
-
|
|
80
|
-
Investigation leads:
|
|
81
|
-
1. Grep agent-swarm for the sentinels above.
|
|
82
|
-
2. Check for scheduled "validate profile" / "test update-profile" tasks.
|
|
83
|
-
3. Audit seed/migration scripts on container restart.
|
|
84
|
-
4. Audit any code path that writes /workspace/SOUL.md or /workspace/IDENTITY.md (original bug was update-profile.ts:231).
|
|
85
|
-
|
|
86
|
-
I did NOT perform the {N}th restore. Profile is corrupted in DB as evidence. Waiting for engineering fix before next restore.
|
|
87
|
-
|
|
88
|
-
See memory: {agent}-{N}th-profile-corruption-{date}
|
|
53
|
+
Repo grep result: <matches or no matches>
|
|
54
|
+
Suspected writer: <hook/seeder/migration/test/tool/unknown>
|
|
55
|
+
Action taken: <restored profile | left as evidence | opened fix PR>
|
|
56
|
+
Evidence: <task/log/memory link>
|
|
89
57
|
```
|
|
90
58
|
|
|
91
59
|
## Gotchas
|
|
92
60
|
|
|
93
|
-
-
|
|
94
|
-
-
|
|
95
|
-
-
|
|
96
|
-
-
|
|
97
|
-
- **Sentinel-grep first, escalate second.** If a 2nd recurrence happens and you haven't grepped the sentinel literal yet, you're escalating prematurely. Grep is cheap (<5 min) and usually wins.
|
|
98
|
-
|
|
99
|
-
## Related
|
|
100
|
-
|
|
101
|
-
- Memory family: `{agent}-Nth-profile-corruption-YYYY-MM-DD`
|
|
102
|
-
- Memory: `reviewer-corruption-hunt-pattern` — the heuristic that solved the 13-recurrence mystery
|
|
103
|
-
- Memory: `picateclas-14th-restore-post-pr374` — restore payload + the fix details
|
|
104
|
-
- Lead rule #9 in CLAUDE.md: "Sentinel-grep before escalating recurring bugs"
|
|
105
|
-
|
|
61
|
+
- Do not rely on repeated manual restores as the fix. They can hide the writer and destroy evidence.
|
|
62
|
+
- Search exact sentinel strings before broad refactors; fixture text is usually distinctive.
|
|
63
|
+
- Do not overwrite unrelated profile files during restoration.
|
|
64
|
+
- Treat a recurrence after a code fix as a new investigation unless you can prove the same writer is still active.
|