lilith-platform.live/codebase/@features/quinn-ai/docs/runbook.md
autocommit 50579082da docs(quinn-ai): 📝 Update architecture, feature gates, and operational runbook documentation for Quinn AI
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-05-16 00:39:33 -07:00

6 KiB

Runbook

Default state

Engine ships in draft-only mode. Nothing auto-sends until the re-arm sequence below is run. The kill-switch (gate ① in gates.md) enforces this even if a downstream caller forgets to consult engine mode independently.

./run quinn-outreach mode get
# mode: draft-only
# updatedBy: quinn
# reason: 2026-05-09 19:35 directive: stop all auto, only draft

Re-arming sequence (when Quinn is ready to auto-fire)

Always go through this sequence — never skip the dry-run step.

# 1. Verify gates work as expected against recent inbound
./run quinn-outreach dry-run --days=7 --output=/tmp/dry-run-report.txt
# Read the report; confirm no obvious false-positives / false-negatives.

# 2. Optionally enable booking + scheduled-send first (no auto-fire risk;
#    these only emit drafts and dispatch already-Quinn-approved sends)
systemctl --user enable --now quinn-outreach-booking.service
systemctl --user enable --now quinn-outreach-scheduled-send.service

# 3. Enable watcher + parser (Quinn-approval flow only — still no auto-fire)
systemctl --user enable --now quinn-outreach-watcher.service
systemctl --user enable --now quinn-outreach-parser.service

# 4. Flip mode to auto-fire WITH ATTRIBUTION (so audit log shows who/why)
./run quinn-outreach mode set auto-fire --by=quinn --reason="re-arm 2026-05-XX"

# 5. Enable autorespond engine (now actually dispatches subject to all 8 gates)
systemctl --user enable --now quinn-outreach-autorespond.service

Kill-switch (any time)

./run quinn-outreach mode set draft-only --by=quinn --reason="<incident>"

Effective on the next gate evaluation (no daemon restart needed). Already-scheduled sends in the queue will still fire — to halt those:

./run quinn-outreach scheduled-send list --status=pending
./run quinn-outreach scheduled-send cancel <id>

For total halt:

systemctl --user stop quinn-outreach-autorespond.service
systemctl --user stop quinn-outreach-scheduled-send.service

Adding a wrong-identity contact (mid-conversation)

Discovered Kat is a friend, not a prospect? Block immediately:

./run quinn-outreach block +15139186564 "Kat - trans peer / Cincinnati piercer"

Hot-reloaded; takes effect on next inbound from that handle. No restart.

Verifying the system

./run quinn-outreach status                 # heartbeat / event-log / awaiting-quinn
./run quinn-outreach mode get               # current mode + when/who
./run quinn-outreach list-blocked           # current block-list
./run quinn-outreach calendar show          # tour calendar slots
./run quinn-outreach test                   # unit tests
bun run typecheck                           # type integrity
bun run build                               # rebuild dist/

Incident response: bot-exposed

Symptom: prospect explicitly names AI tells (em-dash, heart-stack, multi-fire burst).

# 1. Kill-switch
./run quinn-outreach mode set draft-only --by=quinn --reason="bot-exposed in <handle> thread"

# 2. Surface honest disclosure template (don't deny)
#    Template: "tbh that earlier reply was an ai assistant babe 💗 im here now if
#    you wanna keep chatting - ill make sure she learns"
#    Source: src/bot-detection.ts → HONEST_DISCLOSURE_TEMPLATE
#    Per Quinn 2026-05-09: confirmed conversion to $50 FaceTime after disclosure

# 3. Audit which gate failed to catch it (if any)
psql -d quinn_icloud -c "
SELECT created_at, payload
FROM outreach.event_log
WHERE event_type = 'auto_respond_pre_fire'
  AND payload->>'handle' = '<handle>'
ORDER BY created_at DESC LIMIT 10"

# 4. If a gate is missing a signal, add it to bot-detection.ts and ship

Incident response: stuck in draft-only forever

If mode get shows draft-only but Quinn wants to re-arm, check that the file is writable:

ls -la ~/.local/share/quinn-outreach/engine-config.json
./run quinn-outreach mode set auto-fire --by=quinn --reason="re-arm"
./run quinn-outreach mode get   # confirm

If the file is corrupt:

rm ~/.local/share/quinn-outreach/engine-config.json
./run quinn-outreach mode set draft-only --by=quinn --reason="reset after corruption"

(Default on missing file is draft-only, so removing it fails safe.)

Incident response: §37 returning false-negative

If auto_respond_pre_fire events show gate ④ never failing despite Quinn actively typing:

# Check macsync.messages query is reachable
psql -d quinn_icloud -c "SELECT count(*) FROM macsync.messages WHERE is_from_me = true AND sent_at > now() - interval '1 hour'"

# Verify rich-content visibility (mac-sync v2): are URL-bubble / reaction
# messages reaching the classifier, or are they stuck as undecodable?
psql -d quinn_icloud <<'SQL'
SELECT
  COUNT(*) FILTER (WHERE body != '')                              AS plain_body,
  COUNT(*) FILTER (WHERE body = '' AND body_decoded IS NOT NULL)  AS rich_only,
  COUNT(*) FILTER (WHERE body = '' AND body_decoded IS NULL)      AS undecodable
FROM macsync.messages
WHERE sent_at > now() - interval '24 hours';
SQL
# If rich_only is large and undecodable is small, the attributedBody heuristic
# is doing its job. If undecodable is large, the typedstream decoder backfill
# (see mac-sync migration 2026-05-13_message_attributed_body) has not run.

# If permission denied: engine cred lacks icloud SELECT.
#   Fix: GRANT SELECT ON macsync.messages, macsync.conversations TO quinn_icloud;

# If returns 0 unexpectedly: MacSyncApp not mirroring. Check mac-sync-server logs.

The gate gracefully falls back to [] on query error — see quinn-outbound-query.ts.

State files (operator-facing)

~/.local/share/quinn-outreach/
├── engine-config.json    Mode toggle. Edit via CLI only.
└── block-list.json       Wrong-identity registry. Edit via CLI only.

~/.config/systemd/user/
├── quinn-outreach-watcher.service
├── quinn-outreach-parser.service
├── quinn-outreach-scheduled-send.service
├── quinn-outreach-booking.service
└── quinn-outreach-autorespond.service