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.

Ir para a instalação

Informações da origem

Repositório
grahama1970/agent-stack-public
Última atividade na origem
24 de setembro de 2026 às 15:51
Idioma detectado do SKILL.md
inglês
Estrelas
0
Forks
0

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Explorador de arquivos
3 arquivos

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
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
Ver no GitHub
Este SKILL.md e muito grande, entao o SkillsMP mostra aqui apenas a primeira secao. Ver no GitHub