Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
2c58b86
docs: add audit-trail (hash-chain journal) page
przemarzec Jun 4, 2026
b427720
docs: add Core Concepts page
przemarzec Jun 4, 2026
94109dd
docs: correct the cycle "trap" wording and add a quickstart cycle cal…
przemarzec Jun 4, 2026
0c917e6
docs: add memory-backed agent guide + runnable agent_loop example
przemarzec Jun 4, 2026
acb4263
docs: fix agent-loop example + guide (validation round)
przemarzec Jun 4, 2026
a711220
style: apply ruff format to examples/agent_loop.py
przemarzec Jun 4, 2026
1a4a48a
docs: add embeddings setup guide (per-provider) and replace the fake …
przemarzec Jun 4, 2026
5516278
docs: split query-vector behaviour for search_hybrid vs search_similar
przemarzec Jun 4, 2026
10430e7
docs: add recipes cookbook and an end-to-end tutorial
przemarzec Jun 4, 2026
4b71521
docs: move recipes to docs/recipes/, expand to 9, add runnable notes …
przemarzec Jun 4, 2026
1979bd9
docs: complete the API reference (actions, lineage, record_access, bu…
przemarzec Jun 4, 2026
fe09236
docs: add REFLECTION-lineage traversal example + dreaming cross-link
przemarzec Jun 4, 2026
f11a993
docs: document the journal/ttl/ingest/hooks/manifests config sections
przemarzec Jun 4, 2026
37cf92b
docs: correct hooks.class path format and ENGRAVA_DB description
przemarzec Jun 4, 2026
e132062
docs: add positioning + migration guide with scoping/multi-tenancy
przemarzec Jun 4, 2026
cc40e06
docs: correct embedding-storage precondition + harden bulk-import guard
przemarzec Jun 4, 2026
7eed1fe
docs: add troubleshooting and FAQ pages
przemarzec Jun 4, 2026
2708d1e
docs: align FAQ cycle + reflection wording with concepts and code
przemarzec Jun 4, 2026
2d5a18a
docs: add performance and scaling guide
przemarzec Jun 4, 2026
1773ebc
docs: stop describing the sqlite-vec backend as ANN / sub-linear
przemarzec Jun 4, 2026
6926c2c
docs: add data lifecycle, retention and erasure guide
przemarzec Jun 4, 2026
0c398db
docs: correct gc --expired strategy behaviour and ARCHIVED visibility
przemarzec Jun 4, 2026
341cb45
docs: collapse column-aligned code-comment spacing to single space
przemarzec Jun 4, 2026
a043514
docs: add production operations guides (deployment, concurrency, backup)
przemarzec Jun 4, 2026
bce8c44
docs: correct close semantics, ENGRAVA_DB scope, and WAL-safe backup
przemarzec Jun 4, 2026
1986ec3
docs: do not claim store.close() checkpoints the WAL
przemarzec Jun 4, 2026
0a8d2f1
docs: add CLI reference
przemarzec Jun 4, 2026
ad6e2f7
docs: clarify snapshot default output path and --service semantics
przemarzec Jun 4, 2026
d0e224c
docs: correct --service semantics for snapshot and restore
przemarzec Jun 4, 2026
13fbf27
docs: add glossary
przemarzec Jun 4, 2026
c5f5973
docs: fix Core Concepts lifecycle to match verified ARCHIVED behavior
przemarzec Jun 4, 2026
a2e123c
docs: add production monitoring section to observability
przemarzec Jun 4, 2026
41888d7
docs: fix three accuracy gaps in the production monitoring section
przemarzec Jun 4, 2026
bbf89ec
docs: use the correct JournalIntegrityResult.valid attribute
przemarzec Jun 4, 2026
52ff11c
docs: add a dreaming-loop narrative to the top of dreaming.md
przemarzec Jun 4, 2026
e739a73
docs: make the dreaming-loop narrative conditional where the code is
przemarzec Jun 4, 2026
49ef4a1
docs: add rolling-upgrades guidance and refresh the compatibility matrix
przemarzec Jun 4, 2026
199aacb
docs: fix sqlite-vec "ANN" label in the example config comment
przemarzec Jun 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,17 @@ since 0.3.0.
→ See [`docs/benchmarks.md`](docs/benchmarks.md) for reproducible
evidence (synthetic benchmark suite runnable in ~5 minutes).

### Tamper-Evident Audit Trail

Opt-in hash-chain **journal** that records every thought/edge mutation as a
SHA-256-linked, before/after entry — off by default, one config flag to enable.
Query history with `store.journal.get_entries(...)` and validate the chain with
`store.journal.verify_integrity()`.

→ See [`docs/audit-trail.md`](docs/audit-trail.md) for enabling, querying,
verification, and the security model (what "tamper-evident" does and does not
guarantee).

### Multi-Service Isolation

Run multiple independent databases under one `EngravaManager`:
Expand Down Expand Up @@ -203,6 +214,8 @@ engrava --db mydata.db export -o portable.json
`engrava info` now renders the same metrics snapshot contract exposed by
`await store.metrics()`.

See the [CLI reference](docs/cli.md) for every command and option.

## Architecture

- **SQLite** with WAL mode for concurrent reads
Expand All @@ -214,13 +227,30 @@ engrava --db mydata.db export -o portable.json

## Documentation

- [Upgrade Guide](docs/upgrade.md) — compatibility matrix, backups, and troubleshooting
- [Core Concepts](docs/concepts.md) — the mental model (thought, edge, reflection, cycle, …) — start here
- [Positioning](docs/positioning.md) — when Engrava is (and isn't) the right tool, and how it compares
- [Quick Start](docs/quickstart.md) — 5-minute setup guide
- [Tutorial](docs/tutorial.md) — build a small notes memory end to end
- [Recipes](docs/recipes/index.md) — copy-paste snippets for common tasks (store a turn, retrieve context, TTL, dedup, …)
- [Building a memory-backed agent](docs/guides/agent-memory.md) — the end-to-end agent turn loop (ingest → retrieve → generate → consolidate)
- [Migrating from another memory system](docs/guides/migrating-from-other-memory.md) — concept mapping, porting calls, bulk import, and scoping/multi-tenancy
- [Embeddings](docs/guides/embeddings.md) — wiring a real embedding provider (local / OpenAI / Ollama / HuggingFace / custom)
- [Configuration](docs/configuration.md) — YAML config format and options
- [Upgrade Guide](docs/upgrade.md) — compatibility matrix, backups, and troubleshooting
- [Extensions](docs/extensions.md) — Writing custom extensions and hooks
- [Observability](docs/observability.md) — Metrics snapshot API
- [Audit Trail](docs/audit-trail.md) — Tamper-evident hash-chain journal (enabling, querying, verifying, security model)
- [API Reference](docs/api-reference.md) — Full protocol and class reference
- [CLI Reference](docs/cli.md) — every `engrava` command and option
- [Glossary](docs/glossary.md) — quick definitions of every Engrava term
- [MindQL](docs/mindql.md) — Query language syntax and examples
- [Troubleshooting](docs/troubleshooting.md) — symptom → cause → fix for common errors
- [FAQ](docs/faq.md) — quick answers (LLM/keys, embeddings-optional, scale, concurrency, backups, …)
- [Performance & Scaling](docs/performance.md) — the vector-backend switch, bulk-ingest, and dreaming cost at scale
- [Data Lifecycle & Retention](docs/data-lifecycle.md) — lifecycle states, TTL, archive-vs-delete, GDPR erasure, disk reclamation
- [Deployment](docs/deployment.md) — process model, database files on disk, containers, graceful shutdown
- [Concurrency](docs/concurrency.md) — the WAL single-writer model, busy timeout, and per-service isolation
- [Backup & Recovery](docs/backup-and-recovery.md) — WAL-safe backups, snapshot vs file copy, restore verification
- [Known Limitations](docs/known-limitations.md) — Platform notes and constraints

## Development
Expand Down
92 changes: 88 additions & 4 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ keyword arguments and does **not** return a UUID string.
| `await list_thoughts(...)` | `list[ThoughtRecord]` | List with filters (keyword-only) |
| `await count_thoughts(...)` | `int` | Count with filters (keyword-only) |
| `await delete_thought(thought_id)` | `bool` | Hard delete; `True` if a row was removed |
| `await record_access(thought_id)` | `None` | Mark a thought as accessed — bumps `access_count` and sets `last_accessed_at`; raises `ThoughtNotFoundError` if missing. Drives the access-frequency dreaming signal. |

```python
import uuid
Expand Down Expand Up @@ -135,6 +136,40 @@ await store.create_edge(
)
```

#### REFLECTION lineage

Helpers for navigating the `CONSOLIDATED_FROM` graph that dreaming builds
between a REFLECTION and the source thoughts it summarises.

| Method | Returns | Description |
|--------|---------|-------------|
| `await consolidated_member_ids(reflection_id)` | `list[str]` | The thought IDs a REFLECTION was consolidated from |
| `await consolidated_source_statuses(reflection_id)` | `list[str]` | The lifecycle statuses of those source thoughts (e.g. to detect a fully-archived, orphaned cluster) |
| `await reflections_consolidated_from(source_id)` | `list[str]` | The REFLECTION IDs that consolidated a given source thought (the reverse direction) |
| `await thought_exists_by_source(*, source, thought_type_value)` | `bool` | Whether any thought exists with the given `source` and type — keyword-only |

```python
# Walk a REFLECTION down to its sources, and back from a source to its REFLECTIONs.
member_ids = await store.consolidated_member_ids(reflection_id)
for thought_id in member_ids:
source = await store.get_thought(thought_id)
if source is not None:
print(source.essence)

# Detect an orphaned cluster — every source archived/gone:
statuses = await store.consolidated_source_statuses(reflection_id)
is_orphaned = bool(statuses) and all(s != "ACTIVE" for s in statuses)

# Reverse direction: which REFLECTIONs summarise this source?
parents = await store.reflections_consolidated_from(member_ids[0])

# Exact-source existence check (e.g. dreaming's idempotency guard — a REFLECTION's
# source is "dreaming:<cluster_hash>", so match the full value, not a prefix):
exists = await store.thought_exists_by_source(
source="dreaming:abc123def4567890", thought_type_value="REFLECTION"
)
```

#### Embedding Operations

| Method | Returns | Description |
Expand Down Expand Up @@ -167,11 +202,23 @@ returns a single `HybridSearchResult` container.
| `await metrics()` | `EngravaMetrics` | Snapshot of thought/edge counts, storage, and search-latency percentiles (see [Observability](observability.md)) |
| `await cleanup_expired(now=None, *, exclude_id=None)` | `CleanupResult` | Archive or delete thoughts past their `expires_at` |
| `await verify_embedding_model()` | `None` | Raise `EmbeddingModelMismatchError` if the stored model lock disagrees with the configured provider |
| `async with store.suspend_auto_commit():` | context manager | Defer per-call commits so a block of writes commits once (rolls back on error) — use for bulk ingest |
| `await close()` | `None` | Close the owned connection (only when the store opened it via `from_config`) |

```python
# Bulk ingest: one transaction instead of one commit per write.
async with store.suspend_auto_commit():
for record in many_records:
await store.create_thought(record)
# commit happens once on clean exit; any exception rolls the whole block back
```

### `ReadOnlyEngrava`

Wrapper that raises `ReadOnlyViolationError` on any write operation.
A composition wrapper that delegates reads to the wrapped store and raises
`ReadOnlyViolationError` on any write. Use it to hand a retrieval-only view of
shared memory to a component that should never mutate it — e.g. a sub-agent or
worker whose job is only to look things up.

```python
from engrava import ReadOnlyEngrava
Expand Down Expand Up @@ -299,14 +346,51 @@ extension is recommended for filtering queries (`json_extract(metadata_json, '$.

### `ActionRecord`

Records an action the agent took (a tool call, a message, …), linked to the
thought that prompted it, with execution and verification state.

| Field | Type | Description |
|-------|------|-------------|
| `action_id` | `str` | UUID primary key |
| `source_thought_id` | `str` | Linked thought |
| `source_thought_id` | `str` | The thought this action originated from |
| `action_type` | `ActionType` | Action classification |
| `intent` | `str` | Description of intent |
| `status` | `ActionStatus` | Current status |
| `intent` | `str` | Description of intent (min length 1) |
| `status` | `ActionStatus` | Current execution status |
| `verification_status` | `VerificationStatus` | Verification state |
| `raw_metrics_json` | `str \| None` | Optional ground-truth facts for verification |

**Store methods** (on `SqliteEngravaCore`):

| Method | Returns | Description |
|--------|---------|-------------|
| `await create_action(action)` | `ActionRecord` | Persist an `ActionRecord` |
| `await get_actions(thought_id)` | `list[ActionRecord]` | Actions linked to a thought |

`ActionStatus` is a state machine: `PLANNED → EXECUTING → CONFIRMED` / `FAILED`,
and `PLANNED → BLOCKED → PLANNED`. `can_transition_to(...)` / `evolve(...)`
enforce valid transitions (an illegal change raises `InvalidTransitionError`).

```python
import uuid
from engrava import ActionRecord, ActionType, ActionStatus, VerificationStatus

action = ActionRecord(
action_id=str(uuid.uuid4()),
source_thought_id=prompting_thought_id,
action_type=ActionType.TOOL_CALL,
intent="search the web for flight prices",
status=ActionStatus.PLANNED,
verification_status=VerificationStatus.PENDING,
)
await store.create_action(action)

# advance through the lifecycle (frozen model → evolve returns a new instance):
done = action.evolve(status=ActionStatus.EXECUTING).evolve(
status=ActionStatus.CONFIRMED
)

actions = await store.get_actions(prompting_thought_id)
```

### `HybridSearchResult`

Expand Down
Loading
Loading