Skip to content

Archive the trace store weekly and pull it to the GPU box - #220

Merged
tzhouam merged 1 commit into
mainfrom
kb/trace-archive
Sep 28, 2026
Merged

tzhouam merged 1 commit into
mainfrom
kb/trace-archive

Conversation

@tzhouam

@tzhouam tzhouam commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator

Knowledge-service redesign follow-up: the design's weekly off-machine archive of the trace store (archive plus hash manifest).

  • Packing: archive.make_archive (scheduler, every 7 days) packs everything not yet archived into archive/traces-<seq>-<time>.tar.gz. A .sha256 manifest is written last to mark it complete.
  • Tracking: what was archived is recorded explicitly in archived.json (path → content hash), never by timestamps. Blobs are immutable and archived once; a record file that grew is archived again; a blob renamed into place mid-archive lands in the next archive.
  • Names: the sequence number leads, so name order is restore order even if the clock moves backwards; names are never reused or overwritten. index.db is rebuilt and never archived.
  • Pull: the publisher (GPU box, over its existing SSH channel) pulls every complete archive it doesn't have and keeps it only if the hash verifies. Tampered archives are rejected and traced; transport failures are traced, never fatal.
  • Restore: extract archives in name order into an empty root, then run TraceStore.rebuild_index. The tests do this, including after a clock rollback.

Commit hook approved; full suite and SPEC freshness pass locally (rebased on #219).

🤖 Generated with Claude Code

Traces are kept forever, and the design requires a weekly off-machine copy
(archive plus hash manifest) so a single bot host is never their only home.

- archive.make_archive (scheduler, every 7 days) packs everything not yet
  archived into <state_dir>/archive/traces-<seq>-<time>.tar.gz. What was
  archived is tracked explicitly in archive/archived.json (path -> content
  hash), never by timestamps. Blobs are immutable and archived once; a record
  file that grew is archived again; a blob renamed into place mid-archive is
  in the next archive. Names lead with a monotonic sequence, so name order
  is creation (and restore) order even if the clock moves backwards, and
  they are never reused or overwritten. index.db is rebuilt from the records and never archived. A .sha256 manifest (the archive's hash, then
  one line per member) is written last and marks the archive complete.
- The publisher, which already reaches the bot host over SSH, pulls every
  complete archive it does not have (kb publish, each round) and keeps it in
  $KB_PUBLISHER_STATE/archive only if the hash verifies. A mismatch is traced
  and rejected; transport failures are traced, never fatal.
- Restoring means extracting the archives in name order into an empty trace
  root, then running TraceStore.rebuild_index; the test does exactly that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-28T17:08:46.867480Z a1249d5 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@tzhouam
tzhouam merged commit 2947d8e into main Sep 28, 2026
5 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a1249d5ed4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +228 to +231
dest.mkdir(parents=True, exist_ok=True)
(dest / f".{name}.tmp").write_bytes(data)
(dest / f".{name}.tmp").replace(dest / name)
(dest / f"{name}.sha256").write_text(manifest, encoding="utf-8") # last: marks it complete

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep archive persistence failures from stopping the publisher

When a remote archive exists but $KB_PUBLISHER_STATE/archive cannot be created or written—for example because that directory is read-only or a transient filesystem error occurs—these operations raise an uncaught OSError. Since Publisher.serve does not catch errors from sync_archives, the publisher exits and stops processing all future outbox actions; handle local persistence errors per archive and trace them instead.

Useful? React with 👍 / 👎.

Comment on lines +215 to +217
for name in names:
if (dest / f"{name}.sha256").exists():
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Revalidate the local archive before skipping a pull

If a previously pulled tarball is deleted or corrupted while its .sha256 sidecar remains, every subsequent round skips it solely because the sidecar exists. The GPU box then permanently lacks a valid off-machine copy even though the source archive remains available; require both files and verify the stored tarball before treating an archive as already pulled.

Useful? React with 👍 / 👎.

Comment on lines +54 to +58
data = path.read_bytes()
digest = _sha(data)
if archived.get(rel) != digest:
members.append((rel, data))
archived[rel] = digest

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Stream archive members instead of retaining the full delta

On the initial archive or after a busy week, every unarchived prompt/output blob is read into and retained by members, after which the complete compressed tar is also built in a BytesIO. Memory therefore scales to roughly the raw weekly trace volume plus the archive, so a mature trace store can exhaust memory or make every archive attempt fail; write members directly to a temporary tar file and compute hashes while streaming.

Useful? React with 👍 / 👎.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant