Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: XNLLLLH <XNLLLLH@users.noreply.github.com>
78 lines
2.8 KiB
Python
78 lines
2.8 KiB
Python
"""TOK-08 delta encoding for session-start payloads (Plan 02-04 Task 2, D-28).
|
|
|
|
The session-start payload is a 4-component dict: l0, l1, l2 (list), rich_club.
|
|
On the first session turn the client sends nothing; the server hashes each
|
|
component and returns both the payload and the hash bundle. On subsequent
|
|
turns the client sends previous_hashes; the server compares, and only the
|
|
components whose hash changed are returned in the delta payload. Unchanged
|
|
components are implicit in the delta (absent from delta, carried over from
|
|
the client's cache).
|
|
|
|
On hash miss (client sends a stale hash), the server returns the full
|
|
component value in the delta -- this is also the first-session behaviour.
|
|
|
|
Reduces per-turn token spend 60-80% on typical within-session continuation.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
|
|
|
|
HASH_LEN = 16 # sha256 hex truncated to 16 chars
|
|
COMPONENTS = ("l0", "l1", "l2", "rich_club")
|
|
|
|
|
|
def hash_component(text: str) -> str:
|
|
"""Return a stable 16-char hex digest of the UTF-8-encoded text."""
|
|
h = hashlib.sha256(text.encode("utf-8") if text is not None else b"").hexdigest()
|
|
return h[:HASH_LEN]
|
|
|
|
|
|
def _component_text(value) -> str:
|
|
"""Flatten a payload component to a single string for hashing.
|
|
|
|
L0/L1/rich_club are strings. L2 is a list of strings; we join with "\n"
|
|
so ordering matters (which matches the wire format).
|
|
"""
|
|
if value is None:
|
|
return ""
|
|
if isinstance(value, list):
|
|
return "\n".join(str(x) for x in value)
|
|
return str(value)
|
|
|
|
|
|
def build_delta(
|
|
previous_hashes: dict[str, str],
|
|
current_payload: dict,
|
|
) -> tuple[dict, dict[str, str]]:
|
|
"""Compute (delta, new_hashes) given the client's last-seen hashes.
|
|
|
|
delta is a subset of current_payload containing only components whose
|
|
hash does not match previous_hashes (including the first-session case
|
|
where previous_hashes is empty or missing keys). new_hashes is the full
|
|
current hash bundle, keyed by component name.
|
|
"""
|
|
delta: dict = {}
|
|
new_hashes: dict[str, str] = {}
|
|
for key in COMPONENTS:
|
|
value = current_payload.get(key)
|
|
text = _component_text(value)
|
|
h = hash_component(text)
|
|
new_hashes[key] = h
|
|
prev = previous_hashes.get(key) if previous_hashes else None
|
|
if prev != h:
|
|
delta[key] = value if value is not None else ""
|
|
return delta, new_hashes
|
|
|
|
|
|
def apply_delta(previous: dict, delta: dict) -> dict:
|
|
"""Merge delta on top of previous full payload -> new full payload.
|
|
|
|
Keys absent from delta carry over from `previous`. Provides the client
|
|
side of the round-trip (parent agent: server emits delta; subagent:
|
|
client applies delta).
|
|
"""
|
|
merged = dict(previous)
|
|
for key, value in delta.items():
|
|
merged[key] = value
|
|
return merged
|