synap 0.1.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.
- synap-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +56 -0
- synap-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +27 -0
- synap-0.1.0/.github/pull_request_template.md +13 -0
- synap-0.1.0/.github/workflows/ci.yml +30 -0
- synap-0.1.0/.github/workflows/publish.yml +28 -0
- synap-0.1.0/.gitignore +16 -0
- synap-0.1.0/CHANGELOG.md +58 -0
- synap-0.1.0/CONTRIBUTING.md +68 -0
- synap-0.1.0/LICENSE +21 -0
- synap-0.1.0/PKG-INFO +271 -0
- synap-0.1.0/README.md +238 -0
- synap-0.1.0/SECURITY.md +21 -0
- synap-0.1.0/docs/api.md +396 -0
- synap-0.1.0/docs/architecture.md +225 -0
- synap-0.1.0/docs/bootstrap.md +227 -0
- synap-0.1.0/docs/examples.md +296 -0
- synap-0.1.0/pyproject.toml +47 -0
- synap-0.1.0/src/synap/__init__.py +69 -0
- synap-0.1.0/src/synap/_utils.py +51 -0
- synap-0.1.0/src/synap/backends/__init__.py +6 -0
- synap-0.1.0/src/synap/backends/kuzu.py +438 -0
- synap-0.1.0/src/synap/backends/postgres.py +411 -0
- synap-0.1.0/src/synap/backends/sqlite.py +260 -0
- synap-0.1.0/src/synap/bootstrap.py +316 -0
- synap-0.1.0/src/synap/consolidation.py +382 -0
- synap-0.1.0/src/synap/episodic.py +353 -0
- synap-0.1.0/src/synap/facade.py +549 -0
- synap-0.1.0/src/synap/graph.py +259 -0
- synap-0.1.0/src/synap/mcp_server.py +239 -0
- synap-0.1.0/src/synap/persistent_graph.py +292 -0
- synap-0.1.0/src/synap/procedural.py +200 -0
- synap-0.1.0/src/synap/protocols.py +171 -0
- synap-0.1.0/src/synap/py.typed +0 -0
- synap-0.1.0/src/synap/semantic.py +283 -0
- synap-0.1.0/src/synap/tools.py +201 -0
- synap-0.1.0/src/synap/types.py +172 -0
- synap-0.1.0/tests/__init__.py +0 -0
- synap-0.1.0/tests/conftest.py +67 -0
- synap-0.1.0/tests/test_bootstrap.py +191 -0
- synap-0.1.0/tests/test_facade.py +472 -0
- synap-0.1.0/tests/test_graph.py +160 -0
- synap-0.1.0/tests/test_kuzu.py +240 -0
- synap-0.1.0/tests/test_persistent_graph.py +227 -0
- synap-0.1.0/tests/test_sqlite.py +192 -0
- synap-0.1.0/tests/test_subsystems.py +454 -0
- synap-0.1.0/tests/test_tools.py +190 -0
- synap-0.1.0/uv.lock +1494 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
name: Bug Report
|
|
2
|
+
description: Report something that isn't working correctly
|
|
3
|
+
labels: ["bug"]
|
|
4
|
+
body:
|
|
5
|
+
- type: textarea
|
|
6
|
+
id: description
|
|
7
|
+
attributes:
|
|
8
|
+
label: What happened?
|
|
9
|
+
description: A clear description of the bug.
|
|
10
|
+
validations:
|
|
11
|
+
required: true
|
|
12
|
+
|
|
13
|
+
- type: textarea
|
|
14
|
+
id: expected
|
|
15
|
+
attributes:
|
|
16
|
+
label: Expected behavior
|
|
17
|
+
description: What did you expect to happen?
|
|
18
|
+
validations:
|
|
19
|
+
required: true
|
|
20
|
+
|
|
21
|
+
- type: textarea
|
|
22
|
+
id: reproduce
|
|
23
|
+
attributes:
|
|
24
|
+
label: Steps to reproduce
|
|
25
|
+
description: Minimal code or steps to reproduce the issue.
|
|
26
|
+
render: python
|
|
27
|
+
validations:
|
|
28
|
+
required: true
|
|
29
|
+
|
|
30
|
+
- type: input
|
|
31
|
+
id: version
|
|
32
|
+
attributes:
|
|
33
|
+
label: Engram version
|
|
34
|
+
placeholder: "0.1.0"
|
|
35
|
+
validations:
|
|
36
|
+
required: true
|
|
37
|
+
|
|
38
|
+
- type: input
|
|
39
|
+
id: python-version
|
|
40
|
+
attributes:
|
|
41
|
+
label: Python version
|
|
42
|
+
placeholder: "3.12"
|
|
43
|
+
validations:
|
|
44
|
+
required: true
|
|
45
|
+
|
|
46
|
+
- type: dropdown
|
|
47
|
+
id: backend
|
|
48
|
+
attributes:
|
|
49
|
+
label: Storage backend
|
|
50
|
+
options:
|
|
51
|
+
- In-memory (default)
|
|
52
|
+
- KuzuBackend
|
|
53
|
+
- SQLiteBackend
|
|
54
|
+
- Other / N/A
|
|
55
|
+
validations:
|
|
56
|
+
required: true
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: Feature Request
|
|
2
|
+
description: Suggest an idea or improvement
|
|
3
|
+
labels: ["enhancement"]
|
|
4
|
+
body:
|
|
5
|
+
- type: textarea
|
|
6
|
+
id: problem
|
|
7
|
+
attributes:
|
|
8
|
+
label: Problem or motivation
|
|
9
|
+
description: What problem does this solve, or what would it enable?
|
|
10
|
+
validations:
|
|
11
|
+
required: true
|
|
12
|
+
|
|
13
|
+
- type: textarea
|
|
14
|
+
id: proposal
|
|
15
|
+
attributes:
|
|
16
|
+
label: Proposed solution
|
|
17
|
+
description: How would you like this to work? Code sketches welcome.
|
|
18
|
+
validations:
|
|
19
|
+
required: false
|
|
20
|
+
|
|
21
|
+
- type: textarea
|
|
22
|
+
id: alternatives
|
|
23
|
+
attributes:
|
|
24
|
+
label: Alternatives considered
|
|
25
|
+
description: Other approaches you've thought about.
|
|
26
|
+
validations:
|
|
27
|
+
required: false
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
## What does this PR do?
|
|
2
|
+
|
|
3
|
+
<!-- Brief description of the change -->
|
|
4
|
+
|
|
5
|
+
## Why?
|
|
6
|
+
|
|
7
|
+
<!-- Motivation: what problem does this solve, or what issue does it close? -->
|
|
8
|
+
|
|
9
|
+
## Checklist
|
|
10
|
+
|
|
11
|
+
- [ ] Tests added or updated
|
|
12
|
+
- [ ] Documentation updated (if changing public API)
|
|
13
|
+
- [ ] `pytest` passes locally
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
20
|
+
uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: |
|
|
26
|
+
python -m pip install --upgrade pip
|
|
27
|
+
pip install -e ".[dev]"
|
|
28
|
+
|
|
29
|
+
- name: Run tests
|
|
30
|
+
run: pytest
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
id-token: write # Required for trusted publishing
|
|
12
|
+
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Set up Python
|
|
17
|
+
uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.13"
|
|
20
|
+
|
|
21
|
+
- name: Install uv
|
|
22
|
+
uses: astral-sh/setup-uv@v4
|
|
23
|
+
|
|
24
|
+
- name: Build package
|
|
25
|
+
run: uv build
|
|
26
|
+
|
|
27
|
+
- name: Publish to PyPI
|
|
28
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
synap-0.1.0/.gitignore
ADDED
synap-0.1.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **Breaking:** `EpisodicMemory.recall()` now reconstructs episodes from graph traversal instead of an in-memory dict — episodic memory now works correctly across process restarts with persistent storage
|
|
13
|
+
- **Breaking:** `EpisodicMemory.find_patterns()` is now async (`await ep.find_patterns(...)`)
|
|
14
|
+
- **Breaking:** `EpisodicMemory.episode_count` property replaced with async method `episode_count()` (`await ep.episode_count()`)
|
|
15
|
+
- **Breaking:** `CognitiveMemory.evaluate()` is now async (`await memory.evaluate()`)
|
|
16
|
+
- **Breaking:** `ProceduralMemory.match()` now reconstructs procedures from graph — procedures survive process restarts with persistent storage
|
|
17
|
+
- **Breaking:** `ProceduralMemory.get_procedure()` and `list_procedures()` are now async
|
|
18
|
+
- **Breaking:** `StorageBackend` protocol now requires `node_count()` and `edge_count()` methods
|
|
19
|
+
- `EpisodicMemory.record()` now stores structured metadata (episode content, tool calls, correction) on graph nodes for round-trip reconstruction
|
|
20
|
+
- `ProceduralMemory.register()` now stores all Procedure fields in node metadata for reconstruction
|
|
21
|
+
- `ConsolidationEngine.run_periodic()` queries the graph instead of accessing internal episodic state
|
|
22
|
+
- `PersistentGraph.node_count()` and `edge_count()` delegate to backend-native counts instead of loading all rows
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- **Procedural consolidation now actually amends procedures** — `_consolidate_to_procedural` generates a new schema field via LLM, registers a new Procedure version with `supersedes` edge, so `prepare_call()` returns the amended schema
|
|
27
|
+
- **Corrective hints now work** — `_corrective_hints()` reads correction text from episode outcome nodes; hints are injected into schema descriptions of fields with prerequisites (decision fields)
|
|
28
|
+
- `CognitiveMemory.evaluate()` now populates `cold_spots` (task types with ≤2 episodes) — previously always returned empty
|
|
29
|
+
- Warning effectiveness tracking is now per-call, not cumulative across the session
|
|
30
|
+
- **Split-graph guard** — `CognitiveMemory` raises `ValueError` if the domain adapter's graph differs from the graph passed to the constructor
|
|
31
|
+
- Removed misleading `sqlite` optional dependency (`aiosqlite`) — `SQLiteBackend` uses stdlib `sqlite3`
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- `EpisodicMemory._reconstruct_episode()` — rebuilds Episode from graph traversal (cue→content→outcome)
|
|
36
|
+
- `EpisodicMemory.all_episodes()` — reconstructs all episodes from the graph
|
|
37
|
+
- `ProceduralMemory._reconstruct_procedure()` — rebuilds Procedure from graph node metadata
|
|
38
|
+
- `StorageBackend.node_count()` and `edge_count()` — efficient count queries for Kuzu and SQLite backends
|
|
39
|
+
- Shared `_utils.cosine_similarity()` — deduplicated from graph, episodic, and sqlite modules
|
|
40
|
+
|
|
41
|
+
## [0.1.0] - 2026-03-17
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- Three-subsystem cognitive memory architecture: semantic, procedural, episodic
|
|
46
|
+
- `CognitiveMemory` facade with `prepare_call`, `record_outcome`, `consolidate`
|
|
47
|
+
- Pluggable `SemanticDomain` protocol for domain-specific knowledge types
|
|
48
|
+
- Built-in `SemanticMemory` with embedding-based graph traversal
|
|
49
|
+
- `ProceduralMemory` with output schema enforcement via field ordering
|
|
50
|
+
- `EpisodicMemory` with cue-content-outcome subgraphs and pattern detection
|
|
51
|
+
- `ToolCall` tracking for structured tool invocation recording
|
|
52
|
+
- `ConsolidationEngine` for episodic-to-domain learning
|
|
53
|
+
- `Bootstrap` helper for cold-start knowledge extraction
|
|
54
|
+
- In-memory `MemoryGraph` with BFS traversal and cosine similarity
|
|
55
|
+
- `KuzuBackend` with native Cypher traversal and vector search
|
|
56
|
+
- `SQLiteBackend` with JSON blob storage and indexed queries
|
|
57
|
+
- `PersistentGraph` async wrapper for storage backends
|
|
58
|
+
- Async-first public API designed for framework integration
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Contributing to Synap
|
|
2
|
+
|
|
3
|
+
Thanks for your interest in contributing to Synap! This guide will help you get started.
|
|
4
|
+
|
|
5
|
+
## Development Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Clone the repo
|
|
9
|
+
git clone https://github.com/veeeceee/synap.git
|
|
10
|
+
cd synap
|
|
11
|
+
|
|
12
|
+
# Create a virtual environment and install dev dependencies
|
|
13
|
+
python -m venv .venv
|
|
14
|
+
source .venv/bin/activate
|
|
15
|
+
pip install -e ".[dev]"
|
|
16
|
+
|
|
17
|
+
# Or with uv
|
|
18
|
+
uv sync --extra dev
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Running Tests
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pytest
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Tests use fake embedding and LLM providers (defined in `tests/conftest.py`), so no external services are needed.
|
|
28
|
+
|
|
29
|
+
## Making Changes
|
|
30
|
+
|
|
31
|
+
1. **Fork the repo** and create a branch from `main`
|
|
32
|
+
2. **Write tests** for any new functionality
|
|
33
|
+
3. **Run the test suite** to make sure nothing is broken
|
|
34
|
+
4. **Keep your PR focused** — one concern per pull request
|
|
35
|
+
5. **Update documentation** if you're changing public API
|
|
36
|
+
|
|
37
|
+
## Code Style
|
|
38
|
+
|
|
39
|
+
- Type hints on all public functions and methods
|
|
40
|
+
- Docstrings on public classes and methods
|
|
41
|
+
- Follow the existing patterns — protocols for extension points, dataclasses for data
|
|
42
|
+
- Async-first: public API methods should be async
|
|
43
|
+
|
|
44
|
+
## Architecture Notes
|
|
45
|
+
|
|
46
|
+
Before diving in, read [docs/architecture.md](docs/architecture.md) to understand the three-subsystem design and why decisions were made.
|
|
47
|
+
|
|
48
|
+
Key principles:
|
|
49
|
+
- **Protocols over inheritance** — consumers implement `EmbeddingProvider`, `LLMProvider`, `SemanticDomain`, etc.
|
|
50
|
+
- **Zero required dependencies** — core library has no runtime deps; backends are optional
|
|
51
|
+
- **Graph is the integration layer** — all subsystems share one typed property graph
|
|
52
|
+
|
|
53
|
+
## What to Work On
|
|
54
|
+
|
|
55
|
+
- Check [open issues](../../issues) for bugs and feature requests
|
|
56
|
+
- Issues labeled `good first issue` are a great starting point
|
|
57
|
+
- If you want to work on something larger, open an issue first to discuss the approach
|
|
58
|
+
|
|
59
|
+
## Pull Requests
|
|
60
|
+
|
|
61
|
+
- Fill out the PR template
|
|
62
|
+
- Ensure tests pass
|
|
63
|
+
- Keep commits focused and well-described
|
|
64
|
+
- Be open to feedback — code review is collaborative, not adversarial
|
|
65
|
+
|
|
66
|
+
## Questions?
|
|
67
|
+
|
|
68
|
+
Open a [discussion](../../discussions) or comment on the relevant issue.
|
synap-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vibhu Chandrashekhar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
synap-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: synap
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Cognitive memory architecture for LLM agents
|
|
5
|
+
Project-URL: Homepage, https://github.com/veeeceee/synap
|
|
6
|
+
Project-URL: Repository, https://github.com/veeeceee/synap
|
|
7
|
+
Project-URL: Issues, https://github.com/veeeceee/synap/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/veeeceee/synap/blob/main/CHANGELOG.md
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: kuzu>=0.8.0; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
26
|
+
Provides-Extra: kuzu
|
|
27
|
+
Requires-Dist: kuzu>=0.8.0; extra == 'kuzu'
|
|
28
|
+
Provides-Extra: mcp
|
|
29
|
+
Requires-Dist: fastmcp>=2.0.0; extra == 'mcp'
|
|
30
|
+
Provides-Extra: postgres
|
|
31
|
+
Requires-Dist: asyncpg>=0.30.0; extra == 'postgres'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# Synap
|
|
35
|
+
|
|
36
|
+
[](https://github.com/veeeceee/synap/actions/workflows/ci.yml)
|
|
37
|
+
[](https://pypi.org/project/synap/)
|
|
38
|
+
[](LICENSE)
|
|
39
|
+
|
|
40
|
+
Cognitive memory architecture for LLM agents.
|
|
41
|
+
|
|
42
|
+
Synap manages three types of memory — semantic, procedural, and episodic — backed by a shared typed property graph. It resolves the fundamental memory-vs-attention contradiction in transformer-based models: more context degrades reasoning quality. Instead of stuffing everything into the prompt, Synap uses structurally selective retrieval (similarity search finds entry points, then graph traversal returns connected subgraphs instead of flat ranked lists) and output-side enforcement (procedures become output schemas, not instructions).
|
|
43
|
+
|
|
44
|
+
## How is this different?
|
|
45
|
+
|
|
46
|
+
Most agent memory systems (Mem0, Letta, Zep, LangMem) treat memory as a retrieval problem — store text, find similar text, put it in the prompt. Synap takes a different position:
|
|
47
|
+
|
|
48
|
+
- **Structural enforcement, not instructions.** Procedural memory produces output schemas where field ordering *is* the reasoning procedure. The model must generate evidence before conclusions — enforced by the schema, not by telling it to "think step by step."
|
|
49
|
+
- **Graph traversal, not flat retrieval.** Semantic memory returns connected subgraphs where relationships are explicit. A query about "lumbar fusion requirements" traverses `requires` and `includes` edges, not just the top-K similar chunks.
|
|
50
|
+
- **Self-amending procedures.** When the same failure pattern repeats, the consolidation engine generates a new schema field and registers an amended procedure version. The system structurally prevents the mistake from recurring.
|
|
51
|
+
- **Precision over convenience.** Synap is a library, not a managed service. You own the agent loop, the LLM client, and the embedding provider. Memory operations are explicit and auditable.
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install synap
|
|
57
|
+
|
|
58
|
+
# With Kùzu for persistent graph storage (recommended)
|
|
59
|
+
pip install synap[kuzu]
|
|
60
|
+
|
|
61
|
+
# With uv
|
|
62
|
+
uv add synap --extra kuzu
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Providers
|
|
66
|
+
|
|
67
|
+
Synap needs two providers you implement — one for embeddings, one for LLM text generation. Here's a minimal example using OpenAI:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import openai
|
|
71
|
+
|
|
72
|
+
class OpenAIEmbedder:
|
|
73
|
+
def __init__(self, client: openai.AsyncOpenAI, model: str = "text-embedding-3-small"):
|
|
74
|
+
self.client = client
|
|
75
|
+
self.model = model
|
|
76
|
+
|
|
77
|
+
async def embed(self, text: str) -> list[float]:
|
|
78
|
+
response = await self.client.embeddings.create(input=text, model=self.model)
|
|
79
|
+
return response.data[0].embedding
|
|
80
|
+
|
|
81
|
+
async def embed_batch(self, texts: list[str]) -> list[list[float]]:
|
|
82
|
+
response = await self.client.embeddings.create(input=texts, model=self.model)
|
|
83
|
+
return [item.embedding for item in response.data]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class OpenAILLM:
|
|
87
|
+
def __init__(self, client: openai.AsyncOpenAI, model: str = "gpt-4o"):
|
|
88
|
+
self.client = client
|
|
89
|
+
self.model = model
|
|
90
|
+
|
|
91
|
+
async def generate(self, prompt: str, output_schema: dict | None = None) -> str:
|
|
92
|
+
response = await self.client.chat.completions.create(
|
|
93
|
+
model=self.model,
|
|
94
|
+
messages=[{"role": "user", "content": prompt}],
|
|
95
|
+
)
|
|
96
|
+
return response.choices[0].message.content
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Any class matching the `EmbeddingProvider` and `LLMProvider` protocols works — no inheritance required. See [docs/architecture.md](docs/architecture.md#provider-model) for details.
|
|
100
|
+
|
|
101
|
+
## Quick Start
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from synap import (
|
|
105
|
+
CognitiveMemory, CapacityHints, Procedure, EpisodeOutcome,
|
|
106
|
+
SemanticMemory, MemoryGraph,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Create the graph and domain adapter
|
|
110
|
+
graph = MemoryGraph()
|
|
111
|
+
domain = SemanticMemory(graph=graph, embedding_provider=your_embedder)
|
|
112
|
+
|
|
113
|
+
# You provide the embedding and LLM providers
|
|
114
|
+
memory = CognitiveMemory(
|
|
115
|
+
domain=domain,
|
|
116
|
+
embedding_provider=your_embedder,
|
|
117
|
+
llm_provider=your_llm,
|
|
118
|
+
graph=graph,
|
|
119
|
+
capacity=CapacityHints(max_context_tokens=8192),
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Register a procedure — field ordering IS the enforcement
|
|
123
|
+
await memory.procedural.register(Procedure(
|
|
124
|
+
task_type="diagnose_bug",
|
|
125
|
+
description="Diagnose a bug from error logs and code context",
|
|
126
|
+
schema={
|
|
127
|
+
"error_classification": {"type": "string"},
|
|
128
|
+
"root_cause": {"type": "string"},
|
|
129
|
+
"fix_proposal": {"type": "string"},
|
|
130
|
+
},
|
|
131
|
+
field_ordering=["error_classification", "root_cause", "fix_proposal"],
|
|
132
|
+
prerequisite_fields={"fix_proposal": ["error_classification", "root_cause"]},
|
|
133
|
+
))
|
|
134
|
+
|
|
135
|
+
# Seed knowledge
|
|
136
|
+
await domain.store("Stripe webhook payloads vary by event type; always validate shape")
|
|
137
|
+
|
|
138
|
+
# Prepare context for an LLM call
|
|
139
|
+
ctx = await memory.prepare_call(
|
|
140
|
+
task_description="Diagnose TypeError in payment webhook handler"
|
|
141
|
+
)
|
|
142
|
+
# ctx.output_schema → enforces: classify error → find root cause → THEN propose fix
|
|
143
|
+
# ctx.domain_context → relevant facts from the domain adapter
|
|
144
|
+
# ctx.warnings → "Last time you misdiagnosed a similar TypeError..."
|
|
145
|
+
|
|
146
|
+
# Record what happened (including tool calls if any)
|
|
147
|
+
from synap import ToolCall
|
|
148
|
+
|
|
149
|
+
await memory.record_outcome(
|
|
150
|
+
task_description="Diagnose TypeError in payment webhook handler",
|
|
151
|
+
input_data={"error": "Cannot read property 'amount' of undefined"},
|
|
152
|
+
output={"error_classification": "null reference", "root_cause": "...", "fix_proposal": "..."},
|
|
153
|
+
outcome=EpisodeOutcome.SUCCESS,
|
|
154
|
+
task_type="diagnose_bug",
|
|
155
|
+
tool_calls=[
|
|
156
|
+
ToolCall(
|
|
157
|
+
query="find webhook handler source",
|
|
158
|
+
server="code-search",
|
|
159
|
+
tool_name="search_files",
|
|
160
|
+
parameters={"pattern": "handleWebhook"},
|
|
161
|
+
result_summary="Found src/webhooks/stripe.ts:45",
|
|
162
|
+
success=True,
|
|
163
|
+
),
|
|
164
|
+
],
|
|
165
|
+
)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Domain Adapters
|
|
169
|
+
|
|
170
|
+
Synap's semantic layer is pluggable via the `SemanticDomain` protocol. Every project brings its own knowledge types — contradictions and forces for geopolitical analysis, clinical policies for healthcare, code patterns for dev tools.
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
from synap.protocols import SemanticDomain
|
|
174
|
+
from synap.types import DomainResult, MemoryNode
|
|
175
|
+
|
|
176
|
+
class MyDomain:
|
|
177
|
+
"""Implements SemanticDomain — retrieves and absorbs domain knowledge."""
|
|
178
|
+
|
|
179
|
+
async def retrieve(self, task_description, task_type=None, metadata=None):
|
|
180
|
+
# Return domain knowledge relevant to this task
|
|
181
|
+
return [DomainResult(content="...", relevance=0.9, source_id="...")]
|
|
182
|
+
|
|
183
|
+
async def absorb(self, insights, source_episodes, metadata=None):
|
|
184
|
+
# Store consolidated insights in your domain's schema
|
|
185
|
+
return "domain_node_id"
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
`SemanticMemory` is the built-in generic implementation — text nodes with embeddings and graph traversal. Use it to get started, replace it when your domain needs custom types.
|
|
189
|
+
|
|
190
|
+
## Persistence
|
|
191
|
+
|
|
192
|
+
By default, the graph lives in memory. Pass a storage backend for persistence:
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
from synap.backends.kuzu import KuzuBackend
|
|
196
|
+
from synap.persistent_graph import PersistentGraph
|
|
197
|
+
|
|
198
|
+
backend = KuzuBackend("./agent_memory", embedding_dim=768)
|
|
199
|
+
graph = PersistentGraph(backend=backend)
|
|
200
|
+
domain = SemanticMemory(graph=graph, embedding_provider=your_embedder)
|
|
201
|
+
|
|
202
|
+
memory = CognitiveMemory(
|
|
203
|
+
domain=domain,
|
|
204
|
+
embedding_provider=your_embedder,
|
|
205
|
+
llm_provider=your_llm,
|
|
206
|
+
graph=graph,
|
|
207
|
+
)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
For multi-process deployments (web servers, worker pools), use the Postgres backend:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
pip install synap[postgres]
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
import asyncpg
|
|
218
|
+
from synap.backends.postgres import PostgresBackend
|
|
219
|
+
from synap.persistent_graph import PersistentGraph
|
|
220
|
+
|
|
221
|
+
pool = await asyncpg.create_pool("postgresql://localhost:5432/mydb")
|
|
222
|
+
backend = PostgresBackend(pool, embedding_dim=768)
|
|
223
|
+
await backend.init() # Creates tables (idempotent)
|
|
224
|
+
graph = PersistentGraph(backend=backend)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
| Backend | Graph traversal | Vector search | Persistence | Concurrency |
|
|
228
|
+
|---|---|---|---|---|
|
|
229
|
+
| In-memory (default) | Python BFS | Python cosine | None | Single process |
|
|
230
|
+
| `KuzuBackend` | Native Cypher | Native `array_cosine_similarity` | File-based | Single process |
|
|
231
|
+
| `SQLiteBackend` | Python BFS | Python cosine | File-based | Single process |
|
|
232
|
+
| `PostgresBackend` | Recursive CTE | pgvector `<=>` | Server-based | Multi-process safe |
|
|
233
|
+
|
|
234
|
+
## Documentation
|
|
235
|
+
|
|
236
|
+
- [Architecture & Concepts](docs/architecture.md) — How the three memory subsystems work and why
|
|
237
|
+
- [API Reference](docs/api.md) — Complete interface documentation
|
|
238
|
+
- [Bootstrap Guide](docs/bootstrap.md) — Cold start: seeding memory from existing data
|
|
239
|
+
- [Examples](docs/examples.md) — Geopolitical analysis, healthcare, coding agents
|
|
240
|
+
|
|
241
|
+
## How It Works
|
|
242
|
+
|
|
243
|
+
**Semantic memory** is pluggable via the `SemanticDomain` protocol. The built-in `SemanticMemory` stores facts as a knowledge graph with retrieval via graph traversal. Projects with domain-specific types (contradictions, policies, etc.) implement the protocol directly.
|
|
244
|
+
|
|
245
|
+
**Procedural memory** maps task types to output schemas where field ordering *is* the procedure. The model must generate intermediate reasoning before conclusions. Enforced structurally, not instructionally.
|
|
246
|
+
|
|
247
|
+
**Episodic memory** records agent experiences as cue→content→outcome subgraphs. Failed episodes are boosted during retrieval (more learning signal). Over time, repeated patterns consolidate into domain knowledge or procedural amendments. Episodes can include structured **tool call tracking** — which MCP server, tool, parameters, and result — enabling consolidation to detect tool usage patterns (wrong tool selection, parameter malformation) and generate procedural amendments.
|
|
248
|
+
|
|
249
|
+
All three operate on a shared typed property graph. Edges cross partitions — this is how consolidation links episodic experiences to domain facts without a separate join mechanism.
|
|
250
|
+
|
|
251
|
+
## Async-First
|
|
252
|
+
|
|
253
|
+
All public APIs are async. Synap is designed for integration with async frameworks (FastAPI, Sanic, etc.):
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
# All operations are awaitable
|
|
257
|
+
ctx = await memory.prepare_call("task description")
|
|
258
|
+
episode_id = await memory.record_outcome(...)
|
|
259
|
+
results = await memory.consolidate()
|
|
260
|
+
stats = await memory.stats()
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Storage backends stay synchronous (embedded DBs don't benefit from async). `PersistentGraph` bridges with `asyncio.to_thread`.
|
|
264
|
+
|
|
265
|
+
## Contributing
|
|
266
|
+
|
|
267
|
+
Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines.
|
|
268
|
+
|
|
269
|
+
## License
|
|
270
|
+
|
|
271
|
+
MIT — see [LICENSE](LICENSE) for details.
|