zettelforge 2.4.0__tar.gz → 2.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {zettelforge-2.4.0 → zettelforge-2.4.2}/CHANGELOG.md +140 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/PKG-INFO +49 -48
- {zettelforge-2.4.0 → zettelforge-2.4.2}/README.md +48 -47
- {zettelforge-2.4.0 → zettelforge-2.4.2}/config.default.yaml +5 -2
- zettelforge-2.4.2/docs/assets/cf-analytics.js +9 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/how-to/configure-typedb.md +6 -4
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/how-to/troubleshoot.md +24 -2
- zettelforge-2.4.2/docs/human-evaluation-rubric.md +136 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/index.md +10 -4
- zettelforge-2.4.2/docs/reference/architecture-deep-dive.md +380 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/reference/configuration.md +4 -4
- zettelforge-2.4.2/docs/reference/module-inventory.md +461 -0
- zettelforge-2.4.2/docs/rfcs/RFC-007-operational-telemetry.md +321 -0
- zettelforge-2.4.2/docs/rfcs/RFC-009-enrichment-pipeline-v2.md +674 -0
- zettelforge-2.4.2/docs/rfcs/RFC-010-enrichment-hotfix.md +115 -0
- zettelforge-2.4.2/docs/superpowers/research/2026-04-24-phase-0.5-attribution-prelim.md +120 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/mkdocs.yml +5 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/pyproject.toml +1 -1
- {zettelforge-2.4.0 → zettelforge-2.4.2}/server.json +1 -1
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/config.py +12 -2
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/consolidation.py +19 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/llm_providers/ollama_provider.py +15 -2
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/memory_manager.py +99 -3
- zettelforge-2.4.2/src/zettelforge/scripts/human_eval_sampler.py +175 -0
- zettelforge-2.4.2/src/zettelforge/scripts/telemetry_aggregator.py +176 -0
- zettelforge-2.4.2/src/zettelforge/scripts/telemetry_dashboard.py +256 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/sqlite_backend.py +96 -29
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/storage_backend.py +9 -0
- zettelforge-2.4.2/src/zettelforge/telemetry.py +340 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_config.py +53 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_consolidation.py +30 -0
- zettelforge-2.4.2/tests/test_human_eval_sampler.py +125 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_llm_providers.py +18 -3
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_logging_compliance.py +69 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_sqlite_backend.py +76 -2
- zettelforge-2.4.2/tests/test_telemetry_aggregator.py +142 -0
- zettelforge-2.4.2/tests/test_telemetry_collector.py +351 -0
- zettelforge-2.4.2/tests/test_telemetry_dashboard.py +238 -0
- zettelforge-2.4.2/tests/test_telemetry_integration.py +263 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/.github/CODEOWNERS +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/.github/SECURITY.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/.github/dependabot.yml +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/.github/pull_request_template.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/.github/workflows/ci.yml +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/.github/workflows/docs.yml +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/.github/workflows/publish.yml +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/.github/workflows/snyk-security.yml +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/.gitignore +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/ARCHITECTURE.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/CODEOWNERS +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/CODE_OF_CONDUCT.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/CONTRIBUTING.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/Dockerfile +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/GOVERNANCE.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/LICENSE +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/MANIFEST.in +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/SECURITY.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/BENCHMARK_REPORT.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/LOCOMO_BENCHMARK_COMPARISON.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/auto_ralph.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/benchmark_harness.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/cti_benchmark_v2.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/cti_retrieval_benchmark.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/cti_retrieval_results.json +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/cti_v2_results.json +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/ctibench_benchmark.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/ctibench_results.json +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/dataset.json +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/enterprise-attack.json +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/evolve_benchmark.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/evolve_results.json +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/graph_test.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/locomo_benchmark.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/locomo_results.json +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/locomo_results_v1.3.0_baseline.json +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/memoryagentbench.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/memoryagentbench_results.json +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/mempalace_benchmark.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/mempalace_results.json +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/naive_memory.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/opencti_benchmark.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/ragas_benchmark.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/ragas_cti_results.json +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/ragas_results.json +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/results/benchmark_report.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/results/ralph_optimization_log.json +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/scale_benchmark.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/benchmarks/scale_results.json +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/config.example.yaml +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docker/docker-compose.yml +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/.well-known/security.txt +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/CNAME +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/architecture-diagram.mmd +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/archive/PACKAGE_SUMMARY.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/archive/README.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/archive/SKILL.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/assets/ZettelForge_Architecture.mmd +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/assets/architecture-overview.mmd +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/assets/architecture-read-path.mmd +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/assets/architecture-write-path.mmd +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/assets/demo.gif +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/assets/favicon-16.png +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/assets/favicon-32.png +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/assets/favicon-512.png +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/assets/favicon-64.png +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/assets/favicon-apple-touch.png +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/assets/favicon-old.svg +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/assets/favicon.svg +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/assets/logo.svg +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/assets/social-preview.png +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/assets/threatrecall-lockup-monogram.svg +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/assets/threatrecall-lockup.svg +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/assets/threatrecall-logo-flat.svg +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/assets/threatrecall-logo-philosophy.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/assets/threatrecall-logo.png +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/assets/threatrecall-mark.png +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/assets/zettelforge_architecture-light.svg +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/assets/zettelforge_architecture.svg +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/brand/brandIdentity.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/brand/colors_and_type.css +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/explanation/architecture.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/explanation/epistemic-tiers.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/explanation/stix-in-zettelforge.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/explanation/two-phase-pipeline.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/explanation/zettelkasten-philosophy.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/how-to/configure-lancedb.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/how-to/configure-opencti.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/how-to/ingest-news-report.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/how-to/integrate-llm-agent.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/how-to/migrate-jsonl-to-sqlite.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/how-to/query-apt-tools.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/how-to/reproduce-benchmarks.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/how-to/resolve-aliases.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/how-to/run-temporal-query.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/how-to/store-threat-actor.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/how-to/upgrade.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/llms.txt +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/narrative/2026-04-16-the-memory-problem.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/overrides/main.html +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/reference/governance-controls.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/reference/memory-manager-api.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/reference/retrieval-policies.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/reference/stix-schema.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/rfcs/RFC-001-conversational-entity-extractor.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/rfcs/RFC-002-universal-llm-provider.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/rfcs/RFC-003-adversarial-review.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/rfcs/RFC-003-read-path-depth-routing.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/stylesheets/brand-tokens.css +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/stylesheets/extra.css +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/stylesheets/fonts/Neuropol.otf +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/superpowers/research/2026-04-09-ctibench-ragas-benchmarks.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/superpowers/research/2026-04-09-fastembed-local-embeddings.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/superpowers/research/2026-04-09-hybrid-typedb-lancedb-architecture.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/superpowers/research/2026-04-09-local-llm-llama-cpp.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/superpowers/research/2026-04-15-anti-aversion-cleanup.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/superpowers/research/2026-04-15-causal-graph.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/superpowers/research/2026-04-15-ctibench-ate-fix.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/superpowers/research/2026-04-15-format-stability.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/superpowers/research/2026-04-15-memory-evolution.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/superpowers/research/2026-04-15-merge-consolidation.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/superpowers/research/2026-04-15-persistence-semantics.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/superpowers/research/2026-04-15-sqlite-migration.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/superpowers/research/2026-04-17-test-suite-audit.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/superpowers/research/README.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/superpowers/specs/2026-04-15-p1-features-prd.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/tutorials/01-quickstart.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/docs/tutorials/02-first-cti-report.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/examples/athf_bridge.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/examples/mcp_claude_code.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/examples/quickstart.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/governance/controls.yaml +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/scripts/migrate_jsonl_to_sqlite.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/scripts/rebuild_index.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/scripts/record-demo.sh +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/scripts/typedb-setup.sh +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/scripts/zettelforge-rebuild.service +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/scripts/zettelforge-rebuild.timer +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/skills/claude-code-skill.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/skills/openclaw-skill.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/__init__.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/__main__.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/alias_resolver.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/backend_factory.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/blended_retriever.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/cache.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/demo.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/detection/__init__.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/detection/base.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/detection/consumers.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/detection/explainer.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/edition.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/entity_indexer.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/extensions.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/fact_extractor.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/governance_validator.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/graph_retriever.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/integrations/__init__.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/integrations/langchain_retriever.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/intent_classifier.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/json_parse.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/knowledge_graph.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/llm_client.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/llm_providers/__init__.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/llm_providers/base.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/llm_providers/local_provider.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/llm_providers/mock_provider.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/llm_providers/registry.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/log.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/mcp/__init__.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/mcp/__main__.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/mcp/server.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/memory_evolver.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/memory_store.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/memory_updater.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/note_constructor.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/note_schema.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/observability.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/ocsf.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/ontology.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/retry.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/sigma/__init__.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/sigma/cli.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/sigma/entities.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/sigma/ingest.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/sigma/parser.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/sigma/schemas/NOTICE.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/sigma/schemas/__init__.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/sigma/schemas/sigma-correlation-rules-schema.json +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/sigma/schemas/sigma-detection-rule-schema.json +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/sigma/schemas/sigma-filters-schema.json +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/sigma/tags.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/synthesis_generator.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/synthesis_validator.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/vector_memory.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/vector_retriever.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/yara/__init__.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/yara/cccs_metadata.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/yara/cli.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/yara/entities.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/yara/ingest.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/yara/parser.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/yara/schemas/CCCS_YARA.yml +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/yara/schemas/CCCS_YARA_values.yml +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/yara/schemas/NOTICE.md +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/yara/schemas/__init__.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/src/zettelforge/yara/tags.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/__init__.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/benchmark_scale.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/conftest.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/fixtures/sigma/cloud_example.yml +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/fixtures/sigma/correlation_example.yml +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/fixtures/sigma/process_creation_example.yml +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/fixtures/sigma/tagged_example.yml +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/fixtures/yara/malware_hash.yar +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/fixtures/yara/technique_loader.yar +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/fixtures/yara/webshell.yar +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_basic.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_blended_retriever.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_causal_extraction.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_conversational_entities.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_core.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_cti_integration.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_detection_explainer.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_detection_rule_entities.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_edition.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_embedding.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_extensions.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_fact_extractor.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_governance.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_governance_spec_drift.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_graph_retriever.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_intent_classifier.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_json_parse.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_langchain_retriever.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_llm_client.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_mcp_server.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_memory_evolver.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_memory_updater.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_performance.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_recall_integration.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_sigma_entities.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_sigma_ingest.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_sigma_parser.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_sqlite_integration.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_storage_backend.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_temporal_graph.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_two_phase_e2e.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_typedb_client.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_yara_entities.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_yara_ingest.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/tests/test_yara_parser.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/web/app.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/web/auth.py +0 -0
- {zettelforge-2.4.0 → zettelforge-2.4.2}/web/mcp_server.py +0 -0
|
@@ -6,6 +6,146 @@ Versioning follows [Semantic Versioning](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [2.4.2] - 2026-04-24
|
|
10
|
+
|
|
11
|
+
Patch release bundling the RFC-010 enrichment-pipeline hotfix with the
|
|
12
|
+
RFC-009 Phase 0.5 latency-attribution instrumentation. Response to the
|
|
13
|
+
2026-04-24 Vigil telemetry audit.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **RFC-010 hotfix — `OllamaProvider` timeout plumbing** (#88). The
|
|
18
|
+
constructor's `**_: Any` absorbed the configured `timeout` kwarg, so
|
|
19
|
+
`ollama.Client(host=...)` was built with no timeout and `remember()`
|
|
20
|
+
could hang up to 66.5s on a slow backend. `timeout` is now a
|
|
21
|
+
first-class parameter (default 60.0s) threaded through to the client.
|
|
22
|
+
- **RFC-010 hotfix — consolidation shutdown race** (#88). A third
|
|
23
|
+
`iterate_notes()` site at `consolidation.py:224` was not covered by
|
|
24
|
+
PR #84's two-site guard. Added a two-layer defense: fast-path
|
|
25
|
+
`_accepting` pre-check plus a narrow `BackendClosedError` catch on
|
|
26
|
+
the iterator itself. Clean skip instead of `consolidation_failed`
|
|
27
|
+
log noise during `atexit`.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **RFC-009 Phase 0.5 — per-phase timers in `remember()`** (#90).
|
|
32
|
+
`memory_manager.remember()` now wraps each direct-store phase
|
|
33
|
+
(`construct`, `write_note`, `lance_index`, `entity_index`,
|
|
34
|
+
`consolidation_observe`, `supersession`, `kg_update`,
|
|
35
|
+
`enrichment_dispatch`) in `time.perf_counter()` and emits the
|
|
36
|
+
breakdown inside the existing `ocsf_api_activity` event as
|
|
37
|
+
`phase_timings_ms`. Pure observability. Enables Vigil-side latency
|
|
38
|
+
attribution without host-side profilers, which do not apply to a
|
|
39
|
+
library-per-turn deployment. `enrichment_dispatch` is intentionally
|
|
40
|
+
skipped in `sync=True` runs so inline LLM work cannot corrupt the
|
|
41
|
+
dispatch bucket.
|
|
42
|
+
- **Phase 0.5 preliminary attribution artifact** (#91) —
|
|
43
|
+
`docs/superpowers/research/2026-04-24-phase-0.5-attribution-prelim.md`.
|
|
44
|
+
Analyses 961 real `remember()` calls from Vigil's v2.4.1 OCSF log
|
|
45
|
+
and finds **98.4% of `remember()` wall-clock is one LanceDB `Update`
|
|
46
|
+
on the `notes_cti` shard**, which has 7,356 uncompacted fragments
|
|
47
|
+
versus 458 on the healthy `notes_general` shard. Reshapes RFC-009's
|
|
48
|
+
Phase 1–6 priority ordering: those phases target the LLM / queue /
|
|
49
|
+
consolidation paths, which are not what drives the 5.7s average.
|
|
50
|
+
To be refined or falsified with `phase_timings_ms` data from this
|
|
51
|
+
release.
|
|
52
|
+
|
|
53
|
+
### Does NOT address
|
|
54
|
+
|
|
55
|
+
- The ~2,329 enrichment-job drops/day are still present. Those are
|
|
56
|
+
caused by HTTP 200 + empty Ollama responses (Ollama returns
|
|
57
|
+
successfully but with no parseable body), not by hangs — RFC-010's
|
|
58
|
+
timeout fix does not touch them. The durable outbox + circuit
|
|
59
|
+
breaker in RFC-009 Phases 1–3 (v2.5.0) is the real fix.
|
|
60
|
+
- LanceDB fragment accumulation on `notes_cti` is identified here but
|
|
61
|
+
not fixed here. RFC-009 is being revised to add periodic compaction
|
|
62
|
+
to Phase 1 scope.
|
|
63
|
+
|
|
64
|
+
## [2.4.1] - 2026-04-24
|
|
65
|
+
|
|
66
|
+
Operational telemetry (RFC-007), TypeDB authentication hardening, and a
|
|
67
|
+
tranche of SQLite backend correctness fixes surfaced by the sqlite
|
|
68
|
+
review in issue #83.
|
|
69
|
+
|
|
70
|
+
### Added
|
|
71
|
+
|
|
72
|
+
- **Operational telemetry** (RFC-007, #85) — per-query recall /
|
|
73
|
+
synthesis metrics captured to `~/.amem/telemetry/telemetry_YYYY-MM-DD.jsonl`
|
|
74
|
+
when `ZETTELFORGE_LOG_LEVEL=DEBUG`. Five shipped components:
|
|
75
|
+
- `TelemetryCollector` class (`start_query` / `log_recall` /
|
|
76
|
+
`log_synthesis` / `log_feedback` / `auto_feedback_from_synthesis`)
|
|
77
|
+
with INFO/DEBUG-gated field sets, 1-hour TTL on in-memory query
|
|
78
|
+
context, and thread-safe JSONL append.
|
|
79
|
+
- `MemoryManager` integration — `recall()` and `synthesize()` gain a
|
|
80
|
+
non-breaking `actor=` kwarg; OCSF events extended via the
|
|
81
|
+
sanctioned `unmapped` object with a `zf_` prefix (class_uid 6002
|
|
82
|
+
compliant). `recall()` wraps `retriever.retrieve()` and
|
|
83
|
+
`graph_retriever.retrieve_note_ids()` with narrow-scope
|
|
84
|
+
`perf_counter` deltas for `vector_latency_ms` / `graph_latency_ms`.
|
|
85
|
+
- Daily aggregator (`python -m zettelforge.scripts.telemetry_aggregator`)
|
|
86
|
+
emitting a `DailyMetrics` JSON report (latency averages, tier
|
|
87
|
+
distribution, unused-notes count, top-utility notes).
|
|
88
|
+
- Human-evaluation workflow — 6-question rubric (`docs/human-evaluation-rubric.md`),
|
|
89
|
+
sampler script (`python -m zettelforge.scripts.human_eval_sampler`)
|
|
90
|
+
that selects 20 random briefings as a fill-in Markdown template,
|
|
91
|
+
and a `--write-events` path to append `event_type: "human_eval"`
|
|
92
|
+
entries back to telemetry.
|
|
93
|
+
- Optional Streamlit dashboard (`streamlit run
|
|
94
|
+
src/zettelforge/scripts/telemetry_dashboard.py`) — query volume,
|
|
95
|
+
latency p50/p95/max, tier distribution, utility trend,
|
|
96
|
+
unused-notes warning.
|
|
97
|
+
- Privacy contract: raw note content never persisted (IDs / tiers /
|
|
98
|
+
source_types / domains only); query text truncated at 200 chars
|
|
99
|
+
INFO / 500 chars DEBUG; local-only, no network calls.
|
|
100
|
+
|
|
101
|
+
### Fixed
|
|
102
|
+
|
|
103
|
+
- **SQLite shutdown NPE** (#84, issue #83 H3) — `close()` and
|
|
104
|
+
`initialize()` are now lock-protected and idempotent. Readers and
|
|
105
|
+
writers raise a clean `BackendClosedError` (new, in
|
|
106
|
+
`storage_backend`) instead of the opaque `AttributeError: 'NoneType'
|
|
107
|
+
object has no attribute 'execute'` seen 170× in production logs on
|
|
108
|
+
2026-04-23 during atexit. `memory_manager._enrichment_loop` and
|
|
109
|
+
`_drain_enrichment_queue` catch `BackendClosedError` and exit
|
|
110
|
+
cleanly.
|
|
111
|
+
- **SQLite torn snapshot** (#84, issue #83 C1) — `export_snapshot()`
|
|
112
|
+
now uses `sqlite3.Connection.backup()` for a page-consistent copy.
|
|
113
|
+
The previous `shutil.copy2` path could produce a corrupt backup
|
|
114
|
+
missing `-wal` / `-shm` sidecars, unsafe for DR restore.
|
|
115
|
+
- **SQLite reindex race** (#84, issue #83 C2) — `reindex_vector()` now
|
|
116
|
+
uses a single-lock targeted `UPDATE` on the `embedding_vector`
|
|
117
|
+
column. The previous `get_note_by_id → rewrite_note` path spanned
|
|
118
|
+
two lock acquisitions and could clobber concurrent
|
|
119
|
+
`mark_access_dirty` / `evolve` / supersede edits via
|
|
120
|
+
`INSERT OR REPLACE`.
|
|
121
|
+
|
|
122
|
+
### Security
|
|
123
|
+
|
|
124
|
+
- **TypeDB authentication hardening** (#82) — removed known-insecure
|
|
125
|
+
`admin` / `password` defaults from `TypeDBConfig` and
|
|
126
|
+
`config.default.yaml`. `TypeDBConfig.__repr__` now redacts
|
|
127
|
+
non-empty passwords as `***`. The config loader resolves
|
|
128
|
+
`${TYPEDB_USERNAME}` / `${TYPEDB_PASSWORD}` env-var references in
|
|
129
|
+
YAML (same pattern already used for `llm.api_key`), so secrets can
|
|
130
|
+
stay in env / container secret stores rather than on disk.
|
|
131
|
+
Migration: set `TYPEDB_USERNAME` / `TYPEDB_PASSWORD` in your
|
|
132
|
+
environment or use the `${VAR}` references in a local
|
|
133
|
+
`config.yaml`. Direct env overrides (`TYPEDB_USERNAME=…`) already
|
|
134
|
+
worked and are unaffected.
|
|
135
|
+
|
|
136
|
+
### Docs
|
|
137
|
+
|
|
138
|
+
- **Architecture Deep Dive + Module Inventory for v2.4.0** (#80) —
|
|
139
|
+
reference-level architecture documentation.
|
|
140
|
+
- **RFC-007 Operational Telemetry** (#85) — full design doc including
|
|
141
|
+
the four subagent-resolved frictions (caller-opt-in query_id
|
|
142
|
+
correlation, narrow-scope latency instrumentation, OCSF unmapped
|
|
143
|
+
extension, hybrid `__new__`-bypass integration tests).
|
|
144
|
+
- **Human Evaluation Rubric** (#85) — 6-question monthly review
|
|
145
|
+
rubric with scoring summary table.
|
|
146
|
+
- **Troubleshoot guide** (#85) — "Operational telemetry" subsection
|
|
147
|
+
covering the three CLI entry points and the privacy contract.
|
|
148
|
+
|
|
9
149
|
## [2.4.0] - 2026-04-19
|
|
10
150
|
|
|
11
151
|
Detection-rules-as-memory, MCP Registry publication, SQLite concurrency
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: zettelforge
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.2
|
|
4
4
|
Summary: ZettelForge: Agentic Memory System with vector search, knowledge graph, and synthesis
|
|
5
5
|
Project-URL: Homepage, https://github.com/rolandpg/zettelforge
|
|
6
6
|
Project-URL: Documentation, https://docs.threatrecall.ai
|
|
@@ -59,7 +59,9 @@ Description-Content-Type: text/markdown
|
|
|
59
59
|
|
|
60
60
|
**The only agentic memory system built for cyber threat intelligence.**
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
When a senior analyst leaves, two or three years of context walks out with them — customer environments, prior investigations, actor TTPs, false-positive patterns, every hard-won "wait, we've seen this before." ZettelForge is an agentic memory system built so that context stays with the team.
|
|
63
|
+
|
|
64
|
+
It extracts CVEs, threat actors, IOCs, and ATT&CK techniques from analyst notes and threat reports, resolves aliases (APT28 = Fancy Bear = STRONTIUM = Sofacy), builds a STIX 2.1 knowledge graph, and serves every past investigation back to your analysts — and to Claude Code via MCP — in natural language. Runs entirely in-process. No API keys. No cloud. No data leaves the host.
|
|
63
65
|
|
|
64
66
|
[](https://pypi.org/project/zettelforge/)
|
|
65
67
|
[](https://pepy.tech/projects/zettelforge)
|
|
@@ -67,7 +69,7 @@ Persistent memory for AI agents and Claude Code — with CTI entity extraction,
|
|
|
67
69
|
[](https://opensource.org/licenses/MIT)
|
|
68
70
|
[](https://github.com/rolandpg/zettelforge/actions)
|
|
69
71
|
|
|
70
|
-
**[⭐ Star](https://github.com/rolandpg/zettelforge) · [📦 `pip install zettelforge`](https://pypi.org/project/zettelforge/) · [📖 Docs](https://docs.threatrecall.ai/) · [🧪 Hosted](https://threatrecall.ai)**
|
|
72
|
+
**[⭐ Star](https://github.com/rolandpg/zettelforge) · [📦 `pip install zettelforge`](https://pypi.org/project/zettelforge/) · [📖 Docs](https://docs.threatrecall.ai/) · [🧪 Hosted beta](https://threatrecall.ai)**
|
|
71
73
|
|
|
72
74
|
<p align="center">
|
|
73
75
|
<a href="https://www.buymeacoffee.com/xypher22pr0" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-green.png" alt="Buy Me a Coffee" style="height: 60px !important;width: 217px !important;" ></a>
|
|
@@ -78,24 +80,25 @@ Persistent memory for AI agents and Claude Code — with CTI entity extraction,
|
|
|
78
80
|
|
|
79
81
|
> If ZettelForge fits a CTI workflow you run, a star is the fastest signal that this category is worth continuing to invest in.
|
|
80
82
|
|
|
81
|
-
##
|
|
83
|
+
## The problem
|
|
82
84
|
|
|
83
|
-
|
|
85
|
+
Every SOC loses analysts. When they leave, investigation context, actor attribution, and environment-specific false-positive patterns go with them. Their replacements re-open the same tickets, re-read the same reports, and re-build the same mental models from scratch.
|
|
84
86
|
|
|
85
|
-
|
|
87
|
+
General-purpose AI memory systems don't fix this for security teams. They can't tell APT28 from Fancy Bear, don't know that CVE-2024-3094 is the XZ Utils backdoor, can't parse Sigma or YARA, and have no concept of MITRE ATT&CK technique IDs. When a CTI analyst gives them a year of intel reports, they get back fuzzy semantic search over chat history.
|
|
86
88
|
|
|
87
|
-
|
|
89
|
+
ZettelForge was built for analysts who think in threat graphs. It extracts CVEs, threat actors, IOCs, and ATT&CK techniques automatically, resolves aliases across naming conventions, builds a knowledge graph with causal relationships, and retrieves memories using intent-aware blended search — all in-process, with no external API dependency.
|
|
88
90
|
|
|
91
|
+
>"Memory augmentation closes 33% of the gap between small and large models on CTI tasks (CTI-REALM, Microsoft 2026)." [1]
|
|
89
92
|
|
|
90
|
-
|
|
|
91
|
-
|
|
93
|
+
| Capability | ZettelForge | Mem0 | Graphiti | Cognee |
|
|
94
|
+
|---|---|---|---|---|
|
|
92
95
|
| CTI entity extraction (CVEs, actors, IOCs) | Yes | No | No | No |
|
|
93
96
|
| STIX 2.1 ontology | Yes | No | No | No |
|
|
94
97
|
| Threat actor alias resolution | Yes (APT28 = Fancy Bear) | No | No | No |
|
|
95
98
|
| Knowledge graph with causal triples | Yes | No | Yes | Yes |
|
|
96
99
|
| Intent-classified retrieval (5 types) | Yes | No | No | No |
|
|
97
|
-
|
|
|
98
|
-
| OCSF
|
|
100
|
+
| In-process / no external API required | Yes | No | No | No |
|
|
101
|
+
| Audit logs in OCSF schema | Yes | No | No | No |
|
|
99
102
|
| MCP server (Claude Code) | Yes | No | No | No |
|
|
100
103
|
|
|
101
104
|
## Data Pipeline
|
|
@@ -110,21 +113,21 @@ ZettelForge was built from the ground up for analysts who think in threat graphs
|
|
|
110
113
|
|
|
111
114
|
## Features
|
|
112
115
|
|
|
113
|
-
**Entity Extraction**
|
|
116
|
+
**Entity Extraction** — Automatically identifies CVEs, threat actors, IOCs (IPs, domains, hashes, URLs, emails), MITRE ATT&CK techniques, campaigns, intrusion sets, tools, people, locations, and organizations. Regex + LLM NER with STIX 2.1 types throughout.
|
|
114
117
|
|
|
115
|
-
**Knowledge Graph**
|
|
118
|
+
**Knowledge Graph** — Entities become nodes, co-occurrence becomes edges. LLM infers causal triples ("APT28 *uses* Cobalt Strike"). Temporal edges and supersession track how intelligence evolves.
|
|
116
119
|
|
|
117
|
-
**Alias Resolution**
|
|
120
|
+
**Alias Resolution** — APT28, Fancy Bear, Sofacy, STRONTIUM all resolve to the same actor node. Works automatically on store and recall.
|
|
118
121
|
|
|
119
|
-
**Blended Retrieval**
|
|
122
|
+
**Blended Retrieval** — Vector similarity (768-dim fastembed, ONNX) + graph traversal (BFS over knowledge graph edges), weighted by intent classification. Five intent types: factual, temporal, relational, exploratory, causal.
|
|
120
123
|
|
|
121
|
-
**Memory Evolution**
|
|
124
|
+
**Memory Evolution** — With `evolve=True`, new intel is compared to existing memory. LLM decides ADD, UPDATE, DELETE, or NOOP. Stale intel gets superseded. Contradictions get resolved. Duplicates get skipped.
|
|
122
125
|
|
|
123
|
-
**RAG Synthesis**
|
|
126
|
+
**RAG Synthesis** — Synthesize answers across all stored memories with `direct_answer` format.
|
|
124
127
|
|
|
125
|
-
**
|
|
128
|
+
**In-process by architecture** — fastembed (ONNX) for embeddings, llama-cpp-python for optional local LLM inference, SQLite + LanceDB for storage, and Ollama on localhost by default. No external API keys are required. Outbound network access may occur on first run when embedding/LLM models are downloaded; after models are preloaded, it can run fully offline (including on air-gapped hosts).
|
|
126
129
|
|
|
127
|
-
**OCSF
|
|
130
|
+
**Audit logging in OCSF schema** — Every operation emits a structured event in the Open Cybersecurity Schema Framework format. What you do with the log stream (SIEM, WORM store, nothing) is up to you.
|
|
128
131
|
|
|
129
132
|
## Quick Start
|
|
130
133
|
|
|
@@ -137,7 +140,7 @@ from zettelforge import MemoryManager
|
|
|
137
140
|
|
|
138
141
|
mm = MemoryManager()
|
|
139
142
|
|
|
140
|
-
# Store threat intel
|
|
143
|
+
# Store threat intel — entities extracted automatically
|
|
141
144
|
mm.remember("APT28 uses Cobalt Strike for lateral movement via T1021")
|
|
142
145
|
|
|
143
146
|
# Recall with alias resolution
|
|
@@ -148,7 +151,7 @@ results = mm.recall("What tools does Fancy Bear use?")
|
|
|
148
151
|
answer = mm.synthesize("Summarize known APT28 TTPs")
|
|
149
152
|
```
|
|
150
153
|
|
|
151
|
-
No TypeDB, no Ollama, no Docker
|
|
154
|
+
No TypeDB, no Ollama, no Docker — just `pip install`. Embeddings run in-process via fastembed. LLM features (extraction, synthesis) activate when Ollama is available.
|
|
152
155
|
|
|
153
156
|
### With Ollama (enables LLM features)
|
|
154
157
|
|
|
@@ -160,7 +163,7 @@ ollama pull qwen2.5:3b && ollama serve
|
|
|
160
163
|
### Memory Evolution
|
|
161
164
|
|
|
162
165
|
```python
|
|
163
|
-
# New intel arrives
|
|
166
|
+
# New intel arrives — evolve=True enables memory evolution:
|
|
164
167
|
# LLM extracts facts, compares to existing notes, decides ADD/UPDATE/DELETE/NOOP
|
|
165
168
|
mm.remember(
|
|
166
169
|
"APT28 has shifted tactics. They dropped DROPBEAR and now exploit edge devices.",
|
|
@@ -173,25 +176,25 @@ mm.remember(
|
|
|
173
176
|
|
|
174
177
|
Every `remember()` call triggers a pipeline:
|
|
175
178
|
|
|
176
|
-
1. **Entity Extraction**
|
|
177
|
-
2. **Knowledge Graph Update**
|
|
178
|
-
3. **Vector Embedding**
|
|
179
|
-
4. **Supersession Check**
|
|
180
|
-
5. **Dual-Stream Write**
|
|
179
|
+
1. **Entity Extraction** — regex + LLM NER identifies CVEs, intrusion sets, threat actors, tools, campaigns, ATT&CK techniques, IOCs (IPv4, domain, URL, MD5/SHA1/SHA256, email), people, locations, organizations, events, activities, and temporal references (19 types)
|
|
180
|
+
2. **Knowledge Graph Update** — entities become nodes, co-occurrence becomes edges, LLM infers causal triples
|
|
181
|
+
3. **Vector Embedding** — 768-dim fastembed (ONNX, in-process, 7ms/embed) stored in LanceDB
|
|
182
|
+
4. **Supersession Check** — entity overlap detection marks stale notes as superseded
|
|
183
|
+
5. **Dual-Stream Write** — fast path returns in ~45ms; causal enrichment is deferred to a background worker
|
|
181
184
|
|
|
182
185
|
Every `recall()` call blends two retrieval strategies:
|
|
183
186
|
|
|
184
|
-
1. **Vector similarity**
|
|
185
|
-
2. **Graph traversal**
|
|
186
|
-
3. **Intent routing**
|
|
187
|
-
4. **Cross-encoder reranking**
|
|
187
|
+
1. **Vector similarity** — semantic search over embeddings
|
|
188
|
+
2. **Graph traversal** — BFS over knowledge graph edges, scored by hop distance
|
|
189
|
+
3. **Intent routing** — query classified as factual/temporal/relational/causal/exploratory, weights adjusted per type
|
|
190
|
+
4. **Cross-encoder reranking** — ms-marco-MiniLM reorders final results by relevance
|
|
188
191
|
|
|
189
192
|
## Benchmarks
|
|
190
193
|
|
|
191
194
|
Evaluated against published academic benchmarks:
|
|
192
195
|
|
|
193
196
|
| Benchmark | What it measures | Score |
|
|
194
|
-
|
|
197
|
+
|---|---|---|
|
|
195
198
|
| **CTI Retrieval** | Attribution, CVE linkage, multi-hop | **75.0%** |
|
|
196
199
|
| **RAGAS** | Retrieval quality (keyword presence) | **78.1%** |
|
|
197
200
|
| **LOCOMO** (ACL 2024) | Conversational memory recall | **22.0%** *(with Ollama cloud models)* |
|
|
@@ -221,7 +224,7 @@ Exposed tools: `remember`, `recall`, `synthesize`, `entity`, `graph`, `stats`.
|
|
|
221
224
|
|
|
222
225
|
Sigma and YARA rules are first-class memory primitives. Parse, validate, and ingest a rule and its tags become graph edges: MITRE ATT&CK techniques, CVEs, threat-actor aliases, tools, and malware families resolve against the same ontology as every other note. A shared `DetectionRule` supertype carries `SigmaRule` and `YaraRule` subtypes, so a single rule UUID is addressable across both formats.
|
|
223
226
|
|
|
224
|
-
Sigma rules are validated against the vendored [SigmaHQ JSON schema](https://github.com/SigmaHQ/sigma-specification). YARA rules are parsed with plyara and
|
|
227
|
+
Sigma rules are validated against the vendored [SigmaHQ JSON schema](https://github.com/SigmaHQ/sigma-specification). YARA rules are parsed with plyara and checked against the [CCCS YARA metadata standard](https://github.com/CybercentreCanada/CCCS-Yara) (tiers: `strict`, `warn`, `non_cccs`). Ingest is idempotent — re-ingesting an unchanged rule returns the original note via a content-hashed `source_ref`.
|
|
225
228
|
|
|
226
229
|
```python
|
|
227
230
|
from zettelforge import MemoryManager
|
|
@@ -238,11 +241,11 @@ ingest_yara("rules/webshell_china_chopper.yar", mm, tier="warn")
|
|
|
238
241
|
python -m zettelforge.sigma.ingest /path/to/sigma/rules/
|
|
239
242
|
python -m zettelforge.yara.ingest /path/to/yara/rules/ --tier warn
|
|
240
243
|
|
|
241
|
-
# CI fixture check
|
|
244
|
+
# CI fixture check — parse + validate, no writes
|
|
242
245
|
python -m zettelforge.sigma.ingest rules/ --dry-run
|
|
243
246
|
```
|
|
244
247
|
|
|
245
|
-
An LLM rule explainer (`zettelforge.detection.explainer.explain`) produces a structured JSON summary
|
|
248
|
+
An LLM rule explainer (`zettelforge.detection.explainer.explain`) produces a structured JSON summary — intent, key fields, evasion notes, false-positive hypotheses — for any `DetectionRule`. It runs synchronously on demand in v1; async enrichment-queue wiring is v1.1. Rate-limited via `ZETTELFORGE_EXPLAIN_RPM` (default 60 calls/minute).
|
|
246
249
|
|
|
247
250
|
References: [Sigma spec](https://github.com/SigmaHQ/sigma-specification), [SigmaHQ rules](https://github.com/SigmaHQ/sigma), [CCCS YARA](https://github.com/CybercentreCanada/CCCS-Yara), [YARA docs](https://yara.readthedocs.io).
|
|
248
251
|
|
|
@@ -263,35 +266,31 @@ See [examples/athf_bridge.py](examples/athf_bridge.py).
|
|
|
263
266
|
|
|
264
267
|
## Extensions
|
|
265
268
|
|
|
266
|
-
ZettelForge
|
|
267
|
-
Everything documented above works out of the box.
|
|
269
|
+
ZettelForge ships a complete agentic memory core. Everything documented above works from a single `pip install`.
|
|
268
270
|
|
|
269
|
-
For teams that
|
|
270
|
-
or multi-tenant deployment, optional extensions are available:
|
|
271
|
+
For teams that want TypeDB-scale graph storage, OpenCTI integration, or multi-tenant deployment, optional extensions are available:
|
|
271
272
|
|
|
272
273
|
| Extension | What it adds |
|
|
273
|
-
|
|
274
|
+
|---|---|
|
|
274
275
|
| TypeDB STIX 2.1 backend | Schema-enforced ontology with inference rules |
|
|
275
276
|
| OpenCTI sync | Bi-directional sync with OpenCTI instances |
|
|
276
277
|
| Multi-tenant auth | OAuth/JWT with per-tenant isolation |
|
|
277
278
|
| Sigma rule generation | Detection rules from extracted IOCs |
|
|
278
279
|
|
|
279
|
-
Extensions
|
|
280
|
+
Extensions install separately:
|
|
280
281
|
|
|
281
282
|
```bash
|
|
282
283
|
pip install zettelforge-enterprise
|
|
283
284
|
```
|
|
284
285
|
|
|
285
|
-
**Hosted
|
|
286
|
-
managed ZettelForge with all extensions, so you don't have to run
|
|
287
|
-
infrastructure yourself.
|
|
286
|
+
**Hosted (private beta):** [ThreatRecall](https://threatrecall.ai) is the managed SaaS version of ZettelForge with enterprise extensions enabled. Currently accepting waitlist signups and a limited number of design partners.
|
|
288
287
|
|
|
289
288
|
## Configuration
|
|
290
289
|
|
|
291
290
|
| Variable | Default | Description |
|
|
292
|
-
|
|
291
|
+
|---|---|---|
|
|
293
292
|
| `AMEM_DATA_DIR` | `~/.amem` | Data directory |
|
|
294
|
-
| `ZETTELFORGE_BACKEND` | `sqlite` | SQLite community backend. TypeDB
|
|
293
|
+
| `ZETTELFORGE_BACKEND` | `sqlite` | SQLite community backend. TypeDB available via extension. |
|
|
295
294
|
| `ZETTELFORGE_LLM_PROVIDER` | `local` | `local` (llama-cpp) or `ollama` |
|
|
296
295
|
|
|
297
296
|
See [config.default.yaml](config.default.yaml) for all options.
|
|
@@ -302,9 +301,11 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup.
|
|
|
302
301
|
|
|
303
302
|
## License
|
|
304
303
|
|
|
305
|
-
MIT
|
|
304
|
+
MIT — See [LICENSE](LICENSE).
|
|
305
|
+
|
|
306
|
+
## About the author
|
|
306
307
|
|
|
307
|
-
|
|
308
|
+
Built by **Patrick Roland** — Director of SOC Services at Summit 7 Systems, where he built the Vigilance MxDR practice from the ground up. Navy nuclear veteran, CISSP, CCP (CMMC 2.0 Professional). [LinkedIn](https://www.linkedin.com/in/patrickgroland/).
|
|
308
309
|
|
|
309
310
|
## Support the Project
|
|
310
311
|
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
**The only agentic memory system built for cyber threat intelligence.**
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
When a senior analyst leaves, two or three years of context walks out with them — customer environments, prior investigations, actor TTPs, false-positive patterns, every hard-won "wait, we've seen this before." ZettelForge is an agentic memory system built so that context stays with the team.
|
|
8
|
+
|
|
9
|
+
It extracts CVEs, threat actors, IOCs, and ATT&CK techniques from analyst notes and threat reports, resolves aliases (APT28 = Fancy Bear = STRONTIUM = Sofacy), builds a STIX 2.1 knowledge graph, and serves every past investigation back to your analysts — and to Claude Code via MCP — in natural language. Runs entirely in-process. No API keys. No cloud. No data leaves the host.
|
|
8
10
|
|
|
9
11
|
[](https://pypi.org/project/zettelforge/)
|
|
10
12
|
[](https://pepy.tech/projects/zettelforge)
|
|
@@ -12,7 +14,7 @@ Persistent memory for AI agents and Claude Code — with CTI entity extraction,
|
|
|
12
14
|
[](https://opensource.org/licenses/MIT)
|
|
13
15
|
[](https://github.com/rolandpg/zettelforge/actions)
|
|
14
16
|
|
|
15
|
-
**[⭐ Star](https://github.com/rolandpg/zettelforge) · [📦 `pip install zettelforge`](https://pypi.org/project/zettelforge/) · [📖 Docs](https://docs.threatrecall.ai/) · [🧪 Hosted](https://threatrecall.ai)**
|
|
17
|
+
**[⭐ Star](https://github.com/rolandpg/zettelforge) · [📦 `pip install zettelforge`](https://pypi.org/project/zettelforge/) · [📖 Docs](https://docs.threatrecall.ai/) · [🧪 Hosted beta](https://threatrecall.ai)**
|
|
16
18
|
|
|
17
19
|
<p align="center">
|
|
18
20
|
<a href="https://www.buymeacoffee.com/xypher22pr0" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-green.png" alt="Buy Me a Coffee" style="height: 60px !important;width: 217px !important;" ></a>
|
|
@@ -23,24 +25,25 @@ Persistent memory for AI agents and Claude Code — with CTI entity extraction,
|
|
|
23
25
|
|
|
24
26
|
> If ZettelForge fits a CTI workflow you run, a star is the fastest signal that this category is worth continuing to invest in.
|
|
25
27
|
|
|
26
|
-
##
|
|
28
|
+
## The problem
|
|
27
29
|
|
|
28
|
-
|
|
30
|
+
Every SOC loses analysts. When they leave, investigation context, actor attribution, and environment-specific false-positive patterns go with them. Their replacements re-open the same tickets, re-read the same reports, and re-build the same mental models from scratch.
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
General-purpose AI memory systems don't fix this for security teams. They can't tell APT28 from Fancy Bear, don't know that CVE-2024-3094 is the XZ Utils backdoor, can't parse Sigma or YARA, and have no concept of MITRE ATT&CK technique IDs. When a CTI analyst gives them a year of intel reports, they get back fuzzy semantic search over chat history.
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
ZettelForge was built for analysts who think in threat graphs. It extracts CVEs, threat actors, IOCs, and ATT&CK techniques automatically, resolves aliases across naming conventions, builds a knowledge graph with causal relationships, and retrieves memories using intent-aware blended search — all in-process, with no external API dependency.
|
|
33
35
|
|
|
36
|
+
>"Memory augmentation closes 33% of the gap between small and large models on CTI tasks (CTI-REALM, Microsoft 2026)." [1]
|
|
34
37
|
|
|
35
|
-
|
|
|
36
|
-
|
|
38
|
+
| Capability | ZettelForge | Mem0 | Graphiti | Cognee |
|
|
39
|
+
|---|---|---|---|---|
|
|
37
40
|
| CTI entity extraction (CVEs, actors, IOCs) | Yes | No | No | No |
|
|
38
41
|
| STIX 2.1 ontology | Yes | No | No | No |
|
|
39
42
|
| Threat actor alias resolution | Yes (APT28 = Fancy Bear) | No | No | No |
|
|
40
43
|
| Knowledge graph with causal triples | Yes | No | Yes | Yes |
|
|
41
44
|
| Intent-classified retrieval (5 types) | Yes | No | No | No |
|
|
42
|
-
|
|
|
43
|
-
| OCSF
|
|
45
|
+
| In-process / no external API required | Yes | No | No | No |
|
|
46
|
+
| Audit logs in OCSF schema | Yes | No | No | No |
|
|
44
47
|
| MCP server (Claude Code) | Yes | No | No | No |
|
|
45
48
|
|
|
46
49
|
## Data Pipeline
|
|
@@ -55,21 +58,21 @@ ZettelForge was built from the ground up for analysts who think in threat graphs
|
|
|
55
58
|
|
|
56
59
|
## Features
|
|
57
60
|
|
|
58
|
-
**Entity Extraction**
|
|
61
|
+
**Entity Extraction** — Automatically identifies CVEs, threat actors, IOCs (IPs, domains, hashes, URLs, emails), MITRE ATT&CK techniques, campaigns, intrusion sets, tools, people, locations, and organizations. Regex + LLM NER with STIX 2.1 types throughout.
|
|
59
62
|
|
|
60
|
-
**Knowledge Graph**
|
|
63
|
+
**Knowledge Graph** — Entities become nodes, co-occurrence becomes edges. LLM infers causal triples ("APT28 *uses* Cobalt Strike"). Temporal edges and supersession track how intelligence evolves.
|
|
61
64
|
|
|
62
|
-
**Alias Resolution**
|
|
65
|
+
**Alias Resolution** — APT28, Fancy Bear, Sofacy, STRONTIUM all resolve to the same actor node. Works automatically on store and recall.
|
|
63
66
|
|
|
64
|
-
**Blended Retrieval**
|
|
67
|
+
**Blended Retrieval** — Vector similarity (768-dim fastembed, ONNX) + graph traversal (BFS over knowledge graph edges), weighted by intent classification. Five intent types: factual, temporal, relational, exploratory, causal.
|
|
65
68
|
|
|
66
|
-
**Memory Evolution**
|
|
69
|
+
**Memory Evolution** — With `evolve=True`, new intel is compared to existing memory. LLM decides ADD, UPDATE, DELETE, or NOOP. Stale intel gets superseded. Contradictions get resolved. Duplicates get skipped.
|
|
67
70
|
|
|
68
|
-
**RAG Synthesis**
|
|
71
|
+
**RAG Synthesis** — Synthesize answers across all stored memories with `direct_answer` format.
|
|
69
72
|
|
|
70
|
-
**
|
|
73
|
+
**In-process by architecture** — fastembed (ONNX) for embeddings, llama-cpp-python for optional local LLM inference, SQLite + LanceDB for storage, and Ollama on localhost by default. No external API keys are required. Outbound network access may occur on first run when embedding/LLM models are downloaded; after models are preloaded, it can run fully offline (including on air-gapped hosts).
|
|
71
74
|
|
|
72
|
-
**OCSF
|
|
75
|
+
**Audit logging in OCSF schema** — Every operation emits a structured event in the Open Cybersecurity Schema Framework format. What you do with the log stream (SIEM, WORM store, nothing) is up to you.
|
|
73
76
|
|
|
74
77
|
## Quick Start
|
|
75
78
|
|
|
@@ -82,7 +85,7 @@ from zettelforge import MemoryManager
|
|
|
82
85
|
|
|
83
86
|
mm = MemoryManager()
|
|
84
87
|
|
|
85
|
-
# Store threat intel
|
|
88
|
+
# Store threat intel — entities extracted automatically
|
|
86
89
|
mm.remember("APT28 uses Cobalt Strike for lateral movement via T1021")
|
|
87
90
|
|
|
88
91
|
# Recall with alias resolution
|
|
@@ -93,7 +96,7 @@ results = mm.recall("What tools does Fancy Bear use?")
|
|
|
93
96
|
answer = mm.synthesize("Summarize known APT28 TTPs")
|
|
94
97
|
```
|
|
95
98
|
|
|
96
|
-
No TypeDB, no Ollama, no Docker
|
|
99
|
+
No TypeDB, no Ollama, no Docker — just `pip install`. Embeddings run in-process via fastembed. LLM features (extraction, synthesis) activate when Ollama is available.
|
|
97
100
|
|
|
98
101
|
### With Ollama (enables LLM features)
|
|
99
102
|
|
|
@@ -105,7 +108,7 @@ ollama pull qwen2.5:3b && ollama serve
|
|
|
105
108
|
### Memory Evolution
|
|
106
109
|
|
|
107
110
|
```python
|
|
108
|
-
# New intel arrives
|
|
111
|
+
# New intel arrives — evolve=True enables memory evolution:
|
|
109
112
|
# LLM extracts facts, compares to existing notes, decides ADD/UPDATE/DELETE/NOOP
|
|
110
113
|
mm.remember(
|
|
111
114
|
"APT28 has shifted tactics. They dropped DROPBEAR and now exploit edge devices.",
|
|
@@ -118,25 +121,25 @@ mm.remember(
|
|
|
118
121
|
|
|
119
122
|
Every `remember()` call triggers a pipeline:
|
|
120
123
|
|
|
121
|
-
1. **Entity Extraction**
|
|
122
|
-
2. **Knowledge Graph Update**
|
|
123
|
-
3. **Vector Embedding**
|
|
124
|
-
4. **Supersession Check**
|
|
125
|
-
5. **Dual-Stream Write**
|
|
124
|
+
1. **Entity Extraction** — regex + LLM NER identifies CVEs, intrusion sets, threat actors, tools, campaigns, ATT&CK techniques, IOCs (IPv4, domain, URL, MD5/SHA1/SHA256, email), people, locations, organizations, events, activities, and temporal references (19 types)
|
|
125
|
+
2. **Knowledge Graph Update** — entities become nodes, co-occurrence becomes edges, LLM infers causal triples
|
|
126
|
+
3. **Vector Embedding** — 768-dim fastembed (ONNX, in-process, 7ms/embed) stored in LanceDB
|
|
127
|
+
4. **Supersession Check** — entity overlap detection marks stale notes as superseded
|
|
128
|
+
5. **Dual-Stream Write** — fast path returns in ~45ms; causal enrichment is deferred to a background worker
|
|
126
129
|
|
|
127
130
|
Every `recall()` call blends two retrieval strategies:
|
|
128
131
|
|
|
129
|
-
1. **Vector similarity**
|
|
130
|
-
2. **Graph traversal**
|
|
131
|
-
3. **Intent routing**
|
|
132
|
-
4. **Cross-encoder reranking**
|
|
132
|
+
1. **Vector similarity** — semantic search over embeddings
|
|
133
|
+
2. **Graph traversal** — BFS over knowledge graph edges, scored by hop distance
|
|
134
|
+
3. **Intent routing** — query classified as factual/temporal/relational/causal/exploratory, weights adjusted per type
|
|
135
|
+
4. **Cross-encoder reranking** — ms-marco-MiniLM reorders final results by relevance
|
|
133
136
|
|
|
134
137
|
## Benchmarks
|
|
135
138
|
|
|
136
139
|
Evaluated against published academic benchmarks:
|
|
137
140
|
|
|
138
141
|
| Benchmark | What it measures | Score |
|
|
139
|
-
|
|
142
|
+
|---|---|---|
|
|
140
143
|
| **CTI Retrieval** | Attribution, CVE linkage, multi-hop | **75.0%** |
|
|
141
144
|
| **RAGAS** | Retrieval quality (keyword presence) | **78.1%** |
|
|
142
145
|
| **LOCOMO** (ACL 2024) | Conversational memory recall | **22.0%** *(with Ollama cloud models)* |
|
|
@@ -166,7 +169,7 @@ Exposed tools: `remember`, `recall`, `synthesize`, `entity`, `graph`, `stats`.
|
|
|
166
169
|
|
|
167
170
|
Sigma and YARA rules are first-class memory primitives. Parse, validate, and ingest a rule and its tags become graph edges: MITRE ATT&CK techniques, CVEs, threat-actor aliases, tools, and malware families resolve against the same ontology as every other note. A shared `DetectionRule` supertype carries `SigmaRule` and `YaraRule` subtypes, so a single rule UUID is addressable across both formats.
|
|
168
171
|
|
|
169
|
-
Sigma rules are validated against the vendored [SigmaHQ JSON schema](https://github.com/SigmaHQ/sigma-specification). YARA rules are parsed with plyara and
|
|
172
|
+
Sigma rules are validated against the vendored [SigmaHQ JSON schema](https://github.com/SigmaHQ/sigma-specification). YARA rules are parsed with plyara and checked against the [CCCS YARA metadata standard](https://github.com/CybercentreCanada/CCCS-Yara) (tiers: `strict`, `warn`, `non_cccs`). Ingest is idempotent — re-ingesting an unchanged rule returns the original note via a content-hashed `source_ref`.
|
|
170
173
|
|
|
171
174
|
```python
|
|
172
175
|
from zettelforge import MemoryManager
|
|
@@ -183,11 +186,11 @@ ingest_yara("rules/webshell_china_chopper.yar", mm, tier="warn")
|
|
|
183
186
|
python -m zettelforge.sigma.ingest /path/to/sigma/rules/
|
|
184
187
|
python -m zettelforge.yara.ingest /path/to/yara/rules/ --tier warn
|
|
185
188
|
|
|
186
|
-
# CI fixture check
|
|
189
|
+
# CI fixture check — parse + validate, no writes
|
|
187
190
|
python -m zettelforge.sigma.ingest rules/ --dry-run
|
|
188
191
|
```
|
|
189
192
|
|
|
190
|
-
An LLM rule explainer (`zettelforge.detection.explainer.explain`) produces a structured JSON summary
|
|
193
|
+
An LLM rule explainer (`zettelforge.detection.explainer.explain`) produces a structured JSON summary — intent, key fields, evasion notes, false-positive hypotheses — for any `DetectionRule`. It runs synchronously on demand in v1; async enrichment-queue wiring is v1.1. Rate-limited via `ZETTELFORGE_EXPLAIN_RPM` (default 60 calls/minute).
|
|
191
194
|
|
|
192
195
|
References: [Sigma spec](https://github.com/SigmaHQ/sigma-specification), [SigmaHQ rules](https://github.com/SigmaHQ/sigma), [CCCS YARA](https://github.com/CybercentreCanada/CCCS-Yara), [YARA docs](https://yara.readthedocs.io).
|
|
193
196
|
|
|
@@ -208,35 +211,31 @@ See [examples/athf_bridge.py](examples/athf_bridge.py).
|
|
|
208
211
|
|
|
209
212
|
## Extensions
|
|
210
213
|
|
|
211
|
-
ZettelForge
|
|
212
|
-
Everything documented above works out of the box.
|
|
214
|
+
ZettelForge ships a complete agentic memory core. Everything documented above works from a single `pip install`.
|
|
213
215
|
|
|
214
|
-
For teams that
|
|
215
|
-
or multi-tenant deployment, optional extensions are available:
|
|
216
|
+
For teams that want TypeDB-scale graph storage, OpenCTI integration, or multi-tenant deployment, optional extensions are available:
|
|
216
217
|
|
|
217
218
|
| Extension | What it adds |
|
|
218
|
-
|
|
219
|
+
|---|---|
|
|
219
220
|
| TypeDB STIX 2.1 backend | Schema-enforced ontology with inference rules |
|
|
220
221
|
| OpenCTI sync | Bi-directional sync with OpenCTI instances |
|
|
221
222
|
| Multi-tenant auth | OAuth/JWT with per-tenant isolation |
|
|
222
223
|
| Sigma rule generation | Detection rules from extracted IOCs |
|
|
223
224
|
|
|
224
|
-
Extensions
|
|
225
|
+
Extensions install separately:
|
|
225
226
|
|
|
226
227
|
```bash
|
|
227
228
|
pip install zettelforge-enterprise
|
|
228
229
|
```
|
|
229
230
|
|
|
230
|
-
**Hosted
|
|
231
|
-
managed ZettelForge with all extensions, so you don't have to run
|
|
232
|
-
infrastructure yourself.
|
|
231
|
+
**Hosted (private beta):** [ThreatRecall](https://threatrecall.ai) is the managed SaaS version of ZettelForge with enterprise extensions enabled. Currently accepting waitlist signups and a limited number of design partners.
|
|
233
232
|
|
|
234
233
|
## Configuration
|
|
235
234
|
|
|
236
235
|
| Variable | Default | Description |
|
|
237
|
-
|
|
236
|
+
|---|---|---|
|
|
238
237
|
| `AMEM_DATA_DIR` | `~/.amem` | Data directory |
|
|
239
|
-
| `ZETTELFORGE_BACKEND` | `sqlite` | SQLite community backend. TypeDB
|
|
238
|
+
| `ZETTELFORGE_BACKEND` | `sqlite` | SQLite community backend. TypeDB available via extension. |
|
|
240
239
|
| `ZETTELFORGE_LLM_PROVIDER` | `local` | `local` (llama-cpp) or `ollama` |
|
|
241
240
|
|
|
242
241
|
See [config.default.yaml](config.default.yaml) for all options.
|
|
@@ -247,9 +246,11 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup.
|
|
|
247
246
|
|
|
248
247
|
## License
|
|
249
248
|
|
|
250
|
-
MIT
|
|
249
|
+
MIT — See [LICENSE](LICENSE).
|
|
250
|
+
|
|
251
|
+
## About the author
|
|
251
252
|
|
|
252
|
-
|
|
253
|
+
Built by **Patrick Roland** — Director of SOC Services at Summit 7 Systems, where he built the Vigilance MxDR practice from the ground up. Navy nuclear veteran, CISSP, CCP (CMMC 2.0 Professional). [LinkedIn](https://www.linkedin.com/in/patrickgroland/).
|
|
253
254
|
|
|
254
255
|
## Support the Project
|
|
255
256
|
|