一键导入
hgraf-bump-proto-version
Stage a breaking proto change across client/server/frontend without splitting the fleet — deprecate, dual-write, migrate, remove.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Stage a breaking proto change across client/server/frontend without splitting the fleet — deprecate, dual-write, migrate, remove.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Understand and safely change heartbeat interval, stuck threshold, and timeout constants — client cadence, server detection math, frontend signal.
Add a new AnnotationKind — proto, storage, ingress RPC, optional downstream delivery to agents, frontend authoring UI, tests.
Add a new agent Capability — proto enum, client advertisement, server ingest, frontend gating in the control UI.
Add a new ControlKind — capability advertisement, client handler, server routing, ack semantics, frontend button, and tests — all the way through.
Add a new drift kind end-to-end. Goldfive owns the detector + enum; harmonograf reflects the kind through the intervention aggregator and the UI timeline.
Script a complex multi-turn FakeLlm scenario — function calls, tool chains, drift events, side-effect hooks, partial responses — for deterministic adk test coverage.
| name | hgraf-bump-proto-version |
| description | Stage a breaking proto change across client/server/frontend without splitting the fleet — deprecate, dual-write, migrate, remove. |
You need to make a change to proto/harmonograf/v1/*.proto that is not wire-compatible: renaming a field, changing a type, tightening an enum, removing a message. For wire-compatible additions (adding a new field, a new enum value, a new message), the recipes in hgraf-add-proto-field.md and hgraf-add-proto-message.md (batch 1 / batch 2) are sufficient — no version bump needed.
proto/harmonograf/v1/types.proto — the shared vocab (Agent, Span, Task, TaskPlan, Annotation, all enums). Most changes touch this file.proto/harmonograf/v1/telemetry.proto and proto/harmonograf/v1/control.proto — the stream envelopes and control channel. These have oneofs that breaking changes can cascade through.proto/harmonograf/v1/service.proto — the top-level RPC service.proto/harmonograf/v1/frontend.proto — the frontend-facing view projection. If your proto change affects what the frontend sees, both server and frontend stubs must regenerate.client/harmonograf_client/pb/harmonograf/v1/*_pb2.py + .pyiserver/harmonograf_server/pb/harmonograf/v1/*_pb2.py + .pyifrontend/src/pb/harmonograf/v1/*_pb.ts (buf-generated)AGENTS.md for protoc / buf generate.Client and server run as separate processes, often updated on different cadences. A client built last week talks to a server built today. A hard break means old clients stop working the instant the server rolls. The recipes below keep rollouts incremental.
The frontend is loaded from the server at request time, so server + frontend move together. Frontend/server is an atomic unit; client is the one that lags.
Never remove or rename in place. Add.
[deprecated = true] in the proto; keep both.new_name_v2 field with the new type; keep the old one filled.[deprecated = true].Ship this change. Client + server + frontend all understand both shapes. Neither cares which the other sends.
Make every producer fill both the old and new fields. Make every consumer read the new field first and fall back to the old.
Concretely:
client/harmonograf_client/convert.py or the wire-emission path — fill both.server/harmonograf_server/convert.py — when projecting to frontend, fill both.frontend/src/rpc/convert.ts — prefer new, fall back to old.Ship this change. A new-producer → old-consumer works because old-consumer reads the old field. Old-producer → new-consumer works because new-consumer falls back.
Wait at least one release cycle so that the oldest supported client has picked up Phase 2.
Consumers drop the fallback. They read only the new field. Producers still dual-write (so any unupgraded consumers keep working).
Ship this change. The old field is now only written for compatibility; nothing reads it.
Producers stop filling the old field. The proto still declares it as reserved (see "reserving numbers" below). Clean up all references in generated stubs, converters, and tests.
Ship this change. The old field is dead.
When you finally delete a proto field, replace its declaration with reserved <number>; reserved "<name>";. This prevents a future author from re-using the number for a different type — which would silently corrupt data on any client that still had a mental model of the old shape.
Example:
message Span {
reserved 4;
reserved "old_duration_ms";
// new fields here
}
After every proto edit, regenerate all three stub sets in one commit so CI doesn't drift:
# Python (both client and server)
uv run python -m scripts.gen_protos # or whatever the repo uses — grep Makefile
# TypeScript (frontend)
cd frontend && pnpm proto:gen
Verify by diffing the generated files. If only one language regenerated, a consumer will silently see the old shape.
For each phase, a test suite should enforce:
Cross-process round-trip tests live in client/tests/test_transport_mock.py and server integration tests in server/tests/. For a breaking change you want both.
Because the frontend bundle ships from the server, the server deploy bundles a specific frontend build. You can move frontend + server together in one phase. But:
frontend.proto) are only consumed by the bundled frontend, so you can break those at will — the pair moves atomically.If your change only touches frontend.proto, you can skip phases 2/3 — just regenerate stubs on both sides of the server boundary.
TelemetryUp, TelemetryDown, ControlUp, ControlDown are oneofs in telemetry.proto and control.proto. Adding a case is wire-safe. Removing a case is a break. Follow the 4-phase dance for case removal: add the replacement case, dual-emit, stop reading old, remove old.
reserve the old and pick a new one.reserved clause: a new author reuses the old number, a client in the wild with an old stub deserializes the new field into the old field's variable, and your test suite is silent because it runs against the new stubs. Always reserve.client/harmonograf_client/buffer.py holds opaque envelopes but the transport converts them. A field rename means the converter needs both branches during Phase 2. Grep transport.py for the old field name.hgraf-migrate-sqlite-schema.md. The DB column survives a proto rename — you have to do both migrations in lockstep.[deprecated = true] for safety: the deprecation annotation is a warning, not an enforcement. It prevents new code from adopting the old field — it does not stop wire traffic from flowing through it.