skills/ingesting-dms/SKILL.md
Single-user conversation import that creates an enriched canvas scaffold from a DM export.
npx skillsauth add 0xHoneyJar/construct-beehive ingesting-dmsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Import a Discord DM conversation export for a single user, resolve their wallet, pull their Score API profile, and produce an enriched User Truth Canvas with pre-populated quotes, skeleton hypotheses, and journey fragments.
This skill replaces ~10 manual steps with a single invocation. The operator refines hypotheses to Level 3 after the skill completes.
One quote = one data point. Hypothesize, don't conclude. Every extracted hypothesis starts at confidence: Low unless multiple independent signals corroborate it. The canvas is a scaffold — human refinement is the final step.
/ingest-dm <path-to-csv-or-text>
/ingest-dm <path> --username <name>
/ingest-dm <path> --wallet <address>
/ingest-dm <path> --username <name> --wallet <address>
Examples:
/ingest-dm grimoires/keeper/imports/xabbu-discord-export.csv
/ingest-dm grimoires/keeper/imports/newuser.txt --username "NewUser" --wallet 0xabc...
/ingest-dm grimoires/keeper/imports/batch/user3.csv --username "user3"
Arguments:
<path>: Path to CSV export or pasted text file (required)--username <name>: Override extracted username (optional — auto-detected from conversation)--wallet <address>: Provide wallet directly (optional — resolved via wallet-resolve.sh)--responds-to <message-id>: Tag this DM as a response to a specific follow-up message (optional — for growth loop outcome tracking)--signal-quality <high|medium|low>: Rate the quality of information in this response (optional — requires --responds-to)/batch-observe for parallel processing)Before interpreting any user quotes extracted from the DM conversation:
grimoires/keeper/glossary.yamlterm field)meaning field as the canonical interpretationnot field to explicitly avoid the common misinterpretation[glossary: {term}] annotation in the hypothesisRead the input file. Detect format:
If CSV — detect format by headers:
| Platform | Key Headers | Speaker Field | Text Field | Time Field |
|----------|-------------|--------------|------------|------------|
| Discord | AuthorID, Content, Timestamp | Author | Content | Timestamp |
| Twitter/X | sender_screen_name, text, time | sender_screen_name | text | time |
| Telegram | from, text, date | from | text | date |
IMPORTANT: Use Python csv.DictReader for all CSV parsing — never raw cut/awk. CSV fields contain embedded commas, quotes, and JSON blobs (Twitter reactions as {"1":{"type":"STRUCT",...}}). Filter these noise rows:
text starts with {"1":{"type":"STRUCT" (Twitter reaction metadata)(time, sender, text[:50]) tuplepython3 -c "
import csv, sys
with open(sys.argv[1], 'r') as f:
reader = csv.DictReader(f)
for row in reader:
text = row.get('text') or row.get('Content') or ''
if text.startswith('{\"1\":{\"type\":\"STRUCT\"'): continue
sender = row.get('sender_screen_name') or row.get('Author') or row.get('from') or ''
time = row.get('time') or row.get('Timestamp') or row.get('date') or ''
print(f'[{time}] @{sender}: {text}')
" "$input_file"
If plain text (pasted conversation):
username: or timestamp patternsIf --username provided: Use it directly.
Otherwise: Extract from conversation data:
Author fieldNormalize username: lowercase, strip special characters.
If --wallet provided: Use it directly.
Otherwise: Run wallet resolution:
scripts/observer/wallet-resolve.sh <username>
If wallet found: Proceed to enrichment. If "not found": Create canvas without Score API data. Add note:
> Score API enrichment unavailable — wallet not resolved.
> Run: /observe --enrich @{username} --wallet 0x... when wallet is known.
If wallet available:
scripts/observer/score-api-query.sh profile <wallet> --format snapshot
This returns canvas-ready score_snapshot YAML from 4 views:
mv_wallet_profiles — scores, breadth, trustmv_wallet_tiers — combined score, rank, tiersmv_dimension_leaderboard — per-dimension ranksmv_wallet_badge_summary — badge countOn failure: Set score_snapshot: null in frontmatter. Never block canvas creation.
Before appending any quote to the canvas, pipe each extracted message through the provenance gate. This ensures every ingested item is content-hashed, deduplicated, and timestamp-tracked.
For each extracted message:
# Pipe message content via stdin. gate.sh normalizes and deduplicates.
echo -n "$message_text" | scripts/provenance/gate.sh \
--source-type discord_dm \
--timestamp "$parsed_utc_timestamp" \
--timestamp-raw "$verbatim_timestamp_string" \
--confidence exact \
--canvas-target "$username" \
--raw-source-ref "$input_file_path" \
--ingested-by ingest-dm \
--thread-id "thread-discord-${username}-${export_date}" \
--message-index "$zero_indexed_position" \
--parent-hash "$previous_message_content_hash" \
--json
Exit code handling:
| Exit Code | Meaning | Action |
|-----------|---------|--------|
| 0 (INGEST) | New content — add to canvas | Capture content_hash from JSON output for event emission |
| 1 (SKIP) | Duplicate — already ingested | Skip this message, increment skip counter |
| 2 (ERROR) | Input error | Halt and report error |
| 3 (LOCK_FAILED) | Lock contention | Retry once after 2s, halt if still fails |
| 4 (DEPENDENCY) | Missing tool | Halt — operator must install missing dependency |
Discord timestamp parsing:
MM/DD/YY, HH:MM PM → parse to UTC RFC 3339 (2026-02-11T14:23:00Z)--timestamp-raw (e.g., "02/11/26, 2:23 PM")--confidence exact (Discord provides authoritative timestamps)Telegram timestamp parsing:
[HH:MM] or YYYY-MM-DD HH:MM → parse to UTC--confidence day_levelThread context:
--thread-id: thread-discord-{username}-{export_date} (e.g., thread-discord-papa-flavio-2026-02-11)--message-index: 0-indexed position in the exported conversation--parent-hash: content_hash of the previous message (null for first message)After processing all messages, report:
Provenance: Ingested {N} new messages, skipped {M} duplicates
Only messages with exit code 0 (INGEST) proceed to canvas creation in Step 5.
Write canvas to grimoires/keeper/canvas/{username}-canvas.md using the template below.
Auto-populate from conversation:
Quotes Library: Extract direct quotes from user messages. Prioritize:
Skeleton Hypotheses: Generate 2-4 hypotheses from conversation signals:
H{n}: {User} may {behavior/belief} because {evidence from quote}confidence: Low (single conversation = single data source)Journey Fragments: Extract temporal sequence from conversation:
Future Promises: Detect promise language:
DM ingestion converts unstructured conversation exports into structured canvases. The failure modes sit at two levels: mechanical (parsing breaks) and epistemological (wrong framework applied to the data).
The skill accepts a pasted DM transcript, identifies speaker turns by username prefix and timestamp patterns, extracts quotes attributed to the correct speaker, and builds a canvas with Level 3 hypotheses derived from the user's statements (not the operator's). Promise language is detected and tracked. The conversation arc is mapped as a journey fragment with trigger → action → expected → actual → emotion.
Timestamps are parsed tolerantly — the skill handles Discord format (username — MM/DD/YY, HH:MM PM), Telegram format ([HH:MM]), Twitter/X DM format, and bare text with no timestamps. When format detection fails, the skill falls back to sequential ordering without timestamps rather than misattributing turns.
The seductively wrong behavior: assuming consistent timestamp format within a single DM export.
Real DM exports contain format discontinuities:
A rigid parser that locks onto the first detected format will misparse subsequent messages, potentially:
The correct behavior: parse each line independently. Detect speaker turns by the username prefix pattern, not by timestamp format. Use timestamps for ordering when available, but fall back to document order. Never silently drop a line — if it can't be parsed as a new turn, append it to the previous speaker's turn as continuation.
The fundamentally wrong behavior: applying Level 3 diagnostic framework to casual DM conversation as if it were a user research interview.
Level 3 analysis assumes the user is responding to questions about their goals, pain points, and workflows. A DM conversation is different:
Treating "that looks slick" as a validated usability signal, or "I thought I was active on cubquests" as a feature request, collapses the distinction between conversational reaction and considered feedback. The Mom Test framework specifically warns against this: compliments in conversation are not validation.
The correct approach: DM ingestion creates a canvas scaffold — hypotheses at LOW confidence with explicit "Unknown" gaps. The canvas is raw material for follow-up, not a finished research artifact. Journey fragments from DMs should be marked source: dm-import to distinguish them from structured observation data.
Concrete examples from this ecosystem:
Each of these requires a different follow-up strategy. Treating them all as "user feedback" at the same confidence level collapses the signal hierarchy that makes canvases useful. The scaffold should explicitly mark which quotes are pleasantries (LOW value), which are expectation gaps (MEDIUM value, needs follow-up), and which reveal goal-level intent (HIGH value, hypothesis-worthy).
The /follow-up skill downstream depends on this classification to generate anchored conversation frameworks. If every DM quote is treated as equal evidence, the follow-up messages will anchor on pleasantries instead of the moments that actually reveal user intent.
After writing the canvas file, wire it into the knowledge graph if it belongs to any journey:
source scripts/observer/golden-path-lib.sh
wire_canvas_links "grimoires/keeper/canvas/{username}-canvas.md"
This injects <!-- midi:journey-links --> sentinel with Journeys and Related Canvases sections if the canvas appears in any journey's source_canvases. If the canvas is not in any journey, this is a silent no-op.
Update grimoires/keeper/state.yaml:
canvas_count if new canvaslast_observation timestampAfter canvas creation/update, resolve the user's wallet and emit a FeedbackEvent via the Loa event bus:
Step 7a: Resolve wallet identity
# Resolve wallet for data.subject enrichment
resolution_json=$(scripts/observer/wallet-resolve.sh --json "{username}" 2>/dev/null) || resolution_json='{"wallet":null,"confidence":"none","source":"none","username":null}'
Parse resolution_json to build data.subject (same rules as /observe Step 8a).
Step 7b: Emit event with subject
source .claude/scripts/lib/event-bus.sh
emit_event "observer.feedback_captured" \
'{
"domain": "research",
"target": { "type": "user", "selector": "user:{username}" },
"signal": {
"direction": "{inferred from conversation sentiment: positive/negative/neutral}",
"weight": {derived from user tier: high=0.8, medium=0.5, low=0.2},
"specificity": 0.3,
"content": "DM import: {N} quotes extracted, {M} hypotheses generated from conversation",
"kind": "{dominant signal type from extracted quotes: feel | calibration | accuracy | ux}",
"fingerprint": "{content_hash from gate.sh --json output}",
"normalization_version": 1
},
"context": {
"user_id": "{salted hash per redaction-guide.md, or omit if unavailable}",
"user_tier": "{high | medium | low — derived from rank}",
"artifact_path": "grimoires/keeper/canvas/{username}-canvas.md"
},
"subject": {
"resolution_status": "{resolved | unresolved}",
"resolution_source": "{alias | username | leaderboard | supabase | direct | none}",
"resolution_confidence": "{high | medium | ambiguous | none}",
"wallet": "{0x... or omit if unresolved}",
"wallet_checksum": "{EIP-55 checksum or omit if unresolved}"
}
}' \
"observer/ingesting-dms" \
"" "" \
"user:{username}"
The bus auto-generates id, time, specversion in the CloudEvents envelope. The data payload follows grimoires/shared/feedback/schema.json.
Weight derivation: Same as /observe — use score_snapshot rank/tier if available, default to medium (0.5).
Direction inference: Derive from overall conversation sentiment:
positivenegativeneutralSee grimoires/shared/feedback/schema.md for full schema reference.
See grimoires/shared/feedback/redaction-guide.md for hashing and redaction rules.
Display summary:
Canvas created: grimoires/keeper/canvas/{username}-canvas.md
FeedbackEvent emitted: observer.feedback_captured (via Loa event bus)
Score: Rank #{rank} | {crowd_tier}/{elite_tier} | {og}/{nft}/{onchain}
Quotes extracted: {N}
Hypotheses generated: {N} (all Low confidence — refine to Level 3)
Journey fragments: {N}
Future promises: {N}
Next steps:
1. Review and refine hypotheses to Level 3 depth
2. Run /level-3-diagnostic for diagnostic questioning
3. Add context from other signal sources
---
type: user_canvas
user: {username}
wallet: "{wallet_address}"
score_snapshot:
captured_at: "{ISO timestamp}"
wallet: "{wallet_address}"
rank: {N} # from mv_wallet_tiers.overall_rank
combined_score: {N} # from mv_wallet_tiers.combined_score
og_score: {N} # from mv_wallet_profiles
nft_score: {N}
onchain_score: {N}
og_rank: {N} # from mv_dimension_leaderboard
nft_rank: {N}
onchain_rank: {N}
trust_filter: {N} # from mv_wallet_profiles
trust_classification: "{normal|banned_robotic|banned_cluster|flagged_single_factor|flagged_burst}"
og_breadth: {N}
nft_breadth: {N}
onchain_breadth: {N}
crowd_tier: "{tier}" # from mv_wallet_tiers
elite_tier: "{tier or null}"
total_badges: {N} # from mv_wallet_badge_summary.badge_count
model_version: "v0.11.0"
hivemind:
artifact: user_truth_canvas
workstream: discovery
product: [] # e.g. [user_profile, wallet_integration, user_reactivation]
jtbd: [] # e.g. [help_me_feel_smart, find_information, feel_connected_again]
source: "{twitter_dm|discord_dm|supabase_feedback}"
learning_status: directionally_correct
created: "{date}"
updated: "{date}"
linked_journeys: []
linked_observations: []
confidence:
created_at: "{date}"
last_validated_at: "{date}"
last_validated_commit: ""
validation_count: 0
related_paths:
- "lib/score-api/**"
- "grimoires/keeper/canvas/"
schema_version: 2
lifecycle_state: "{new_user|reactivating|power_user|churning}"
last_enriched: "{ISO timestamp}"
enrichment_trigger: ingest-dm
chronicle_refs: []
---
# {Username} Canvas
## User Profile
| Field | Value |
|-------|-------|
| **Signals Observed** | {behavioral signals extracted from conversation} |
| **Theories** | {possible interpretations — NOT conclusions} |
| **Confidence** | **{Low|Medium}** ({reason}) |
| **Unknown** | {what we cannot determine from this conversation} |
| **Stakes** | {what they have invested, engagement history} |
---
## Score Context
| Field | Value |
|-------|-------|
| **Wallet** | `{wallet_address}` |
| **Rank** | **#{rank}** |
| **Combined Score** | **{combined_score}** |
| **OG** | {og_score} / rank #{og_rank} / breadth {og_breadth} |
| **NFT** | {nft_score} / rank #{nft_rank} / breadth {nft_breadth} |
| **Onchain** | {onchain_score} / rank #{onchain_rank} / breadth {onchain_breadth} |
| **Trust** | {trust_filter} ({trust_classification}) |
| **Crowd Tier** | **{crowd_tier}** |
| **Elite Tier** | {elite_tier or None} |
| **Badges** | {total_badges} earned |
| **Signal Weight** | **{HIGH|MEDIUM|LOW}** ({crowd_tier} tier, rank #{rank}) |
| **Model Version** | {model_version} |
| **Captured At** | {date} |
> **Enrichment insight**: {analysis of dimension imbalances, breadth patterns, activity gaps}
## Level 3 Hypotheses
> Skeleton hypotheses from DM import. Refine to Level 3 depth.
### Hypothesis 1: {hypothesis_title}
<!-- hivemind:product:UNTAGGED -->
- **Quote anchor**: "{exact words that led to this theory}"
- **Context**: {surrounding context and behavioral evidence}
- **Alternative interpretations**: {other valid readings}
- **Confidence**: Low (single DM conversation)
- **What would validate**: {observable behavior or statement that confirms}
- **What would invalidate**: {observable behavior or statement that disproves}
- **Design implication**: {what this means for the product if true}
### Hypothesis 2: {hypothesis_title}
<!-- hivemind:product:UNTAGGED -->
- **Quote anchor**: "..."
- **Context**: ...
- **Alternative interpretations**: ...
- **Confidence**: Low
- **What would validate**: ...
- **What would invalidate**: ...
---
## Future Promises (Unvalidated)
| Promise | Date | Follow-up Trigger | Status |
|---------|------|-------------------|--------|
| "{promise language from conversation}" | {ISO date} | {condition for follow-up} | **PENDING** |
---
## Journey Fragments
| Trigger | Action | Expected | Actual | Emotion |
|---------|--------|----------|--------|---------|
| {what initiated} | {what user did} | {what they expected} | {what actually happened} | {emotional response} |
---
## Expectation Gaps (Code-Grounded)
*No code-grounded gaps identified yet — first session was {exploratory|bug-reporting|etc}. Gaps may emerge when {user} engages with {specific features}.*
---
## Conversation Frameworks
{Priority context for next conversation}
**If they mention [{topic from conversation}]:**
- Opener: "{anchored to their exact words}"
- Dig deeper: "{follow-up that reveals Level 3 goal}"
- Watch for: {specific behavioral signals}
**Red flags to listen for:**
- {specific to this user's pattern}
---
## Quotes Library
> "{exact user quote}"
> — {Platform} DM, {ISO date}
> "{another quote}"
> — {Platform} DM, {ISO date}
---
## Linked Artifacts
### Journeys
*None yet — will link when cross-user patterns emerge*
### Related Canvases
*Potential links: {users with similar patterns}*
---
## Feedback Entries (from UI)
| Time (UTC) | Type | Source | Page Context | Note | Signal | Weight |
|------------|------|--------|-------------|------|--------|--------|
*No UI feedback entries yet. Will be populated by /daily-synthesis.*
If --responds-to flag is present, record the response outcome in the user's growth state.
If --responds-to is NOT provided, skip this step entirely — existing behavior unchanged.
source scripts/observer/growth-state.sh
responds_to="{message-id from --responds-to flag}"
signal_quality="{value from --signal-quality flag, or null}"
user="{username}"
# Input validation — prevent yq expression injection via crafted message IDs
if [[ ! "$responds_to" =~ ^fu-[0-9]{4}-[0-9]{2}-[0-9]{2}-[a-z0-9_-]+-[0-9]+-[0-9]+$ ]]; then
echo "ERROR: Invalid message ID format: $responds_to"
echo "Expected: fu-YYYY-MM-DD-username-batchseq-msgnum"
# Continue with normal DM ingestion (do NOT halt)
exit 0 # or skip to next step
fi
if [[ -n "$signal_quality" ]] && [[ ! "$signal_quality" =~ ^(high|medium|low)$ ]]; then
echo "WARNING: Invalid signal_quality '$signal_quality' — ignoring"
signal_quality=""
fi
# Check growth state exists
if ! growth_exists "$user"; then
echo "WARNING: No growth state for $user — skipping outcome recording"
# Continue with normal DM ingestion (do NOT halt)
else
# Read growth state under flock
lock_path="$(growth_lock_path "$user")"
_ensure_flock
exec 201>"$lock_path"
"$_FLOCK" -x -w 10 201
content="$(growth_read "$user")"
# Find message and record outcome
# Complete transition table:
# pending → responded: increment responded_count
# silence → responded: decrement silence_count, increment responded_count, set late_response=true
# unknown → responded: decrement unknown_count, increment responded_count
# responded → responded: no-op (idempotent, log warning)
# missing → error logged, ingestion continues
previous_outcome=$(echo "$content" | yq -r --arg mid "$responds_to" '
.follow_ups[].message_ids[] | select(.id == $mid) | .outcome
' | head -1)
if [ -z "$previous_outcome" ] || [ "$previous_outcome" = "null" ]; then
echo "ERROR: Message ID $responds_to not found in growth state for $user"
exec 201>&-
# Continue with normal DM ingestion
elif [ "$previous_outcome" = "responded" ]; then
echo "WARNING: Message $responds_to already responded — no-op"
exec 201>&-
else
now_ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
late_response="false"
if [ "$previous_outcome" = "silence" ]; then
late_response="true"
fi
# Update message outcome via yq (--arg binding prevents injection)
content=$(echo "$content" | yq --arg mid "$responds_to" --arg ts "$now_ts" --arg sq "$signal_quality" --arg lr "$late_response" '
(.follow_ups[].message_ids[] | select(.id == $mid)) .outcome = "responded" |
(.follow_ups[].message_ids[] | select(.id == $mid)) .outcome_at = $ts |
(.follow_ups[].message_ids[] | select(.id == $mid)) .response_date = $ts |
(.follow_ups[].message_ids[] | select(.id == $mid)) .signal_quality = $sq |
(.follow_ups[].message_ids[] | select(.id == $mid)) .late_response = ($lr == "true")
')
# Update pattern counters (transition-safe)
pattern=$(echo "$content" | yq -r --arg mid "$responds_to" '
.follow_ups[].message_ids[] | select(.id == $mid) | .question_pattern
' | head -1)
# Validate pattern against known allowlist (defense-in-depth against yq injection)
allowed_patterns="probe_gap verify_action challenge_assumption explore_motivation surface_unspoken test_commitment follow_breadcrumb"
pattern_valid="false"
if [ -n "$pattern" ] && [ "$pattern" != "null" ]; then
for allowed in $allowed_patterns; do
if [ "$pattern" = "$allowed" ]; then pattern_valid="true"; break; fi
done
fi
if [ "$pattern_valid" = "true" ]; then
# Decrement old bucket
case "$previous_outcome" in
silence) content=$(echo "$content" | yq --arg p "$pattern" '.question_patterns[$p].silence_count -= 1') ;;
unknown) content=$(echo "$content" | yq --arg p "$pattern" '.question_patterns[$p].unknown_count -= 1') ;;
pending) ;; # pending has no counter bucket
esac
# Increment responded_count
content=$(echo "$content" | yq --arg p "$pattern" '.question_patterns[$p].responded_count += 1')
# Update signal quality accumulators
sq_numeric=""
case "$signal_quality" in
high) sq_numeric="1.0" ;;
medium) sq_numeric="0.5" ;;
low) sq_numeric="0.0" ;;
esac
if [ -n "$sq_numeric" ]; then
content=$(echo "$content" | yq --arg p "$pattern" --arg sq "$sq_numeric" '
.question_patterns[$p].signal_quality_sum += ($sq | tonumber) |
.question_patterns[$p].signal_quality_count += 1
')
fi
# Recompute derived metrics (response_rate, avg_signal_quality, effectiveness_score)
source scripts/observer/growth-state.sh
content=$(_recompute_pattern_metrics "$content" "$pattern")
fi
# Update hypothesis evidence (reset decay)
hypothesis_ref=$(echo "$content" | yq -r --arg mid "$responds_to" '
.follow_ups[].message_ids[] | select(.id == $mid) | .hypothesis
' | head -1)
# Extract hypothesis ID (e.g., "H1" from "H1: title" or "Hypothesis H1: title")
h_id=$(echo "$hypothesis_ref" | grep -oE 'H[0-9_]+' | head -1)
if [ -n "$h_id" ]; then
content=$(echo "$content" | yq --arg hid "$h_id" --arg ts "$now_ts" '
(.hypotheses[] | select(.id == $hid)).cycles_without_evidence = 0 |
(.hypotheses[] | select(.id == $hid)).decay_state = "active" |
(.hypotheses[] | select(.id == $hid)).last_evidence_date = $ts
')
fi
content=$(echo "$content" | yq --arg ts "$now_ts" '.last_updated = $ts')
# Write updated growth state (atomic, under lock)
growth_write "$user" "$content"
exec 201>&-
echo "Growth outcome recorded: $responds_to → responded (was: $previous_outcome)"
if [ "$late_response" = "true" ]; then
echo " Late response: silence → responded"
fi
if [ -n "$sq_numeric" ]; then
echo " Signal quality: $signal_quality ($sq_numeric)"
fi
fi
fi
If --responds-to is NOT provided: This entire step is skipped. The DM is ingested normally via Steps 1-8 without any growth state interaction.
As the final step, append a JSONL line to grimoires/keeper/agent-logs/{YYYY-MM-DD}.jsonl:
{
"ts": "{RFC 3339 UTC}",
"pack": "observer",
"skill": "ingesting-dms",
"status": "{success | error}",
"duration_ms": "{approximate wall-clock from skill start to end}",
"artifacts_written": 1,
"events_emitted": 1,
"error": "{error message if status=error, omit if success}"
}
Notes:
duration_ms is approximate (wall-clock estimate, not precise timer)artifacts_written = canvas files created/updatedevents_emitted = FeedbackEvents emittedgrimoires/keeper/agent-logs/ directory if it doesn't existgrimoires/shared/feedback/agent-log-format.md for format reference| Signal Type | Regex Pattern | Example |
|-------------|---------------|---------|
| FEEL | Emotional language, bare reactions | "this is cool", "love it", "feels off" |
| WEIGHTINGS | /dimension\|factor\|weight\|score.*should\|too (high\|low)/i | "OG should matter more" |
| ACCURACY | /missing\|wrong\|didn't\|transaction\|minted\|count\|data/i | "I minted 3 but shows 2" |
| UX | /can't find\|confus\|button\|page\|navigate\|broken\|UI\|UX/i | "Can't find settings" |
| Pattern | Type | |---------|------| | "I'll...", "I want to...", "Let me..." | User intent | | "Can you show me...", "I'd like to see..." | Feature request | | "I need to check...", "Going to try..." | Deferred action |
| Error | Resolution |
|-------|------------|
| Input file not found | Error with path suggestion |
| No messages from user in CSV | Error — operator may have wrong export |
| Wallet not resolved | Create canvas without Score API data, add enrichment note |
| Score API query fails | Create canvas with score_snapshot: null, add note |
| Canvas already exists for user | Append new quotes to existing canvas instead of overwriting |
| Username collision | Prompt operator to provide --username override |
scripts/observer/wallet-resolve.sh, scripts/observer/score-api-query.sh/level-3-diagnostic (hypothesis refinement), /shape (journey synthesis)/daily-synthesis (appends UI feedback entries)/batch-observe (multi-user processing)Rules for any downstream skill or agent referencing provenance data from this ingestion:
Cite provenance: When making temporal claims about user feedback (e.g., "user said X after release Y"), you MUST cite the provenance record_id and verify source_timestamp_confidence is exact.
Version attribution requires proof: Attributing feedback to a specific version (e.g., "user was upset after v0.2.0") requires:
source_timestamp_confidence = exactfeedback_timestamp ∈ [release_timestamp, next_release_timestamp)Hedging for low confidence: If source_timestamp_confidence is unknown or inferred, temporal claims MUST be hedged ("around this time" / "date uncertain") and MUST NOT be used for version attribution.
Raw timestamp preservation: --timestamp-raw MUST be passed to gate.sh to preserve the verbatim timestamp string from the source material. This enables audit of parse correctness.
/observe — Manual single-quote observation/import-research — Bulk legacy profile conversion/level-3-diagnostic — Hypothesis refinement after canvas creation/batch-observe — Parallel multi-user processing/daily-synthesis — Automated feedback pipelinedata-ai
Cognition orchestrator — analyze canvases, distill fears via /distill subagent, run gap analysis, optional cross-user synthesis.
development
Golden path /speak — generate RLM-isolated follow-ups with chronicle temporal context injection.
testing
# /snapshot — MiDi Experience Record (MER) Capture Capture a point-in-time MER for a wallet. Produces a 4-layer snapshot: data state, visual screenshot, user perception, and decision context. ## Usage ``` /snapshot <wallet-or-alias> /snapshot xabbu --trigger feedback /snapshot xabbu --data-only /snapshot --cohort /snapshot --cohort --diff MER-2026-001 ``` ## Arguments | Argument | Description | Required | |----------|-------------|----------| | `wallet-or-alias` | Wallet address or alias fr
development
Golden path /shape — consolidate journey patterns across canvases and file gap issues.