Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- **Read tracking**: Auto-logs when entries are accessed by `get_knowledge` and `get_alerts`. Query with `get_reads(entry_id?, since?, platform?, limit?)`.
- **Action tracking**: `acted_on(entry_id, action, platform?, detail?, tags?)` records concrete actions agents take because of entries. Query with `get_actions(entry_id?, since?, platform?, tags?, limit?)`.
- **Unread entries**: `get_unread(since?)` returns entries with zero reads — cleanup candidates and dead knowledge.
- **Activity feed**: `get_activity(since?, platform?, limit?)` returns combined reads + actions chronologically.
- **Read count enrichment**: List mode (`mode="list"`) now includes `read_count` and `last_read` on each entry.
- **Actions have tags**: Tags on action records (default: copied from referenced entry) enable filtered action queries.
- Alembic migration for `reads` and `actions` tables with indexes
- 17 new tests (213 total)

## [0.6.1] - 2026-03-23

### Added
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ See [Security considerations](docs/deployment-guide.md#security-considerations)
- Suppression system with time-based expiry and escalation overrides

### MCP interface
- Full MCP API: 6 resources + 18 tools + 5 prompts
- Full MCP API: 6 resources + 23 tools + 5 prompts
- Read tool mirrors for tools-only clients
- User-defined custom prompts from store entries with `{{var}}` templates
- Streamable HTTP + stdio transports
Expand All @@ -269,7 +269,7 @@ See [Security considerations](docs/deployment-guide.md#security-considerations)
- Secret path auth + Cloudflare WAF for edge-level access control
- Docker Compose with Postgres, named Cloudflare Tunnel, or ephemeral quick tunnel
- Request timing instrumentation and `/health` endpoint
- 196 tests (all against real Postgres), strict type checking, CI pipeline with coverage, QA gate
- 213 tests (all against real Postgres), strict type checking, CI pipeline with coverage, QA gate

### Not yet implemented
- Layer 2 (baseline) detection — rolling averages and deviation calculation
Expand Down
51 changes: 51 additions & 0 deletions alembic/versions/c3a7f2d91e4b_add_reads_and_actions_tables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""add reads and actions tables

Revision ID: c3a7f2d91e4b
Revises: 9184f91831f8
Create Date: 2026-03-23 17:00:00.000000

"""

from typing import Sequence, Union

from alembic import op

# revision identifiers, used by Alembic.
revision: str = "c3a7f2d91e4b"
down_revision: Union[str, Sequence[str], None] = "9184f91831f8"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
"""Create reads and actions tables for read/action tracking."""
op.execute("""
CREATE TABLE IF NOT EXISTS reads (
id SERIAL PRIMARY KEY,
entry_id TEXT NOT NULL REFERENCES entries(id) ON DELETE CASCADE,
timestamp TIMESTAMPTZ NOT NULL DEFAULT now(),
platform TEXT,
tool_used TEXT
);
CREATE INDEX IF NOT EXISTS idx_reads_entry ON reads(entry_id);
CREATE INDEX IF NOT EXISTS idx_reads_timestamp ON reads(timestamp);

CREATE TABLE IF NOT EXISTS actions (
id SERIAL PRIMARY KEY,
entry_id TEXT NOT NULL REFERENCES entries(id) ON DELETE CASCADE,
timestamp TIMESTAMPTZ NOT NULL DEFAULT now(),
platform TEXT,
action TEXT NOT NULL,
detail TEXT,
tags JSONB NOT NULL DEFAULT '[]'
);
CREATE INDEX IF NOT EXISTS idx_actions_entry ON actions(entry_id);
CREATE INDEX IF NOT EXISTS idx_actions_timestamp ON actions(timestamp);
CREATE INDEX IF NOT EXISTS idx_actions_tags_gin ON actions USING GIN (tags);
""")


def downgrade() -> None:
"""Drop reads and actions tables."""
op.execute("DROP TABLE IF EXISTS actions")
op.execute("DROP TABLE IF EXISTS reads")
42 changes: 42 additions & 0 deletions docs/data-dictionary.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,48 @@ Written by agents via `set_preference`. Keyed by `key` + `scope` (upserted). Por
- **Staleness:** Status entries with `ttl_sec` are marked stale in the briefing if no update arrives within the TTL window. The entry itself is not deleted.
- **Change tracking:** `update_entry` appends previous field values to the `changelog` array in `data`. Use `get_knowledge(include_history="true")` to see changes, or `include_history="only"` to find entries that have been modified.
- **Hard deletes:** The API only performs soft deletes. Manual SQL `DELETE` statements bypass the trash — that data is gone permanently. Back up regularly.
- **Read/action cleanup:** The `reads` and `actions` tables use `ON DELETE CASCADE` on `entry_id`. This means read and action records are automatically removed when an entry is **hard deleted** (auto-purge or manual SQL). Soft delete (`delete_entry`) does **not** cascade — reads and actions persist for trashed entries until the 30-day purge.

## Table: `reads`

Auto-populated when entries are accessed via `get_knowledge` and `get_alerts`. Fire-and-forget — read log failures never block tool responses.

| Column | Type | Nullable | Description |
|--------|------|----------|-------------|
| `id` | SERIAL | No | Auto-incrementing primary key. |
| `entry_id` | TEXT | No | References `entries(id)` with `ON DELETE CASCADE`. |
| `timestamp` | TIMESTAMPTZ | No | When the read occurred. Default: `now()`. |
| `platform` | TEXT | Yes | Which platform performed the read (e.g., `"claude-code"`). |
| `tool_used` | TEXT | Yes | Which tool triggered the read (e.g., `"get_knowledge"`). |

### Indexes

| Index | Columns | Type | Purpose |
|-------|---------|------|---------|
| `idx_reads_entry` | `entry_id` | B-tree | Look up reads for a specific entry |
| `idx_reads_timestamp` | `timestamp` | B-tree | Time-range queries |

## Table: `actions`

Agent-reported records of concrete actions taken because of an entry. Permanent audit trail.

| Column | Type | Nullable | Description |
|--------|------|----------|-------------|
| `id` | SERIAL | No | Auto-incrementing primary key. |
| `entry_id` | TEXT | No | References `entries(id)` with `ON DELETE CASCADE`. |
| `timestamp` | TIMESTAMPTZ | No | When the action was recorded. Default: `now()`. |
| `platform` | TEXT | Yes | Which platform reported the action (e.g., `"claude-code"`). |
| `action` | TEXT | No | What was done (e.g., `"created GitHub issue #42"`). |
| `detail` | TEXT | Yes | Optional structured reference (PR URL, issue number, etc.). |
| `tags` | JSONB | No | Tags for filtered queries. Default: copied from referenced entry. |

### Indexes

| Index | Columns | Type | Purpose |
|-------|---------|------|---------|
| `idx_actions_entry` | `entry_id` | B-tree | Look up actions for a specific entry |
| `idx_actions_timestamp` | `timestamp` | B-tree | Time-range queries |
| `idx_actions_tags_gin` | `tags` | GIN | Fast tag containment queries |

## Backend details

Expand Down
4 changes: 2 additions & 2 deletions docs/deployment-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,9 +348,9 @@ The current approach uses two layers:
## Notes

- **The store persists** in the data directory. Restart the server and your data is still there.
- **Not all clients support all MCP features** — the MCP spec defines [resources](https://modelcontextprotocol.io/docs/concepts/resources), [tools](https://modelcontextprotocol.io/docs/concepts/tools), and [prompts](https://modelcontextprotocol.io/docs/concepts/prompts). Client support varies: some only surface tools (e.g., Claude.ai), some don't support prompts. All 18 tools work everywhere. Read tools mirror the resources so tools-only clients get full functionality. Prompts (including user-defined custom prompts) are available in clients that support them — VS Code, Claude Desktop, Cursor.
- **Not all clients support all MCP features** — the MCP spec defines [resources](https://modelcontextprotocol.io/docs/concepts/resources), [tools](https://modelcontextprotocol.io/docs/concepts/tools), and [prompts](https://modelcontextprotocol.io/docs/concepts/prompts). Client support varies: some only surface tools (e.g., Claude.ai), some don't support prompts. All 23 tools work everywhere. Read tools mirror the resources so tools-only clients get full functionality. Prompts (including user-defined custom prompts) are available in clients that support them — VS Code, Claude Desktop, Cursor.

- **18 tools, 5 prompts, user-defined prompts** — tools include `remember` (general notes), `learn_pattern` (operational knowledge), `add_context` (time-limited), `update_entry` (in-place updates with changelog), `get_stats` (store summary), `get_tags` (tag discovery), plus alerting and data management. Built-in prompts: `agent_instructions`, `project_context`, `system_status`, `write_guide`, `catchup`. Store entries with `source="custom-prompt"` to create your own. See the [README](../README.md#tools) for the full list.
- **23 tools, 5 prompts, user-defined prompts** — tools include `remember` (general notes), `learn_pattern` (operational knowledge), `add_context` (time-limited), `update_entry` (in-place updates with changelog), `get_stats` (store summary), `get_tags` (tag discovery), plus alerting and data management. Built-in prompts: `agent_instructions`, `project_context`, `system_status`, `write_guide`, `catchup`. Store entries with `source="custom-prompt"` to create your own. See the [README](../README.md#tools) for the full list.
- **Model matters** — best experience with Claude Sonnet 4.6 or Opus 4.6. Smaller models (Haiku, GPT-4o-mini) may not follow MCP prompts or call tools proactively.
- **Suppression matching is content-aware** — a suppression tagged `["qbittorrent"]` will match alerts whose alert_id or message contains "qbittorrent", even if the alert's structural tags differ.
- **Soft delete is safe** — `delete_entry` moves entries to trash (30-day retention). Bulk deletes show a dry-run count first and require `confirm=True`. Delete and restore by tags with AND logic (e.g., `delete_entry(tags=["demo"], confirm=True)` deletes entries matching all given tags). Use `get_deleted` and `restore_entry` to recover — restore also supports tags (e.g., `restore_entry(tags=["demo"])`).
Expand Down
Loading
Loading