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
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ validation.

Export previews report exact serialized UTF-8 bytes and a model-agnostic `ceil(bytes / 4)` approximate AI-input-token estimate. Compact JSON is the default for AI sharing; Pretty JSON remains available for human review, and preview estimates reflect the selected serialization. This is a sharing-budget aid, not tokenizer output or a billing guarantee. Exports exclude fixed prompts, raw provider payloads, credentials, authorization headers, private reasoning, and unbounded diagnostics.

World Setup also configures server-owned provider-attempt and conservative
credit-admission limits. The per-attempt credit reservation bounds admission
exposure using exact decimal accounting; it is not an upstream provider-account
spending cap or billing guarantee.

## Opt-in real-provider smoke

The smoke command performs exactly one bounded real decision request and validates it. It is never part of default tests or CI:
Expand Down
6 changes: 5 additions & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ Persistent short- and long-term objectives, compact memories, plan revision, sum

## PR 6 — Persistent autonomous world

Scheduled turns, snapshots, replay, retries, idempotency, budgets, failure recovery, and operation without the World Lab browser being open.
Scheduled turns, snapshots, replay, retries, idempotency, durable budget/attempt
ledgers, failure recovery, and operation without the World Lab browser being
open. The current process-local attempt and credit-admission ceilings are an
operator safety boundary, not runtime persistence or provider-account balance
enforcement.

Player development begins only after these agent milestones demonstrate compelling behavior.
6 changes: 4 additions & 2 deletions apps/game-api/src/app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,10 @@ describe('game API simulation boundary', () => {
globalModelId: 'deterministic-script',
},
executionLimits: {
version: 'execution-limits-v1',
version: 'execution-limits-v2',
providerAttemptLimit: 1,
creditLimit: null,
reservationCreditsPerAttempt: '0.01',
},
}),
});
Expand All @@ -122,7 +124,7 @@ describe('game API simulation boundary', () => {
error: {
code: 'experiment_budget_exhausted',
message:
'The experiment does not have enough provider attempts remaining for a complete tick.',
'The experiment does not have enough provider-attempt or credit-admission capacity for a complete tick.',
},
});
expect(calls).toBe(0);
Expand Down
105 changes: 101 additions & 4 deletions apps/game-api/src/attempt-accounting.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { AttemptAccounting } from './attempt-accounting';

describe('AttemptAccounting', () => {
it('reserves atomically and finalizes known and unknown cost exactly once', () => {
const accounting = new AttemptAccounting(3);
const accounting = new AttemptAccounting(3, '0.03', '0.01');
expect(accounting.reserve(3)).toBe(true);
expect(accounting.reserve(1)).toBe(false);
const first = accounting.startReserved()!;
Expand All @@ -30,15 +30,21 @@ describe('AttemptAccounting', () => {
attemptsFinalized: 3,
attemptsInFlight: 0,
remainingAttempts: 0,
knownCostCredits: 0.3,
creditLimit: '0.03',
reservationCreditsPerAttempt: '0.01',
unstartedReservedCredits: '0',
committedCreditExposure: '0.31',
remainingAdmissionCredits: '0',
knownFinalizedCostCredits: '0.3',
reservationOverageCredits: '0.28',
attemptsWithUnknownCost: 1,
exhausted: true,
exhaustionReason: 'provider-attempt-limit',
exhaustionReason: 'credit-reservation-overrun',
});
});

it('releases unused reservations without charging them', () => {
const accounting = new AttemptAccounting(3);
const accounting = new AttemptAccounting(3, '0.03', '0.01');
expect(accounting.reserve(3)).toBe(true);
const permit = accounting.startReserved()!;
accounting.finalize(permit);
Expand All @@ -48,6 +54,8 @@ describe('AttemptAccounting', () => {
attemptsFinalized: 1,
remainingAttempts: 2,
attemptsWithUnknownCost: 1,
committedCreditExposure: '0.01',
unstartedReservedCredits: '0',
exhausted: false,
});
});
Expand All @@ -63,6 +71,95 @@ describe('AttemptAccounting', () => {
});
});

it('reports configured decimal inputs canonically', () => {
const accounting = new AttemptAccounting(2, '1.00', '0.0100');
expect(accounting.snapshot()).toMatchObject({
creditLimit: '1',
reservationCreditsPerAttempt: '0.01',
remainingAdmissionCredits: '1',
});
});

it('uses exact decimal admission, refunds known cost, and retains unknown exposure', () => {
const accounting = new AttemptAccounting(null, '0.00000003', '0.00000001');
expect(accounting.reserve(3)).toBe(true);
expect(accounting.reserve(1)).toBe(false);
const known = accounting.startReserved()!;
const unknown = accounting.startReserved()!;
accounting.finalize(known, {
provider: 'scripted-test',
model: 'deterministic-script' as never,
latencyMs: 0,
costCredits: 0.000000004,
});
accounting.finalize(unknown);
accounting.releaseReservations();
expect(accounting.snapshot()).toMatchObject({
committedCreditExposure: '0.000000014',
knownFinalizedCostCredits: '0.000000004',
attemptsWithUnknownCost: 1,
remainingAdmissionCredits: '0.000000016',
});
});

it('rejects a reservation atomically and retains in-flight coverage', () => {
const rejected = new AttemptAccounting(10, '0.02', '0.01');
expect(rejected.reserve(3)).toBe(false);
expect(rejected.reserve(1)).toBe(false);
expect(rejected.snapshot()).toMatchObject({
reservedPermits: 0,
unstartedReservedCredits: '0',
exhaustionReason: 'credit-admission-limit',
});
const accounting = new AttemptAccounting(10, '0.02', '0.01');
expect(accounting.reserve(2)).toBe(true);
expect(accounting.startReserved()).not.toBeNull();
expect(accounting.snapshot()).toMatchObject({
committedCreditExposure: '0.01',
unstartedReservedCredits: '0.01',
});
});

it('stops future admission after reported cost exceeds its reservation', () => {
const accounting = new AttemptAccounting(null, '1', '0.01');
const permit = accounting.startAdditional()!;
accounting.finalize(permit, {
provider: 'scripted-test',
model: 'deterministic-script' as never,
latencyMs: 0,
costCredits: 0.015,
});
accounting.finalize(permit);
expect(accounting.startAdditional()).toBeNull();
expect(accounting.snapshot()).toMatchObject({
committedCreditExposure: '0.015',
knownFinalizedCostCredits: '0.015',
reservationOverageCredits: '0.005',
exhaustionReason: 'credit-reservation-overrun',
attemptsFinalized: 1,
});
});

it('reports an overage without exhausting unlimited credit admission', () => {
const accounting = new AttemptAccounting(null, null, '0.01');
const permit = accounting.startAdditional()!;
accounting.finalize(permit, {
provider: 'scripted-test',
model: 'deterministic-script' as never,
latencyMs: 0,
costCredits: 0.015,
});
expect(accounting.startAdditional()).not.toBeNull();
expect(accounting.snapshot()).toMatchObject({
creditLimit: null,
committedCreditExposure: '0.025',
knownFinalizedCostCredits: '0.015',
reservationOverageCredits: '0.005',
exhausted: false,
exhaustionReason: null,
});
});

it.each([0, -1, 1.5, Number.NaN])(
'rejects invalid internal reservation count %s',
(count) => {
Expand Down
Loading
Loading