@adaptic/maestro 1.1.6 → 1.1.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.
@@ -0,0 +1,438 @@
1
+ # Outbound Governance Setup Guide
2
+
3
+ How the agent's outbound communication safety system works: pre-send audit hooks, factual validation, outbound deduplication (atomic locks + LLM semantic checks), information barriers, disclosure assessment, and audit logging. This is the governance layer that ensures every outbound message is safe, accurate, and non-duplicated.
4
+
5
+ **Prerequisites**: At least one communication channel configured ([Email](email-setup.md), [Slack](slack-setup.md), [WhatsApp](whatsapp-setup.md), or [SMS](voice-sms-setup.md)).
6
+
7
+ ---
8
+
9
+ ## Architecture Overview
10
+
11
+ Every outbound message passes through multiple safety layers before being sent:
12
+
13
+ ```
14
+ ┌─────────────────────────────────────────────────────────────────┐
15
+ │ Agent drafts a message │
16
+ │ │ │
17
+ │ ▼ │
18
+ │ ┌─── Layer 0: Factual Validation ──────────────────────────┐ │
19
+ │ │ validate-outbound.py / validate_outbound.py │ │
20
+ │ │ Checks: relationship accuracy, AI disclosure, scheduling │ │
21
+ │ │ Result: PASS / WARN / BLOCK │ │
22
+ │ └──────────────────────────────────────────────────────────┘ │
23
+ │ │ │
24
+ │ ▼ │
25
+ │ ┌─── Layer 1: LLM Semantic Dedup (email only) ─────────────┐ │
26
+ │ │ llm_email_dedup.py │ │
27
+ │ │ Asks Claude Haiku: "Was this topic already addressed?" │ │
28
+ │ │ Result: PASS / DEDUP_SKIP │ │
29
+ │ └──────────────────────────────────────────────────────────┘ │
30
+ │ │ │
31
+ │ ▼ │
32
+ │ ┌─── Layer 2: Information Barrier Check ────────────────────┐ │
33
+ │ │ disclosure_assessment.py + disclosure_boundaries.py │ │
34
+ │ │ Checks: recipient access level, content provenance │ │
35
+ │ │ Result: PASS / WARN / STRIP / BLOCK │ │
36
+ │ └──────────────────────────────────────────────────────────┘ │
37
+ │ │ │
38
+ │ ▼ │
39
+ │ ┌─── Layer 3: Content-Hash Dedup ──────────────────────────┐ │
40
+ │ │ outbound-dedup.sh / outbound_dedup.py │ │
41
+ │ │ Atomic mkdir lock: sha256(to + subject + body[:100]) │ │
42
+ │ │ Result: CLAIMED (send) / DEDUP_SKIP (another session) │ │
43
+ │ └──────────────────────────────────────────────────────────┘ │
44
+ │ │ │
45
+ │ ▼ │
46
+ │ ┌─── Layer 4: Pre-Send Audit Hook ─────────────────────────┐ │
47
+ │ │ hooks/pre-send-audit.sh │ │
48
+ │ │ Rate limits: 3,000/hour, 20,000/day │ │
49
+ │ │ Result: ALLOWED / BLOCKED │ │
50
+ │ └──────────────────────────────────────────────────────────┘ │
51
+ │ │ │
52
+ │ ▼ │
53
+ │ SEND ──▶ Post-action log (hooks/post-action-log.sh) │
54
+ │ Session end log (hooks/session-end-log.sh) │
55
+ └─────────────────────────────────────────────────────────────────┘
56
+ ```
57
+
58
+ **Design principle**: Fail-open on infrastructure errors. If a dedup lock fails or a validation script crashes, the message sends anyway. It's better to send a duplicate than to silently drop a message.
59
+
60
+ ---
61
+
62
+ ## 1. Claude Code Hooks (`scripts/hooks/`)
63
+
64
+ Hooks are shell scripts registered in `.claude/settings.json` that execute before or after tool calls.
65
+
66
+ ### 1.1 Pre-Send Audit (`pre-send-audit.sh`)
67
+
68
+ **Trigger**: `PreToolUse` hook, fires before Slack/Gmail send tools.
69
+
70
+ **What it does**:
71
+ 1. Reads the tool input from stdin
72
+ 2. Checks daily send counter against rate limits (3,000/hour, 20,000/day)
73
+ 3. Logs the send to `logs/audit/YYYY-MM-DD-sends.jsonl`
74
+ 4. Exit 0 = allowed (message shown to Claude)
75
+ 5. Exit 2 = blocked (rejection message shown to Claude)
76
+
77
+ **Rate limit state**: `logs/audit/send-counter.yaml` — tracks hourly and daily totals, resets at midnight.
78
+
79
+ ### 1.2 Post-Action Log (`post-action-log.sh`)
80
+
81
+ **Trigger**: `PostToolUse` hook, fires after every tool execution.
82
+
83
+ **What it does**:
84
+ - Logs tool name and completion timestamp to `logs/audit/YYYY-MM-DD-actions.jsonl`
85
+ - Consumes stdin (tool result) without blocking
86
+
87
+ ### 1.3 MCP Slack Send Block (`block-mcp-slack-send.sh`)
88
+
89
+ **Trigger**: `PreToolUse` hook, matches MCP Slack send tools.
90
+
91
+ **What it does**:
92
+ - Unconditionally blocks MCP Slack sends (exit 2)
93
+ - Enforces CEO directive: all Slack sends must use `scripts/slack-send.sh` with User OAuth Token
94
+ - MCP Slack adds "Sent using @Claude" label, breaking agent identity
95
+
96
+ ### 1.4 Session End Log (`session-end-log.sh`)
97
+
98
+ **Trigger**: `Stop` hook, fires when a Claude session ends.
99
+
100
+ **What it does**:
101
+ 1. Logs session completion to `logs/sessions/YYYY-MM-DD-sessions.jsonl`
102
+ 2. Spawns the post-interaction indexer in the background (`post-interaction-indexer.py --scan-today`)
103
+ 3. Fire-and-forget: indexer runs asynchronously, doesn't block session teardown
104
+
105
+ ### 1.5 Hook Registration
106
+
107
+ Hooks are registered in `.claude/settings.json`:
108
+
109
+ ```json
110
+ {
111
+ "hooks": {
112
+ "PreToolUse": [
113
+ {
114
+ "matcher": "mcp__claude_ai_Slack__slack_send_message",
115
+ "command": "bash scripts/hooks/block-mcp-slack-send.sh"
116
+ },
117
+ {
118
+ "matcher": "Bash",
119
+ "command": "bash scripts/hooks/pre-send-audit.sh slack"
120
+ }
121
+ ],
122
+ "PostToolUse": [
123
+ {
124
+ "matcher": "*",
125
+ "command": "bash scripts/hooks/post-action-log.sh"
126
+ }
127
+ ],
128
+ "Stop": [
129
+ {
130
+ "command": "bash scripts/hooks/session-end-log.sh"
131
+ }
132
+ ]
133
+ }
134
+ }
135
+ ```
136
+
137
+ ---
138
+
139
+ ## 2. Factual Validation (`validate-outbound.py`)
140
+
141
+ Checks outbound messages for factual errors before sending.
142
+
143
+ ### 2.1 What It Checks
144
+
145
+ | Check | Severity | Example |
146
+ |---|---|---|
147
+ | Relationship claims | Block | Claiming a meeting happened that didn't |
148
+ | AI disclosure | Block | Revealing the agent is AI in external comms |
149
+ | In-person scheduling | Block | Scheduling the agent for a physical meeting |
150
+ | Title accuracy | Warn | Using wrong title for a contact |
151
+ | Date accuracy | Warn | Referencing incorrect dates |
152
+
153
+ ### 2.2 Usage
154
+
155
+ Called automatically by send scripts, or manually:
156
+
157
+ ```bash
158
+ echo "Let's meet at the office tomorrow" | python3 scripts/validate-outbound.py --type slack --recipient "C1234567890"
159
+ ```
160
+
161
+ **Exit codes**:
162
+ - 0: passed (safe to send)
163
+ - 1: issues found (check JSON output for blockers vs warnings)
164
+
165
+ ### 2.3 Integration
166
+
167
+ Every send script calls `validate-outbound.py` in its pre-send pipeline. If the validator finds `block`-severity issues, the send is aborted with an error message explaining what to fix.
168
+
169
+ ---
170
+
171
+ ## 3. Outbound Deduplication
172
+
173
+ ### 3.1 Content-Hash Dedup (`outbound-dedup.sh`)
174
+
175
+ Prevents concurrent Claude sessions from sending identical messages.
176
+
177
+ **Mechanism**: Atomic `mkdir`-based locking (POSIX-guaranteed atomic).
178
+
179
+ **Key generation**:
180
+
181
+ | Channel | Hash Input |
182
+ |---|---|
183
+ | Email | `sha256(to + subject + body[:100])` |
184
+ | SMS | `sha256(to + body[:100])` |
185
+ | WhatsApp | `sha256(to + body[:100])` |
186
+ | Slack | Channel + message_ts (passthrough) |
187
+
188
+ **Commands**:
189
+
190
+ ```bash
191
+ # Generate a dedup key
192
+ ./scripts/outbound-dedup.sh generate-key email "to@example.com" "Subject" "Body text"
193
+
194
+ # Acquire a lock (returns CLAIMED or DEDUP_SKIP)
195
+ ./scripts/outbound-dedup.sh acquire email <key> <session_id>
196
+
197
+ # Confirm send succeeded (audit trail)
198
+ ./scripts/outbound-dedup.sh confirm email <key> "preview text"
199
+
200
+ # Check lock status
201
+ ./scripts/outbound-dedup.sh check email <key>
202
+
203
+ # Clean up expired locks
204
+ ./scripts/outbound-dedup.sh cleanup [max_age_minutes]
205
+ ```
206
+
207
+ **Lock TTL**: 720 minutes (12 hours) — long because email dedup needs to span multiple sessions.
208
+
209
+ **Lock directory**: `state/locks/outbound/{channel}/{dedup-key}/`
210
+
211
+ ### 3.2 Slack Response Dedup (`slack-responded.sh`)
212
+
213
+ Specialised dedup for Slack responses. Uses the same atomic mkdir pattern but keyed by `channel + message_ts`:
214
+
215
+ ```bash
216
+ # Acquire response lock
217
+ ./scripts/slack-responded.sh acquire <channel> <message_ts> <session_id>
218
+
219
+ # Confirm response sent
220
+ ./scripts/slack-responded.sh confirm <channel> <message_ts> "preview text"
221
+ ```
222
+
223
+ ### 3.3 LLM Semantic Dedup (`llm_email_dedup.py`)
224
+
225
+ For email only. Uses Claude Haiku to check if a topic has already been addressed:
226
+
227
+ 1. Fetches recent sent emails to the same recipient via IMAP
228
+ 2. Sends both the draft and recent emails to Haiku with the question: "Has this specific topic already been addressed in a recent email?"
229
+ 3. Returns `DEDUP_SKIP` if the LLM says yes
230
+
231
+ This catches cases where the content-hash doesn't match (different wording, same topic).
232
+
233
+ ### 3.4 Cleanup
234
+
235
+ Stale locks are cleaned by:
236
+
237
+ ```bash
238
+ # Clean locks older than 5 minutes (default)
239
+ ./scripts/outbound-dedup.sh cleanup
240
+
241
+ # Clean locks older than 60 minutes
242
+ ./scripts/outbound-dedup.sh cleanup 60
243
+
244
+ # Bulk cleanup script
245
+ ./scripts/outbound-dedup-cleanup.sh
246
+ ```
247
+
248
+ ---
249
+
250
+ ## 4. Information Barriers & Disclosure Assessment
251
+
252
+ ### 4.1 Disclosure Assessment (`disclosure_assessment.py`)
253
+
254
+ The hard gate that prevents information leakage across recipient boundaries.
255
+
256
+ **What it does**:
257
+ 1. Loads the recipient's user profile from `memory/profiles/users/`
258
+ 2. Loads the channel profile from `memory/profiles/channels/` (if Slack)
259
+ 3. Extracts keywords and entities from the draft message
260
+ 4. Checks message content against the recipient's information boundaries
261
+ 5. Checks content provenance (where the information originated)
262
+ 6. Assigns severity: low (warn), medium (strip), high (block), critical (block + escalate)
263
+
264
+ **Exit codes**:
265
+ - 0: passed or warn (safe to send)
266
+ - 1: stripped (send but redact flagged content)
267
+ - 2: blocked (do not send)
268
+
269
+ **Usage**:
270
+
271
+ ```bash
272
+ # Check a message before sending
273
+ echo "The JVA negotiation is progressing well" | \
274
+ python3 scripts/disclosure_assessment.py --recipient shayan-kargarian
275
+
276
+ # With channel context
277
+ python3 scripts/disclosure_assessment.py \
278
+ --recipient shayan-kargarian --channel rollup-strategy \
279
+ --message "The acquisition target has confirmed AED 40K"
280
+ ```
281
+
282
+ ### 4.2 Disclosure Boundaries (`disclosure_boundaries.py`)
283
+
284
+ Defines the information boundary rules:
285
+
286
+ - What information each recipient can receive
287
+ - What topics are restricted per relationship type
288
+ - What sources/matters are confidential to specific deal teams
289
+
290
+ Rules are derived from user profiles and the information barriers policy.
291
+
292
+ ### 4.3 Information Barriers Policy
293
+
294
+ Defined in `policies/information-barriers.yaml` (if present). Specifies:
295
+
296
+ - **Chinese walls** between deal teams
297
+ - **Restricted topics** per recipient classification
298
+ - **Provenance tracking** — which information came from which source/matter
299
+ - **Escalation rules** — what happens when a barrier is breached
300
+
301
+ ### 4.4 Testing Information Barriers
302
+
303
+ ```bash
304
+ python3 scripts/test-information-barriers.py
305
+ ```
306
+
307
+ Runs test scenarios to verify barriers are correctly enforced.
308
+
309
+ ---
310
+
311
+ ## 5. Audit Trail
312
+
313
+ ### 5.1 Log Files
314
+
315
+ | Log | Path | Contents |
316
+ |---|---|---|
317
+ | Actions | `logs/audit/YYYY-MM-DD-actions.jsonl` | All tool executions, sends, dedup events |
318
+ | Sends | `logs/audit/YYYY-MM-DD-sends.jsonl` | Pre-send audit decisions (allowed/blocked) |
319
+ | Validation | `logs/audit/YYYY-MM-DD-validation.jsonl` | Factual validation results |
320
+ | Pre-draft | `logs/audit/YYYY-MM-DD-pre-draft-lookups.jsonl` | Context lookups before drafting |
321
+ | Sessions | `logs/sessions/YYYY-MM-DD-sessions.jsonl` | Session start/end events |
322
+
323
+ ### 5.2 Rate Limit State
324
+
325
+ `logs/audit/send-counter.yaml`:
326
+
327
+ ```yaml
328
+ date: "2026-04-09"
329
+ hourly: {}
330
+ totals:
331
+ slack: 42
332
+ gmail: 8
333
+ whatsapp: 3
334
+ total: 53
335
+ limits:
336
+ per_hour: 3000
337
+ per_day: 20000
338
+ ```
339
+
340
+ Resets daily at midnight.
341
+
342
+ ---
343
+
344
+ ## 6. Self-Optimization Metrics
345
+
346
+ `scripts/self-optimization/compute-metrics.py` calculates governance metrics:
347
+
348
+ - Dedup hit rate (what percentage of sends were caught as duplicates)
349
+ - Validation block rate (what percentage of messages had issues)
350
+ - Average response latency per channel
351
+ - Session concurrency utilisation
352
+
353
+ ---
354
+
355
+ ## 7. Testing
356
+
357
+ | # | Test | How to Verify |
358
+ |---|---|---|
359
+ | 1 | Pre-send hook | Send a Slack message — check `logs/audit/YYYY-MM-DD-sends.jsonl` |
360
+ | 2 | Rate limit | Manually set counter to 19999 in `send-counter.yaml` — next send should block |
361
+ | 3 | MCP block | Attempt MCP Slack send — should be blocked |
362
+ | 4 | Factual validation | Send message mentioning in-person meeting — should block |
363
+ | 5 | Content-hash dedup | Send identical email twice — second should `DEDUP_SKIP` |
364
+ | 6 | LLM dedup | Send two different emails about same topic — second should warn |
365
+ | 7 | Slack response dedup | Reply to same message from two sessions — one should skip |
366
+ | 8 | Disclosure assessment | Test with restricted content + external recipient |
367
+ | 9 | Audit logging | Verify all sends appear in `logs/audit/` |
368
+ | 10 | Lock cleanup | Run `./scripts/outbound-dedup.sh cleanup` — stale locks removed |
369
+
370
+ ---
371
+
372
+ ## 8. Troubleshooting
373
+
374
+ ### All sends blocked: "Daily send limit reached"
375
+
376
+ 1. Check `logs/audit/send-counter.yaml` — if total is at 20,000, wait for midnight reset
377
+ 2. For emergencies, manually reset the counter: delete the file (it recreates on next send)
378
+ 3. Consider if a runaway session is sending in a loop
379
+
380
+ ### Dedup false positives (legitimate messages being skipped)
381
+
382
+ 1. Check lock directory: `ls state/locks/outbound/email/`
383
+ 2. Clean stale locks: `./scripts/outbound-dedup.sh cleanup 5`
384
+ 3. For email: reduce lock TTL if 12 hours is too long for your use case
385
+ 4. Use `--force` flag on send scripts to bypass dedup for exceptional cases
386
+
387
+ ### Disclosure assessment blocking legitimate messages
388
+
389
+ 1. Check the recipient's user profile in `memory/profiles/users/`
390
+ 2. Review what information boundaries are set
391
+ 3. Run the assessment manually to see the exact output
392
+ 4. Update the user profile if boundaries are too restrictive
393
+
394
+ ### Hooks not firing
395
+
396
+ 1. Check `.claude/settings.json` has the hooks registered
397
+ 2. Verify hook scripts are executable: `chmod +x scripts/hooks/*.sh`
398
+ 3. Check hook script paths are correct (relative to repo root)
399
+ 4. Test hook manually: `echo '{}' | bash scripts/hooks/pre-send-audit.sh slack`
400
+
401
+ ---
402
+
403
+ ## Key Files
404
+
405
+ | File | Purpose |
406
+ |---|---|
407
+ | **Hooks** | |
408
+ | `scripts/hooks/pre-send-audit.sh` | Rate limiting and send logging |
409
+ | `scripts/hooks/post-action-log.sh` | Tool completion logging |
410
+ | `scripts/hooks/block-mcp-slack-send.sh` | MCP Slack send block |
411
+ | `scripts/hooks/session-end-log.sh` | Session end logging + indexer trigger |
412
+ | **Validation** | |
413
+ | `scripts/validate-outbound.py` | Factual validation (relationship, dates, disclosure) |
414
+ | `scripts/validate_outbound.py` | Alternate import path for Python scripts |
415
+ | **Dedup** | |
416
+ | `scripts/outbound-dedup.sh` | Atomic content-hash deduplication |
417
+ | `scripts/outbound_dedup.py` | Python dedup module (imported by send scripts) |
418
+ | `scripts/outbound-dedup-cleanup.sh` | Stale lock cleanup |
419
+ | `scripts/slack-responded.sh` | Slack response deduplication |
420
+ | `scripts/llm_email_dedup.py` | LLM semantic email deduplication |
421
+ | **Information Barriers** | |
422
+ | `scripts/disclosure_assessment.py` | Post-draft disclosure analysis |
423
+ | `scripts/disclosure_boundaries.py` | Information boundary rule definitions |
424
+ | `scripts/test-information-barriers.py` | Barrier test suite |
425
+ | **Policy** | |
426
+ | `policies/action-classification.yaml` | Action risk levels and approval rules |
427
+ | `policies/information-barriers.yaml` | Information barrier definitions |
428
+ | `.claude/settings.json` | Hook registration |
429
+
430
+ ---
431
+
432
+ ## Related Documents
433
+
434
+ - [Email Setup](email-setup.md) — Email dedup pipeline integration
435
+ - [Slack Setup](slack-setup.md) — Slack dedup and MCP block
436
+ - [Communications Policy](../governance/communications-policy.md) — Voice modes and approval rules
437
+ - [Action Approval Model](../governance/action-approval-model.md) — Approval workflows
438
+ - [Information Barrier Design](../superpowers/specs/2026-04-05-information-barrier-design.md) — Technical specification