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
73 changes: 73 additions & 0 deletions devlog/2026-08-13_v1-reported-usage/DESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# DESIGN - V1 reported usage for nudge calculations

- Task ID: `2026-08-13_v1-reported-usage`
- Home Repo: `billion-context-opencode`
- Created: 2026-08-13
- Status: Accepted

## 1. Problem Statement

V1 receives a provider token-usage snapshot on assistant messages but currently sends only a tokenizer estimate to the kernel. The snapshot must be used when it is demonstrably valid and current, while the framework-agnostic core and V2 path remain unchanged.

## 2. Goals & Non-Goals

- **Goals**:
- Extract and validate the newest OpenCode V1 assistant usage snapshot in the host adapter.
- Reject snapshots from summary/error assistants, a different current model, or before/at the latest compression block.
- Prefer the reported total over the estimate for V1; make status reuse the exact transform result through the existing turn cache.
- **Non-Goals**:
- V2 usage support, kernel changes, provider-name conversion, or a persistent usage cache.

## 3. Current Architecture

The V1 message transform converts OpenCode messages to kernel `CoreMessage[]`, estimates tokens, calls `processTurn`, stores the state/cores/result in `AcpRuntime`, and reassembles messages. `bili_status` independently estimates tokens before trying to reuse the cached turn.

## 4. Proposed Architecture

```text
V1 messages
├─ octoToCoreMessages ──► cores ──► estimateTokens (always)
└─ latestReportedUsage + current model + compression timestamps
valid snapshot? ── yes ──► reported total
│ no
└──────────► estimate
processTurn(tokenCount)
cache state + cores + modelLimit + tokenCount + turn
status exact-input cache hit? ──► reuse turn/count
else estimate
```

The host helper returns provenance (`assistant id/time/model`, five components, total, or a fallback reason). `AcpRuntime` stores only the final turn inputs/result in its existing per-session cache. Cache validity remains reference-based for state/cores and adds the resolved model limit; no usage snapshot is retained separately.

## 5. Design Decisions & Rationale

| Decision | Options Considered | Chosen | Why |
|---|---|---|---|
| Reported vs estimate | max of both; always estimate; reported with fallback | reported with fallback | Matches PI's real-value-first principle and avoids inflating a valid smaller provider total. |
| Host boundary | Put OpenCode types in `@bili/core`; host helper | host helper | Keeps the core framework-agnostic and V2-independent. |
| Latest assistant | array position; timestamp and id | timestamp, then id | Compaction can reorder the message array. |
| Stale compression | persistent fingerprint/usage map; block timestamp check | block timestamp check | Uses existing state, has no new lifecycle or persistence burden, and restores automatically with a newer assistant. |
| Status consistency | independent usage cache; recompute estimate; existing turn cache | existing turn cache with model limit + final count | Avoids a second cache and invalidates naturally on state/cores/model changes. |

## 6. Impact Analysis

- **Backward compatibility**: Compression state JSON is unchanged. V1/V2 dual-shape export and tool call/result pairing are untouched. Cache entries are in-memory only and old entries simply miss after code reload.
- **Performance**: One linear assistant scan and five-number validation per V1 transform; status avoids a duplicate kernel turn when inputs match.
- **Security**: Untrusted host metadata is treated as invalid unless strictly finite/non-negative and model-matching.
- **Dependencies**: No new packages.

## 7. Migration Plan

1. Deploy the V1 adapter change; existing state files remain readable.
2. If the provider omits/changes usage fields, the helper falls back to the existing estimate.
3. V2 remains estimate-based until a stable usage interface is verified.

## 8. Open Questions

- OpenCode's provider snapshot can lag behind newly appended user/tool content; this accepted V1 limitation is documented in the PR body.
61 changes: 61 additions & 0 deletions devlog/2026-08-13_v1-reported-usage/REQ.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# REQ - V1 reported usage for nudge calculations

- Task ID: `2026-08-13_v1-reported-usage`
- Home Repo: `billion-context-opencode`
- Created: 2026-08-13
- Status: Done
- Priority: P1
- Owner: 5258MF
- References: PR E (`feat(v1): prefer reported usage for nudge calculations`)

## 1. Background & Problem Statement

- **Context**: OpenCode V1 assistant messages expose the provider's latest token usage snapshot. The adapter currently drives the kernel only with a text/tokenizer estimate.
- **Current behavior (symptom)**: V1 nudge and growth calculations ignore a valid provider usage snapshot, while V2 has no verified stable usage contract.
- **Expected behavior**: V1 uses the newest valid, model-matching provider usage snapshot directly and falls back to the existing estimate when the snapshot is absent, invalid, stale, or from before compression. V2 remains estimate-based.
- **Impact**: V1 nudge timing and `bili_status` growth should follow the provider Meter when a trustworthy snapshot is available without changing the kernel or persisted state schema.

## 2. Reproduction (if applicable)

- **Environment**:
- Node: 22/24
- OS/Arch: Windows development host; CI Linux
- **Minimal reproduction steps**:
1. Transform V1 messages containing an assistant `tokens` snapshot.
2. Compare the token count sent to `processTurn` with the provider five-field total.
- **Relevant configuration**: Existing adapter options and kernel configuration; no new option.

## 3. Constraints & Non-Goals

- **Constraints**:
- Keep `@bili/core` host-agnostic; OpenCode message types stay in the host adapter.
- Preserve the V1/V2 dual-shape export, call/result pairing, persisted compression state, LRU behavior, and status output format.
- A provider snapshot is the latest assistant request snapshot, not an exact next-request context total; trailing messages remain an accepted V1 limitation.
- No independent usage cache or persistent usage state.
- **Non-Goals**:
- Do not change V2 usage behavior.
- Do not modify `acp-kernel` or implement provider-name conversion.
- Do not claim that `CONTEXT BREAKDOWN` is provider Meter data; it remains text-estimate based.

## 4. Acceptance Criteria (must be testable)

- **Correctness**:
- [ ] Sum input/output/reasoning/cache.read/cache.write only when every component is finite, non-negative, and the total is greater than zero.
- [ ] Select the newest assistant by `time.created`, then `id`; reject summary/error/invalid/model-mismatched snapshots without falling back to an older assistant.
- [ ] Reject a snapshot when any compression block was created at or after its assistant timestamp.
- [ ] V1 uses `validReportedUsage ?? estimatedTokens`; V2 continues using the estimate.
- [ ] `bili_status` reuses a transform's exact cached token count only when state, cores, and model limit still match.
- **Performance / Stability**:
- [ ] No extra persistent state, usage Map, or change to the existing LRU/session lifecycle.
- **Regression**:
- [ ] New/modified test cases added and passing (`npm run test`).

## 5. Proposed Approach

- **Affected modules & entry files**:
- `packages/billion-context-opencode/src/messages-v1.ts` and new V1 usage helper: host snapshot types/extraction/freshness.
- `packages/billion-context-opencode/src/index.ts`: V1-only selection and debug provenance; V2 estimate path unchanged.
- `packages/core/src/runtime.ts`, `status-tool.ts`: extend the existing turn cache with model limit and expose the cached final token count.
- `packages/billion-context-opencode/tests/`: usage, freshness, pipeline/cache coverage.
- **Risks**: OpenCode may omit usage/model fields or reorder compacted messages; all such cases must safely fall back to estimation.
- **Rollback strategy**: Revert the feature commit; no persisted schema migration is required.
72 changes: 72 additions & 0 deletions devlog/2026-08-13_v1-reported-usage/WORKLOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# WORKLOG - V1 reported usage for nudge calculations

- Task ID: `2026-08-13_v1-reported-usage`
- Home Repo: `billion-context-opencode`
- Status: Done
- Updated: 2026-08-13

## 1. Summary

- **What was done**: Added strict V1 provider-usage extraction/freshness selection, wired reported-or-estimated token counts into the V1 transform, and made `bili_status` reuse the matching transform turn cache.
- **Why**: Prefer a trustworthy OpenCode provider snapshot for V1 nudge calculations while retaining safe estimation fallback and leaving V2 unchanged.
- **Behavior / compatibility changes**: Yes — V1 tokenCount source can now be provider-reported; persisted compression state and status output shape are unchanged.
- **Risk level**: Medium

## 2. Change Log

### Commits

| Commit | Description |
|--------|-------------|
| pending | Implementation and verification commit |

### Key Files

- `packages/billion-context-opencode/src/usage-v1.ts` — strict latest-assistant usage extraction, model matching, compression freshness, and V1 selection.
- `packages/billion-context-opencode/src/messages-v1.ts` — internal V1 assistant usage/model/summary/error and tool metadata types.
- `packages/billion-context-opencode/src/index.ts` — V1 reported-or-estimated pipeline and cache calls; V2 remains estimate-based.
- `packages/core/src/runtime.ts` — existing turn cache now records resolved config/model limit and exposes final tokenCount for exact-input status reuse.
- `packages/core/src/status-tool.ts` — status cache lookup before text-estimate fallback.
- `packages/billion-context-opencode/tests/usage-v1.test.ts` — usage validation, ordering, mismatch, freshness, and source preference coverage.
- `packages/billion-context-opencode/tests/runtime-cache.test.ts` — config/state/cores cache matching and lifecycle invalidation coverage.

## 3. Design & Implementation Notes

- **Entry point / key function**: `runPipelineV1` calls `selectV1TokenCount`; `AcpRuntime.getCachedTurnForInputs` serves status.
- **Key configuration items**: No new options; V1 uses `validReportedUsage ?? estimatedTokens`, V2 always uses `estimatedTokens`.
- **Key logic explanation**: The newest assistant is selected by `(time.created, id)` independent of array order. Its five usage components must all be finite and non-negative with a positive total. Summary/error/invalid/model-mismatched snapshots and snapshots not newer than all compression blocks fall back without reusing older usage.

## 4. Testing & Verification

### Build & Test Commands

```sh
npm run typecheck
npm test
npm run build
"$GIT_BASH" scripts/ci/check-pr.sh 2026-08-13_v1-reported-usage upstream/master
git diff --check
```

### Test Coverage

- New/modified test files: `tests/usage-v1.test.ts`, `tests/runtime-cache.test.ts`.
- Test count: 38 total, 38 pass, 0 fail.
- Key scenarios verified: strict five-field sum, zero components, invalid values, summary/error, timestamp/id ordering, no stale fallback, model mismatch, compression invalidation/restoration, reported value on either side of estimate, cache config/state/cores matching, dropSession and LRU eviction.

### Results

- **PASS**: `npm run typecheck`, `npm test`, `npm run build`, `git diff --check`.
- **PASS**: PR validation after adding this required WORKLOG.
- **Review**: Two independent agents reviewed the implementation. One found and prompted the invalid-timestamp stale-usage fix; the follow-up test and fix now pass all checks. The second found no blocking contract issue.

## 5. Risk Assessment & Rollback

- **Risk points**: Provider snapshots lag trailing user/tool content; missing or changed host fields safely fall back to estimation. Cache entries are reference/config guarded.
- **Rollback method**: Revert the implementation commit; no persisted migration is needed.
- **Compatibility notes**: No persisted `CompressionState` schema change; V1/V2 dual-shape export and tool call/result pairing remain untouched.

## 6. Follow-ups

- [ ] Obtain a stable V2 provider-usage interface or fixture before adding V2 usage support.
- [ ] Re-run combined tests if PR A/B/C changes later create conflicts.
22 changes: 18 additions & 4 deletions packages/billion-context-opencode/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
deriveSessionId,
type OctoMessage,
} from "./messages-v1.js"
import { selectV1TokenCount } from "./usage-v1.js"
import {
v2ToCoreMessages,
reassemble as reassembleV2,
Expand Down Expand Up @@ -87,9 +88,22 @@ async function runPipelineV1(
const state: CompressionState = await runtime.stateFor(sessionID)

const coveredIds = collectCoveredMessageIds(state)
const tokenCount = estimateTokens(cores, coveredIds)
const resolved = runtime.configFor(runtime.getModelLimit(sessionID))
debug("transform-in", { sid: sessionID, msgs: msgs.length, cores: cores.length, tokens: tokenCount, limit: resolved.modelContextLimit, blocks: state.blocks.length })
const estimatedTokens = estimateTokens(cores, coveredIds)
const usage = selectV1TokenCount(msgs, state, estimatedTokens)
const { tokenCount } = usage
debug("transform-in", {
sid: sessionID,
msgs: msgs.length,
cores: cores.length,
estimatedTokens,
reportedTokens: usage.reported?.total,
tokenCount,
tokenSource: usage.source,
usageFallbackReason: usage.fallbackReason,
limit: resolved.modelContextLimit,
blocks: state.blocks.length,
})

const turn = runtime.core.processTurn({
messages: cores,
Expand All @@ -100,7 +114,7 @@ async function runPipelineV1(
})

runtime.setCores(sessionID, cores)
runtime.cacheTurn(sessionID, turn.state, cores, tokenCount, turn)
runtime.cacheTurn(sessionID, turn.state, cores, tokenCount, turn, resolved)
await runtime.save(turn.state, sessionID)

const reassembled = reassembleV1(turn.messages, msgs, partIdToCoreIds, sessionID)
Expand Down Expand Up @@ -236,7 +250,7 @@ async function runPipelineV2(
})

runtime.setCores(sessionID, cores)
runtime.cacheTurn(sessionID, turn.state, cores, tokenCount, turn)
runtime.cacheTurn(sessionID, turn.state, cores, tokenCount, turn, resolved)
await runtime.save(turn.state, sessionID)

const reassembled = reassembleV2(turn.messages, msgs, conversion, sessionID)
Expand Down
32 changes: 31 additions & 1 deletion packages/billion-context-opencode/src/messages-v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,48 @@ export interface OctoPart {
input?: unknown
output?: unknown
error?: string
metadata?: {
interrupted?: boolean
output?: unknown
[key: string]: unknown
}
title?: string
[key: string]: unknown
}
[key: string]: unknown
}

export interface OctoTokenUsage {
total?: number
input?: number
output?: number
reasoning?: number
cache?: {
read?: number
write?: number
[key: string]: unknown
}
[key: string]: unknown
}

export interface OctoModelRef {
providerID: string
modelID: string
variant?: string
}

export interface OctoMessageInfo {
id: string
sessionID: string
role: "user" | "assistant"
time: { created: number; completed?: number }
agent?: string
model?: { providerID: string; modelID: string }
model?: Partial<OctoModelRef>
providerID?: string
modelID?: string
tokens?: OctoTokenUsage
summary?: boolean
error?: unknown
[key: string]: unknown
}

Expand Down
Loading
Loading