Commit Graph

34 Commits

Author SHA1 Message Date
ClaudioDrews 1abce75a05 fix: strip YAML quotes in curate_entry ID comparison (fixes #20)
m.group(1) captures 'f95842e2' (with quotes) from YAML id: field.
Comparison with unquoted entry_id always failed → no entry ever found.
Added .strip('"') after .strip() to normalize before comparison.
2026-06-07 08:14:40 -03:00
ClaudioDrews 6418ed30a1 v0.2.0 — automated install, community infra, 20+ audit fixes
- setup.sh: one-command install (curl | bash), 10 phases, idempotent
- smoke_test.sh + test_ingestion.py: post-install verification
- .github/: issue templates, PR template, contributing guide
- QUICKSTART.md: quick start with setup.sh as primary method
- README: release notes, install links
- install.md: banner redirecting to automated install
- All docs translated to English
2026-06-05 01:51:24 -03:00
ClaudioDrews 67ceb78202 refactor: extract shared parse_entry into icarus/parsing.py (fixes D01, D09)
D01 — parse_entry YAML fallback was duplicated and buggy (split ': '
breaks on URLs). Primary parser (yaml.safe_load) already handled
URLs correctly; manual fallback is now centralized in one module.

D09 — export-training.py had a duplicate parse_entry with the same
bug. Now imports from parsing.py.

Both files use try/except ImportError to support both package-mode
(icarus plugin) and standalone execution (CLI scripts).

Shared module returns both naming conventions (_body/body, _file/file)
for backward compatibility with fabric-retrieve and export-training.
2026-06-03 17:00:19 -03:00
ClaudioDrews 327b18bf47 Merge PR #13: provider-agnostic LLM extraction (brian-doherty)
Combines HERMES_AGENT_NAME + DeepSeek/custom endpoint vars in
.env.example Strongly Recommended section.
2026-06-03 16:37:38 -03:00
ClaudioDrews 0975b26382 fix: rename docker-compose volume vars to MEMORY_OS_* prefix
B3.1 — CRITICAL: docker-compose.yml inherited host env vars
(WIKI_PATH, HERMES_HOME, FABRIC_DIR), causing dev stacks to
accidentally mount production volumes.

Fix:
- docker-compose.yml: ~/Vault/wiki →
- docker-compose.yml: ~/.hermes →
- docker-compose.yml: ~/Vault/fabric →
- .env.example: document new vars with safety warning
- install.md: add optional volume override section with ⚠️ note

Fallback defaults (./wiki, ./hermes, ./fabric) are preserved —
zero-config dev setup still works out of the box.
2026-06-03 16:24:35 -03:00
ClaudioDrews 545a882157 fix: docker-compose.yml — Qdrant healthcheck usa grep em vez de curl
A imagem qdrant/qdrant:v1.17.1 não inclui curl, wget, python, ps,
pgrep, nc nem /dev/tcp. O healthcheck anterior nunca passava porque
o binário curl não existe.

Corrigido para: sh -c 'grep -q :18BD /proc/net/tcp'
(6333 = 0x18BD)

Bug encontrado durante Nível 2 de testes (stack isolado).
2026-06-03 16:08:24 -03:00
ClaudioDrews 35f85066a4 fix: semantic_dedup.py — adicionar using='dense' ao QueryRequest
Collection knowledge_base usa named vectors (dense + sparse). O Qdrant
exige o parâmetro 'using' em collections com múltiplos vetores nomeados.
Sem ele, query_batch_points retornava 'Not existing vector name error'
em 100% das batches (44/44). O script reportava falsos 0 near-duplicates.

Correção: 1 linha — using='dense' em models.QueryRequest (linha 176).

Validado com --dry-run --threshold 0.95 contra 8886 pontos reais:
0 erros, 1776 near-duplicate pairs detectados.
2026-06-03 15:59:28 -03:00
ClaudioDrews 3fd7cd6faf docs: Fase C — R3 (Ollama), R4 (cron jobs), R5 (agent name), R6 (Qdrant)
R3: .env.example — Embedding backend reestruturado com Option A (Ollama,
recomendado para produção) e Option B (OpenRouter). install.md passo 4
atualizado para mencionar Ollama como opção local.

R4: install.md passo 8 — adicionados 3 cron jobs stack-base na tabela
e nos exemplos CLI: holographic-memory-backup (backup semanal do
memory_store.db), wiki-raw-ingest-monitor (detecção de drift em raw/),
maas-heartbeat (healthcheck Qdrant/Redis/ARQ).

R5: .env.example + install.md passo 6 — adicionado HERMES_AGENT_NAME.
state.py write_entry agora loga warning quando AGENT_NAME não está
definido.

R6: install.md Troubleshooting — nota sobre coexistência de coleções
Qdrant (knowledge_base + outras coleções de agentes externos).
2026-06-03 15:46:31 -03:00
ClaudioDrews 6b11dc554a docs: Fase B — R1 (rulebook amendments) + R2 (soul-rulebook additions)
R1: setup/rulebook.md reescrito como pointer para o novo arquivo
modifications/execution-agent-protocol.md, que formata as 3 seções
(Memory Architecture, Memory OS Docker stack, Mandatory Verifications)
como amendments ao Execution Agent protocol. Cada amendment especifica
onde inserir relativo às regras do protocolo. install.md passo 6
atualizado para refletir o novo formato.

R2: modifications/soul-rulebook.md ganhou duas novas seções:
- Fact feedback rule (fecha o loop de qualidade dos fatos)
- Honcho deprecation (avisa para não configurar Honcho)
2026-06-03 15:32:34 -03:00
ClaudioDrews 170a6ba6be fix: Fase A — 3 correções de código (R7, R8, R9)
R7 (V5.03/B11): hooks.py _search_qdrant — os.environ mutation agora usa
try/finally com restore. Chave OPENROUTER_API_KEY é injetada
temporariamente só durante o import do context_enhancer.

R8 (V5.06/B06): state.py write_entry e write_memory_file — substituído
path.write_text() por tmp + rename (escrita atômica). Evita corrupção
de arquivo em caso de crash durante o write.

R9 (item 5): state.py read_pending — normalizado agent = AGENT_NAME or
'agent' para consistência com write_entry. Corrige lógica de assignment
quando HERMES_AGENT_NAME não está definido.
2026-06-03 15:05:54 -03:00
Brian Doherty 2d6281e86c feat: provider-agnostic LLM extraction
Make the Icarus extraction pipeline work with any LLM provider
instead of hardcoding OpenRouter.

New env vars (all optional, backward compatible):
  DEEPSEEK_API_KEY       — route directly to api.deepseek.com
  ICARUS_ENDPOINT        — fully custom v1/chat/completions URL
  ICARUS_API_KEY_ENV     — env var name for the custom endpoint key

Resolution priority:
  1. ICARUS_ENDPOINT + ICARUS_API_KEY_ENV (fully custom)
  2. DEEPSEEK_API_KEY → api.deepseek.com
  3. OPENROUTER_API_KEY → openrouter.ai (existing behaviour, unchanged)

Model names are automatically normalized: OpenRouter-style
deepseek/deepseek-v4-flash slugs have the provider prefix
stripped for direct API calls.

No breaking changes. If DEEPSEEK_API_KEY and ICARUS_ENDPOINT
are both unset, behaviour is identical to previous versions.
2026-06-03 11:29:15 -05:00
ClaudioDrews 144795bf74 perf: Qdrant query_batch_points + SQLite FTS5 for semantic_dedup and fabric-retrieve
V4.01: Replace O(n²) brute-force cosine similarity in semantic_dedup.py
  with Qdrant query_batch_points() — delegates nearest-neighbor search
  to the native HNSW index. Batch size auto-scales (min 200, adjusted
  for collection size). Self-match excluded server-side via
  HasIdCondition filter. Graceful fallback if qdrant-client not installed.

V4.03: Replace glob+parse in fabric-retrieve.py retrieve() with SQLite
  fabric_index + fabric_fts (FTS5). _ensure_fabric_index() rebuilds the
  index lazily when any fabric .md mtime exceeds the stored max mtime.
  Transactional rebuild (BEGIN; DELETE; DELETE; ...; COMMIT) prevents
  index corruption on partial failure. All score_entry fields preserved
  as columns — no YAML parsing after initial index build.
2026-06-03 12:14:30 -03:00
ClaudioDrews 45e2b075f7 perf: O(1) lookups for obsidian _find_entry_file + export-training _resolve_ref
V4.02: Replace glob+parse scan in _find_entry_file with in-memory dict index
  - _build_entry_index() scans fabric dirs once, builds (agent,id)→stem dict
  - Index rebuilds lazily when fabric_dir.stat().st_mtime changes
  - O(n) rebuild only on changes, O(1) per lookup thereafter

V4.07: Replace 4× O(n) linear scans in _resolve_ref with prebuilt indices
  - index_by_id, index_by_cycle: exact dict lookups (covers ~90% of calls)
  - by_agent_entries: scoped substring scans (per-agent, not full list)
  - Slow path preserved for standalone calls without indices
  - All 4 call sites in build_pairs() updated to pass indices
2026-06-03 12:04:14 -03:00
ClaudioDrews 1422a6d583 fix: resolve 5 easy Phase 4 performance/resilience issues
V4.04: Add retry_transient() to dlq_manager — re-enables reported transient
  failures for retry with --retry CLI flag, increments retry_count/last_retry

V4.05: save_report() now truncates JSONL to MAX_REPORT_HISTORY entries
  after each append (best-effort rotation)

V4.06: Wrap datetime.fromisoformat() in try/except (ValueError, TypeError)
  in get_status_summary() — skips entries with malformed timestamps

V4.08: Replace body[:50] dedup key with hashlib.sha256(body).hexdigest()
  in fabric-retrieve.py deduplicate()

V4.09: Add OrderedDict LRU cache (256 entries) to embedding.py get_embedding()
  — avoids recomputing embeddings for repeated text
2026-06-03 11:53:18 -03:00
ClaudioDrews e5edb58a19 fix: resolve all 8 Phase 3 config/path issues
V3.01-V3.03: Replace hardcoded Path.home() / 'Vault' with os.environ.get()
  - wiki_continuous_ingest.py: WIKI_ROOT via env with fallback
  - bulk_wiki_ingest.py: WIKI_ROOT + EMBEDDING_MODEL via env with fallback
  - backfill_decay_metadata.py: VAULT_ROOT via VAULT_PATH env with fallback

V3.04-V3.05: Replace hardcoded EMBEDDING_MODEL string with os.environ.get()
  - context_enhancer.py + bulk_wiki_ingest.py

V3.06: Replace ~/.hermes til fallback with ./hermes (Docker doesn't expand ~)

V3.07: Add /fabric volume mount (~/Vault/fabric:/fabric:rw)

V3.08: Add Qdrant healthcheck + change worker qdrant dependency from
  service_started to service_healthy
2026-06-03 11:44:22 -03:00
ClaudioDrews 384395b78d fix: resolve all 7 Phase 1 setup/infra issues
V1.01: Add fastembed, httpx, redis to requirements.txt (now 10 lines, 9 deps)
V1.03: Create setup/setup_db.py — idempotent SQLite schema for state.db + memory_store.db
V1.04: Create .dockerignore (21 patterns — .env, *.key, __pycache__, etc.)
V1.05: Dockerfile COPY --chown=appuser:appuser (after useradd, before USER switch)
V1.06: Add EXPOSE 8000 + HEALTHCHECK (Redis ping, 30s interval)
V1.07: Document database setup as Step 2 in install.md (pip install + setup_db.py + table listing)
V1.08: Remove change-me from REDIS_PASSWORD in .env.example
V1.09: Replace gcc with build-essential + python3-dev in Dockerfile
2026-06-03 11:31:04 -03:00
ClaudioDrews 55ba9cee1c fix: move get_sparse_vector outside get_embedding body (V2.12 regression)
get_sparse_vector was accidentally inserted between get_embedding's
signature and its body, disconnecting the embedding call and leaving
module-level code referencing undefined variables. Moved to standalone
function before get_embedding.
2026-06-03 11:03:18 -03:00
ClaudioDrews a3d644a5c9 fix: resolve 4 code-critical issues from Fase 2 audit
V2.07 (A01): Replace subprocess.run(['arq', ...]) with programmatic
  arq.worker.run_worker(WorkerSettings). Fixes signal handling,
  graceful shutdown, and logging unification.

V2.10 (D11): Fix filename → file_path in backfill_decay_metadata.py
  resolve_timestamp() now reads payload.file_path (consistent with
  what bulk_wiki_ingest.py writes). Replaced VAULT_ROOT path
  reconstruction with direct Path(file_path).

V2.11 (D05): Auto-create Qdrant collection in bulk_wiki_ingest.py
  If collection doesn't exist, creates it with hybrid schema
  (dense 4096d Cosine + sparse BM25) instead of sys.exit(1).

V2.12 (D27): Add sparse vector generation to bulk_wiki_ingest.py
  Optional fastembed BM25 sparse vectors alongside dense embeddings.
  Falls back to dense-only if fastembed not installed. Vectors stored
  as Qdrant named vectors: {'dense': ..., 'sparse': ...}
2026-06-03 10:54:53 -03:00
ClaudioDrews ff781f9824 feat: add EMBEDDING_API_KEY for authenticated non-OpenRouter endpoints (fixes #8)
embedding.py:
- Add EMBEDDING_API_KEY env var (no fallback — explicit per-branch)
- OpenRouter branch: uses OPENROUTER_API_KEY + vendor headers
- Generic branch: sends Authorization: Bearer <EMBEDDING_API_KEY> if set
- Unauth fallback: no header (localhost, no auth needed)
- Credential isolation: no cross-branch key leakage

.env.example + setup/install.md:
- Document EMBEDDING_API_KEY as optional for non-OpenRouter
  authenticated endpoints (vLLM --api-key, custom hosted services)
2026-06-03 09:37:38 -03:00
ClaudioDrews 4e1f543a1d docs: rewrite installation guide with real scripts and idempotent modifications (fixes #11)
Replace incomplete/outdated install instructions with accurate,
detailed steps grounded in production infrastructure.

setup/install.md:
- Step 3: run docker compose in-place (not via cp to ~/memory-os/)
- Step 5: explicit ~/.hermes/rulebook.md path, create-if-missing,
  idempotency guard with Memory OS Additions v1 marker
- Step 6: document VAULT_PATH, wiki directory structure,
  link to vault-curator as optional enrichment tool
- Step 7: replace fictional crontab with real maintenance scripts
  and hermes cron create commands; add backfill prerequisite note
  and exempt-prefix env var docs

setup/rulebook.md (NEW):
- Generic template with 3 blocks: Memory Architecture,
  Memory OS infrastructure, Mandatory Verifications
- Idempotency markers on every block

modifications/soul-rulebook.md:
- SOUL.md-only (rulebook sections moved to setup/rulebook.md)
- Conditional instructions (add level 2 vs full hierarchy)
- Conflict resolution table (4 rules)
- Context injection convention with acting vs reasoning distinction

scripts/:
- 8 maintenance scripts updated with sanitized equivalents
- decay_scanner.py, semantic_dedup.py: collection_ → DECAY/DEDUP_EXEMPT_PREFIXES env vars
- reflection_trigger.py: hardcoded venv path → /usr/bin/env python3
- All shebangs normalized, all internal references removed
2026-06-03 09:29:19 -03:00
ClaudioDrews b33b8d4196 fix: SyntaxError on QDRANT_API_KEY placeholder (fixes #10)
Split the corrupted line 15 into two valid assignments:
- QDRANT_API_KEY = os.environ.get('QDRANT_API_KEY', '')
- EMBEDDING_DIMS = int(os.environ.get('EMBEDDING_DIMS', '4096'))

The merged line 'QDRANT_API_KEY=os.env...DIMS = ...' was a leftover
placeholder that prevented the ARQ worker from starting.

Reported-by: CG1up
2026-06-03 08:18:32 -03:00
ClaudioDrews 598eaf19d0 fix(sanitize): change system prefix replacement from silent removal to [REDACTED]
- Silent removal (empty string) left grammatically broken sentences
  and no audit trail. [REDACTED] preserves context structure.
- Control chars and zero-width Unicode keep empty string replacement
  (invisible characters, removal is correct).
- Updated 4 test assertions in _test_sanitize.py.
2026-06-02 13:45:02 -03:00
David Soff a5c1344afb
security(hooks, context_enhancer): add dual-layer prompt injection sanitization (#5)
security(hooks, context_enhancer): add dual-layer prompt injection sanitization

- Layer 1 (hooks.py): aggressive sanitization at prompt boundary with
  11 regex patterns + heuristic. Detects override directives, template
  injection, URI schemes, system prefixes, HTML/XML, control chars,
  zero-width Unicode, and code fences. Applied to fabric, Qdrant,
  sessions, and facts injection points. Complements existing
  _is_system_injection() (which filters ingest, not egest).

- Layer 2 (context_enhancer.py): lightweight sanitization on all
  search result content_preview outputs (hybrid, dense, sparse,
  lexical, sqlite).

- _test_sanitize.py: manual validation script with 24 test cases.

- Replacement strategy: [REDACTED] preserves audit trail and
  grammatical context instead of silent removal.
2026-06-02 13:43:34 -03:00
David Soff 0d6fc33c00
fix(context_enhancer): refactor embed_query_sparse to use stdin (#4)
fix(context_enhancer): refactor embed_query_sparse to use stdin instead of f-string interpolation

- Eliminates shlex.quote() crash with apostrophes (SyntaxError in subprocess)
- Eliminates user text interpolation into Python code string
- Hardcodes BM25 model name (was already hardcoded module-level)
- Corrects FastEmbed API usage: model.embed([query]) instead of model.embed(string)
2026-06-02 13:28:32 -03:00
brian-doherty 02018160d2
fix(worker): add missing import os, Qdrant auth, and env config (#6)
Fix 4 bugs that prevent the ARQ worker from booting with a
Qdrant instance that has an API key set:

1. reflection.py: add missing 'import os' (NameError crash at
   COLLECTION_NAME = os.environ.get(...))

2. local_qdrant.py: read QDRANT_API_KEY from environment

3. local_qdrant.py: pass api_key= to AsyncQdrantClient and set
   https=False to prevent SSL error when API key triggers
   auto-HTTPS against a local HTTP Qdrant instance

4. docker-compose.yml: pass QDRANT_API_KEY env var to the
   worker service so the key is available inside the container
2026-06-02 13:21:30 -03:00
Claudio Drews 8e8ea95458
fix(embedding): make OpenRouter auth and headers conditional — unblock 100% local usage (#7)
- Remove unconditional OPENROUTER_API_KEY check that blocked local providers
  (Ollama, vLLM, llama.cpp) from running without an API key
- Send OpenRouter-specific headers (HTTP-Referer, X-Title) only when
  EMBEDDING_API_BASE contains 'openrouter'
- Stop sending empty Authorization header to non-OpenRouter endpoints
- Update .env.example and setup/install.md to clarify that
  OPENROUTER_API_KEY is only required for OpenRouter

Closes #1

Co-authored-by: ClaudioDrews <claudio@drews.com.br>
2026-06-02 13:05:12 -03:00
Claudio Drews bae035f8fb
Merge pull request #3 from ClaudioDrews/fix/embedding-provider-agnostic
fix: make embedding provider-agnostic via env vars (closes #1)
2026-06-02 10:11:58 -03:00
Claudio Drews d206c3eae2
Merge pull request #2 from ClaudioDrews/fix/6-to-7-layers
docs: fix "Six" → "Seven" memory layers in README
2026-06-02 10:11:54 -03:00
Claudio Drews 1281d7a778 fix: make embedding provider-agnostic via env vars
- embedding.py: EMBEDDING_API_BASE and EMBEDDING_MODEL now read from env
  with OpenRouter defaults (preserves backward compatibility)
- .env.example: document embedding backend config with Qwen3 rationale
- setup/install.md: update prerequisite to reflect configurable backend
- layers/05-qdrant.md: add "Why Qwen3-Embedding-8B" section
  (multilingual, quality, speed, cost)

Closes #1
2026-06-02 09:45:31 -03:00
Claudio Drews 7c9a8f02ae docs: fix "Six" → "Seven" memory layers in README 2026-06-02 09:42:58 -03:00
Claudio Drews a4ca094a7b docs: add ground-truth hierarchy layer (07) — the critical missing piece
Context injection delivers memory into the prompt, but without an explicit
Ground Truth hierarchy the agent treats it as optional suggestion. This
layer documents the fix: injected memory ([qdrant], [fabric], [sessions],
[facts]) ranked as authoritative for documented knowledge, with clear
conflict-resolution rules against terminal output and training knowledge.
2026-06-01 19:23:55 -03:00
ClaudioDrews b7d637a52c Add pixel-art banner to README 2026-05-31 23:32:27 -03:00
ClaudioDrews 636acabac7 Add 3 operational skills: memory-architecture, context-injection, llm-wiki 2026-05-31 16:59:12 -03:00
ClaudioDrews 0b32ffcfc2 Initial commit: Memory OS — 6-layer memory architecture for Hermes Agent 2026-05-31 16:50:37 -03:00