Chuyển tới nội dung chính

Lưu trữ phiên

Đại lý Hermes sử dụng cơ sở dữ liệu SQLite (

~/.Hermes/state.db

) để duy trì phiên siêu dữ liệu, lịch sử tin nhắn đầy đủ và cấu hình mô hình trên CLI và cổng phiên. Điều này thay thế cách tiếp cận tệp JSONL mỗi phiên trước đó.

Tệp nguồn: `Hermes_state.py

`

Tổng quan về kiến trúc

` ~/.Hermes/state.db (SQLite, WAL mode) ├── sessions — Session metadata, token counts, billing ├── messages — Full message history per session ├── messages_fts — FTS5 virtual table (content + tool_name + tool_calls) ├── messages_fts_trigram — FTS5 virtual table with trigram tokenizer (CJK / substring search) ├── state_meta — Key/value metadata table └── schema_version — Single-row table tracking migration state

` ``Các quyết định thiết kế chính:

  • Chế độ WAL dành cho người đọc đồng thời + một người viết (cổng đa nền tảng)
  • Bảng ảo FTS5 để tìm kiếm văn bản nhanh chóng trên tất cả các tin nhắn trong phiên
  • Dòng phiên thông qua chuỗi parent_session_id (phân tách được kích hoạt bằng nén)
  • Gắn thẻ nguồn ( `CLI

, `Telegram

, `Discord

, v.v.) để lọc nền tảng

  • Quỹ đạo hàng loạt và quỹ đạo RL KHÔNG được lưu trữ ở đây (các hệ thống riêng biệt)

Lược đồ SQLite

Bảng phiên

`sql CREATE TABLE IF NOT EXISTS sessions ( id TEXT PRIMARY KEY, source TEXT NOT NULL, user_id TEXT, model TEXT, model_config TEXT, system_prompt TEXT, parent_session_id TEXT, started_at REAL NOT NULL, ended_at REAL, end_reason TEXT, message_count INTEGER DEFAULT 0, tool_call_count INTEGER DEFAULT 0, input_tokens INTEGER DEFAULT 0, output_tokens INTEGER DEFAULT 0, cache_read_tokens INTEGER DEFAULT 0, cache_write_tokens INTEGER DEFAULT 0, reasoning_tokens INTEGER DEFAULT 0, billing_provider TEXT, billing_base_url TEXT, billing_mode TEXT, estimated_cost_usd REAL, actual_cost_usd REAL, cost_status TEXT, cost_source TEXT, pricing_version TEXT, title TEXT, API_call_count INTEGER DEFAULT 0, FOREIGN KEY (parent_session_id) REFERENCES sessions(id) );

CREATE INDEX IF NOT EXISTS idx_sessions_source ON sessions(source); CREATE INDEX IF NOT EXISTS idx_sessions_parent ON sessions(parent_session_id); CREATE INDEX IF NOT EXISTS idx_sessions_started ON sessions(started_at DESC); CREATE UNIQUE INDEX IF NOT EXISTS idx_sessions_title_unique ON sessions(title) WHERE title IS NOT NULL;

`

Bảng tin nhắn

`sql CREATE TABLE IF NOT EXISTS messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL REFERENCES sessions(id), role TEXT NOT NULL, content TEXT, tool_call_id TEXT, tool_calls TEXT, tool_name TEXT, timestamp REAL NOT NULL, token_count INTEGER, finish_reason TEXT, reasoning TEXT, reasoning_content TEXT, reasoning_details TEXT, Codex_reasoning_items TEXT, Codex_message_items TEXT );

CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id, timestamp);

` ``Ghi chú:

tool_calls được lưu trữ dưới dạng chuỗi JSON (danh sách các đối tượng gọi công cụ được tuần tự hóa)

`reasoning_details

, Codex_reasoning_items Codex_message_items được lưu trữ dưới dạng chuỗi JSON

reasoning lưu trữ văn bản lý luận thô cho các nhà cung cấp hiển thị nó

  • Dấu thời gian là các dấu phẩy kỷ nguyên Unix ( `time.time()

)

Tìm kiếm toàn văn FTS5

`sql CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5( content, content=messages, content_rowid=id );

``Bảng FTS5 được giữ đồng bộ thông qua ba trình kích hoạt kích hoạt INSERT, UPDATE, và DELETE của bảng messages

:

`sql CREATE TRIGGER IF NOT EXISTS messages_fts_insert AFTER INSERT ON messages BEGIN INSERT INTO messages_fts(rowid, content) VALUES (new.id, new.content); END;

CREATE TRIGGER IF NOT EXISTS messages_fts_delete AFTER DELETE ON messages BEGIN INSERT INTO messages_fts(messages_fts, rowid, content) VALUES('delete', old.id, old.content); END;

CREATE TRIGGER IF NOT EXISTS messages_fts_update AFTER UPDATE ON messages BEGIN INSERT INTO messages_fts(messages_fts, rowid, content) VALUES('delete', old.id, old.content); INSERT INTO messages_fts(rowid, content) VALUES (new.id, new.content); END;

`

`

Phiên bản lược đồ và di chuyển

Phiên bản lược đồ hiện tại: 11

Bảng schema_version lưu trữ một số nguyên duy nhất. Việc bổ sung cột đơn giản được _reconcile_columns() xử lý theo cách khai báo (khác biệt các cột trực tiếp với SCHEMA_SQL và THÊM bất kỳ cột nào bị thiếu). Chuỗi kiểm soát phiên bản được dành riêng cho việc di chuyển dữ liệu và thay đổi chỉ mục/FTS không thể được biểu thị dưới dạng khai báo:

Phiên bảnThay đổi
1Lược đồ ban đầu (phiên, tin nhắn, FTS5)
2Thêm cột
`finish_reason
` vào tin nhắn
3Thêm cột
`title
` vào phiên
4Thêm chỉ mục duy nhất trên
`title
` (Cho phép NULL, không phải NULL phải là duy nhất)
5Thêm các cột thanh toán:
`cache_read_tokens

, `cache_write_tokens

, `reasoning_tokens

, `billing_provider

, `billing_base_url

, `billing_mode

, `estimated_cost_usd

, `actual_cost_usd

, `cost_status

, `cost_source

, pricing_version | | 6 | Thêm cột lý do vào tin nhắn: `reasoning

, `reasoning_details

, Codex_reasoning_items | | 7 | Thêm cột reasoning_content vào tin nhắn | | 8 | Thêm cột API_call_count vào phiên | | 9 | Thêm cột Codex_message_items vào các tin nhắn cho phát lại id/giai đoạn tin nhắn Codex Responses | | 10 | Thêm bảng ảo messages_fts_trigram (mã thông báo bát quái cho tìm kiếm CJK / chuỗi con) và chèn lấp các hàng hiện có | | 11 | Lập chỉ mục lại messages_fts messages_fts_trigram để bao gồm `tool_name

tool_calls và chuyển từ chế độ nội dung bên ngoài sang chế độ nội tuyến; bỏ các trình kích hoạt cũ và chèn lấp mọi hàng tin nhắn |

Cột khai báo thêm sử dụng ALTER TABLE ADD COLUMN được gói trong thử/ngoại trừ để xử lý trường hợp cột đã tồn tại (idempotent). Số phiên bản bị thay đổi sau mỗi khối di chuyển thành công.

Viết Xử lý tranh chấp

Nhiều quy trình Hermes (cổng + phiên CLI + tác nhân cây công việc) chia sẻ một `state.db

. Lớp SessionDB xử lý tranh chấp ghi với:

  • Thời gian chờ SQLite ngắn (1 giây) thay vì 30 giây mặc định
  • Thử lại ở cấp độ ứng dụng với jitter ngẫu nhiên (20-150 mili giây, tối đa 15 lần thử lại)
  • BẮT ĐẦU NGAY LẬP TỨC giao dịch để giải quyết tranh chấp khóa bề mặt khi bắt đầu giao dịch
  • Điểm kiểm tra WAL định kỳ cứ sau 50 lần ghi thành công (chế độ THỤ ĐỘNG)

Điều này tránh được "hiệu ứng đoàn xe" trong đó sự chờ đợi nội bộ mang tính quyết định của SQLite khiến tất cả người viết cạnh tranh phải thử lại ở những khoảng thời gian như nhau.

` _WRITE_MAX_RETRIES = 15 _WRITE_RETRY_MIN_S = 0.020 # 20ms _WRITE_RETRY_MAX_S = 0.150 # 150ms _CHECKPOINT_EVERY_N_WRITES = 50

`

`

Hoạt động chung

Khởi tạo

from Hermes_state import SessionDB`db = SessionDB()                           # Default: ~/.Hermes/state.db
db = SessionDB(db_path=Path("/tmp/test.db")) # Custom path

`

### Tạo và quản lý phiên

`Python

# Create a new session
db.create_session(
session_id="sess_abc123",
source="CLI",
model="Anthropic/Claude-sonnet-4.6",
user_id="user_1",
parent_session_id=None, # or previous session ID for lineage
)

# End a session
db.end_session("sess_abc123", end_reason="user_exit")

# Reopen a session (clear ended_at/end_reason)
db.reopen_session("sess_abc123")

`

### Lưu trữ tin nhắn

``` python
msg_id = db.append_message(
session_id="sess_abc123",
role="assistant",
content="Here's the answer...",
tool_calls=[\{"id": "call_1", "function": \{"name": "terminal", "arguments": "\{}"}}],
token_count=150,
finish_reason="stop",
reasoning="Let me think about this...",
)

`

### Truy xuất tin nhắn

`Python

# Raw messages with all metadata
messages = db.get_messages("sess_abc123")

# OpenAI conversation format (for API replay)
conversation = db.get_messages_as_conversation("sess_abc123")
# Returns: [\{"role": "user", "content": "..."}, \{"role": "assistant", ...}]

`

### Tiêu đề phiên

``` python

# Set a title (must be unique among non-NULL titles)
db.set_session_title("sess_abc123", "Fix Docker Build")

# Resolve by title (returns most recent in lineage)
session_id = db.resolve_session_by_title("Fix Docker Build")

# Auto-generate next title in lineage
next_title = db.get_next_title_in_lineage("Fix Docker Build")
# Returns: "Fix Docker Build #2"

`

`
## Tìm kiếm toàn văn

Phương thức
`search_messages()
` hỗ trợ cú pháp truy vấn FTS5 với tính năng tự động
vệ sinh đầu vào của người dùng.

### Tìm kiếm cơ bản

``` python
results = db.search_messages("Docker deployment")

`

### Cú pháp truy vấn FTS5| Cú pháp | Ví dụ | Ý nghĩa |
|--------|----------|---------|
| Từ khóa |

Docker deployment
` | Cả hai thuật ngữ (ẩn AND) |
| Cụm từ được trích dẫn |

"exact phrase"
` | Đối sánh cụm từ chính xác |
| Boolean HOẶC |

Docker OR Kubernetes
` | Hoặc là kỳ hạn |
| Boolean KHÔNG |

Python NOT java
` | Loại trừ thuật ngữ |
| Tiền tố |

deploy*
` | Trận đấu tiền tố |

### Tìm kiếm đã lọc

`Python

# Search only CLI sessions
results = db.search_messages("error", source_filter=["CLI"])

# Exclude gateway sessions
results = db.search_messages("bug", exclude_sources=["Telegram", "Discord"])

# Search only user messages
results = db.search_messages("help", role_filter=["user"])

`

### Định dạng kết quả tìm kiếm

Mỗi kết quả bao gồm:
-
`id

,
`session_id

,
`role

,
`timestamp

-
`snippet

- Đoạn mã do FTS5 tạo với các điểm đánh dấu

>>>match<<<

-
`context
` — 1 tin nhắn trước và sau trận đấu (nội dung được cắt ngắn còn 200 ký tự)
-
`source

,
`model

,
`session_started
` — từ phiên chính

Phương thức
`_sanitize_fts5_query()
` xử lý các trường hợp khó khăn:
- Loại bỏ các trích dẫn chưa từng có và các ký tự đặc biệt
- Bao bọc các thuật ngữ có dấu gạch nối trong dấu ngoặc kép (
`chat-send
` →

"chat-send"

)
- Loại bỏ các toán tử boolean lơ lửng (
`hello AND
` →
`hello

)

## Dòng dõi phiên

Các phiên có thể tạo thành chuỗi thông qua
`parent_session_id

. Điều này xảy ra khi bối cảnh
quá trình nén sẽ kích hoạt sự phân chia phiên trong cổng.

### Truy vấn: Tìm dòng phiên

`sql
-- Find all ancestors of a session
WITH RECURSIVE lineage AS (
SELECT * FROM sessions WHERE id = ?
UNION ALL
SELECT s.* FROM sessions s
JOIN lineage l ON s.id = l.parent_session_id
)
SELECT id, title, started_at, parent_session_id FROM lineage;`-- Find all descendants of a session
WITH RECURSIVE descendants AS (
SELECT * FROM sessions WHERE id = ?
UNION ALL
SELECT s.* FROM sessions s
JOIN descendants d ON s.parent_session_id = d.id
)
SELECT id, title, started_at FROM descendants;

`

### Truy vấn: Phiên gần đây có bản xem trước

`sql
SELECT s.*,
COALESCE(
(SELECT SUBSTR(m.content, 1, 63)
FROM messages m
WHERE m.session_id = s.id AND m.role = 'user' AND m.content IS NOT NULL
ORDER BY m.timestamp, m.id LIMIT 1),
''
) AS preview,
COALESCE(
(SELECT MAX(m2.timestamp) FROM messages m2 WHERE m2.session_id = s.id),
s.started_at
) AS last_active
FROM sessions s
ORDER BY s.started_at DESC
LIMIT 20;

`

### Truy vấn: Thống kê sử dụng token

`sql
-- Total tokens by model
SELECT model,
COUNT(*) as session_count,
SUM(input_tokens) as total_input,
SUM(output_tokens) as total_output,
SUM(estimated_cost_usd) as total_cost
FROM sessions
WHERE model IS NOT NULL
GROUP BY model
ORDER BY total_cost DESC;`-- Sessions with highest token usage
SELECT id, title, model, input_tokens + output_tokens AS total_tokens,
estimated_cost_usd
FROM sessions
ORDER BY total_tokens DESC
LIMIT 10;

`

`
## Xuất và dọn dẹp

``` python

# Export a single session with messages
data = db.export_session("sess_abc123")

# Export all sessions (with messages) as list of dicts
all_data = db.export_all(source="CLI")

# Delete old sessions (only ended sessions)
deleted_count = db.prune_sessions(older_than_days=90)
deleted_count = db.prune_sessions(older_than_days=30, source="Telegram")

# Clear messages but keep the session record
db.clear_messages("sess_abc123")

# Delete session and all messages
db.delete_session("sess_abc123")

`

`
## Vị trí cơ sở dữ liệu

Đường dẫn mặc định:

~/.Hermes/state.db
``Điều này bắt nguồn từ
`Hermes_constants.get_Hermes_home()

, giải quyết thành

~/.Hermes/
` theo mặc định hoặc giá trị của biến môi trường
`Hermes_HOME

.

Tệp cơ sở dữ liệu, tệp WAL (
`state.db-wal

) và tệp bộ nhớ dùng chung
(
`state.db-shm

) đều được tạo trong cùng một thư mục.