| name | observing-users |
| description | Capture user feedback as hypothesis-first research using Level 3 diagnostic. Forms theories (not conclusions) from quotes. |
| user-invocable | true |
| allowed-tools | Read, Write, Glob, Grep, Edit |
Observing Users
Capture user feedback as structured diagnostic observations using the Level 3 framework. Create or update individual user research canvases from feedback quotes.
Core Principle
Hypothesize, don't conclude.
One quote = one data point. Never:
- Classify users into "types" from usernames or single quotes
- Claim to know their Level 3 goal (form hypotheses instead)
- Treat future promises as commitments
Always:
- Flag confidence levels explicitly
- Note what we DON'T know
- Generate frameworks for deeper conversation, not assumptions
Triggers
/observe @{username} "{quote}"
/observe @{username} "{quote}" --context "{source}"
/observe @{username} "{quote}" --wallet 0x...
/observe @{username} "{quote}" --wallet 0x... --context "{source}"
/observe --enrich @{username} --wallet 0x...
Examples:
/observe @papa-flavio "planning henlo burns"
/observe @tchallason "realtime harvesting counter" --context "Discord #feedback"
/observe @xabbu "og score feels low" --wallet 0xabc123...
/observe --enrich @xabbu --wallet 0xabc123...
Flags:
--wallet 0x... — Provide wallet address for Score API enrichment. Persisted in canvas frontmatter for future use.
--enrich — Retroactive enrichment mode. Loads existing canvas, runs Score API enrichment only (no new quote, no Level 3 diagnostic). Requires existing canvas.
The Three Levels
| Level | Question | Example | Value |
|---|
| Level 1 | What did they say? | "Rewards aren't updating" | Surface symptom |
| Level 2 | What do they want? | "I want to see my rewards" | Stated desire |
| Level 3 | What are they trying to accomplish? | "Decide when to burn based on accumulation" | Actionable truth |
Always dig to Level 3. Level 1-2 lead to building the wrong thing.
When to Use
- User reports an issue or request
- Feedback appears in Discord/Telegram/support channels
- You notice behavioral patterns worth investigating
- Before building features based on user requests
Workflow
Step 0.5: Load Domain Glossary
Before interpreting any user quotes:
- Read
grimoires/keeper/glossary.yaml
- For each quote being annotated, check if any glossary term appears in the text (case-insensitive match on the
term field)
- If a match is found:
- Use the
meaning field as the canonical interpretation
- Note the
not field to explicitly avoid the common misinterpretation
- Include
[glossary: {term}] annotation in the hypothesis
- If glossary file does not exist, proceed without — log a warning to the operator
Step 1: Parse Arguments
Extract from command:
username: Target user (required)
quote: Exact user quote (required)
context: Source/channel (optional, default: "direct feedback")
Step 2: Load or Create Canvas
Check if canvas exists:
grimoires/keeper/canvas/{username}-canvas.md
If exists: Read current canvas, prepare to append
If not exists: Create new canvas with template
Step 2.5: Score API Enrichment (if wallet available)
Always attempt wallet resolution. Only skip enrichment if resolution fails AND no wallet is available.
Determine wallet address (in priority order):
- Auto-resolve: Run
scripts/observer/wallet-resolve.sh "{username}" — cache-first resolution with Supabase fallback
--wallet flag from command invocation (overrides auto-resolve if both succeed)
- Existing
wallet field in canvas frontmatter (from prior enrichment)
- Direct lookup via
midi_profiles table if above methods fail
If wallet address is available:
- Call
getWalletProfile(wallet) from lib/score-api/client.ts
- Returns
WalletProfile (see lib/score-api/types.ts:160-199)
- Call
getWalletBadges(wallet) from lib/score-api/client.ts
- Returns
WalletBadgesResponse (see lib/score-api/types.ts:82-93)
- Construct
score_snapshot from responses:
| ScoreSnapshot Field | Source (DB View) |
|---|
captured_at | Current ISO timestamp |
wallet | Wallet address used |
rank | mv_wallet_tiers.overall_rank |
combined_score | mv_wallet_tiers.combined_score |
og_score | mv_wallet_profiles.og_score |
nft_score | mv_wallet_profiles.nft_score |
onchain_score | mv_wallet_profiles.onchain_score |
og_rank | mv_dimension_leaderboard.og_rank |
nft_rank | mv_dimension_leaderboard.nft_rank |
onchain_rank | mv_dimension_leaderboard.onchain_rank |
trust_filter | mv_wallet_profiles.trust_filter |
trust_classification | mv_wallet_profiles.trust_classification |
og_breadth | mv_wallet_profiles.og_breadth |
nft_breadth | mv_wallet_profiles.nft_breadth |
onchain_breadth | mv_wallet_profiles.onchain_breadth |
crowd_tier | mv_wallet_tiers.crowd_tier |
elite_tier | mv_wallet_tiers.elite_tier |
total_badges | mv_wallet_badge_summary.badge_count |
model_version | Hardcode current version (e.g. "v0.11.0") |
- Add
wallet and score_snapshot to canvas frontmatter
- Render
## Score Context section (after ## User Profile) as markdown table:
## Score Context
| Field | Value |
|-------|-------|
| **Wallet** | `{wallet}` |
| **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** | {captured_at} |
If canvas already has score_snapshot:
If enrichment fails (Score API error, timeout, wallet not found):
- Proceed without enrichment — never block canvas creation
- Add note to canvas:
> Note: Score API unavailable at time of observation ({date})
- Set
score_snapshot: null in frontmatter if no prior snapshot exists
If wallet not available:
- Skip enrichment silently (no warning needed)
Step 2.5E: Retroactive Enrichment Mode (--enrich)
When invoked with --enrich flag:
- Load existing canvas — error if
grimoires/keeper/canvas/{username}-canvas.md not found
- Run Step 2.5 (Score API Enrichment) using provided
--wallet or canvas frontmatter wallet
- Add/update
## Score Context section in canvas
- Update canvas frontmatter with
wallet and score_snapshot
- Update
updated timestamp in frontmatter
- Skip Step 3 (Level 3 diagnostic) — no new quote to analyze
- Skip Step 5 (Conversation Frameworks) — no new quote context
- Proceed to Step 7 (Update Laboratory State) and Step 8 (Report Output)
Report output for --enrich mode:
✓ Canvas enriched: grimoires/keeper/canvas/{username}-canvas.md
Score Context:
Rank: #{rank} | Combined: {combined_score}
Crowd Tier: {crowd_tier} | Elite: {elite_tier or None}
Trust: {trust_filter} ({trust_classification})
{score_delta summary if applicable}
Next Steps:
- Add observations: /observe @{username} "..."
- Shape journeys: /shape --run
Step 3: Apply Level 3 Diagnostic Framework
Quote → Level 1 (What they said)
→ Level 2 (What they want)
→ Level 3 Hypothesis (What they might be trying to accomplish)
Analyze the quote to extract:
-
User Profile (if new canvas):
- Signals Observed (behavioral evidence only)
- Theories (possible interpretations - NOT conclusions)
- Confidence: Low | Medium (never High from single quote)
- Unknown (what we can't determine)
- Stakes (what they have invested, if mentioned)
-
Level 3 Hypothesis:
- What might they be trying to accomplish?
- Quote anchor (exact words that led to theory)
- Alternative interpretations
- What would validate / invalidate
-
Future Promises (if detected):
- Flag any "will", "would", "later", "tomorrow" statements
- Add to Promise table for follow-up tracking
-
Journey Fragment (if applicable):
- Trigger → Action → Expected → Actual → Emotion
-
Expectation Gap (if discovered):
- Expected vs Actual mismatch
- Gap type: Bug | Feature | Discoverability
Step 3.5: Provenance Gate
Before appending the quote to the canvas, run the provenance gate to ensure idempotent ingestion and content-hash tracking.
echo -n "{quote}" | scripts/provenance/gate.sh \
--source-type manual_quote \
--confidence "{confidence}" \
--canvas-target "{username}" \
--raw-source-ref "observe-{username}-{date}" \
--ingested-by observe
Timestamp confidence resolution:
| Operator input | --confidence | --timestamp |
|---|
Full ISO 8601 (e.g. 2026-02-11T14:30:00Z) | exact | The provided timestamp |
Date only (e.g. 2026-02-11) | day_level | {date}T00:00:00Z |
| No date provided | unknown | (omit flag — gate stores null) |
Context flag mapping:
--context "Discord #feedback 2026-02-11" → parse date if present, set confidence accordingly
--context "Discord #feedback" → no date parseable, confidence unknown
Exit code handling:
| Exit Code | Meaning | Action |
|---|
| 0 | INGESTED | Proceed with canvas update (Step 4) |
| 1 | SKIPPED (duplicate) | Skip canvas append for this quote. Report: "Quote already ingested (duplicate detected via content hash)" |
| 2 | ERROR (missing flags) | Log error, skip provenance tracking, proceed with canvas update (degrade gracefully) |
| 3+ | LOCK/CORRUPTION | Log warning, skip provenance tracking, proceed with canvas update |
No thread context: /observe handles standalone quotes. No --thread-id or --message-index flags.
Provenance hash in Quotes Library: When the gate returns exit 0, capture the provenance record ID from stdout. Include it in the Quotes Library entry:
> "{quote}" — {context}, {date} `[prov:{record_id}]`
Step 4: Update Canvas
New Canvas Template:
---
type: user_canvas
user: {username}
wallet: "0x..." # Persisted for future enrichment (null if not provided)
score_snapshot: # Populated by Step 2.5 (null if no wallet/enrichment failed)
captured_at: "{ISO timestamp}"
wallet: "0x..."
rank: null # from mv_wallet_tiers.overall_rank
combined_score: null # from mv_wallet_tiers.combined_score
og_score: null # from mv_wallet_profiles
nft_score: null
onchain_score: null
og_rank: null # from mv_dimension_leaderboard
nft_rank: null
onchain_rank: null
trust_filter: null # from mv_wallet_profiles
trust_classification: null
og_breadth: null
nft_breadth: null
onchain_breadth: null
crowd_tier: null # from mv_wallet_tiers
elite_tier: null
total_badges: null # from mv_wallet_badge_summary.badge_count
model_version: null
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: "{direct_feedback|discord_dm|twitter_dm|supabase_feedback}"
learning_status: directionally_correct
created: {timestamp}
updated: {timestamp}
linked_journeys: []
linked_observations: []
confidence:
created_at: "{timestamp}"
last_validated_at: "{timestamp}"
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 or null}"
enrichment_trigger: observe
chronicle_refs: []
---
# {username} Canvas
## User Profile
| Field | Value |
|-------|-------|
| **Signals Observed** | {behavioral signals from quote} |
| **Theories** | {possible interpretations - NOT conclusions} |
| **Confidence** | Low / Medium |
| **Unknown** | {what we cannot determine from this quote} |
| **Stakes** | {what they have invested, if mentioned} |
---
## Level 3 Hypotheses
### Hypothesis 1: {theory about what they might be trying to accomplish}
<!-- hivemind:product:UNTAGGED -->
- **Quote anchor**: "{exact words that led to this theory}"
- **Context**: {surrounding context and behavioral evidence}
- **Alternative interpretations**: {other valid readings of this quote}
- **Confidence**: Low | Medium
- **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}
---
## Future Promises (Unvalidated)
| Promise | Date | Follow-up Trigger |
|---------|------|-------------------|
| {quoted promise} | {date} | {condition for follow-up} |
---
## Journey Fragments
| Trigger | Action | Expected | Actual | Emotion |
|---------|--------|----------|--------|---------|
| {if applicable} | | | | |
---
## Expectation Gaps
| Expected | Actual | Source | Resolution |
|----------|--------|--------|------------|
---
## Conversation Frameworks
When this user returns, anchor on their words:
**If they mention [{topic from quote}]:**
- Opener: "You mentioned [exact words]. How did that go?"
- Dig deeper: "Walk me through what happened."
- Past behavior: "When was the last time you [action]?"
**Red flags to listen for:**
- Future promises ("I would...", "I might...")
- Opinion without behavior ("That sounds useful")
- Compliments without specifics
---
## Quotes Library
> "{quote}" — {context}, {date}
Existing Canvas Update:
- Append to Level 3 Hypotheses if new hypothesis detected
- Append to Quotes Library
- Update
updated timestamp in frontmatter
- Add to Future Promises if promise language detected
- Add Journey Fragment if quote contains flow information
- Add Expectation Gap if mismatch detected
- Update Conversation Frameworks with new anchors
Step 4.5: Wire Obsidian Links
After canvas creation or update, 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. Skip this step in --enrich mode.
Step 5: Generate Conversation Frameworks
Create contextual follow-up frameworks (NOT template questions):
Anchor to their words:
- Use exact phrases from their quotes
- Reference specific topics they mentioned
Structure:
**If they mention [{topic}]:**
- Opener: "You mentioned [their words]. How did that go?"
- Dig deeper: "Walk me through what happened."
- Past behavior: "When was the last time you [action]?"
For detailed framework patterns, see conversation-frameworks.md.
Step 6: Link Existing Observations
Check for existing observations:
grimoires/artisan/observations/{username}-*.md
If found, add to linked_observations in frontmatter.
Step 7: Update Laboratory State
Update grimoires/keeper/state.yaml:
active:
phase: discovery
canvas: {username}
canvases:
{username}:
created: {timestamp}
updated: {timestamp}
quotes_count: {n}
hypotheses_count: {n}
linked_journeys: []
queue:
pending_synthesis:
- {username}
Step 8: Emit FeedbackEvent
After canvas write, resolve the user's wallet and emit a FeedbackEvent via the Loa event bus:
Step 8a: Resolve wallet identity
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:
- If
wallet is not null → resolution_status: "resolved", include wallet and wallet_checksum (EIP-55)
- If
wallet is null → resolution_status: "unresolved", omit wallet fields
- Map
confidence and source directly from resolution output
Step 8b: 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 Level 3 diagnostic: positive/negative/neutral}",
"weight": {derived from user tier: high=0.8, medium=0.5, low=0.2},
"specificity": 0.3,
"content": "{AI-summarized redaction of the observation — NEVER raw quote}",
"kind": "{inferred: feel | calibration | accuracy | ux}",
"fingerprint": "{content_hash from provenance gate, or null if gate skipped}",
"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/observing-users" \
"" "" \
"user:{username}"
The bus auto-generates id, time, specversion in the CloudEvents envelope. The data payload above follows grimoires/shared/feedback/schema.json.
Weight derivation (from score_snapshot if available):
- Rank ≤ 50 or crowd_tier in [eternal, godfather, all_night] →
high (weight 0.8)
- Rank ≤ 200 or crowd_tier in [devoted, regular] →
medium (weight 0.5)
- Otherwise →
low (weight 0.2)
- No score_snapshot →
medium (weight 0.5, default)
Direction inference:
- Hypothesis polarity positive (user satisfied/engaged) →
positive
- Hypothesis polarity negative (user frustrated/confused) →
negative
- Neutral/informational observation →
neutral
Kind inference:
- Signal type FEEL →
feel
- Signal type WEIGHTINGS →
calibration
- Signal type ACCURACY →
accuracy
- Signal type UX →
ux
See grimoires/shared/feedback/schema.md for full schema reference.
See grimoires/shared/feedback/redaction-guide.md for hashing and redaction rules.
Skip this step in --enrich mode (no new observation to emit).
Step 9: Report Output
Display summary to user:
✓ Canvas updated: grimoires/keeper/canvas/{username}-canvas.md
✓ FeedbackEvent emitted: observer.feedback_captured (via Loa event bus)
Level 3 Hypothesis Extracted:
"{summarized hypothesis}"
Confidence: Low | Medium
Unknown: {what we don't know}
Canvas Status:
- Quotes: {n}
- Hypotheses: {n}
- Promises Tracked: {n}
Next Steps:
- Add more quotes: /observe @{username} "..."
- Shape journeys: /shape --run
Counterfactuals — Wallet Enrichment & Canvas Fidelity
Understanding where this skill fails requires distinguishing between data absence and data unavailability — and between behavioral evidence and identity assumptions.
Target (Correct Behavior)
When a wallet is provided or resolved, the skill calls the Score API, receives dimension scores, tiers, trust classification, and badge counts. This data populates the score_snapshot in frontmatter and renders the ## Score Context section. The user's signal weight (HIGH/MEDIUM/LOW) is derived from their rank and crowd tier — behavioral position, not identity.
When enrichment fails (timeout, 404, network error), the skill proceeds without enrichment, adds a timestamped note to the canvas (> Note: Score API unavailable at time of observation), and sets score_snapshot: null. The canvas is still created with the Level 3 diagnostic intact. Signal weight defaults to MEDIUM (unknown ≠ low).
When no wallet is available at all, enrichment is silently skipped. No warning, no note — the absence is expected, not exceptional.
The three states — enriched, failed-enrichment, and no-wallet — must remain distinguishable in the canvas frontmatter. Collapsing them into a single "no score data" state destroys information that downstream skills need for correct behavior.
Near Miss — Concept Impermanence
The seductively wrong behavior: treating enrichment failure as "no data" rather than "data unavailable." The difference matters because:
- "No data" implies the user has no score position → weight defaults to LOW
- "Data unavailable" implies we don't know their position → weight defaults to MEDIUM
A canvas created during a Score API outage should not permanently encode the user as low-weight. The score_snapshot: null field is a temporal marker — it says "we haven't looked yet," not "there's nothing to find." If the skill treats null snapshots as equivalent to low scores, every user onboarded during an outage gets permanently deprioritized in synthesis.
The correct fix: always check score_snapshot presence vs. value. Null means "enrich later" (flag for /refresh). Zero means "Score API returned zeros" (real data). Missing field means "pre-enrichment era canvas" (backfill candidate).
The downstream consequences propagate silently:
/daily-synthesis uses score_snapshot for signal weight classification — null snapshot → MEDIUM default
/shape aggregates canvases by weight tier — misclassified canvases skew pattern detection
/follow-up prioritizes by weight — a whale classified as MEDIUM gets deprioritized follow-ups
Each skill downstream trusts the canvas frontmatter. A single enrichment error at observation time cascades through the entire pipeline unless the null-vs-zero-vs-missing distinction is preserved.
Category Error — Semantic Collapse
The fundamentally wrong behavior: inferring user tier, engagement level, or conviction from their username, greeting style, or message tone instead of wallet data.
Examples of this collapse:
- "Bear-themed name" → must be a whale (identity ≠ position)
- "henlo" greeting → casual user (cultural norm ≠ engagement)
- Short message → low effort (brevity ≠ disinterest)
- Enthusiastic tone → high conviction (sentiment ≠ behavior)
- Active in Discord → must be high-conviction (social presence ≠ on-chain activity)
- Multiple DMs sent → engaged user (frequency ≠ depth)
The Observer skill exists precisely because what people say is not what they do. The Level 3 framework drills past stated desires to actual goals. Applying the same surface-level inference to the observer's classification of the user defeats the entire purpose.
The wallet data provides the only behavioral ground truth available: on-chain actions, badge accumulation, governance participation. Everything else is hypothesis material, never classification input.
A concrete example from this ecosystem: a user with a bear-themed name who sends enthusiastic messages daily in Discord might have zero on-chain activity (rank > 1000, no badges). Conversely, a user who sends one terse DM per month might be rank #5 with 20 badges. The observation skill must resist the intuitive mapping of communication style to conviction — that mapping is the exact bias the scoring system was built to correct.
Step 10: Emit Agent Interaction Log
As the final step, append a JSONL line to grimoires/keeper/agent-logs/{YYYY-MM-DD}.jsonl:
{
"ts": "{RFC 3339 UTC}",
"pack": "observer",
"skill": "observing-users",
"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 = number of canvas files written/updated
events_emitted = number of FeedbackEvents emitted (0 for --enrich mode)
- Create
grimoires/keeper/agent-logs/ directory if it doesn't exist
- See
grimoires/shared/feedback/agent-log-format.md for format reference
Canvas Template Reference
---
type: user_canvas
user: {username}
wallet: "0x..." | null
score_snapshot: {ScoreSnapshot} | null
hivemind:
artifact: user_truth_canvas
workstream: discovery
product: []
jtbd: []
source: "{direct_feedback|discord_dm|twitter_dm|supabase_feedback}"
learning_status: directionally_correct
created: {ISO timestamp}
updated: {ISO timestamp}
linked_journeys: []
linked_observations: []
confidence:
created_at: "{ISO timestamp}"
last_validated_at: "{ISO timestamp}"
last_validated_commit: ""
validation_count: 0
related_paths: []
schema_version: 2
lifecycle_state: "{new_user|reactivating|power_user|churning}"
last_enriched: "{ISO timestamp or null}"
enrichment_trigger: observe
chronicle_refs: []
---
Sections:
- User Profile (signals, theories, confidence, unknown, stakes)
- Score Context (rank, percentile, dimension scores, tiers, trust, breadth, badges) — via Step 2.5
- Level 3 Hypotheses (hypotheses with validation criteria)
- Future Promises (unvalidated commitments to track)
- Journey Fragments (trigger → action → expected → actual → emotion)
- Expectation Gaps (expected vs actual mismatches)
- Conversation Frameworks (anchored follow-up patterns)
- Quotes Library (raw quotes with context)
Reference Material
For detailed guidance, see these supporting files:
Signal Patterns
Behavioral signals that may indicate user intent (use as hypotheses, not classifications):
| Signal Pattern | Possible Interpretation | Confidence Limit |
|---|
| "planning", "deciding" | May be optimizing timing | Low-Medium |
| "checking", "verify" | May be validating expectations | Low |
| "API", "integrate" | May want programmatic access | Low-Medium |
| "trying", "wondering" | Exploring, not committed | Low |
Important: These are hypothesis generators, not type classifiers. See cultural-context.md for what NOT to infer.
Promise Detection
Flag these signal words and add to Future Promises table:
| Category | Signal Words |
|---|
| Future intent | will, would, might, going to, plan to |
| Temporal | later, tomorrow, soon, eventually |
| Conditional | if I..., when I..., once I... |
| Hedged | probably, maybe, I think I'll |
Note: Insights are synthesized only after validation, not from initial quotes.
Integration Points
- shaping-journeys: Canvases feed into journey synthesis (Score Context provides user weight for pattern prioritization)
- Laboratory state: Updates
state.yaml for cross-session tracking
- level-3-diagnostic: Uses same diagnostic framework
- Score API (
lib/score-api/client.ts): getWalletProfile() and getWalletBadges() for enrichment
- synthesizing-feedback: Feedback entries share
score_snapshot format for consistent enrichment
Validation
After canvas update:
Error Handling
| Error | Resolution |
|---|
| No username provided | Prompt for @username |
| No quote provided | Prompt for quote in quotes (not required for --enrich mode) |
| Canvas corrupted | Create backup, reinitialize |
| Observation link broken | Remove from linked_observations |
--enrich but no existing canvas | Error: "No canvas found for @{username}. Use /observe @{username} \"quote\" to create one first." |
| Score API timeout/error | Proceed without enrichment, add note to canvas |
| Wallet not found in Score API (404) | Proceed without enrichment, add note: "Wallet not found in Score API" |
| Invalid wallet address format | Warn user, skip enrichment |
Temporal Claim Enforcement (G-4)
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 = exact
feedback_timestamp ∈ [release_timestamp, next_release_timestamp)
- Both timestamps from authoritative sources (provenance index + chronicle)
-
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: For /observe, the operator may not provide a timestamp. When --confidence unknown is used, downstream skills MUST NOT fabricate temporal context.
Related
/shape - Extract journeys from canvases
/diagram - Generate diagrams from journeys
/craft - Generate with observation context
/plan-and-analyze - Full PRD discovery