Skip to main content

best-practices-arangodb

Repo-specific ArangoDB best practices: leverage text_en analyzer (stop words, stemming, BM25), use AQL functions (LEVENSHTEIN_DISTANCE, TOKENS, NGRAM_SIMILARITY, COSINE_SIMILARITY), store domain knowledge in collections not Python code, and never duplicate DB capabilities.

الانتقال إلى التثبيت

معلومات المصدر

المستودع
grahama1970/agent-stack-public
آخر نشاط في المصدر
٢٤ سبتمبر ٢٠٢٦ في ١٥:٥١
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٠
التفرعات
٠

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

مستكشف الملفات
3 ملفات

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
best-practices-arangodb
description
Repo-specific ArangoDB best practices: leverage text_en analyzer (stop words, stemming, BM25), use AQL functions (LEVENSHTEIN_DISTANCE, TOKENS, NGRAM_SIMILARITY, COSINE_SIMILARITY), store domain knowledge in collections not Python code, and never duplicate DB capabilities.
triggers
["best practices arangodb","arangodb conventions","arango search","bm25 search","stop words","stemming","levenshtein","fuzzy matching","domain terms","entity classification","aql query"]
license
MIT
metadata
{"database":"ArangoDB","client":"python-arango","defaults":{"database_name":"memory","analyzers":["text_en","identity"],"search_pattern":"BM25 + cosine rerank","domain_collections":["domain_terms","taxonomy_vocabulary","sparta_controls"],"max_python_stopwords":0,"max_hardcoded_domain_terms":0,"performance_budgets":{"bm25_ms":100,"hybrid_ms":300,"entity_extraction_ms":1000,"exact_lookup_ms":10}}}
taxonomy
["precision","resilience"]
provides
["best-practices-arangodb","skill-validation"]
composes
["best-practices-python","memory","ingest-code","agentic-evals"]
disciplines
["engineering-standards","memory-knowledge"]
> STOP. READ THIS ENTIRE SKILL.MD BEFORE CALLING ANY ENDPOINT. # ArangoDB Best Practices (Project Skill) This skill codifies rules for using ArangoDB correctly in this repo. The core principle: **ArangoDB's built-in features MUST be used instead of reimplementing them in Python.** ## The Anti-Pattern This Skill Prevents Python code that duplicates what ArangoDB already does: - Stop word lists in Python (ArangoDB `text_en` already strips them) - Hand-rolled stemming in Python (ArangoDB `text_en` already stems via Snowball) - Regex patterns that classify entity types (ArangoDB collections already know what things are) - Hardcoded domain term lists (ArangoDB collections are the source of truth) - Python-side BM25/TF-IDF scoring (ArangoDB `BM25()` does this server-side) - Python-side cosine similarity loops (ArangoDB `COSINE_SIMILARITY()` does this server-side) See [references/anti-patterns-duplicated-functionality.md](references/anti-patterns-duplicated-functionality.md) for 7 real removed-from-codebase examples. ## When to Apply Use this skill whenever you: - Write AQL queries or ArangoSearch code - Add new collections or ArangoSearch views - Work with entity extraction, intent mapping, or text search - Add domain knowledge (terms, keywords, frameworks) to the system - Implement fuzzy matching, spellcheck, or text classification ## Rules (priority order) ### 1. CRITICAL: No Stop Words in Python — `arango-no-python-stopwords` ArangoDB's `text_en` analyzer already removes English stop words. NEVER maintain a stop word list in Python. ```python # BAD — duplicates text_en analyzer _STOPWORDS = frozenset({"the", "a", "an", "of", "for", "in", ...}) # GOOD — let ArangoDB handle it cursor = db.aql.execute("RETURN TOKENS(@query, 'text_en')", bind_vars={"query": text}) ``` ### 2. CRITICAL: No Hand-Rolled Stemming — `arango-no-python-stemming` ArangoDB's `text_en` uses Snowball stemming. NEVER strip suffixes in Python. ```python # BAD for suffix in ("s", "es", "ing", "ed", "ly"): if word.endswith(suffix): stem = word[:-len(suffix)] # GOOD — ArangoDB stems automatically: "recommendations" matches "recommendation" ``` ### 3. CRITICAL: Domain Knowledge in ArangoDB — `arango-no-hardcoded-domain-lists` Domain terms, keywords, frameworks belong in ArangoDB collections, not Python dicts/frozensets. Collections: `domain_terms`, `taxonomy_vocabulary`, `sparta_controls`. ### 4. CRITICAL: No Regex for Entity Classification — `arango-no-regex-classification` Regex is for TOKENIZATION (finding candidates). Classification (what is it?) comes from ArangoDB. **Exception**: `_extract_control_ids()` in trace.py uses regex to FIND ID-like patterns — that's tokenization, not classification. ### 5. HIGH: Use AQL Functions — `arango-use-aql-functions` | Need | AQL Function | Python Anti-Pattern | |------|-------------|-------------------| | Fuzzy match | `LEVENSHTEIN_DISTANCE(a, b)` | `rapidfuzz` loop over all docs | | Tokens | `TOKENS(@q, 'text_en')` | Python stop words + stemming | | N-gram | `NGRAM_SIMILARITY(a, b, n)` | Hand-rolled comparison | | BM25 | `BM25(doc)` | Python TF-IDF | | Cosine | `COSINE_SIMILARITY(a, b)` | `numpy` dot product on all docs | ### 6. HIGH: Two-Stage BM25 + Cosine Rerank — `arango-two-stage-search` NEVER brute-force scan all embeddings. BM25 top-100 → cosine rerank to top-k. ### 7. HIGH: One AQL Round-Trip — `arango-single-roundtrip` NEVER split one server-side operation into two network round-trips. ### 8. MEDIUM: Batch Operations — `arango-batch-operations` Use bulk AQL for large-scale operations (10K+ documents): ```aql -- BAD: 222K individual UPDATE calls (3.3/s = 18 hours) UPDATE {_key: @key} WITH {field: @val} IN collection -- GOOD: Bulk update (50-100 docs per query) FOR item IN @updates UPDATE {_key: item.key} WITH {field: item.val} IN collection RETURN 1 ``` For batch exact matches, use one AQL with `FOR cid IN @cids` instead of N individual queries. For HTTP endpoints with internal ThreadPoolExecutor, use batch endpoints (e.g., `/create-evidence-case-batch` with `max_workers: 32`). ### 9. MEDIUM: Identity Analyzer for Exact Matches — `arango-identity-for-exact` Use `identity` (not `text_en`) for control IDs, framework names, categories. ### 10. MEDIUM: Database Name is Always "memory" — `arango-db-name-memory` The only database is `memory`. `ARANGO_DB=lessons` anywhere = bug. ### 11. LOW: Cache Vocabulary Lookups — `arango-cache-vocab` Module-level `_cache: T | None = None` + `_get_*()` function. ### 12. CRITICAL: BM25 Score > 0 Is NOT Grounding — `arango-bm25-not-grounding` BM25 ALWAYS returns results for any security-adjacent query. A score > 0 does NOT prove a term exists. ID-like terms must be grounded via exact `control_id` match or fuzzy edit distance. ```python # BAD — BM25 returns results for "X23-MUSTARD" because it matches "spoofing" # GOOD — exact match: FOR c IN sparta_controls FILTER UPPER(c.control_id) == @cid ``` ### 13. HIGH: Pre-Filter Before LEVENSHTEIN_DISTANCE — `arango-prefilter-fuzzy` Full table scan = 268ms. Length ±3 + prefix overlap pre-filter = 4ms. ```aql -- GOOD — pre-filter reduces 9,337 → 61 candidates FOR c IN sparta_controls LET cid = UPPER(c.control_id) FILTER LENGTH(cid) >= LENGTH(@candidate) - 3 AND LENGTH(cid) <= LENGTH(@candidate) + 3 FILTER LEFT(cid, 1) == LEFT(@candidate, 1) OR CONTAINS(cid, LEFT(@candidate, 2)) LET dist = LEVENSHTEIN_DISTANCE(cid, @candidate) FILTER dist <= 2 SORT dist LIMIT 3 RETURN {control_id: c.control_id, distance: dist} ``` ### 14. HIGH: Concurrent Queries — `arango-concurrent-queries` ArangoDB is multi-threaded. Use `ThreadPoolExecutor` for independent queries. ```python with ThreadPoolExecutor(max_workers=3) as pool: f1 = pool.submit(lambda: list(db.aql.execute("..."))) f2 = pool.submit(lambda: list(db.aql.execute("..."))) results1, results2 = f1.result(), f2.result() ``` ### 15. HIGH: Performance Budgets — `arango-performance-budgets` | Operation | Budget | |-----------|--------| | BM25 text search | <100ms | | Hybrid search (BM25 + cosine) | <300ms | | Entity extraction (full pipeline) | <1s | | Exact lookup by indexed field | <10ms | Use `tests/test_memory_performance.py` to validate. ### 16. MEDIUM: No DOCUMENT() in AQL Loops — `arango-no-document-in-loops` ```aql -- BAD: DOCUMENT('lessons', key) in a loop -- GOOD: FOR l IN lessons FILTER l._key == key LIMIT 1 ``` ### 17. CRITICAL: Always Hybrid Search — `arango-always-hybrid-search` Every retrieval query MUST use BM25 + semantic embedding + multi-hop graph traversal. NEVER use a single lane alone. ```python # GOOD — via RecallSources from graph_memory.hybrid_search import hybrid_search_sparta_qra results = hybrid_search_sparta_qra(query, db, embedding_service, k=12) ``` Direct single-lane queries only in unit tests and diagnostics. ### 18. CRITICAL: No Silent Fallback — `arango-no-silent-fallback` When a search lane fails, the code MUST: 1. **Log at `logger.error`** (NOT `logger.debug`) with exception context 2. **Signal degradation** in the result so the caller knows 3. **NEVER** silently return partial results as if complete 4. **NEVER** use bare `except Exception:` — always capture as `except Exception as exc:` `logger.debug` in exception handlers is a silent fallback — invisible in production. This rule applies to ALL AQL queries, view searches, and collection operations. ### 18b. CRITICAL: Every Collection Searchable via /recall — `arango-recall-all-collections` Every document collection with an ArangoSearch view MUST be searchable via `/recall`. Use scope routing to filter (e.g., `scope=sparta` → only `sparta_*` collections). New collections MUST be added to both the ArangoSearch view AND `bm25_rank()`. `/recall` MUST return BM25 + semantic (cosine) + multi-hop graph traversal for ALL collections. ### 18c. CRITICAL: Use @@coll bind variables — `arango-no-collection-interpolation` Collection names in AQL MUST use `@@coll` bind variables, NOT f-string interpolation. ```python # WRONG — AQL injection risk aql = f"FOR d IN {collection} FILTER d._key == @key RETURN d" # RIGHT — bind variable for collection aql = "FOR d IN @@coll FILTER d._key == @key RETURN d" db.aql.execute(aql, bind_vars={"@coll": collection, "key": key}) ``` ### 19. MEDIUM: Cache Errors Must Log — `arango-cache-must-log` DB-backed cache `except` blocks MUST log at `logger.error`. Cross-ref: `/best-practices-python` rule `correctness-no-silent-fallback`. ### 20. HIGH: No Null Filters for Backfills — `arango-no-null-backfill` NEVER use `FILTER doc.field == null` for batch backfill operations on large collections. **Why it fails:** As documents get updated, fewer match `field == null`. Without an index on the field, ArangoDB scans progressively more documents to find fewer matches. Rate degrades from 20/s → 7/s → worse. ```aql -- BAD — progressively slower as nulls decrease (no index helps) FOR doc IN sparta_qra FILTER doc.lineage == null FILTER doc._key > @last_key LIMIT 200 RETURN doc -- GOOD — version-based, indexable, constant performance FOR doc IN sparta_qra FILTER doc.lineage.graph_version < @target_version OR doc.lineage == null FILTER doc._key > @last_key LIMIT 200 RETURN doc ``` **Pattern:** 1. Add a version field (e.g., `lineage.graph_version`) 2. Create persistent index: `db.sparta_qra.ensureIndex({type: "persistent", fields: ["lineage.graph_version"]})` 3. Query by version, not null 4. Bump version constant when schema changes **Real incident (2026-04-13):** 171K QRA lineage backfill started at 20/s, degraded to 7/s by 50% completion. Root cause: unindexed `lineage == null` filter. ### 21. HIGH: Sparse Vector Index UPDATE Bug — `arango-vector-update-bug` ArangoDB versions < 3.12.9 have a bug where sparse vector indexes block UPDATE operations on documents that don't have the vector field, even though `sparse: true` should allow this. **Error:** `[HTTP 400][ERR 10] vector field not present in document <key>` **GitHub Issue:** [arangodb/arangodb#22568](https://github.com/arangodb/arangodb/issues/22568) ```python # BAD — fails on docs without embedding (versions < 3.12.9) db.aql.execute("UPDATE {_key: @key} WITH {lineage: @lin} IN sparta_qra", ...) # WORKAROUND — drop index, update, recreate coll = db.collection('sparta_qra') idx_config = next(i for i in coll.indexes() if i['type'] == 'vector') coll.delete_index(idx_config['id']) # ... do all updates ... coll.add_index({ 'type': 'vector', 'name': idx_config['name'], 'fields': idx_config['fields'], 'params': idx_config['params'], 'sparse': True, 'inBackground': True }) ``` **When to apply:** Any batch update to a collection with a sparse vector index where some docs lack the vector field. **Real incident (2026-04-14):** Lineage backfill failed on 269 docs without embeddings. Error message was misleading ("vector field not present") even when providing the embedding in the UPDATE — the index validates against the OLD document state. ### 22. CRITICAL: Arango NEVER Stores Embeddings — `arango-no-embedding-arrays` **Operator ruling (2026-07-31), supersedes the old `arango-require-embeddings` rule.** ArangoDB must never hold embedding/vector arrays. Qdrant is the only vector store. Rationale: embedding arrays inflate the dataset past the ArangoDB community-edition size cap, at which point the server shuts down and demands a paid license. Arango documents carry pointer metadata only: `qdrant_collection`, `qdrant_point_id`, `embedding_model`, `embedding_version`, `text_hash`, `semantic_sync_state`. The dense lane of hybrid search queries Qdrant via the memory daemon, never `COSINE_SIMILARITY` over Arango-resident arrays. ```python # BAD — writing a vector into Arango (community-edition size bomb) db.aql.execute("INSERT {question: @q, answer: @a, embedding: @emb} INTO sparta_qra", ...) # GOOD — canonical doc through the memory daemon; semantic sync owns Qdrant client.post("/upsert", json={ "collection": "sparta_qra", "documents": [{"_key": "...", "question": q, "answer": a}], }) ``` **Monitoring (count should be 0 — a hit is a violation, not a gap):** ```aql FOR d IN sparta_qra FILTER HAS(d, "embedding") AND d.embedding != null COLLECT WITH COUNT INTO cnt RETURN cnt ``` **Detection:** `/ops-arango embeddings` reports violations. `--fix` is refused by design; migration is owned by the memory repo (`scripts/migrate_arango_embeddings_to_qdrant.py`). Rules 21 (sparse vector index UPDATE bug) and the vector-index server flags in rule 24 are legacy context for pre-migration collections; do not add new vector indexes to Arango. **Real incident (2026-04-16):** 2,937 QRAs missing embeddings discovered during batch update. These were created by a script that skipped the embedding step. ### 22b. CRITICAL: Qdrant Is Single-Owner — `qdrant-single-owner-access` Qdrant is the counterpart of rule 22: since it is the **only** vector store, it is also a **single-owner** store. The memory repo (`graph_memory`) owns all Qdrant collection config, upserts, and semantic sync. A skill MUST NOT embed raw Qdrant client authoring code. - **NEVER** `from qdrant_client import QdrantClient` (the raw PyPI library) in a
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub