ai-knot 0.3.0__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.
Files changed (96) hide show
  1. ai_knot-0.3.0/.github/ISSUE_TEMPLATE/bug_report.md +41 -0
  2. ai_knot-0.3.0/.github/ISSUE_TEMPLATE/feature_request.md +32 -0
  3. ai_knot-0.3.0/.github/PULL_REQUEST_TEMPLATE.md +45 -0
  4. ai_knot-0.3.0/.github/SECURITY.md +32 -0
  5. ai_knot-0.3.0/.github/workflows/benchmark.yml +41 -0
  6. ai_knot-0.3.0/.github/workflows/ci.yml +113 -0
  7. ai_knot-0.3.0/.github/workflows/npm-publish.yml +32 -0
  8. ai_knot-0.3.0/.github/workflows/publish.yml +30 -0
  9. ai_knot-0.3.0/.github/workflows/release.yml +34 -0
  10. ai_knot-0.3.0/.gitignore +30 -0
  11. ai_knot-0.3.0/.mlc-config.json +29 -0
  12. ai_knot-0.3.0/.pre-commit-config.yaml +16 -0
  13. ai_knot-0.3.0/ARCHITECTURE.md +157 -0
  14. ai_knot-0.3.0/CHANGELOG.md +162 -0
  15. ai_knot-0.3.0/CLAUDE.md +40 -0
  16. ai_knot-0.3.0/CODE_OF_CONDUCT.md +41 -0
  17. ai_knot-0.3.0/CONTRIBUTING.md +89 -0
  18. ai_knot-0.3.0/DECISIONS.md +75 -0
  19. ai_knot-0.3.0/DEVELOPMENT.md +164 -0
  20. ai_knot-0.3.0/LICENSE +21 -0
  21. ai_knot-0.3.0/MANIFEST.in +4 -0
  22. ai_knot-0.3.0/PKG-INFO +724 -0
  23. ai_knot-0.3.0/README.md +684 -0
  24. ai_knot-0.3.0/examples/openai_integration.py +52 -0
  25. ai_knot-0.3.0/examples/quickstart.py +42 -0
  26. ai_knot-0.3.0/npm/.gitignore +2 -0
  27. ai_knot-0.3.0/npm/.npmignore +4 -0
  28. ai_knot-0.3.0/npm/README.md +176 -0
  29. ai_knot-0.3.0/npm/package-lock.json +1459 -0
  30. ai_knot-0.3.0/npm/package.json +63 -0
  31. ai_knot-0.3.0/npm/scripts/postinstall.js +48 -0
  32. ai_knot-0.3.0/npm/src/__tests__/client.test.ts +204 -0
  33. ai_knot-0.3.0/npm/src/__tests__/index.test.ts +261 -0
  34. ai_knot-0.3.0/npm/src/client.ts +230 -0
  35. ai_knot-0.3.0/npm/src/index.ts +132 -0
  36. ai_knot-0.3.0/npm/src/types.ts +62 -0
  37. ai_knot-0.3.0/npm/tsconfig.build.json +6 -0
  38. ai_knot-0.3.0/npm/tsconfig.json +18 -0
  39. ai_knot-0.3.0/npm/vitest.config.ts +8 -0
  40. ai_knot-0.3.0/pyproject.toml +93 -0
  41. ai_knot-0.3.0/skills/principal_architect.md +179 -0
  42. ai_knot-0.3.0/skills/principal_devops.md +169 -0
  43. ai_knot-0.3.0/skills/principal_python_developer.md +141 -0
  44. ai_knot-0.3.0/skills/principal_qa_engineer.md +195 -0
  45. ai_knot-0.3.0/skills/user_guide.md +296 -0
  46. ai_knot-0.3.0/src/ai_knot/__init__.py +17 -0
  47. ai_knot-0.3.0/src/ai_knot/cli.py +222 -0
  48. ai_knot-0.3.0/src/ai_knot/extractor.py +200 -0
  49. ai_knot-0.3.0/src/ai_knot/forgetting.py +77 -0
  50. ai_knot-0.3.0/src/ai_knot/integrations/__init__.py +7 -0
  51. ai_knot-0.3.0/src/ai_knot/integrations/openai.py +78 -0
  52. ai_knot-0.3.0/src/ai_knot/integrations/openclaw.py +244 -0
  53. ai_knot-0.3.0/src/ai_knot/knowledge.py +411 -0
  54. ai_knot-0.3.0/src/ai_knot/mcp_server.py +364 -0
  55. ai_knot-0.3.0/src/ai_knot/providers/__init__.py +126 -0
  56. ai_knot-0.3.0/src/ai_knot/providers/anthropic.py +54 -0
  57. ai_knot-0.3.0/src/ai_knot/providers/base.py +112 -0
  58. ai_knot-0.3.0/src/ai_knot/providers/openai_compat.py +67 -0
  59. ai_knot-0.3.0/src/ai_knot/providers/yandex.py +61 -0
  60. ai_knot-0.3.0/src/ai_knot/py.typed +0 -0
  61. ai_knot-0.3.0/src/ai_knot/retriever.py +99 -0
  62. ai_knot-0.3.0/src/ai_knot/storage/__init__.py +51 -0
  63. ai_knot-0.3.0/src/ai_knot/storage/base.py +61 -0
  64. ai_knot-0.3.0/src/ai_knot/storage/postgres_storage.py +146 -0
  65. ai_knot-0.3.0/src/ai_knot/storage/sqlite_storage.py +213 -0
  66. ai_knot-0.3.0/src/ai_knot/storage/yaml_storage.py +229 -0
  67. ai_knot-0.3.0/src/ai_knot/types.py +78 -0
  68. ai_knot-0.3.0/tests/__init__.py +0 -0
  69. ai_knot-0.3.0/tests/conftest.py +115 -0
  70. ai_knot-0.3.0/tests/test_cli.py +146 -0
  71. ai_knot-0.3.0/tests/test_cli_errors.py +132 -0
  72. ai_knot-0.3.0/tests/test_concurrent.py +146 -0
  73. ai_knot-0.3.0/tests/test_conflict_resolution.py +168 -0
  74. ai_knot-0.3.0/tests/test_examples.py +107 -0
  75. ai_knot-0.3.0/tests/test_extractor.py +88 -0
  76. ai_knot-0.3.0/tests/test_extractor_errors.py +173 -0
  77. ai_knot-0.3.0/tests/test_forgetting.py +120 -0
  78. ai_knot-0.3.0/tests/test_forgetting_scenarios.py +129 -0
  79. ai_knot-0.3.0/tests/test_integration.py +125 -0
  80. ai_knot-0.3.0/tests/test_integrations_openclaw.py +198 -0
  81. ai_knot-0.3.0/tests/test_knowledge.py +237 -0
  82. ai_knot-0.3.0/tests/test_knowledge_edge_cases.py +109 -0
  83. ai_knot-0.3.0/tests/test_mcp_e2e.py +336 -0
  84. ai_knot-0.3.0/tests/test_mcp_server.py +380 -0
  85. ai_knot-0.3.0/tests/test_openai_integration.py +69 -0
  86. ai_knot-0.3.0/tests/test_performance.py +495 -0
  87. ai_knot-0.3.0/tests/test_providers.py +190 -0
  88. ai_knot-0.3.0/tests/test_retriever.py +110 -0
  89. ai_knot-0.3.0/tests/test_retriever_relevance.py +67 -0
  90. ai_knot-0.3.0/tests/test_simulation_scenarios.py +630 -0
  91. ai_knot-0.3.0/tests/test_snapshots.py +225 -0
  92. ai_knot-0.3.0/tests/test_sqlite_storage.py +115 -0
  93. ai_knot-0.3.0/tests/test_storage_compat.py +69 -0
  94. ai_knot-0.3.0/tests/test_storage_factory.py +39 -0
  95. ai_knot-0.3.0/tests/test_types.py +79 -0
  96. ai_knot-0.3.0/tests/test_yaml_storage.py +111 -0
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: Bug report
3
+ about: Something doesn't work as expected
4
+ labels: bug
5
+ ---
6
+
7
+ ## Environment
8
+
9
+ - OS: <!-- e.g. macOS 14, Ubuntu 22.04, Windows 11 -->
10
+ - Python version: <!-- e.g. 3.11.9 -->
11
+ - ai-knot version: <!-- e.g. 0.1.0 — run `ai-knot --version` or `pip show ai-knot` -->
12
+ - Storage backend: <!-- yaml / sqlite / postgres -->
13
+ - LLM provider: <!-- openai / anthropic / yandex / gigachat / qwen / openai-compat -->
14
+
15
+ ## Steps to reproduce
16
+
17
+ 1.
18
+ 2.
19
+ 3.
20
+
21
+ ## Expected behaviour
22
+
23
+ <!-- What should happen -->
24
+
25
+ ## Actual behaviour
26
+
27
+ <!-- What actually happens — include the full traceback if there is one -->
28
+
29
+ ```
30
+ paste error output here
31
+ ```
32
+
33
+ ## Minimal reproducing example
34
+
35
+ ```python
36
+ # Paste the smallest snippet that triggers the bug
37
+ ```
38
+
39
+ ## Additional context
40
+
41
+ <!-- Anything else that might be relevant: custom storage path, concurrent access, specific LLM model, etc. -->
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: Feature request
3
+ about: Propose a new feature or improvement
4
+ labels: enhancement
5
+ ---
6
+
7
+ ## Problem
8
+
9
+ <!-- What are you trying to do that ai-knot doesn't support yet?
10
+ Be specific: "I want to store facts in Redis" is better than "more backends". -->
11
+
12
+ ## Proposed solution
13
+
14
+ <!-- How would you like it to work? Include API sketch if relevant. -->
15
+
16
+ ```python
17
+ # Example of what the API could look like
18
+ ```
19
+
20
+ ## Alternatives considered
21
+
22
+ <!-- What workarounds have you tried? Why don't they work for your case? -->
23
+
24
+ ## Would you be willing to implement this?
25
+
26
+ - [ ] Yes, I can open a PR
27
+ - [ ] I can help test/review
28
+ - [ ] No, just reporting the need
29
+
30
+ ## Additional context
31
+
32
+ <!-- Links, related issues, prior art in other libraries, etc. -->
@@ -0,0 +1,45 @@
1
+ ## Summary
2
+
3
+ <!-- What does this PR do? 2-3 bullet points. -->
4
+
5
+ -
6
+ -
7
+
8
+ ## Type of change
9
+
10
+ - [ ] Bug fix
11
+ - [ ] New feature
12
+ - [ ] Refactoring (no functional change)
13
+ - [ ] Documentation
14
+ - [ ] CI / DevOps
15
+
16
+ ## Principal Python Developer review checklist
17
+
18
+ > Reviewer applies `skills/principal_python_developer.md` before approving.
19
+
20
+ - [ ] All public classes and functions have docstrings
21
+ - [ ] `from __future__ import annotations` present in every new module
22
+ - [ ] All type annotations correct and `mypy --strict` passes
23
+ - [ ] No bare `except:` — specific exceptions only
24
+ - [ ] No mutable default arguments (use `field(default_factory=...)`)
25
+ - [ ] `StorageBackend` protocol fully satisfied (if storage changes)
26
+ - [ ] No hardcoded secrets or absolute paths
27
+ - [ ] `ruff check` and `ruff format --check` pass clean
28
+
29
+ ## Test checklist
30
+
31
+ - [ ] New behaviour covered by unit tests
32
+ - [ ] LLM calls mocked (no real API calls in tests)
33
+ - [ ] Storage tests include round-trip + multi-agent isolation
34
+ - [ ] `pytest --cov-fail-under=80` passes
35
+ - [ ] Both YAML and SQLite backends tested (if storage-related)
36
+
37
+ ## Documentation
38
+
39
+ - [ ] `CHANGELOG.md` updated under `[Unreleased]`
40
+ - [ ] Docstrings updated for changed public API
41
+ - [ ] `ARCHITECTURE.md` updated if new layer or extension point added
42
+
43
+ ## Screenshots / output (if applicable)
44
+
45
+ <!-- Paste CLI output or relevant logs -->
@@ -0,0 +1,32 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ | Version | Supported |
6
+ |---------|-----------|
7
+ | 0.1.x | Yes |
8
+
9
+ ## Reporting a Vulnerability
10
+
11
+ If you discover a security vulnerability in ai-knot, please report it responsibly:
12
+
13
+ 1. **Do not** open a public issue.
14
+ 2. Email the maintainers or open a private security advisory via
15
+ [GitHub Security Advisories](https://github.com/alsoleg89/ai-knot/security/advisories/new).
16
+ 3. Include a description of the vulnerability, steps to reproduce, and potential impact.
17
+
18
+ We will acknowledge receipt within 48 hours and aim to release a fix within 7 days
19
+ for critical issues.
20
+
21
+ ## Scope
22
+
23
+ - ai-knot library code (`src/ai_knot/`)
24
+ - CLI interface
25
+ - Storage backends (YAML, SQLite, PostgreSQL)
26
+ - LLM provider integrations
27
+
28
+ ## Out of Scope
29
+
30
+ - Vulnerabilities in third-party dependencies (report upstream)
31
+ - Issues requiring physical access to the machine
32
+ - Social engineering attacks
@@ -0,0 +1,41 @@
1
+ name: Benchmarks
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ benchmark:
14
+ name: Performance Benchmarks
15
+ runs-on: ubuntu-latest
16
+
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - uses: actions/setup-python@v5
21
+ with:
22
+ python-version: "3.12"
23
+ cache: pip
24
+
25
+ - name: Install dependencies
26
+ run: pip install -e ".[dev]"
27
+
28
+ - name: Run benchmarks
29
+ run: |
30
+ pytest tests/test_performance.py -m slow \
31
+ --benchmark-json=benchmark.json \
32
+ --benchmark-min-rounds=5 \
33
+ -v --no-cov
34
+
35
+ - name: Upload benchmark results
36
+ uses: actions/upload-artifact@v4
37
+ with:
38
+ name: benchmark-results-${{ github.run_id }}-${{ github.sha }}
39
+ path: benchmark.json
40
+ retention-days: 90
41
+
@@ -0,0 +1,113 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ lint:
14
+ name: Lint & Type Check
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - uses: actions/setup-python@v5
20
+ with:
21
+ python-version: "3.12"
22
+ cache: pip
23
+
24
+ - name: Install dependencies
25
+ run: pip install -e ".[dev]"
26
+
27
+ - name: Ruff check
28
+ run: ruff check src/ tests/
29
+
30
+ - name: Ruff format check
31
+ run: ruff format --check src/ tests/
32
+
33
+ - name: Mypy
34
+ run: mypy src/ai_knot/
35
+
36
+ test:
37
+ name: Test (Python ${{ matrix.python-version }})
38
+ runs-on: ubuntu-latest
39
+ strategy:
40
+ fail-fast: false
41
+ matrix:
42
+ python-version: ["3.11", "3.12"]
43
+
44
+ steps:
45
+ - uses: actions/checkout@v4
46
+
47
+ - uses: actions/setup-python@v5
48
+ with:
49
+ python-version: ${{ matrix.python-version }}
50
+ cache: pip
51
+
52
+ - name: Install dependencies
53
+ run: pip install -e ".[dev,openai,mcp]"
54
+
55
+ - name: Run tests
56
+ run: |
57
+ if [ "${{ matrix.python-version }}" = "3.12" ]; then
58
+ pytest -n auto --timeout=10 -m "not slow" \
59
+ --cov=ai_knot --cov-report=xml --cov-report=term-missing
60
+ else
61
+ pytest -n auto --timeout=10 -m "not slow" --no-cov
62
+ fi
63
+
64
+ - name: Upload coverage
65
+ if: matrix.python-version == '3.12'
66
+ uses: codecov/codecov-action@v4
67
+ with:
68
+ files: coverage.xml
69
+ fail_ci_if_error: false
70
+
71
+ test-npm:
72
+ name: Test (TypeScript)
73
+ runs-on: ubuntu-latest
74
+ defaults:
75
+ run:
76
+ working-directory: npm
77
+
78
+ steps:
79
+ - uses: actions/checkout@v4
80
+
81
+ - uses: actions/setup-node@v4
82
+ with:
83
+ node-version: "20"
84
+ cache: npm
85
+ cache-dependency-path: npm/package-lock.json
86
+
87
+ - name: Install dependencies
88
+ run: npm ci
89
+
90
+ - name: Type check
91
+ run: npm run typecheck
92
+
93
+ - name: Run tests
94
+ run: npm test
95
+
96
+ test-e2e:
97
+ name: E2E (MCP server)
98
+ runs-on: ubuntu-latest
99
+
100
+ steps:
101
+ - uses: actions/checkout@v4
102
+
103
+ - uses: actions/setup-python@v5
104
+ with:
105
+ python-version: "3.12"
106
+ cache: pip
107
+
108
+ - name: Install dependencies
109
+ run: pip install -e ".[dev,mcp]"
110
+
111
+ - name: Run E2E tests
112
+ # --no-cov: coverage is measured via subprocess, not import; 80% threshold would fail
113
+ run: pytest tests/test_mcp_e2e.py -v -m integration --no-cov
@@ -0,0 +1,32 @@
1
+ name: Publish to npm
2
+
3
+ on:
4
+ workflow_dispatch:
5
+
6
+ jobs:
7
+ publish:
8
+ runs-on: ubuntu-latest
9
+ defaults:
10
+ run:
11
+ working-directory: npm
12
+
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - uses: actions/setup-node@v4
17
+ with:
18
+ node-version: "20"
19
+ registry-url: "https://registry.npmjs.org"
20
+
21
+ - name: Install dev dependencies
22
+ run: npm ci
23
+ env:
24
+ AI_KNOT_SKIP_PYTHON_INSTALL: "1"
25
+
26
+ - name: Build (ESM + CJS)
27
+ run: npm run build
28
+
29
+ - name: Publish to npm
30
+ run: npm publish --access public
31
+ env:
32
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -0,0 +1,30 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ workflow_dispatch:
5
+
6
+ permissions:
7
+ contents: read
8
+
9
+ jobs:
10
+ publish:
11
+ runs-on: ubuntu-latest
12
+ environment: pypi
13
+ permissions:
14
+ id-token: write
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - uses: actions/setup-python@v5
20
+ with:
21
+ python-version: "3.12"
22
+
23
+ - name: Install build tools
24
+ run: pip install hatch
25
+
26
+ - name: Build package
27
+ run: hatch build
28
+
29
+ - name: Publish to PyPI
30
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,34 @@
1
+ name: Create Release
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ version:
7
+ description: "Version to release (e.g. 0.2.0)"
8
+ required: true
9
+ type: string
10
+
11
+ permissions:
12
+ contents: write
13
+
14
+ jobs:
15
+ tag:
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ with:
20
+ fetch-depth: 0
21
+
22
+ - name: Validate version format
23
+ run: |
24
+ if ! echo "${{ inputs.version }}" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
25
+ echo "Error: version must be in X.Y.Z format (e.g. 0.2.0)"
26
+ exit 1
27
+ fi
28
+
29
+ - name: Create and push tag
30
+ run: |
31
+ git config user.name "alsoleg89"
32
+ git config user.email "155813332+alsoleg89@users.noreply.github.com"
33
+ git tag "v${{ inputs.version }}"
34
+ git push origin "v${{ inputs.version }}"
@@ -0,0 +1,30 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.so
5
+ dist/
6
+ build/
7
+ *.egg-info/
8
+ *.egg
9
+ .eggs/
10
+ *.whl
11
+ .venv/
12
+ venv/
13
+ env/
14
+ .env
15
+ .mypy_cache/
16
+ .ruff_cache/
17
+ .pytest_cache/
18
+ htmlcov/
19
+ coverage.xml
20
+ .coverage
21
+ .coverage.*
22
+ *.log
23
+ .DS_Store
24
+ Thumbs.db
25
+ .idea/
26
+ .vscode/
27
+ *.swp
28
+ *.swo
29
+ *~
30
+ .ai_knot/
@@ -0,0 +1,29 @@
1
+ {
2
+ "ignorePatterns": [
3
+ {
4
+ "pattern": "^https://shields\\.io"
5
+ },
6
+ {
7
+ "pattern": "^https://img\\.shields\\.io"
8
+ },
9
+ {
10
+ "pattern": "^https://github\\.com/.*/actions/workflows"
11
+ },
12
+ {
13
+ "pattern": "^https://pypi\\.org/project/ai-knot"
14
+ },
15
+ {
16
+ "pattern": "^http://localhost"
17
+ },
18
+ {
19
+ "pattern": "^https://github\\.com/alsoleg89/ai-knot/compare/"
20
+ },
21
+ {
22
+ "pattern": "^https://github\\.com/alsoleg89/ai-knot/releases/tag/"
23
+ }
24
+ ],
25
+ "timeout": "10s",
26
+ "retryOn429": true,
27
+ "retryCount": 3,
28
+ "aliveStatusCodes": [200, 206]
29
+ }
@@ -0,0 +1,16 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.4.10
4
+ hooks:
5
+ - id: ruff
6
+ args: [--fix]
7
+ - id: ruff-format
8
+
9
+ - repo: https://github.com/pre-commit/mirrors-mypy
10
+ rev: v1.10.0
11
+ hooks:
12
+ - id: mypy
13
+ additional_dependencies:
14
+ - types-PyYAML>=6.0
15
+ - httpx>=0.27
16
+ args: [--strict]
@@ -0,0 +1,157 @@
1
+ # ai-knot — Architecture
2
+
3
+ ## Design goals
4
+
5
+ | Goal | How |
6
+ |---|---|
7
+ | **Zero vendor lock-in** | Pluggable `StorageBackend` protocol |
8
+ | **Zero mandatory cloud deps** | Only `click`, `pyyaml`, `httpx` required |
9
+ | **Human-readable storage** | YAML files by default |
10
+ | **Signal over noise** | LLM distillation + Ebbinghaus decay |
11
+ | **Framework-agnostic** | Plain Python objects, no base classes to inherit |
12
+
13
+ ---
14
+
15
+ ## Layer diagram
16
+
17
+ ```
18
+ ┌─────────────────────────────────────────────────────────────┐
19
+ │ CLI (ai_knot.cli) Integrations (*.integrations) │
20
+ ├─────────────────────────────────────────────────────────────┤
21
+ │ KnowledgeBase (ai_knot.knowledge) │ ← public API
22
+ ├──────────────┬──────────────────┬───────────────────────────┤
23
+ │ Extractor │ TFIDFRetriever │ apply_decay / forgetting │
24
+ │ (LLM calls) │ (TF-IDF search) │ (Ebbinghaus curve) │
25
+ ├──────────────┴──────────────────┴───────────────────────────┤
26
+ │ StorageBackend (protocol) │
27
+ │ YAMLStorage SQLiteStorage (future: PG …) │
28
+ ├─────────────────────────────────────────────────────────────┤
29
+ │ Core types: Fact, MemoryType, ConversationTurn │
30
+ └─────────────────────────────────────────────────────────────┘
31
+ ```
32
+
33
+ ### Dependency rules (no circular imports)
34
+
35
+ ```
36
+ types ← storage ← forgetting
37
+ ← retriever
38
+ ← extractor
39
+ ← knowledge ← cli
40
+ ← integrations
41
+ ```
42
+
43
+ `knowledge.py` is the top of the internal dependency graph.
44
+ Nothing in `storage/`, `forgetting.py`, or `retriever.py` may import from `knowledge.py`.
45
+
46
+ ---
47
+
48
+ ## Core types (`ai_knot.types`)
49
+
50
+ ### `Fact`
51
+ The atomic unit of knowledge.
52
+
53
+ | Field | Type | Description |
54
+ |---|---|---|
55
+ | `content` | `str` | Human-readable knowledge string |
56
+ | `type` | `MemoryType` | `semantic` / `procedural` / `episodic` |
57
+ | `importance` | `float` | 0.0–1.0; controls decay speed |
58
+ | `retention_score` | `float` | Current Ebbinghaus score (updated on recall) |
59
+ | `access_count` | `int` | Times retrieved — increases stability |
60
+ | `tags` | `list[str]` | Optional labels |
61
+ | `id` | `str` | 8-char UUID hex |
62
+ | `created_at` | `datetime` | UTC |
63
+ | `last_accessed` | `datetime` | UTC |
64
+
65
+ ### `MemoryType`
66
+ `SEMANTIC` — facts about the world/user
67
+ `PROCEDURAL` — how the user wants things done
68
+ `EPISODIC` — specific past events
69
+
70
+ ---
71
+
72
+ ## Storage layer
73
+
74
+ ### `StorageBackend` protocol
75
+
76
+ ```python
77
+ def save(self, agent_id: str, facts: list[Fact]) -> None: ...
78
+ def load(self, agent_id: str) -> list[Fact]: ...
79
+ def delete(self, agent_id: str, fact_id: str) -> None: ...
80
+ def list_agents(self) -> list[str]: ...
81
+ ```
82
+
83
+ `save` is a **full replace** — the caller always loads, mutates, then saves.
84
+
85
+ Backends are responsible for:
86
+ - Namespacing by `agent_id`
87
+ - Serialising all `Fact` fields including datetime with UTC timezone
88
+
89
+ ### YAMLStorage
90
+ File layout: `{base_dir}/{agent_id}/knowledge.yaml`
91
+ Key: `fact.id` (8-char hex)
92
+ Human-readable, Git-trackable, editable by hand.
93
+
94
+ ### SQLiteStorage
95
+ Single `.db` file. Schema:
96
+ ```sql
97
+ CREATE TABLE facts (
98
+ id TEXT, agent_id TEXT,
99
+ content TEXT, type TEXT,
100
+ importance REAL, retention REAL,
101
+ access_count INTEGER, tags TEXT,
102
+ created_at TEXT, last_accessed TEXT,
103
+ PRIMARY KEY (agent_id, id)
104
+ )
105
+ ```
106
+
107
+ ---
108
+
109
+ ## Forgetting curve (`ai_knot.forgetting`)
110
+
111
+ ```
112
+ retention(t) = exp(−t / stability)
113
+
114
+ stability = 168h × importance × (1 + ln(1 + access_count))
115
+ ```
116
+
117
+ - `t` = hours since `last_accessed`
118
+ - Base stability = 168 h (1 week) for `importance=1.0, access_count=0`
119
+ - Applied via `apply_decay(facts)` **before** every retrieval in `KnowledgeBase.recall()`
120
+ - After retrieval, `access_count` is incremented and `last_accessed` is updated → reinforcement
121
+
122
+ ---
123
+
124
+ ## Retrieval (`ai_knot.retriever`)
125
+
126
+ `TFIDFRetriever.search(query, facts, top_k)` — zero external dependencies.
127
+ Returns `list[tuple[Fact, float]]` — each tuple is `(fact, hybrid_score)`.
128
+
129
+ Scoring:
130
+ ```
131
+ hybrid_score = 0.6 × tfidf + 0.2 × retention_score + 0.2 × importance
132
+ ```
133
+
134
+ TF-IDF is computed fresh per query (no index) — suitable for knowledge bases up to ~10k facts.
135
+
136
+ `KnowledgeBase.recall_facts_with_scores()` exposes `(Fact, float)` pairs to callers that need
137
+ relevance scores (e.g. integration adapters). `recall()` and `recall_facts()` unpack the scores
138
+ internally and work as before.
139
+
140
+ ---
141
+
142
+ ## Extraction (`ai_knot.extractor`)
143
+
144
+ `Extractor.extract(turns)` sends the conversation to an LLM and parses the returned JSON array of facts.
145
+ Supports `openai` and `anthropic` providers via `httpx`.
146
+ Deduplication: Jaccard word-similarity ≥ 0.8 → keep first occurrence.
147
+
148
+ ---
149
+
150
+ ## Extension points
151
+
152
+ | What | Where | Interface |
153
+ |---|---|---|
154
+ | New storage backend | `src/ai_knot/storage/` | `StorageBackend` protocol |
155
+ | New LLM provider | `Extractor._call_<provider>` | Returns `list[dict]` from LLM |
156
+ | New integration | `src/ai_knot/integrations/` | Import `KnowledgeBase`, wrap |
157
+ | New retriever | Replace `TFIDFRetriever` | `.search(query, facts, top_k)` |