Skip to content

[P2.71] Daily spend limit reads tenant-writable data: move it to a server-side ledger #1344

Description

@frankbria

Spun off from PR #1343 (issue #1303) during post-merge disposition.

Context

CODEFRAME_USER_DAILY_COST_LIMIT_USD (codeframe/core/spend_limit.py) sums today's token_usage from each workspace's .codeframe/state.db. Agent code running in that workspace can write the file, so a tenant can delete rows, or the DB, and lower its own meter. Three related approximations from the same design:

  • Attribution is by workspace (workspace_spend_users), not by caller, so a shared workspace counts in full toward each user who ran in it. This fails closed, but it is inexact.
  • In-flight holds live in process memory, so a multi-worker server under-counts (same caveat as stream tickets, [P1.18] Stop putting long-lived JWTs in SSE/WebSocket URL query strings #745).
  • Delegated engines (claude-code, codex, opencode) are refused while a limit is set, because their spend never reaches token_usage.

Why it was not done in that PR

It needs its own design: a ledger the server owns and children cannot write. That is the same prerequisite as hosted execution (#1266, #1322).

Definition of done

  • Spend that counts toward the limit is recorded in the control-plane DB, keyed by principal, by the server or a channel the agent cannot write.
  • spend_today_usd reads that ledger; deleting a workspace's state.db no longer lowers the meter (test).
  • Holds survive multiple workers (shared storage), or the server refuses to start multi-worker with a limit set.

Activity

  1. frankbria commented on Oct 2, 2026

    @frankbria
    OwnerAuthor

    Gated (needs-owner): this needs an OS-level isolation decision (bubblewrap vs. a separate uid vs. a container per run) that the owner chose to defer on 2026-10-02 in favour of the P1 launch blockers. Hosted execution is already refused (#1266), so this only matters once hosted mode launches.

  2. frankbria commented on Oct 6, 2026

    @frankbria
    OwnerAuthor

    Owner steps (2026-10-06)

    Priority 7 of 7. Deferred, together with #1322: it needs the same isolation decision, so make that choice on #1322 and nothing more here, with one extra sub-decision.

    When hosted mode is scheduled:

    1. Make the #1322 decision (isolation model). See that issue's steps.

    2. Decide how in-flight spend holds behave with several server workers.

    gh issue comment 1344 --repo frankbria/codeframe --body "Decision: holds = <refuse multi-worker | redis>."

    3. The agent then moves spend to a server-side ledger in the control-plane DB, keyed by principal. Its acceptance test: deleting a workspace's state.db no longer lowers the meter. You approve the PR.

  3. added
    FutureDeferred - beyond v1/v2 scope, consider for future versions
    and removed
    needs-ownerGated on an owner decision the agent must not invent
    on Oct 6, 2026
  4. frankbria commented on Oct 6, 2026

    @frankbria
    OwnerAuthor

    Relabelled needs-owner → Future (owner, 2026-10-06). It stays deferred until hosted mode is scheduled; hosted execution is refused until then (#1266). The isolation-model decision steps above still apply when it's picked up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    FutureDeferred - beyond v1/v2 scope, consider for future versionsP2-medium-betaMedium priority - nice to have for betapriority:mediumsecurity

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions