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
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@
.research-usage-tail { border-inline-start: 2px solid var(--pw-caution); padding-inline-start: 16px; margin-block: 20px; }
.research-usage-tail strong { font-weight: 500; color: var(--pw-text); }
.research-request-coverage { border-block-start: 1px solid var(--pw-line); padding-block-start: 16px; margin-block: 20px; }
.research-input-share-value { display: flex; align-items: baseline; flex-wrap: wrap; gap: 12px; }
.research-input-share-value strong { font-size: var(--text-title); font-variant-numeric: tabular-nums; }
.research-input-share-value > span { color: var(--pw-muted); }
.research-request-values { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 24px; margin-block: 12px; }
.research-request-values > div { min-width: 0; }
.research-request-values dt { font-size: var(--text-body); color: var(--pw-muted); }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,38 @@ export function verdictLabel(metric: TrajectoryMetric, verdict: AssessmentVerdic
return verdict === "yes" ? (zh ? "有依据" : "Supported") : (zh ? "未建立" : "Not established");
}

function LoopXInputShare({usage, zh}: {usage: TrajectorySolverUsage | undefined; zh: boolean}) {
const attribution = usage?.loopx_input;
const percent = (n: number) => new Intl.NumberFormat(zh ? "zh-CN" : "en", {style: "percent", maximumFractionDigits: 1}).format(n);
const measured = attribution && attribution.status !== "unavailable" ? attribution : null;
const estimated = attribution?.method === "tiktoken_o200k_base_input_estimate_v1";
return <section className="research-request-coverage" aria-label={zh ? "LoopX 结构开销" : "LoopX input overhead"}>
<div className="research-usage-heading"><h4>{estimated ? (zh ? "LoopX Turn packet · 估计" : "LoopX Turn packet · estimate") : (zh ? "LoopX 输入占比" : "LoopX input share")}</h4><ContextHelp label={zh ? "结构输入占比口径" : "Input share basis"}>
<p>{zh ? "同一捕获窗口内,LoopX packet 进入各次模型请求的 token 总量 ÷ 全部请求输入 token 总量,包含缓存输入。重复消费按各次请求计算,同一响应的重复回执只计一次。" : "LoopX packet tokens processed across requests divided by all request input tokens in the same capture, including cached input. Reprocessed context counts in each request; duplicate receipts for one response count once."}</p>
<p>{estimated ? (zh ? "分子仅计宿主明确记录的 Turn packet 文本,使用 o200k_base 分词估计;分母是同请求的原生输入计数。尚未核验原生模型分词器,不含其他 LoopX 命令输出、封装或压缩摘要,缓存归属未知;这是已记录部分的估计,不是 provider 实测或净效率损失。" : "Only explicitly recorded Turn packet text is estimated with o200k_base; the denominator is native input for the same requests. The provider model's tokenizer is unverified. Other LoopX tool output, framing and compacted summaries are excluded; cache allocation is unknown. This estimates the recorded component, not provider-measured attribution or net efficiency loss.") : (zh ? "需要宿主提供逐请求 token 归因;包字节、字符数和命令次数不能替代。缺失归因保持未知;上下文压缩也不自动全部归为 LoopX。这个比例衡量结构开销,不能单独证明净效率损失或因果影响。" : "Requires host-supplied per-request token attribution, not packet bytes, characters or command counts. Missing attribution stays unknown; compaction is not automatically all attributed to LoopX. This measures input overhead, not net efficiency loss or causal impact.")}</p>
</ContextHelp></div>
<p className="research-input-share-value"><strong data-loopx-input-share>{measured?.input_fraction == null ? "—" : percent(measured.input_fraction)}</strong>
{measured ? <span>{measured.attributed_requests} / {measured.total_requests} {estimated ? (zh ? "次请求已匹配" : "requests matched") : (zh ? "次请求已归因" : "requests attributed")}</span> : null}
</p>
{!attribution ? <p>{zh ? "未采集 packet 的 token 归因" : "Packet token attribution was not captured"}</p>
: attribution.status === "unavailable" ? <p className="research-progress-warning" role="status">{estimated ? (zh ? "输入估计暂不可用,占比未知。" : "Input estimate unavailable; share is unknown.") : (zh ? "归因记录未通过核对,占比未知。" : "Attribution records could not be reconciled; share is unknown.")}</p>
: attribution.status === "partial" ? <p>{estimated ? (zh ? "部分请求已匹配 · 全窗口占比未知" : "Some requests matched · whole-capture share unknown") : (zh ? "部分归因 · 全窗口占比未知" : "Partial attribution · whole-capture share unknown")}</p>
: attribution.total_input_tokens === 0 ? <p>{zh ? "捕获输入为零,比例不适用。" : "Captured input is zero; the ratio is undefined."}</p> : null}
{attribution ? <details className="research-usage-detail"><summary>{estimated ? (zh ? "估计明细" : "Estimate details") : (zh ? "归因明细" : "Attribution details")}</summary>
{measured ? <dl className="research-usage-basis">
<dt>{estimated ? (zh ? "已记录 packet 文本估计" : "Recorded packet text estimate") : (zh ? "已归因 LoopX 输入" : "Attributed LoopX input")}</dt><dd>{measured.loopx_input_tokens.toLocaleString()} tokens</dd>
<dt>{zh ? "全部请求输入" : "All request input"}</dt><dd>{measured.total_input_tokens.toLocaleString()} tokens</dd>
<dt>{zh ? "已覆盖请求输入" : "Input in attributed requests"}</dt><dd>{measured.attributed_input_tokens.toLocaleString()} tokens</dd>
<dt>{zh ? "LoopX 输入中的缓存" : "Cached subset of LoopX input"}</dt><dd>{measured.loopx_cached_input_tokens?.toLocaleString() ?? "—"} tokens</dd>
<dt>{zh ? "占未缓存输入" : "Share of uncached input"}</dt><dd>{measured.uncached_input_fraction == null ? "—" : percent(measured.uncached_input_fraction)}</dd>
{measured.status === "partial" && measured.input_fraction_bounds ? <><dt>{zh ? "缺失归因的可能范围" : "Possible range with missing attribution"}</dt><dd>{measured.input_fraction_bounds.map(percent).join(" – ")}<p>{zh ? "假定未归因请求全非 LoopX / 全为 LoopX;不是置信区间。" : "Unattributed requests treated as none / all LoopX; not a confidence interval."}</p></dd></> : null}
</dl> : <p>{attribution.status === "unavailable" ? attribution.reason : null}</p>}
{measured?.estimate_basis ? <p>{measured.estimate_basis.tokenizer} {measured.estimate_basis.version} · {measured.estimate_basis.encoding}<br/>{measured.estimate_basis.models.join(", ")}</p> : null}
<p><code>{attribution.method}</code></p>
</details> : null}
</section>;
}

// The assessment owner supplies localized questions. Both readers use that
// copy without inventing short titles or translating the original evidence.
export const episodeQuestion = (result: TrajectoryAssessment, id: string, episode?: TrajectoryEpisode) =>
Expand Down Expand Up @@ -194,6 +226,7 @@ export function ResearchProgress({pack: p, packs, selectedId, zh, onSelect, onCi
<p>{incomplete.usage ? <>{zh ? "其中末轮" : "Of which, unfinished Turn"} · {zh ? "输入" : "input"} <strong>{number(incomplete.usage.input_tokens)}</strong> · {zh ? "输出" : "output"} <strong>{number(incomplete.usage.output_tokens)}</strong> tokens</> : (zh ? "末轮尚无计数观测,不记零" : "No counter observation for the unfinished Turn; not zero")}</p>
<p>{zh ? "仅含已持久化用量,不能据此判定运行已结束。" : "Persisted usage only; this does not establish termination."}</p>
</div> : null}
<LoopXInputShare usage={usage} zh={zh}/>
{usage ? <div className="research-request-coverage">
<div className="research-usage-heading"><h4>{zh ? "请求覆盖" : "Request coverage"}</h4><ContextHelp label={zh ? "请求计数口径" : "Request counter basis"}>
<p>{zh ? "逐响应去重后,核对轮次与会话累计量、上下文压缩记录及会话计数。以下是独立核对口径,与上方会话计数不相加,也不改写 Goal 账本。" : "Deduplicated responses are reconciled against Turn and session totals, compaction records and session counters. This separate basis is not added to the session counters above and does not rewrite the Goal ledger."}</p>
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
119 changes: 119 additions & 0 deletions docs/reference/trajectory-assessment.md
Original file line number Diff line number Diff line change
Expand Up @@ -686,3 +686,122 @@ existing TypeScript owner; replacement history and response text are excluded.
Registration retains the source hash, and changed or missing sources mark the
saved capture historical. Re-registering a source with this additional
projection can create a new pack identity; old packs are never rewritten.

### LoopX packet share of model input

Research → Sources and usage also exposes **LoopX input share**. For the same
reconciled requests and capture window, the metric is:

`sum(LoopX packet input tokens) / sum(all request input tokens)`

Both include cached input. Reprocessed packet context counts in each request;
duplicate receipts and compaction mirrors count once. A compaction request is
not automatically all LoopX input. The separate uncached share requires an
explicit cache attribution, rather than charging every LoopX token as uncached.
This is a measured resource share, not a percentage loss of task effectiveness,
avoidable time, monetary cost or LoopX's causal impact. Compare native outcomes
and complete costs separately; a lower packet share alone is not an improvement.

The existing frozen usage JSONL import accepts an optional **host-produced
attribution receipt**, bound to a native `token_usage_record` in the same
capture. For example, a synthetic receipt for a request with 100 input tokens:

```json
{"type":"loopx_input_token_attribution","payload":{
"method":"provider_input_token_attribution_v1",
"session_id":"synthetic-session","thread_id":"synthetic-session",
"turn_id":"synthetic-turn","root_turn_id":"synthetic-turn",
"response_id":"synthetic-response",
"input_tokens":100,"loopx_input_tokens":20,"loopx_cached_input_tokens":12
}}
```

The host is responsible for classifying the actual delivered packet spans and
counting them on the **same provider token basis** as that request's input.
Count each span once even when several LoopX components describe it. References
or packets emitted but not consumed are not processed input. Do not classify
arbitrary prose or commands as LoopX usage by substring matching, divide byte or
character counts into estimated tokens, or append invented receipts to original
client logs. A recorder may export an immutable annotated capture; preserve the
original log separately. The import validates identities and arithmetic, not
the truth of a host's attribution or its billing completeness. Raw prompts,
packet text and account metadata never cross the counter transport.

This reader does **not** add instrumentation to Codex or reconstruct attribution
from historical counters. Existing Codex captures without these receipts retain
their exact saved projection and show **uncaptured**, not zero. Hosts capable of
producing attribution use the existing `--usage-jsonl` registration path; no
model call, second accounting store or new configuration is introduced.

With provider receipts, the TypeScript usage owner adds
`solver_usage.projection.loopx_input`. Every receipt must bind the original session, response,
Turn/root identity and input total. Counts must fit the native input/cache
subsets; conflicts withhold only attribution, leaving valid session and request
counters intact. Complete coverage shows the ratio. Partial coverage retains
the attributed request/input denominators and withholds the whole-window ratio;
its optional bounds allocate missing request input as none/all LoopX, **not a
confidence interval**. Zero input has an undefined ratio. Missing cache
attribution keeps the uncached ratio unknown. Source changes retain historical
values with the existing warning and never borrow another pack's measurement.

中文:同一请求窗口内,LoopX packet 的实际输入 token 总量除以全部输入 token 总量,
含缓存;重复处理按请求计量,重复回执不重复记账。需要宿主真实归因,现有历史计数
不能反推 packet token;未采集、部分覆盖与真实零分开。比例是结构开销,不能单独
当作净效率损失、可避免成本或因果贡献。普通阅读不调用模型。

### Recorded Turn packet estimate

The existing native `codex-cli` Turn host can record its actual injected packet
when the invocation already has `CODEX_ROLLOUT_TRACE_ROOT` enabled. This is a
local, opt-in Codex diagnostic feature; LoopX does not enable it globally or
change a running experiment. The Agent prepares a private, invocation-scoped
trace directory through the existing host environment. Native tracing contains
sensitive prompts and outputs; keep it and `loopx-input-sources/*.json` private.
The host records the exact injection only beside its newly observed session
bundle. It never edits the original client rollout or changes task results,
session authority, permissions, account selection or model execution.

An Agent can prepare the optional computation provider with
`uv sync --extra input-token-estimates` in a source checkout, or install the
`input-token-estimates` package extra in its existing qualified runtime. This
uses [tiktoken 0.12.0](https://pypi.org/project/tiktoken/0.12.0/) and the explicitly named `o200k_base` encoding. Its first
encoding load can download the public encoding asset; packet text stays local.
It does **not** establish the tokenizer of an unknown provider model.

Register a frozen host source with the existing usage capture using
`--loopx-input-json <relative-source-json>` alongside `--usage-jsonl` and
`--usage-session-id`. The Agent prepares those artifacts within the existing
source root; the owner need not discover ids or copy a command. Registration
reads the native trace and tokenizes locally, without running a model. Ordinary
Research reads use the saved projection and make no tokenizer or model call.

The same TS request owner binds the recorded source digest to one unambiguous
native input item, then follows that item and content across full requests.
Each native response, Turn, model and all six counters must match the existing
usage capture. Repeated consumption counts per request; duplicate responses
do not increase totals, and copies under another item id are not classified as
extra LoopX input. Changed source files become historical. Missing dependencies,
invalid references and conflicting identities withhold only this observation,
preserving valid native totals. Compressed websocket requests need verified
replay; compaction and uncovered requests cannot supply invented attribution.

Research labels this **LoopX Turn packet · estimate** with method
`tiktoken_o200k_base_input_estimate_v1`. Its numerator covers only the explicitly
recorded Turn packet text; it excludes other LoopX tool output, model framing
and compacted summaries. It is not the total LoopX footprint. The denominator
is native input, including cache, for the same captured requests. The native
model, encoding and tokenizer version remain available in details. Unknown
cache allocation stays unknown. Partial request coverage withholds the whole
capture ratio; estimates provide no mathematical or confidence bounds. A token
estimate larger than its native input is withheld, never clamped. Existing
provider-measured receipts take precedence; the two methods are not mixed.

This producer supplies a recorded-component estimate, not a scientific
calibration of model tokenization, a retrospective reconstruction, or proof of
net efficiency loss. Existing uninstrumented experiments remain unknown; do not
launch or retune a frozen solver just to populate this field.

中文:Agent 可在既有获准调用的私有原生 trace 中记录真实注入的 Turn packet,
并在登记同一会话用量时以 o200k_base 做明确标注的文本估计。只统计已记录的
Turn packet,不冒充全部 LoopX 输入或 provider 实测;原生模型分词器、缓存归属、
压缩回放和缺失范围保持各自边界。原始记录不改,读取不调用模型,不重启既有实验。
Loading
Loading