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.
- ai_knot-0.3.0/.github/ISSUE_TEMPLATE/bug_report.md +41 -0
- ai_knot-0.3.0/.github/ISSUE_TEMPLATE/feature_request.md +32 -0
- ai_knot-0.3.0/.github/PULL_REQUEST_TEMPLATE.md +45 -0
- ai_knot-0.3.0/.github/SECURITY.md +32 -0
- ai_knot-0.3.0/.github/workflows/benchmark.yml +41 -0
- ai_knot-0.3.0/.github/workflows/ci.yml +113 -0
- ai_knot-0.3.0/.github/workflows/npm-publish.yml +32 -0
- ai_knot-0.3.0/.github/workflows/publish.yml +30 -0
- ai_knot-0.3.0/.github/workflows/release.yml +34 -0
- ai_knot-0.3.0/.gitignore +30 -0
- ai_knot-0.3.0/.mlc-config.json +29 -0
- ai_knot-0.3.0/.pre-commit-config.yaml +16 -0
- ai_knot-0.3.0/ARCHITECTURE.md +157 -0
- ai_knot-0.3.0/CHANGELOG.md +162 -0
- ai_knot-0.3.0/CLAUDE.md +40 -0
- ai_knot-0.3.0/CODE_OF_CONDUCT.md +41 -0
- ai_knot-0.3.0/CONTRIBUTING.md +89 -0
- ai_knot-0.3.0/DECISIONS.md +75 -0
- ai_knot-0.3.0/DEVELOPMENT.md +164 -0
- ai_knot-0.3.0/LICENSE +21 -0
- ai_knot-0.3.0/MANIFEST.in +4 -0
- ai_knot-0.3.0/PKG-INFO +724 -0
- ai_knot-0.3.0/README.md +684 -0
- ai_knot-0.3.0/examples/openai_integration.py +52 -0
- ai_knot-0.3.0/examples/quickstart.py +42 -0
- ai_knot-0.3.0/npm/.gitignore +2 -0
- ai_knot-0.3.0/npm/.npmignore +4 -0
- ai_knot-0.3.0/npm/README.md +176 -0
- ai_knot-0.3.0/npm/package-lock.json +1459 -0
- ai_knot-0.3.0/npm/package.json +63 -0
- ai_knot-0.3.0/npm/scripts/postinstall.js +48 -0
- ai_knot-0.3.0/npm/src/__tests__/client.test.ts +204 -0
- ai_knot-0.3.0/npm/src/__tests__/index.test.ts +261 -0
- ai_knot-0.3.0/npm/src/client.ts +230 -0
- ai_knot-0.3.0/npm/src/index.ts +132 -0
- ai_knot-0.3.0/npm/src/types.ts +62 -0
- ai_knot-0.3.0/npm/tsconfig.build.json +6 -0
- ai_knot-0.3.0/npm/tsconfig.json +18 -0
- ai_knot-0.3.0/npm/vitest.config.ts +8 -0
- ai_knot-0.3.0/pyproject.toml +93 -0
- ai_knot-0.3.0/skills/principal_architect.md +179 -0
- ai_knot-0.3.0/skills/principal_devops.md +169 -0
- ai_knot-0.3.0/skills/principal_python_developer.md +141 -0
- ai_knot-0.3.0/skills/principal_qa_engineer.md +195 -0
- ai_knot-0.3.0/skills/user_guide.md +296 -0
- ai_knot-0.3.0/src/ai_knot/__init__.py +17 -0
- ai_knot-0.3.0/src/ai_knot/cli.py +222 -0
- ai_knot-0.3.0/src/ai_knot/extractor.py +200 -0
- ai_knot-0.3.0/src/ai_knot/forgetting.py +77 -0
- ai_knot-0.3.0/src/ai_knot/integrations/__init__.py +7 -0
- ai_knot-0.3.0/src/ai_knot/integrations/openai.py +78 -0
- ai_knot-0.3.0/src/ai_knot/integrations/openclaw.py +244 -0
- ai_knot-0.3.0/src/ai_knot/knowledge.py +411 -0
- ai_knot-0.3.0/src/ai_knot/mcp_server.py +364 -0
- ai_knot-0.3.0/src/ai_knot/providers/__init__.py +126 -0
- ai_knot-0.3.0/src/ai_knot/providers/anthropic.py +54 -0
- ai_knot-0.3.0/src/ai_knot/providers/base.py +112 -0
- ai_knot-0.3.0/src/ai_knot/providers/openai_compat.py +67 -0
- ai_knot-0.3.0/src/ai_knot/providers/yandex.py +61 -0
- ai_knot-0.3.0/src/ai_knot/py.typed +0 -0
- ai_knot-0.3.0/src/ai_knot/retriever.py +99 -0
- ai_knot-0.3.0/src/ai_knot/storage/__init__.py +51 -0
- ai_knot-0.3.0/src/ai_knot/storage/base.py +61 -0
- ai_knot-0.3.0/src/ai_knot/storage/postgres_storage.py +146 -0
- ai_knot-0.3.0/src/ai_knot/storage/sqlite_storage.py +213 -0
- ai_knot-0.3.0/src/ai_knot/storage/yaml_storage.py +229 -0
- ai_knot-0.3.0/src/ai_knot/types.py +78 -0
- ai_knot-0.3.0/tests/__init__.py +0 -0
- ai_knot-0.3.0/tests/conftest.py +115 -0
- ai_knot-0.3.0/tests/test_cli.py +146 -0
- ai_knot-0.3.0/tests/test_cli_errors.py +132 -0
- ai_knot-0.3.0/tests/test_concurrent.py +146 -0
- ai_knot-0.3.0/tests/test_conflict_resolution.py +168 -0
- ai_knot-0.3.0/tests/test_examples.py +107 -0
- ai_knot-0.3.0/tests/test_extractor.py +88 -0
- ai_knot-0.3.0/tests/test_extractor_errors.py +173 -0
- ai_knot-0.3.0/tests/test_forgetting.py +120 -0
- ai_knot-0.3.0/tests/test_forgetting_scenarios.py +129 -0
- ai_knot-0.3.0/tests/test_integration.py +125 -0
- ai_knot-0.3.0/tests/test_integrations_openclaw.py +198 -0
- ai_knot-0.3.0/tests/test_knowledge.py +237 -0
- ai_knot-0.3.0/tests/test_knowledge_edge_cases.py +109 -0
- ai_knot-0.3.0/tests/test_mcp_e2e.py +336 -0
- ai_knot-0.3.0/tests/test_mcp_server.py +380 -0
- ai_knot-0.3.0/tests/test_openai_integration.py +69 -0
- ai_knot-0.3.0/tests/test_performance.py +495 -0
- ai_knot-0.3.0/tests/test_providers.py +190 -0
- ai_knot-0.3.0/tests/test_retriever.py +110 -0
- ai_knot-0.3.0/tests/test_retriever_relevance.py +67 -0
- ai_knot-0.3.0/tests/test_simulation_scenarios.py +630 -0
- ai_knot-0.3.0/tests/test_snapshots.py +225 -0
- ai_knot-0.3.0/tests/test_sqlite_storage.py +115 -0
- ai_knot-0.3.0/tests/test_storage_compat.py +69 -0
- ai_knot-0.3.0/tests/test_storage_factory.py +39 -0
- ai_knot-0.3.0/tests/test_types.py +79 -0
- 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 }}"
|
ai_knot-0.3.0/.gitignore
ADDED
|
@@ -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)` |
|