hermes-next 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.
Files changed (36) hide show
  1. hermes_next-0.1.0/.github/workflows/ci.yml +64 -0
  2. hermes_next-0.1.0/.github/workflows/publish.yml +31 -0
  3. hermes_next-0.1.0/.gitignore +33 -0
  4. hermes_next-0.1.0/LICENSE +17 -0
  5. hermes_next-0.1.0/PKG-INFO +118 -0
  6. hermes_next-0.1.0/README.md +91 -0
  7. hermes_next-0.1.0/hermes_next/__init__.py +11 -0
  8. hermes_next-0.1.0/hermes_next/cache/__init__.py +1 -0
  9. hermes_next-0.1.0/hermes_next/cache/connection.py +100 -0
  10. hermes_next-0.1.0/hermes_next/cache/policies.py +94 -0
  11. hermes_next-0.1.0/hermes_next/cache/schema.py +100 -0
  12. hermes_next-0.1.0/hermes_next/cache/skills.py +89 -0
  13. hermes_next-0.1.0/hermes_next/cache/traces.py +160 -0
  14. hermes_next-0.1.0/hermes_next/cache/vector.py +58 -0
  15. hermes_next-0.1.0/hermes_next/config.py +152 -0
  16. hermes_next-0.1.0/hermes_next/memos/__init__.py +1 -0
  17. hermes_next-0.1.0/hermes_next/memos/capture.py +76 -0
  18. hermes_next-0.1.0/hermes_next/memos/id.py +79 -0
  19. hermes_next-0.1.0/hermes_next/memos/pipeline.py +281 -0
  20. hermes_next-0.1.0/hermes_next/memos/policy.py +394 -0
  21. hermes_next-0.1.0/hermes_next/memos/retrieval.py +90 -0
  22. hermes_next-0.1.0/hermes_next/memos/reward.py +236 -0
  23. hermes_next-0.1.0/hermes_next/memos/skill.py +219 -0
  24. hermes_next-0.1.0/hermes_next/memos/types.py +148 -0
  25. hermes_next-0.1.0/hermes_next/memos/world_model.py +466 -0
  26. hermes_next-0.1.0/hermes_next/ov/__init__.py +1 -0
  27. hermes_next-0.1.0/hermes_next/ov/client.py +175 -0
  28. hermes_next-0.1.0/hermes_next/ov/session.py +101 -0
  29. hermes_next-0.1.0/hermes_next/provider.py +325 -0
  30. hermes_next-0.1.0/hermes_next/retrieval/__init__.py +1 -0
  31. hermes_next-0.1.0/hermes_next/retrieval/pipeline.py +154 -0
  32. hermes_next-0.1.0/hermes_next/retrieval/ranker.py +112 -0
  33. hermes_next-0.1.0/hermes_next/viewer/__init__.py +5 -0
  34. hermes_next-0.1.0/hermes_next/viewer/server.py +637 -0
  35. hermes_next-0.1.0/plugin.yaml +7 -0
  36. hermes_next-0.1.0/pyproject.toml +69 -0
@@ -0,0 +1,64 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ lint:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: actions/setup-python@v5
15
+ with:
16
+ python-version: "3.12"
17
+ - name: Install ruff
18
+ run: pip install ruff
19
+ - name: Lint
20
+ run: ruff check hermes_next/ tests/
21
+ - name: Format check
22
+ run: ruff format --check hermes_next/ tests/ || true
23
+
24
+ type-check:
25
+ runs-on: ubuntu-latest
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+ - uses: actions/setup-python@v5
29
+ with:
30
+ python-version: "3.12"
31
+ - name: Install dependencies
32
+ run: |
33
+ pip install mypy
34
+ pip install -e "."
35
+ - name: Type check
36
+ run: mypy hermes_next/ --ignore-missing-imports --follow-imports=skip || true
37
+
38
+ test:
39
+ runs-on: ubuntu-latest
40
+ strategy:
41
+ matrix:
42
+ python-version: ["3.10", "3.11", "3.12"]
43
+
44
+ steps:
45
+ - uses: actions/checkout@v4
46
+ - name: Set up Python ${{ matrix.python-version }}
47
+ uses: actions/setup-python@v5
48
+ with:
49
+ python-version: ${{ matrix.python-version }}
50
+
51
+ - name: Install uv
52
+ run: |
53
+ curl -LsSf https://astral.sh/uv/install.sh | sh
54
+ echo "$HOME/.cargo/bin" >> $GITHUB_PATH
55
+
56
+ - name: Install package
57
+ run: |
58
+ uv pip install --system -e "."
59
+ uv pip install --system pytest pytest-asyncio httpx numpy
60
+
61
+ - name: Test
62
+ run: pytest tests/ -v -k "not integration" --timeout=30
63
+ env:
64
+ HERMES_NEXT_OV_URL: ${{ secrets.HERMES_NEXT_OV_URL || 'http://localhost:1933' }}
@@ -0,0 +1,31 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ environment: release
11
+ permissions:
12
+ id-token: write
13
+
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: "3.12"
19
+
20
+ - name: Install build tools
21
+ run: pip install hatchling
22
+
23
+ - name: Build package
24
+ run: |
25
+ python -m hatchling build
26
+ ls -la dist/
27
+
28
+ - name: Publish to PyPI
29
+ uses: pypa/gh-action-pypi-publish@release/v1
30
+ with:
31
+ skip-existing: true
@@ -0,0 +1,33 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+
8
+ # Cache
9
+ *.db
10
+ *.db-wal
11
+ *.db-shm
12
+
13
+ # IDE
14
+ .vscode/
15
+ .idea/
16
+ *.swp
17
+ *.swo
18
+
19
+ # Environment
20
+ .env
21
+ .env.*
22
+
23
+ # OS
24
+ .DS_Store
25
+ Thumbs.db
26
+
27
+ # Test artifacts
28
+ .pytest_cache/
29
+ .mypy_cache/
30
+ .ruff_cache/
31
+
32
+ # Project
33
+ *.lock
@@ -0,0 +1,17 @@
1
+ GNU AFFERO GENERAL PUBLIC LICENSE
2
+ Version 3, 19 November 2007
3
+
4
+ Copyright (C) 2024 jigeagent
5
+
6
+ This program is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU Affero General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
10
+
11
+ This program is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU Affero General Public License for more details.
15
+
16
+ You should have received a copy of the GNU Affero General Public License
17
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
@@ -0,0 +1,118 @@
1
+ Metadata-Version: 2.4
2
+ Name: hermes-next
3
+ Version: 0.1.0
4
+ Summary: Next-generation memory provider for Hermes Agent — fusing OpenViking vector storage with MemOS cognitive engine
5
+ Project-URL: Homepage, https://github.com/jigeagent/hermes-next
6
+ Project-URL: Repository, https://github.com/jigeagent/hermes-next.git
7
+ Project-URL: Documentation, https://github.com/jigeagent/hermes-next#readme
8
+ Project-URL: Issues, https://github.com/jigeagent/hermes-next/issues
9
+ Author-email: jigeagent <oss@jigeagent.ai>
10
+ License: AGPL-3.0-or-later
11
+ License-File: LICENSE
12
+ Keywords: cognitive,hermes-agent,memory,memos,openviking,rag,vector-search
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: httpx
24
+ Requires-Dist: numpy
25
+ Requires-Dist: openviking>=0.3.22
26
+ Description-Content-Type: text/markdown
27
+
28
+ # Hermes Next
29
+
30
+ **Next-generation memory provider for Hermes Agent.**
31
+
32
+ Hermes Next fuses [OpenViking](https://github.com/bytedance/openviking) vector storage with a Python-native [MemOS](https://github.com/memtensor/memos) cognitive engine, giving Hermes Agent agents persistent,结构化记忆能力。
33
+
34
+ ## Features
35
+
36
+ - **OpenViking Backend** — Long-term vector storage, semantic retrieval, session management
37
+ - **MemOS Cognitive Pipeline** — L1 Trace capture → reward backpropagation → L2 Policy induction → L3 World Model → Skill crystallization
38
+ - **Python Native** — Zero bridging overhead, runs in-process
39
+ - **Local SQLite Cache** — FTS5 full-text search + numpy-based cosine similarity, zero dependencies beyond stdlib
40
+ - **Fusion Retrieval** — 6-step pipeline combining semantic search, full-text search, policy matching, timeline, recency boost, and MMR diversification
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ pip install hermes-next
46
+ ```
47
+
48
+ Requires Python 3.10+ and a running OpenViking server (v0.3.22+).
49
+
50
+ ## Configuration
51
+
52
+ Create a `hermes-next.yaml` in your config directory:
53
+
54
+ ```yaml
55
+ openviking:
56
+ base_url: "http://localhost:1933"
57
+ api_key: null
58
+
59
+ cache:
60
+ path: "~/.hermes-next/cache.db"
61
+ enable_fts: true
62
+
63
+ agent:
64
+ name: "default"
65
+ ```
66
+
67
+ Or set environment variables:
68
+
69
+ ```bash
70
+ export HERMES_NEXT_OV_URL="http://localhost:1933"
71
+ export HERMES_NEXT_CACHE_PATH="~/.hermes-next/cache.db"
72
+ ```
73
+
74
+ ## Usage with Hermes Agent
75
+
76
+ ```python
77
+ from hermes_next import HermesNextProvider
78
+
79
+ provider = HermesNextProvider()
80
+ provider.initialize(session_id="my-session")
81
+
82
+ # The provider handles prefetching, storage, and retrieval automatically
83
+ context = provider.prefetch("What did we discuss about RAG?")
84
+ print(context)
85
+ ```
86
+
87
+ Or via CLI:
88
+
89
+ ```bash
90
+ hermes agent --memory-provider hermes-next
91
+ ```
92
+
93
+ ## Tools
94
+
95
+ The provider exposes these tools to the agent:
96
+
97
+ | Tool | Description |
98
+ |------|-------------|
99
+ | `memos_search(query, k)` | Semantic search across traces |
100
+ | `memos_get(trace_id)` | Read a specific trace |
101
+ | `memos_timeline(limit)` | Recent activity timeline |
102
+
103
+ ## Project Structure
104
+
105
+ ```
106
+ hermes-next/
107
+ ├── hermes_next/
108
+ │ ├── ov/ # OpenViking REST client
109
+ │ ├── memos/ # MemOS cognitive engine
110
+ │ ├── cache/ # SQLite local cache
111
+ │ └── retrieval/ # Fusion retrieval pipeline
112
+ ├── tests/
113
+ └── plugin.yaml # Hermes plugin manifest
114
+ ```
115
+
116
+ ## License
117
+
118
+ AGPL-3.0 — This project is a derivative of OpenViking (AGPL-3.0).
@@ -0,0 +1,91 @@
1
+ # Hermes Next
2
+
3
+ **Next-generation memory provider for Hermes Agent.**
4
+
5
+ Hermes Next fuses [OpenViking](https://github.com/bytedance/openviking) vector storage with a Python-native [MemOS](https://github.com/memtensor/memos) cognitive engine, giving Hermes Agent agents persistent,结构化记忆能力。
6
+
7
+ ## Features
8
+
9
+ - **OpenViking Backend** — Long-term vector storage, semantic retrieval, session management
10
+ - **MemOS Cognitive Pipeline** — L1 Trace capture → reward backpropagation → L2 Policy induction → L3 World Model → Skill crystallization
11
+ - **Python Native** — Zero bridging overhead, runs in-process
12
+ - **Local SQLite Cache** — FTS5 full-text search + numpy-based cosine similarity, zero dependencies beyond stdlib
13
+ - **Fusion Retrieval** — 6-step pipeline combining semantic search, full-text search, policy matching, timeline, recency boost, and MMR diversification
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pip install hermes-next
19
+ ```
20
+
21
+ Requires Python 3.10+ and a running OpenViking server (v0.3.22+).
22
+
23
+ ## Configuration
24
+
25
+ Create a `hermes-next.yaml` in your config directory:
26
+
27
+ ```yaml
28
+ openviking:
29
+ base_url: "http://localhost:1933"
30
+ api_key: null
31
+
32
+ cache:
33
+ path: "~/.hermes-next/cache.db"
34
+ enable_fts: true
35
+
36
+ agent:
37
+ name: "default"
38
+ ```
39
+
40
+ Or set environment variables:
41
+
42
+ ```bash
43
+ export HERMES_NEXT_OV_URL="http://localhost:1933"
44
+ export HERMES_NEXT_CACHE_PATH="~/.hermes-next/cache.db"
45
+ ```
46
+
47
+ ## Usage with Hermes Agent
48
+
49
+ ```python
50
+ from hermes_next import HermesNextProvider
51
+
52
+ provider = HermesNextProvider()
53
+ provider.initialize(session_id="my-session")
54
+
55
+ # The provider handles prefetching, storage, and retrieval automatically
56
+ context = provider.prefetch("What did we discuss about RAG?")
57
+ print(context)
58
+ ```
59
+
60
+ Or via CLI:
61
+
62
+ ```bash
63
+ hermes agent --memory-provider hermes-next
64
+ ```
65
+
66
+ ## Tools
67
+
68
+ The provider exposes these tools to the agent:
69
+
70
+ | Tool | Description |
71
+ |------|-------------|
72
+ | `memos_search(query, k)` | Semantic search across traces |
73
+ | `memos_get(trace_id)` | Read a specific trace |
74
+ | `memos_timeline(limit)` | Recent activity timeline |
75
+
76
+ ## Project Structure
77
+
78
+ ```
79
+ hermes-next/
80
+ ├── hermes_next/
81
+ │ ├── ov/ # OpenViking REST client
82
+ │ ├── memos/ # MemOS cognitive engine
83
+ │ ├── cache/ # SQLite local cache
84
+ │ └── retrieval/ # Fusion retrieval pipeline
85
+ ├── tests/
86
+ └── plugin.yaml # Hermes plugin manifest
87
+ ```
88
+
89
+ ## License
90
+
91
+ AGPL-3.0 — This project is a derivative of OpenViking (AGPL-3.0).
@@ -0,0 +1,11 @@
1
+ """Hermes Next — Next-generation memory provider for Hermes Agent."""
2
+
3
+ from hermes_next.provider import HermesNextProvider
4
+
5
+ __version__ = "0.1.0"
6
+ __all__ = ["HermesNextProvider", "register"]
7
+
8
+
9
+ def register():
10
+ """Plugin entry point — returns the provider class for Hermes Agent discovery."""
11
+ return HermesNextProvider
@@ -0,0 +1 @@
1
+ """SQLite local cache layer."""
@@ -0,0 +1,100 @@
1
+ """SQLite connection management with performance optimizations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sqlite3
6
+ import threading
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+
11
+ # Shared statement cache across threads
12
+ _STMT_CACHE: dict[str, sqlite3.Cursor] = {}
13
+
14
+
15
+ class CacheConnection:
16
+ """Thread-safe SQLite connection manager with performance tuning.
17
+
18
+ Optimizations:
19
+ - WAL mode for concurrent reads
20
+ - 64MB cache for hot data
21
+ - memory-mapped I/O (256MB)
22
+ - Lazy pragma initialization (deferred until first query)
23
+ """
24
+
25
+ def __init__(self, db_path: str, wal_mode: bool = True):
26
+ self._db_path = str(Path(db_path).expanduser())
27
+ self._wal = wal_mode
28
+ self._local = threading.local()
29
+ self._lock = threading.Lock()
30
+ self._initialized = False
31
+
32
+ # Ensure parent directory exists
33
+ Path(self._db_path).parent.mkdir(parents=True, exist_ok=True)
34
+
35
+ def _ensure_init(self) -> None:
36
+ """Apply performance pragmas once per connection."""
37
+ if self._initialized:
38
+ return
39
+ conn = self._get_conn_raw()
40
+ if self._wal:
41
+ conn.execute("PRAGMA journal_mode=WAL")
42
+ conn.executescript("""
43
+ PRAGMA synchronous=NORMAL;
44
+ PRAGMA foreign_keys=ON;
45
+ PRAGMA cache_size=-65536;
46
+ PRAGMA mmap_size=268435456;
47
+ PRAGMA temp_store=MEMORY;
48
+ PRAGMA busy_timeout=5000;
49
+ """)
50
+ self._initialized = True
51
+
52
+ def _get_conn_raw(self) -> sqlite3.Connection:
53
+ """Create a raw connection without pragma setup."""
54
+ if not hasattr(self._local, "conn") or self._local.conn is None:
55
+ conn = sqlite3.connect(
56
+ self._db_path,
57
+ check_same_thread=False,
58
+ isolation_level=None, # autocommit mode
59
+ )
60
+ conn.row_factory = sqlite3.Row
61
+ self._local.conn = conn
62
+ return self._local.conn
63
+
64
+ @property
65
+ def conn(self) -> sqlite3.Connection:
66
+ self._ensure_init()
67
+ return self._get_conn_raw()
68
+
69
+ def execute(self, sql: str, params: tuple = ()) -> sqlite3.Cursor:
70
+ """Execute with automatic pragma init."""
71
+ self._ensure_init()
72
+ return self._get_conn_raw().execute(sql, params)
73
+
74
+ def executemany(self, sql: str, params: list[tuple]) -> sqlite3.Cursor:
75
+ """Batch execute with automatic pragma init."""
76
+ self._ensure_init()
77
+ return self._get_conn_raw().executemany(sql, params)
78
+
79
+ def close(self) -> None:
80
+ """Close the connection for the current thread."""
81
+ if hasattr(self._local, "conn") and self._local.conn is not None:
82
+ try:
83
+ self._local.conn.execute("PRAGMA optimize")
84
+ except Exception:
85
+ pass
86
+ self._local.conn.close()
87
+ self._local.conn = None
88
+ self._initialized = False
89
+
90
+ def close_all(self) -> None:
91
+ """Force close via lock (use sparingly)."""
92
+ with self._lock:
93
+ if hasattr(self._local, "conn") and self._local.conn is not None:
94
+ try:
95
+ self._local.conn.execute("PRAGMA optimize")
96
+ except Exception:
97
+ pass
98
+ self._local.conn.close()
99
+ self._local.conn = None
100
+ self._initialized = False
@@ -0,0 +1,94 @@
1
+ """Policy repository — local SQLite CRUD for policies."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import Any, Optional
7
+
8
+ from hermes_next.cache.connection import CacheConnection
9
+ from hermes_next.cache.schema import ensure_schema
10
+ from hermes_next.memos.types import PolicyRow
11
+
12
+
13
+ class PolicyRepository:
14
+ """Persist and query policies locally."""
15
+
16
+ def __init__(self, cache: CacheConnection):
17
+ self._cache = cache
18
+ ensure_schema(cache)
19
+
20
+ def insert(self, policy: PolicyRow) -> None:
21
+ """Insert a policy into local cache."""
22
+ conn = self._cache.conn
23
+ conn.execute(
24
+ """
25
+ INSERT OR REPLACE INTO policies
26
+ (id, name, description, trigger_pattern, action_template,
27
+ embedding, confidence, activation_count, source_trace_ids,
28
+ metadata, created_at, synced)
29
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
30
+ """,
31
+ (
32
+ policy.id,
33
+ policy.name,
34
+ policy.description,
35
+ policy.trigger_pattern,
36
+ policy.action_template,
37
+ json.dumps(policy.embedding) if policy.embedding else None,
38
+ policy.confidence,
39
+ policy.activation_count,
40
+ json.dumps(policy.source_trace_ids, ensure_ascii=False),
41
+ json.dumps(policy.metadata, ensure_ascii=False, default=str),
42
+ policy.created_at,
43
+ 0,
44
+ ),
45
+ )
46
+ conn.commit()
47
+
48
+ def get(self, policy_id: str) -> Optional[PolicyRow]:
49
+ """Get a policy by ID."""
50
+ row = self._cache.conn.execute(
51
+ "SELECT * FROM policies WHERE id = ?", (policy_id,)
52
+ ).fetchone()
53
+ if row is None:
54
+ return None
55
+ return self._row_to_policy(row)
56
+
57
+ def list_active(self, min_confidence: float = 0.3, limit: int = 20) -> list[PolicyRow]:
58
+ """List policies with confidence above threshold."""
59
+ rows = self._cache.conn.execute(
60
+ "SELECT * FROM policies WHERE confidence >= ? ORDER BY confidence DESC LIMIT ?",
61
+ (min_confidence, limit),
62
+ ).fetchall()
63
+ return [self._row_to_policy(r) for r in rows]
64
+
65
+ def increment_activation(self, policy_id: str) -> None:
66
+ """Increment activation count for a policy."""
67
+ self._cache.conn.execute(
68
+ "UPDATE policies SET activation_count = activation_count + 1 WHERE id = ?",
69
+ (policy_id,),
70
+ )
71
+ self._cache.conn.commit()
72
+
73
+ def count(self) -> int:
74
+ """Total policy count."""
75
+ row = self._cache.conn.execute("SELECT COUNT(*) as cnt FROM policies").fetchone()
76
+ return row["cnt"] if row else 0
77
+
78
+ @staticmethod
79
+ def _row_to_policy(row: Any) -> PolicyRow:
80
+ return PolicyRow(
81
+ id=row["id"],
82
+ name=row["name"],
83
+ description=row["description"],
84
+ trigger_pattern=row["trigger_pattern"],
85
+ action_template=row["action_template"],
86
+ embedding=json.loads(row["embedding"]) if row["embedding"] else None,
87
+ confidence=row["confidence"],
88
+ activation_count=row["activation_count"],
89
+ source_trace_ids=json.loads(row["source_trace_ids"])
90
+ if isinstance(row["source_trace_ids"], str)
91
+ else [],
92
+ metadata=json.loads(row["metadata"]) if isinstance(row["metadata"], str) else {},
93
+ created_at=row["created_at"],
94
+ )
@@ -0,0 +1,100 @@
1
+ """SQLite schema — traces, policies, skills tables with FTS5."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from hermes_next.cache.connection import CacheConnection
6
+
7
+
8
+ def ensure_schema(conn_or_cache: CacheConnection) -> None:
9
+ """Create all tables and indexes if they don't exist."""
10
+ conn = conn_or_cache.conn if isinstance(conn_or_cache, CacheConnection) else conn_or_cache
11
+
12
+ conn.executescript("""
13
+ CREATE TABLE IF NOT EXISTS traces (
14
+ id TEXT PRIMARY KEY,
15
+ session_id TEXT NOT NULL,
16
+ turn_index INTEGER NOT NULL DEFAULT 0,
17
+ user_content TEXT NOT NULL,
18
+ assistant_content TEXT NOT NULL DEFAULT '',
19
+ embedding BLOB,
20
+ reward REAL NOT NULL DEFAULT 0.0,
21
+ tags TEXT DEFAULT '',
22
+ metadata TEXT DEFAULT '{}',
23
+ created_at TEXT NOT NULL,
24
+ synced INTEGER NOT NULL DEFAULT 0
25
+ );
26
+
27
+ CREATE INDEX IF NOT EXISTS idx_traces_session
28
+ ON traces(session_id);
29
+ CREATE INDEX IF NOT EXISTS idx_traces_created
30
+ ON traces(created_at);
31
+ CREATE INDEX IF NOT EXISTS idx_traces_synced
32
+ ON traces(synced);
33
+
34
+ CREATE TABLE IF NOT EXISTS policies (
35
+ id TEXT PRIMARY KEY,
36
+ name TEXT NOT NULL,
37
+ description TEXT NOT NULL DEFAULT '',
38
+ trigger_pattern TEXT NOT NULL DEFAULT '',
39
+ action_template TEXT NOT NULL DEFAULT '',
40
+ embedding BLOB,
41
+ confidence REAL NOT NULL DEFAULT 0.0,
42
+ activation_count INTEGER NOT NULL DEFAULT 0,
43
+ source_trace_ids TEXT DEFAULT '[]',
44
+ metadata TEXT DEFAULT '{}',
45
+ created_at TEXT NOT NULL,
46
+ synced INTEGER NOT NULL DEFAULT 0
47
+ );
48
+
49
+ CREATE INDEX IF NOT EXISTS idx_policies_confidence
50
+ ON policies(confidence DESC);
51
+
52
+ CREATE TABLE IF NOT EXISTS skills (
53
+ name TEXT PRIMARY KEY,
54
+ description TEXT NOT NULL DEFAULT '',
55
+ usage_guide TEXT NOT NULL DEFAULT '',
56
+ source_policy_ids TEXT DEFAULT '[]',
57
+ version INTEGER NOT NULL DEFAULT 1,
58
+ metadata TEXT DEFAULT '{}',
59
+ created_at TEXT NOT NULL
60
+ );
61
+
62
+ CREATE VIRTUAL TABLE IF NOT EXISTS traces_fts
63
+ USING fts5(
64
+ user_content,
65
+ assistant_content,
66
+ tags,
67
+ content='traces',
68
+ content_rowid='rowid'
69
+ );
70
+
71
+ CREATE TRIGGER IF NOT EXISTS traces_ai AFTER INSERT ON traces BEGIN
72
+ INSERT INTO traces_fts(rowid, user_content, assistant_content, tags)
73
+ VALUES (new.rowid, new.user_content, new.assistant_content, new.tags);
74
+ END;
75
+
76
+ CREATE TRIGGER IF NOT EXISTS traces_ad AFTER DELETE ON traces BEGIN
77
+ INSERT INTO traces_fts(traces_fts, rowid, user_content, assistant_content, tags)
78
+ VALUES ('delete', old.rowid, old.user_content, old.assistant_content, old.tags);
79
+ END;
80
+
81
+ CREATE TRIGGER IF NOT EXISTS traces_au AFTER UPDATE ON traces BEGIN
82
+ INSERT INTO traces_fts(traces_fts, rowid, user_content, assistant_content, tags)
83
+ VALUES ('delete', old.rowid, old.user_content, old.assistant_content, old.tags);
84
+ INSERT INTO traces_fts(rowid, user_content, assistant_content, tags)
85
+ VALUES (new.rowid, new.user_content, new.assistant_content, new.tags);
86
+ END;
87
+ """)
88
+ conn.commit()
89
+
90
+
91
+ def drop_schema(conn_or_cache: CacheConnection) -> None:
92
+ """Drop all tables (for testing)."""
93
+ conn = conn_or_cache.conn if isinstance(conn_or_cache, CacheConnection) else conn_or_cache
94
+ conn.executescript("""
95
+ DROP TABLE IF EXISTS traces_fts;
96
+ DROP TABLE IF EXISTS skills;
97
+ DROP TABLE IF EXISTS policies;
98
+ DROP TABLE IF EXISTS traces;
99
+ """)
100
+ conn.commit()