mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
171 lines
6.1 KiB
Python
171 lines
6.1 KiB
Python
"""026_token_usage_tables
|
|||
|
|
|
||
|
|
Create token usage instrumentation tables:
|
||
|
|
- agent_spawn_sessions: tracks each agent container spawn with token totals
|
||
|
|
- token_usage_snapshots: periodic snapshots of token usage per session
|
||
|
|
- daily_usage_rollups: aggregated daily usage per agent/team/model
|
||
|
|
|
||
|
|
Revision ID: 026_token_usage_tables
|
||
|
|
Revises: 025_agentrole_prompter
|
||
|
|
Create Date: 2026-06-09
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import sqlalchemy as sa
|
||
|
|
from alembic import op
|
||
|
|
from sqlalchemy.dialects.postgresql import UUID
|
||
|
|
|
||
|
|
revision = "026_token_usage_tables"
|
||
|
|
# Rebased onto 026_completed_dependency_ids so the chain stays linear: the
|
||
|
|
# master merge brought in a second migration off 025_agentrole_prompter, which
|
||
|
|
# forked the head. Chain is now 025 -> 026_completed_dependency_ids -> this.
|
||
|
|
down_revision = "026_completed_dependency_ids"
|
||
|
|
branch_labels = None
|
||
|
|
depends_on = None
|
||
|
|
|
||
|
|
|
||
|
|
def upgrade() -> None:
|
||
|
|
# ------------------------------------------------------------------
|
||
|
|
# agent_spawn_sessions
|
||
|
|
# One row per container spawn. Opened on spawn, closed on stop.
|
||
|
|
# ------------------------------------------------------------------
|
||
|
|
op.create_table(
|
||
|
|
"agent_spawn_sessions",
|
||
|
|
sa.Column("id", UUID(as_uuid=True), primary_key=True),
|
||
|
|
sa.Column("agent_slug", sa.String(100), nullable=False),
|
||
|
|
sa.Column("team", sa.String(50), nullable=False),
|
||
|
|
sa.Column("role", sa.String(50), nullable=False),
|
||
|
|
sa.Column("model", sa.String(100), nullable=False),
|
||
|
|
sa.Column("task_id", sa.String(36), nullable=True),
|
||
|
|
sa.Column(
|
||
|
|
"started_at",
|
||
|
|
sa.DateTime(timezone=True),
|
||
|
|
nullable=False,
|
||
|
|
server_default=sa.func.now(),
|
||
|
|
),
|
||
|
|
sa.Column("ended_at", sa.DateTime(timezone=True), nullable=True),
|
||
|
|
# BIGINT for token counts — they can exceed INT32 for long sessions
|
||
|
|
sa.Column("tokens_input", sa.BigInteger, nullable=False, server_default="0"),
|
||
|
|
sa.Column("tokens_output", sa.BigInteger, nullable=False, server_default="0"),
|
||
|
|
sa.Column(
|
||
|
|
"tokens_cache_read", sa.BigInteger, nullable=False, server_default="0"
|
||
|
|
),
|
||
|
|
sa.Column(
|
||
|
|
"tokens_cache_write", sa.BigInteger, nullable=False, server_default="0"
|
||
|
|
),
|
||
|
|
sa.Column("exit_reason", sa.String(100), nullable=True),
|
||
|
|
sa.Column("estimated_cost_usd", sa.Float, nullable=True),
|
||
|
|
)
|
||
|
|
|
||
|
|
# Indexes for common query patterns
|
||
|
|
op.create_index(
|
||
|
|
"ix_agent_spawn_sessions_agent_slug",
|
||
|
|
"agent_spawn_sessions",
|
||
|
|
["agent_slug"],
|
||
|
|
)
|
||
|
|
op.create_index(
|
||
|
|
"ix_agent_spawn_sessions_started_at",
|
||
|
|
"agent_spawn_sessions",
|
||
|
|
["started_at"],
|
||
|
|
)
|
||
|
|
op.create_index(
|
||
|
|
"ix_agent_spawn_sessions_ended_at",
|
||
|
|
"agent_spawn_sessions",
|
||
|
|
["ended_at"],
|
||
|
|
)
|
||
|
|
op.create_index(
|
||
|
|
"ix_agent_spawn_sessions_team",
|
||
|
|
"agent_spawn_sessions",
|
||
|
|
["team"],
|
||
|
|
)
|
||
|
|
|
||
|
|
# ------------------------------------------------------------------
|
||
|
|
# token_usage_snapshots
|
||
|
|
# Periodic snapshots (every ~60s) of token counts for active sessions.
|
||
|
|
# ------------------------------------------------------------------
|
||
|
|
op.create_table(
|
||
|
|
"token_usage_snapshots",
|
||
|
|
sa.Column("id", UUID(as_uuid=True), primary_key=True),
|
||
|
|
sa.Column(
|
||
|
|
"agent_spawn_session_id",
|
||
|
|
UUID(as_uuid=True),
|
||
|
|
sa.ForeignKey(
|
||
|
|
"agent_spawn_sessions.id", ondelete="CASCADE", name="fk_snapshot_session"
|
||
|
|
),
|
||
|
|
nullable=False,
|
||
|
|
),
|
||
|
|
sa.Column(
|
||
|
|
"snapshotted_at",
|
||
|
|
sa.DateTime(timezone=True),
|
||
|
|
nullable=False,
|
||
|
|
server_default=sa.func.now(),
|
||
|
|
),
|
||
|
|
sa.Column("tokens_input", sa.BigInteger, nullable=False, server_default="0"),
|
||
|
|
sa.Column("tokens_output", sa.BigInteger, nullable=False, server_default="0"),
|
||
|
|
sa.Column(
|
||
|
|
"tokens_cache_read", sa.BigInteger, nullable=False, server_default="0"
|
||
|
|
),
|
||
|
|
sa.Column(
|
||
|
|
"tokens_cache_write", sa.BigInteger, nullable=False, server_default="0"
|
||
|
|
),
|
||
|
|
)
|
||
|
|
|
||
|
|
op.create_index(
|
||
|
|
"ix_token_usage_snapshots_session_id",
|
||
|
|
"token_usage_snapshots",
|
||
|
|
["agent_spawn_session_id"],
|
||
|
|
)
|
||
|
|
op.create_index(
|
||
|
|
"ix_token_usage_snapshots_snapshotted_at",
|
||
|
|
"token_usage_snapshots",
|
||
|
|
["snapshotted_at"],
|
||
|
|
)
|
||
|
|
|
||
|
|
# ------------------------------------------------------------------
|
||
|
|
# daily_usage_rollups
|
||
|
|
# Pre-aggregated daily totals per (date, agent_slug, team, model).
|
||
|
|
# Populated by the sweeper; upserted on each sweep so re-runs are safe.
|
||
|
|
# ------------------------------------------------------------------
|
||
|
|
op.create_table(
|
||
|
|
"daily_usage_rollups",
|
||
|
|
sa.Column("id", UUID(as_uuid=True), primary_key=True),
|
||
|
|
sa.Column("date", sa.Date, nullable=False),
|
||
|
|
sa.Column("agent_slug", sa.String(100), nullable=False),
|
||
|
|
sa.Column("team", sa.String(50), nullable=False),
|
||
|
|
sa.Column("model", sa.String(100), nullable=False),
|
||
|
|
sa.Column("tokens_input", sa.BigInteger, nullable=False, server_default="0"),
|
||
|
|
sa.Column("tokens_output", sa.BigInteger, nullable=False, server_default="0"),
|
||
|
|
sa.Column(
|
||
|
|
"tokens_cache_read", sa.BigInteger, nullable=False, server_default="0"
|
||
|
|
),
|
||
|
|
sa.Column(
|
||
|
|
"tokens_cache_write", sa.BigInteger, nullable=False, server_default="0"
|
||
|
|
),
|
||
|
|
sa.Column("total_cost_usd", sa.Float, nullable=False, server_default="0"),
|
||
|
|
sa.Column("session_count", sa.Integer, nullable=False, server_default="0"),
|
||
|
|
)
|
||
|
|
|
||
|
|
# Unique constraint enables ON CONFLICT upsert in the sweeper
|
||
|
|
op.create_unique_constraint(
|
||
|
|
"uq_daily_rollup_date_agent_team_model",
|
||
|
|
"daily_usage_rollups",
|
||
|
|
["date", "agent_slug", "team", "model"],
|
||
|
|
)
|
||
|
|
op.create_index(
|
||
|
|
"ix_daily_rollups_date",
|
||
|
|
"daily_usage_rollups",
|
||
|
|
["date"],
|
||
|
|
)
|
||
|
|
op.create_index(
|
||
|
|
"ix_daily_rollups_agent_slug",
|
||
|
|
"daily_usage_rollups",
|
||
|
|
["agent_slug"],
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def downgrade() -> None:
|
||
|
|
op.drop_table("daily_usage_rollups")
|
||
|
|
op.drop_table("token_usage_snapshots")
|
||
|
|
op.drop_table("agent_spawn_sessions")
|