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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ grouped by area, with links to the docs for the details.
- **Sessions.** A session that recorded weight edits gets `stats.weightEdits` (edits, distinct tensors, weights changed) and the `weights:edited` tag, and is listed even when it holds nothing else. `coding.sh show`, the viewer and MCP `session_get` print one line per edit, and `--where 'weights.changed>1000'` filters by size. Stored sessions are folded again. [Docs](https://coding.sh/docs/api)
- **OpenTelemetry.** A span or log record named `weights.edit` with `coding_sh.weights.*` attributes is read as a weight edit, and the `otlp` sink sends one `weights.edit` span per edit (so does `[serve.forward]`, for edits posted to `/v1/ingest`). The `invoke_agent` span carries `coding_sh.stats.weight_edits` and `coding_sh.stats.weights_changed`. Metadata mode keeps only the counts and norms. [Docs](https://coding.sh/docs/api)
- **Evals.** `coding.sh exp run --weights <SHA256>` records which checkpoint a run's task ran (the SHA-256 of the weights file, which you compute). `exp compare`, `exp show`, `exp list`, the viewer and the MCP tools show the stamp, and a comparison warns when both sides ran the same weights or only one recorded them. [Compare an edited model with its base](https://github.com/jackulau/coding.sh/blob/main/docs/guides/evals.md#compare-an-edited-model-with-its-base)
- **SDK.** `coding_sh.weights_checkpoint()` and `weightsCheckpoint()` record the file a script saved its edited weights to: one `notification` event with `payload.kind: "weights.checkpoint"`, the file's SHA-256 (the value `exp run --weights` takes) and optionally the base file's. Both SDKs' tests check one fixture. [Docs](https://coding.sh/docs/api)
- **Sessions.** A session that recorded a weights checkpoint lists the file hashes in `stats.weightEdits.checkpoints` and gets the `weights:checkpoint` tag. `coding.sh show`, the viewer and MCP `session_get` print `checkpoint 58899f90572c ← 0496e5111097`, and `--where 'weights.checkpoint:58899f90572c*'` finds the session. Metadata capture keeps both hashes. Stored sessions are folded again. [Docs](https://coding.sh/docs/api)
- **OpenTelemetry.** A span or log record named `weights.checkpoint` with `coding_sh.weights.sha256` is read as a checkpoint. The `otlp` sink and `[serve.forward]` send one `weights.checkpoint` span per event. Metadata mode keeps the two file hashes and drops the note. [Docs](https://coding.sh/docs/api)
- **Evals.** `exp show` and `exp compare` name the session whose checkpoint recorded an experiment's `--weights` hash, with its edits. `--json` and the MCP tools carry the ids as `weights.sessions`. `coding.sh show <session>` and MCP `session_get` list the experiments that ran its checkpoints. [Docs](https://coding.sh/docs/experiments#weights)
- **Viewer.** A weights stamp on the experiment and compare pages links the session that recorded that file as a checkpoint, and the session's header links each experiment that ran it. `/api/compare`, `/api/experiments/{id}` and `/api/sessions/{id}` carry the same ids as the CLI's `--json`. [Docs](https://coding.sh/docs/experiments#weights)
- **Evals.** `coding.sh exp compare A B --given <grader>` compares only the cases where that grader passes in both runs, and prints how many it kept and dropped. With a `terminated` grader this separates "finished more answers" from "got more of the finished answers right". [Compare an edited model with its base](https://github.com/jackulau/coding.sh/blob/main/docs/guides/evals.md#compare-an-edited-model-with-its-base)
- **MCP.** In 2026-07-28 requests, list, read and discover results carry `ttlMs` and `cacheScope`: the tool, prompt and template lists are cached for an hour, `server/discover` for five minutes, and sessions are not cached. [MCP server](https://coding.sh/docs/mcp#protocol)

Expand Down
61 changes: 61 additions & 0 deletions apps/site/src/pages/docs/experiments.astro
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,41 @@ coding.sh exp plateau --series triage-loop`;

const metricsCmd = `coding.sh exp metrics --arm base='node bench.mjs --dir a' --arm cand='node bench.mjs --dir b'`;

// The weights outputs are real too: a six-question toy task whose base model is cut off on every
// other answer, with the edits recorded by the Python SDK, under a throwaway HOME and
// CODING_SH_HOME, 2026-11-12.
const weightsPy = `import coding_sh

with coding_sh.trace("stop-looping edit"):
for name, base, edited in patches:
coding_sh.weights_edit(name, base_sha256=sha256_tensor(base), edited_sha256=sha256_tensor(edited),
**coding_sh.weights_delta(base, edited))
save(model, "edited.safetensors")
coding_sh.weights_checkpoint(sha256_file("edited.safetensors"),
base_sha256=sha256_file("base.safetensors"))
coding_sh.flush()`;

const weightsCmd = `coding.sh exp run -d math --task base.json -g terminated.json -g correct.json --name base \\
--weights 6a78961da782b2795fa5bb6f0968f79c75799fbf1919aba8798c965001caf221
coding.sh exp run -d math --task edited.json -g terminated.json -g correct.json --name edited \\
--weights 6e4c46150bc5c8ef6f1223e3880c5415795233187be390daf290a1ee6cd72e5b
coding.sh exp compare base edited`;

const weightsOut = `A base exp_01M793B934C7G6QEAP1DFBA810 pass 50.0% weights 6a78961da782
B edited exp_01M793B998T36PYVGCE527YKMT pass 100.0% weights 6e4c46150bc5
edits 74a74605-6002-4ae3-b53c-8aa065ba6ede 2 edits, 2 tensors, 3 weights changed
…`;

const weightsShowCmd = `coding.sh sessions --where 'weights.checkpoint:6e4c46150bc5*'
coding.sh show 74a74605-6002-4ae3-b53c-8aa065ba6ede`;

const weightsShowOut = ` Weight edits 2 edits, 2 tensors, 3 weights changed, checkpoint 6e4c46150bc5
Experiments 6e4c46150bc5 ran in edited (exp_01M793B998T36PYVGCE527YKMT)

+0ms weights layers.0.weight [2×2] · 1 of 4 weights · L2 0.5 · max 0.5 · 2927a02adf33 → ecfbe5382ed5
+0ms weights layers.1.weight [1×3] · 2 of 3 weights · L2 1.414 · max 1 · d0a1c13caf23 → dca6036fedd3
+0ms weights checkpoint 6e4c46150bc5 ← 6a78961da782`;

const replayCmd = `coding.sh exp replay latest#-1 --vary model=claude-haiku-4-5 --dry-run
coding.sh exp replay latest#-1 --vary model=claude-sonnet-4-5,claude-haiku-4-5 --n 5`;
---
Expand Down Expand Up @@ -309,6 +344,32 @@ coding.sh exp replay latest#-1 --vary model=claude-sonnet-4-5,claude-haiku-4-5 -
</table>
</div>

<h2 id="weights">Compare an edited model with its base</h2>
<p>
When the change is an edit to a model's weights, record it from the script that makes it. <code>weights_edit()</code> records each
tensor the script changes, and <code>weights_checkpoint()</code> the file it saves, by SHA-256. You compute the hashes; coding.sh
hashes nothing.
</p>
<Code title="edit.py" code={weightsPy} />
<p>
<code>patches</code>, <code>sha256_tensor</code>, <code>sha256_file</code> and <code>save</code> stand for your own code. The
TypeScript SDK has <code>weightsEdit()</code> and <code>weightsCheckpoint()</code>, and an OpenTelemetry span named
<code>weights.checkpoint</code> records the same event. Then give each run the hash of the weights file its task ran:
</p>
<Code code={weightsCmd} />
<Code title="output" code={weightsOut} />
<p>
The <code>edits</code> line names the session that saved B's file, with what it changed. The comparison warns when both sides ran the
same weights, or only one recorded them. The session names the experiments that ran its file:
</p>
<Code code={weightsShowCmd} />
<Code title="output" code={weightsShowOut} />
<p>
The MCP tools carry the same session ids and experiments. The viewer links the session from the weights stamp on the experiment and
compare pages, and links each experiment from the session's header. See
<a href="/docs/concepts#weights-checkpoint">Weights checkpoint</a> for where each field appears.
</p>

<h2 id="exp-replay">Replay one model call</h2>
<p>
<code>exp replay</code> sends a call that <a href="/docs/how-to/capture-with-proxy">the proxy</a> logged again, with the model, the system
Expand Down
3 changes: 3 additions & 0 deletions docs/ADAPTERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,9 @@ Apps in production don't use an adapter. They send data over the network to `cod
- A span or a log record named `weights.edit` with `coding_sh.weights.*` attributes is stored as
the weight edit event the SDKs send (`notification`, `payload.kind: "weights.edit"`); the
attributes are listed in [reference/api.md](reference/api.md).
- One named `weights.checkpoint` with `coding_sh.weights.sha256` is stored as the checkpoint event
(`payload.kind: "weights.checkpoint"`). Without a hash of 64 hex characters it is not read as a
checkpoint.
- Hooks, transcript backfill and `coding.sh run` stay local. Serve does not take spool lines.
- The ingest key picks the project, and session ids are stored as `<project>/<sessionId>`.
- SDK sessions that never send `session.end` have no `endedAt` yet; see the open item in
Expand Down
5 changes: 3 additions & 2 deletions docs/PRODUCTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,11 +265,12 @@ max_buffer_spans = 20000
```

- OTLP posted to `/v1/traces` keeps its traceId, spanId and parent ids. Native `/v1/ingest` batches
are sent as `invoke_agent`, `execute_tool`, `chat` and `weights.edit` spans, the same mapping as the `otlp` sink.
are sent as `invoke_agent`, `execute_tool`, `chat`, `weights.edit` and `weights.checkpoint` spans, the same mapping as the `otlp` sink.
Every resource gets `coding_sh.project` set to the ingest key's project.
- `metadata` mode keeps an allowlist: gen_ai operation, provider, model, response id and finish
reasons, tool name, call id and type, agent and conversation ids, token usage, `service.*`,
`telemetry.*`, `coding_sh.*` (except the session title, the repo root, and a weight edit's tensor name, shape, hashes and note), and numbers and booleans.
`telemetry.*`, `coding_sh.*` (except the session title, the repo root, a weight edit's tensor name, shape, hashes and note, and a weights checkpoint's note), and numbers and booleans.
A `weights.checkpoint` span keeps `coding_sh.weights.sha256` and `coding_sh.weights.base_sha256`: they join the session to an experiment.
Messages, prompts, tool arguments and results, `input.value`, `output.value`, span event
attributes other than `exception.type`, and status messages are dropped. `full` mode sends every attribute after `capture.redact`.
- Spans are sent in batches of up to 1,000, about once a second. A network error, 408, 429 or 5xx
Expand Down
21 changes: 21 additions & 0 deletions docs/concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,27 @@ session that recorded one has the `weights:edited` tag and counts its edits in `
or any OpenTelemetry SDK as a span named `weights.edit`. The fields are in the
[API reference](https://coding.sh/docs/api).

### Weights checkpoint

The file a script saved its edited weights to, recorded as one event: the SHA-256 of the file, and
of the file the edits started from. An edit's hashes cover one tensor, and
`coding.sh exp run --weights` records the hash of a whole file. The checkpoint is what links the
two. A session that recorded one has the `weights:checkpoint` tag and lists the hashes in
`stats.weightEdits.checkpoints`.

- In the viewer: a `Saved weights` row on the session page, a `ran in <name>` link in its header
for each experiment that ran the file, and a link to the session next to an experiment's
weights stamp.
- In the CLI: `coding.sh sessions --where 'weights.checkpoint:58899f90572c*'` finds the session
that saved a file, and `coding.sh show <id>` prints `checkpoint 58899f90572c ← 0496e5111097`
and the experiments that ran that file.
- In evals: `coding.sh exp show` and `exp compare` name the session under the experiment's
`weights` line, with its edits. See
[Compare an edited model with its base](https://coding.sh/docs/experiments#weights).
- Recorded by the Python SDK's `coding_sh.weights_checkpoint()`, the TypeScript SDK's
`weightsCheckpoint()`, or any OpenTelemetry SDK as a span named `weights.checkpoint`. You compute
the hash; coding.sh hashes nothing.

### Cost

What a session's model calls cost, from their token counts and each model's price. A `~` marks an
Expand Down
53 changes: 52 additions & 1 deletion docs/guides/evals.md
Original file line number Diff line number Diff line change
Expand Up @@ -788,9 +788,40 @@ count such as tokens used has to become a pass or fail before it is a grader.
{"kind": "json-path", "name": "correct", "path": "$.answer"}
```

Record what the editing script did, so the comparison can name it. The script records each tensor
it changes with `weights_edit()`, and the file it saves with `weights_checkpoint()`: the SHA-256
of the saved file, and of the file it started from. Put them in one `trace()`, so they are one
session. `CODING_SH_URL` points the SDK at your `coding.sh serve`
([SDK reference](https://coding.sh/docs/sdk)).

```python
import hashlib
import coding_sh

def sha256_file(path):
with open(path, "rb") as f:
return hashlib.sha256(f.read()).hexdigest()

with coding_sh.trace("stop-looping edit"):
for name, base, edited in patches:
coding_sh.weights_edit(name, base_sha256=sha256_tensor(base), edited_sha256=sha256_tensor(edited),
**coding_sh.weights_delta(base, edited))
save(model, "edited.safetensors")
coding_sh.weights_checkpoint(sha256_file("edited.safetensors"),
base_sha256=sha256_file("base.safetensors"))
coding_sh.flush()
```

`patches`, `sha256_tensor` and `save` stand for your own code: the tensors you changed with their
values before and after, a hash of one tensor, and the call that writes the file.
`weights_delta()` reads small arrays in pure Python; for real tensors pass `shape`, `changed`, `l2`
and `max_abs` from your own framework. The TypeScript SDK has the same two calls, `weightsEdit()`
and `weightsCheckpoint({ sha256, baseSha256 })`.

Run each model, then compare. `--weights` records which checkpoint each task ran: the SHA-256 of
its weights file, 64 hex characters. You compute it, for example with
`shasum -a 256 model.safetensors`; coding.sh stores the value and hashes nothing.
`shasum -a 256 model.safetensors`; coding.sh stores the value and hashes nothing. Give the edited
run the hash the script recorded as its checkpoint.

```sh
coding.sh exp run -d math --task base.json -g terminated.json -g correct.json --name base \
Expand All @@ -811,6 +842,26 @@ the same stamp. `exp list` adds a `WEIGHTS` column when a listed run has one, an
rows carry `weights.sha256`. `exp run` without `--weights` stores no stamp, and a comparison of two such runs
prints nothing about weights.

When a session recorded a side's stamp as a checkpoint, `exp compare` and `exp show` name it on the
next line, with the edits that session recorded:

```
A base exp_… pass 66.7% weights 0496e5111097
B edited exp_… pass 100.0% weights 58899f90572c
edits <session id> 2 edits, 2 tensors, 66,560 weights changed
```

- `coding.sh show <session id>` prints each edit, and an `Experiments` row with the runs that used
the file.
- `--json` carries the session ids as `weights.sessions`: on `comparison.a` and `comparison.b`
for `exp compare`, on each row of `exp list`, and in a top-level `weights` object
(`sha256`, `sessions`) for `exp show`. The MCP tools `experiments_list` and `experiment_compare`
carry the same field.
- At most 5 sessions are named for a hash, newest first.
- The viewer links the session from the stamp on the experiment and compare pages, and links
each experiment from the session's header as `ran in <name>`.
- A stamp no session recorded prints as before, and so does a store that cannot answer the lookup.

The first compare prints a paired delta for `terminated` and one for `correct`, each with its
bootstrap interval. If the edited model only stopped looping, `terminated` is up and `correct`
is up with it. `--given terminated` keeps the cases where `terminated` passes in both runs, and
Expand Down
Loading
Loading