Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
8fee1bc
fix(usage): store disjoint input, cache, and output token counters
Sep 28, 2026
c8fba99
feat: query single events, session outlines, and merged trajectories
Sep 28, 2026
ea2b384
Merge branch 'feat/token-usage-breakdown'
Sep 28, 2026
dd6eef5
feat: list sessions by latest activity
Sep 29, 2026
8b90475
feat: filter the session list by provider and model used
Sep 29, 2026
14a13a2
feat: name sessions from captured title-generation calls
Sep 30, 2026
9904c12
fix(trajectory): deduplicate replayed snapshots by message identity a…
Sep 30, 2026
45d1a65
feat(trajectory): show Codex context compaction as one item
Sep 30, 2026
04bae7f
Revert "fix(trajectory): deduplicate replayed snapshots by message id…
Sep 30, 2026
d27c8eb
fix(trajectory): project archived bodies without changing stored records
Sep 30, 2026
00a5be4
fix(trajectory): remove text-based role guessing and prefix trimming
Sep 30, 2026
2e72a57
feat: match Codex naming threads with the task sessions they name
Sep 30, 2026
9c52723
Merge branch 'feat/codex-title-links'
Sep 30, 2026
de51e22
refactor(trajectory): reuse parsed messages when assigning streams
Oct 2, 2026
0fb9891
fix: scope session title adapters and recover Codex task links
Oct 2, 2026
7f4f996
refactor: simplify Codex title matching
Oct 2, 2026
20dd3b2
fix: use first user input for default session titles
Oct 2, 2026
26f7eb3
feat(filters): match any of several agents, sessions, providers, and …
Oct 2, 2026
0fa747d
Merge branch 'main' into feat/multi-select-filters
Oct 2, 2026
c6f3980
perf(sqlite): aggregate usage summaries in SQL
Oct 2, 2026
ba0b55f
perf(sqlite): answer summaries and the session list from covering ind…
Oct 2, 2026
1ab25f2
Merge branch 'main' into perf/frontend-jank
Oct 2, 2026
1b5626d
feat(trajectory): expose usage for every request and response
Oct 2, 2026
881c1ce
Merge branch 'main' into feat/request-usage
Oct 2, 2026
c6bd7b5
feat(events): add a content-free list projection
Oct 2, 2026
36a8bb9
fix(trajectory): name streams by their opening prompt alone
Oct 4, 2026
56047fd
perf(search): rank and page sessions in SQL, fragment only returned m…
Oct 8, 2026
40f15d8
feat(usage): consume structured AgentEvent fields instead of provider…
Oct 13, 2026
c45a9d1
build: satisfy Clippy 1.99 lints
Oct 13, 2026
59a4daa
refactor(schema)!: consolidate migrations into one initial schema
Oct 14, 2026
28a9b14
refactor(storage): replace hand-written SQL with the Diesel DSL
Oct 14, 2026
1587b9c
feat(ingest)!: store AgentEvent values as reported and place turns by…
Oct 14, 2026
6b9b6f6
feat(search)!: index tool input as written
Oct 14, 2026
5f82fd4
feat(trajectory)!: place streams by declared keys and keep roles as r…
Oct 14, 2026
366ac5e
test(blackbox): follow the AgentEvent ingest contract
Oct 14, 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/target/
__pycache__/
.DS_Store
.env
69 changes: 64 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ and optional full-text search.
- Health and storage-readiness probes.
- Embedded migrations for both supported storage profiles.

A session is titled by the `session_title` the collector reports for a call in
which the Agent names its own session (Claude Code); otherwise by its first
nonempty user input, collapsed to one line and truncated to 80 characters with
an ellipsis. An Agent title is never replaced by a default title. Requests whose
`purpose` marks them as naming calls are not user input. Codex names tasks from
separate threads that do not say which session they name; those threads are
marked `title_generation` from their `thread_source` and are not linked to a
task. Naming calls retain their own events and usage. Existing sessions receive
default titles through a one-time backfill.

Exactly one storage profile is compiled into a binary:

- `postgres-es` uses PostgreSQL as the source of truth and optionally projects
Expand Down
1 change: 1 addition & 0 deletions crates/abyss-backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ sqlite-fts = [

[dependencies]
axum = "0.8"
axum-extra = { version = "0.12", default-features = false, features = ["query"] }
base64 = "0.21"
chrono = { version = "0.4", features = ["serde"] }
diesel = { version = "2.2", default-features = false, features = ["32-column-tables", "r2d2", "with-deprecated"], optional = true }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
DROP TABLE session_titles;
DROP TABLE usage_events_fts;
DROP TABLE agent_diagnostic_capture_events;
DROP TABLE agent_diagnostic_captures;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,21 @@ CREATE TABLE agent_sessions (
CREATE INDEX agent_sessions_user_time_idx
ON agent_sessions (user_id, started_at DESC);

-- turn_key is the turn the Agent named for its requests, unique within the
-- session; it is NULL for the turn opened by requests naming none. turn_index
-- is the turn's position in the session by start time.
CREATE TABLE agent_turns (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL REFERENCES app_users(id) ON DELETE CASCADE,
session_pk TEXT NOT NULL REFERENCES agent_sessions(id) ON DELETE CASCADE,
turn_index INTEGER NOT NULL CHECK (turn_index >= 1),
turn_key TEXT,
started_at INTEGER NOT NULL,
ended_at INTEGER,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
UNIQUE (session_pk, turn_index)
UNIQUE (session_pk, turn_index),
UNIQUE (session_pk, turn_key)
) STRICT;

CREATE INDEX agent_turns_user_session_idx
Expand Down Expand Up @@ -85,6 +90,12 @@ CREATE TABLE llm_usage_events (
reasoning_tokens INTEGER NOT NULL DEFAULT 0 CHECK (reasoning_tokens >= 0),
total_tokens INTEGER NOT NULL DEFAULT 0 CHECK (total_tokens >= 0),
observed_at INTEGER NOT NULL,
-- Agent-declared thread of the exchange; equal values continue one conversation.
stream_id TEXT,
-- Provider identity of the response the exchange produced.
response_id TEXT,
-- Response whose conversation the exchange's request continues.
previous_response_id TEXT,
metadata TEXT NOT NULL DEFAULT '{}' CHECK (json_valid(metadata)),
created_at INTEGER NOT NULL
) STRICT;
Expand Down Expand Up @@ -154,7 +165,35 @@ CREATE VIRTUAL TABLE usage_events_fts USING fts5(
content,
tool_names,
tool_content,
commands,
file_paths,
tokenize = 'unicode61'
);


-- Session names read back from the Agents' own title-generation calls, kept
-- apart from agent_sessions so session rows keep their shape. purpose marks
-- sessions that exist only to generate a title, such as Codex naming threads,
-- and parent_session_pk the task session such a thread was matched to.
CREATE TABLE session_titles (
session_pk TEXT PRIMARY KEY REFERENCES agent_sessions(id) ON DELETE CASCADE,
user_id TEXT NOT NULL REFERENCES app_users(id) ON DELETE CASCADE,
title TEXT,
title_event_pk TEXT REFERENCES llm_usage_events(id) ON DELETE SET NULL,
title_observed_at INTEGER,
purpose TEXT,
parent_session_pk TEXT REFERENCES agent_sessions(id) ON DELETE SET NULL,
updated_at INTEGER NOT NULL
) STRICT;

-- Event rows hold whole request bodies in text and metadata, so reading a
-- token column from the row walks through megabytes of overflow pages. These
-- indexes carry every column the usage summary and the session list read,
-- letting both answer from the index alone.
CREATE INDEX llm_usage_events_summary_cover_idx
ON llm_usage_events (
user_id, observed_at, agent_name, llm_provider, llm_model, event_type,
device_context_id, session_pk, turn_pk, session_id,
input_tokens, output_tokens, cache_read_tokens, cache_write_tokens,
reasoning_tokens, total_tokens
);
CREATE INDEX llm_usage_events_session_activity_idx
ON llm_usage_events (session_pk, observed_at, total_tokens);
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
-- Drop in reverse dependency order so every foreign key remains valid while
-- the consolidated standalone schema is removed.
DROP TABLE IF EXISTS session_titles;
DROP TABLE IF EXISTS search_outbox;
DROP TABLE IF EXISTS agent_diagnostic_capture_events;
DROP TABLE IF EXISTS agent_diagnostic_captures;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ CREATE TABLE devices (
CREATE INDEX devices_user_seen_idx
ON devices (user_id, last_seen_at DESC);

-- Sessions use the Agent-native session ID plus canonical Agent name. The
-- Sessions use the Agent-native session ID plus the reported Agent name. The
-- device reference follows the most recently observed device for that session.
CREATE TABLE agent_sessions (
id uuid PRIMARY KEY,
Expand All @@ -53,18 +53,21 @@ CREATE TABLE agent_sessions (
CREATE INDEX agent_sessions_user_time_idx
ON agent_sessions (user_id, started_at DESC);

-- Turn indexes are backend-normalized during ingest when stable provider or
-- Agent metadata reveals that a collector restarted its local counter.
-- turn_key is the turn the Agent named for its requests, unique within the
-- session; it is NULL for the turn opened by requests naming none. turn_index
-- is the turn's position in the session by start time.
CREATE TABLE agent_turns (
id uuid PRIMARY KEY,
user_id uuid NOT NULL REFERENCES app_users(id) ON DELETE CASCADE,
session_pk uuid NOT NULL REFERENCES agent_sessions(id) ON DELETE CASCADE,
turn_index integer NOT NULL CHECK (turn_index >= 1),
turn_key text,
started_at timestamptz NOT NULL,
ended_at timestamptz,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (session_pk, turn_index)
UNIQUE (session_pk, turn_index),
UNIQUE (session_pk, turn_key)
);

CREATE INDEX agent_turns_user_session_idx
Expand Down Expand Up @@ -95,6 +98,12 @@ CREATE TABLE llm_usage_events (
reasoning_tokens bigint NOT NULL DEFAULT 0 CHECK (reasoning_tokens >= 0),
total_tokens bigint NOT NULL DEFAULT 0 CHECK (total_tokens >= 0),
observed_at timestamptz NOT NULL,
-- Agent-declared thread of the exchange; equal values continue one conversation.
stream_id text,
-- Provider identity of the response the exchange produced.
response_id text,
-- Response whose conversation the exchange's request continues.
previous_response_id text,
metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
created_at timestamptz NOT NULL DEFAULT now()
);
Expand Down Expand Up @@ -186,3 +195,19 @@ CREATE TABLE search_outbox (
CREATE INDEX search_outbox_pending_idx
ON search_outbox (available_at, id)
WHERE processed_at IS NULL AND dead_lettered_at IS NULL;


-- Session names read back from the Agents' own title-generation calls, kept
-- apart from agent_sessions so session rows keep their shape. purpose marks
-- sessions that exist only to generate a title, such as Codex naming threads,
-- and parent_session_pk the task session such a thread was matched to.
CREATE TABLE session_titles (
session_pk uuid PRIMARY KEY REFERENCES agent_sessions(id) ON DELETE CASCADE,
user_id uuid NOT NULL REFERENCES app_users(id) ON DELETE CASCADE,
title text,
title_event_pk uuid REFERENCES llm_usage_events(id) ON DELETE SET NULL,
title_observed_at timestamptz,
purpose text,
parent_session_pk uuid REFERENCES agent_sessions(id) ON DELETE SET NULL,
updated_at timestamptz NOT NULL DEFAULT now()
);
Loading
Loading