Install
openclaw skills install @indigokarasu/ocas-styxTransaction data store with merchant enrichment. Provides a clean, queryable interface over raw bank transaction data. Enriches garbled/obfuscated transaction names into real business entities using SearXNG search plus LLM resolution. Includes financial sync (Plaid API) for pulling transactions and balances daily. Other skills (Taste, Rally, Vesper, Sands) read from Styx for consumption signals, spending analysis, and pattern detection. NOT for creating transactions (use bank), budgeting strategy (use Rally), or email-based consumption scanning (use Taste).
openclaw skills install @indigokarasu/ocas-styxStyx is the system's transaction intelligence layer. It sits between raw bank data (from Plaid via financial-sync) and consumer skills that need clean merchant information (Taste, Rally, Vesper, Sands).
receipt_line_items tableStyx operates a continuous ingest-enrich-serve workflow because raw transaction data requires normalization before it becomes useful to downstream skills.
Example: a transaction from "UNK MERCHANT 1234" is enriched via SearXNG search → identified as "Whole Foods Market" → stored with clean merchant name → Taste queries for spending patterns.
See references/data-flow.md for the data flow diagram.
Styx maintains its own SQLite database at <hermes-home>/data/styx.db.
IMPORTANT: Hardcode this path. Do NOT use {agent_root} — it resolves to the indigo profile home, not the shared data directory.
The active DBs are:
<hermes-home>/data/transactions.db — raw Plaid transaction data (1,187 transactions, last: 2026-06-24)<hermes-home>/data/styx.db — enriched merchant data (1,193 transaction_merchants links, 493 merchants)Note: Plaid /transactions/sync cursor can get stuck and miss transactions. If MAX(date) is stale, use /transactions/get backfill pattern (see references/plaid-sync-cursor-recovery.md). Sync cursors reset after backfill.
A second copy exists at <hermes-home>/commons/data/ocas-styx/styx.db but it is a stale 0-byte stub — ignore it.
Three core tables: merchants, transaction_merchants, enrichment_runs.
Receipt parsing table: receipt_line_items (23 columns — see below).
Full DDL: references/schema.md
Used for storing parsed email receipt line items (e.g., Rainbow Grocery eReceipts).
See references/receipt-line-items-insert.md for the correct INSERT pattern and gotchas.
The enrichment pipeline resolves garbled/obfuscated transaction names into real businesses. The default script only enriches food merchants. For full coverage, use the universal enrichment script:
Script: styx_universal_enrichment.md ← read this reference first
# Universal enrichment — all non-financial categories
# Created 2026-06-20. Script exists at:
# <hermes-home>/profiles/indigo/skills/ocas-styx/scripts/styx_universal_enrich.py
# references/styx_universal_enrichment.md if needed.
# Last known path (may not exist): <hermes-home>/commons/data/ocas-styx/styx_universal_enrich.py
# Food-only (original script) — confirmed working
python3 <hermes-home>/profiles/indigo/skills/ocas-styx/scripts/styx_places_enrich.py --all
Categories covered by universal script: retail, service, entertainment, transport, personal_care, medical, home, government, housing, travel, food/restaurant (all 10 food subcategories).
Categories skipped (no physical location): transfer, income, bank_fees, loan_payments,
loan_disbursements. These get source: 'internal'.
For garbled names that Google Places can't resolve: exact match → fuzzy match → SearXNG search → LLM resolution → manual review queue. Full details: references/enrichment-pipeline.md
Other skills read from Styx using these patterns:
DB path: {agent_root}/data/styx.db
When parsing email receipts (e.g., Rainbow Grocery):
get_gmail_messages_content_batch — large results persisted to /tmp/hermes-results/<uuid>.txt\n\nMessage ID: , then extract between --- BODY --- and ---\n\nreceipt_line_items INSERT pattern above (22 values, id auto-increments)Taste reads from Styx to discover restaurants and food businesses that has transacted with but that didn't appear in email/calendar.
Taste queries:
m.category IN ('restaurant', 'cafe', 'bar', 'food') for diningm.category IN ('grocery', 'supermarket', 'food_store') for food shoppingpersonal_finance_category = 'FOOD_AND_DRINK' as fallbackTaste does NOT write to Styx. It writes to its own signals.jsonl and items.jsonl.
Rally reads from Styx for spending analysis and budget tracking.
Vesper reads from Styx for daily/weekly spending summaries in briefings.
Sands reads from Styx for calendar-based spending context.
{skill_root}/scripts/plaid_sync.py (incremental, daily 7 AM cron){skill_root}/scripts/plaid_history.py (full 24-month pull){agent_root}/data/transactions.db (raw, read-only)a418e00ee21e: daily 7 AM, no_agent: trueError handling in styx follows a strict never-modify-raw-data policy: if enrichment fails, log the error, mark the record as unresolved, and continue processing.
git pull — git stash only stashes tracked files. New (untracked) files in the skill directory will block the merge. Move them aside before pulling, then compare/restore afterward.git stash pop can produce merge conflicts if both the pulled changes and the stashed changes touch the same lines.query.py --health-check does not exist — Use inline Python to verify DB integrity instead.transactions.db.DD *DOORDASH ROYALINDI, ABM-350 MISSION GARAGE). Strip prefixes before matching.***************) are skipped entirely.id auto-increments.google_auth_mcp import path is profile-dependent — When running under the indigo Hermes profile, Path.home() returns <hermes-home>/profiles/indigo/home instead of /root. Scripts that do sys.path.insert(0, str(Path.home() / '.hermes' / 'scripts')) or sys.path.insert(0, str(AGENT_ROOT / 'scripts')) will fail to find google_auth_mcp.py. Fix: Hardcode sys.path.insert(0, str(Path('<hermes-home>/scripts'))) in any script that imports google_auth_mcp. Affected scripts (all fixed as of 2026-06-04): dispatch: triage.py, check_unread.py, gmail_search.py, gmail_scan.py; taste: email_scan.py, run_historical_scans.py; scripts: email_check.py, dream_journal_pipeline.py.client_secret — The token file at <gworkspace-creds>/credentials/<third-party-or-user-email>.json may only have access_token, refresh_token, client_id — but google_auth_mcp.py needs client_secret for token refresh and a token key alias. Fix: Add client_secret from the cached client secret file. Also add token as an alias for access_token and token_uri: 'https://oauth2.googleapis.com/token'.<hermes-home>.old/data/ (styx.db, transactions.db) and secrets at <hermes-home>.old/secrets/plaid.env, but all scripts hardcode <hermes-home>/data/ and <hermes-home>/secrets/. Workaround: Create symlinks before running scripts:
mkdir -p <hermes-home>/data
ln -sf <hermes-home>.old/data/styx.db <hermes-home>/data/styx.db
ln -sf <hermes-home>.old/data/transactions.db <hermes-home>/data/transactions.db
ln -sf <hermes-home>.old/secrets <hermes-home>/secrets
<hermes-home>.old/commons/data/ocas-styx/styx_universal_enrich.py (not in the skill's scripts/ or commons/data/) must be run from that location.access_token key — When refreshing 's token, the Google OAuth response includes access_token (not token). The original file used token as the key. After refresh, both keys exist. google_auth_mcp.py reads token_data.get("token"), so ensure the token key is present.llm_resolve.py does NOT work in cron/background context — The script calls hermes ask --no-stream via subprocess, which returns no output when there is no interactive session.styx_universal_enrich.py for all categories. See references/styx_universal_enrichment.md.enrich.py script defaults to http://localhost:8888 (not 8880). If SearXNG errors with "Connection refused", verify the container port mapping: docker ps | grep searx.<hermes-home>/profiles/indigo/skills/ocas-styx/scripts/styx_universal_enrich.py. Covers retail, service, entertainment, transport, personal_care, medical, home, government, housing, travel. Skips financial categories (transfer, income, bank_fees, loan_payments, loan_disbursements). Run: python3 styx_universal_enrich.py --limit 0 to enrich all pending non-food merchants. Includes name cleaning (strips FSP*, SP , ABM-, etc.) and international address parsing (UK postcodes, city-only addresses).<hermes-home>/profiles/indigo/skills/ocas-styx/scripts/styx_places_enrich.py, NOT at <hermes-home>/skills/ocas-styx/scripts/styx_places_enrich.py (that path doesn't exist).taste_full_enrich.py reports "Failed: N" for items requiring LLM resolution. This is because llm_resolve.py calls hermes ask --no-stream which returns no output in non-interactive/cron context. These items are not lost — they remain in the Taste items queue and will be retried on the next interactive or non-cron enrichment run. Do NOT treat these failures as pipeline errors.MAX(date) in transactions.db), styx_universal_enrich.py may still find 5–15 merchants to re-enrich. This is normal: the script re-queriers pending/unresolved merchants against Google Places on each run. no_result responses are expected for heavily obfuscated names (e.g., DD *DOORDASH *********, SP THANKS ICON).When invoked as a scheduled cron job, run the full pipeline in sequence:
# Step 1: Universal merchant enrichment (all categories)
python3 <hermes-home>/profiles/indigo/skills/ocas-styx/scripts/styx_universal_enrich.py
# Step 2: Ingest enriched merchants into Taste
python3 <hermes-home>/commons/data/ocas-taste/scripts/taste_full_enrich.py
# Step 3: Deduplicate same-day Taste signals
python3 <hermes-home>/commons/data/ocas-taste/scripts/taste_signals_dedup.py
Taste signal emission: Steps 2–3 satisfy the consumption-signal contract — enriched merchant records (with Taste categories) are ingested into Taste and consumption signals are emitted to Taste's intake (deduped same-day). This is the canonical Styx→Taste enrichment path; do not bypass it. See spec-ocas-suite-cross-skill-updates.md (Preference & Data layer).
IMPORTANT script paths:
styx_universal_enrich.py is at <hermes-home>/profiles/indigo/skills/ocas-styx/scripts/ (NOT <hermes-home>/commons/data/ocas-styx/)<hermes-home>/commons/data/ocas-taste/scripts/Expected cron behaviors:
styx_universal_enrich.py may still find a small number (5–15) of merchants to re-enrich. These are already-enriched merchants being re-queried against Google Places. no_result is expected for garbled names that Google can't match — the existing enrichment from prior runs (searxng, plaid_merchant_name, internal) is preserved.taste_full_enrich.py may report "Failed: N" for items that need LLM resolution. This is a known cron limitation (llm_resolve.py calls hermes ask which returns no output without an interactive session). Items will be retried on the next non-cron enrichment run.Report format: After cron run, report: merchants enriched by category, new Taste items created, signals deduped, and any errors. See references/cron-gotchas.md for expected cron behaviors that are NOT errors.
After every enrichment run, verify the results before marking the run as complete:
transaction_merchants records at random.enrichment_runs table row for this run shows status completed.review_queue.jsonl has been updated with any new low-confidence matches.Pull the latest Styx package from GitHub source. Full procedure: references/self_update.md.
| File | When to read |
|---|---|
references/styx_universal_enrichment.md | Before running Google Places enrichment — use this instead of the food-only default |
references/financial-sync.md | Before configuring Plaid sync |
references/scripts.md | Before running enrichment or query scripts |
references/schema.md | Before querying or modifying the database |
references/query-api.md | Before writing consumer queries |
references/enrichment-pipeline.md | Before running or debugging LLM enrichment |
references/styx_universal_enrichment.md | Before running Google Places enrichment (read FIRST) |
references/self_update.md | Before running self-update |
references/cron-gotchas.md | Before debugging cron enrichment failures |
references/verify-bank-alert.md | Before cross-checking a bank/card email alert against local ledger |
See references/storage-layout.md for the full file table.
public