From 8003fdc0daa5d4e48dc3b6b4625170536eba2161 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Fri, 16 Oct 2026 12:36:37 -0400 Subject: [PATCH 1/8] Own configuration status labels and represent unobserved runtime info --- src/agent_comms/acp_extension.py | 9 ++++++++ src/agent_comms/agent_activity.py | 6 ++--- src/agent_comms/runtime_info.py | 29 ++++++++++++++++++++----- src/agent_comms/thread_configuration.py | 4 +--- src/agent_comms/thread_management.py | 2 +- 5 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/agent_comms/acp_extension.py b/src/agent_comms/acp_extension.py index a356982dd9..7d4c2e6ed8 100644 --- a/src/agent_comms/acp_extension.py +++ b/src/agent_comms/acp_extension.py @@ -467,6 +467,15 @@ class CoordinationChangedUpdate(AgentCommsUpdate): title: str context_usage: ContextUsage | None + @property + def status_label(self) -> str: + parts = [] + if self.model: + parts.append(self.model) + if self.context_usage is not None: + parts.append(self.context_usage.label) + return " · ".join(parts) + @property def source_identity(self): from .transcript_receipts import AssignedSourceIdentity diff --git a/src/agent_comms/agent_activity.py b/src/agent_comms/agent_activity.py index b5fe4d1b63..9a1ccae68d 100644 --- a/src/agent_comms/agent_activity.py +++ b/src/agent_comms/agent_activity.py @@ -24,7 +24,7 @@ from .errors import RelationViolationError from .registry_document import RegistrySnapshot from .routing import TurnRouting -from .runtime_info import AgentRuntimeInfo, RuntimeInfoStore +from .runtime_info import RuntimeInfo, RuntimeInfoStore from .store_files import _store_lock from .thread_identity import OwnerIdentity, ThreadIncarnation from .threads import Thread @@ -266,5 +266,5 @@ def observe_runtime_info(self, thread: str, observation: RuntimeInfoObservation) canonical = self.registry.require(thread).name self.runtime_info.observe(canonical, time.time(), observation) - def agent_info_of(self, thread: str) -> AgentRuntimeInfo | None: - return self.runtime_info.read().get(self.registry.require(thread).name) + def agent_info_of(self, thread: str) -> RuntimeInfo: + return self.runtime_info.for_thread(self.registry.require(thread).name) diff --git a/src/agent_comms/runtime_info.py b/src/agent_comms/runtime_info.py index 293d007760..18854eb241 100644 --- a/src/agent_comms/runtime_info.py +++ b/src/agent_comms/runtime_info.py @@ -2,6 +2,7 @@ from __future__ import annotations +from abc import ABC, abstractmethod from collections.abc import Sequence from dataclasses import dataclass, field, replace from typing import TYPE_CHECKING, ClassVar @@ -17,8 +18,27 @@ from .threads import Thread +class RuntimeInfo(ABC): + """A runtime-info read is observed or explicitly unobserved.""" + + __slots__ = () + model: str | None + + @property + @abstractmethod + def context_usage(self) -> ContextUsage | None: ... + + +class UnobservedRuntimeInfo(RuntimeInfo): + """No observation exists; do not invent a model or observation timestamp.""" + + __slots__ = () + model = None + context_usage = None + + @dataclass(frozen=True, slots=True) -class AgentRuntimeInfo: +class AgentRuntimeInfo(RuntimeInfo): """Declares mutable runtime metadata for one registered thread.""" thread: str @@ -51,10 +71,6 @@ def context_usage(self) -> ContextUsage | None: return ContextUsage(self.context_used, self.context_size) - @classmethod - def optional_context_usage(cls, info: AgentRuntimeInfo | None) -> ContextUsage | None: - return info.context_usage if info is not None else None - @dataclass(frozen=True, slots=True) class RuntimeInfoStore(ThreadOwnedState, LockedStore[dict[str, AgentRuntimeInfo]]): @@ -70,6 +86,9 @@ def record_type(self) -> type[dict[str, AgentRuntimeInfo]]: def empty(self) -> dict[str, AgentRuntimeInfo]: return {} + def for_thread(self, thread: str) -> RuntimeInfo: + return self.read().get(thread, UnobservedRuntimeInfo()) + def observe(self, thread: str, timestamp: float, observation: RuntimeInfoObservation) -> None: """Apply only this observation's fields in the original store transaction.""" def apply(values: dict[str, AgentRuntimeInfo]) -> dict[str, AgentRuntimeInfo]: diff --git a/src/agent_comms/thread_configuration.py b/src/agent_comms/thread_configuration.py index faedf22fa2..11a03da155 100644 --- a/src/agent_comms/thread_configuration.py +++ b/src/agent_comms/thread_configuration.py @@ -8,7 +8,6 @@ from .acp_extension import CoordinationChangedUpdate from .pi_vocabulary import ThinkingLevel from .registration import Registration -from .runtime_info import AgentRuntimeInfo from .thread_identity import ThreadIncarnation from .thread_management import ThreadManagement from .agent_activity import AgentActivity @@ -24,7 +23,6 @@ def __init__(self, root: Path, registry: Registration, threads: ThreadManagement def answer(self, name: str) -> CoordinationChangedUpdate: thread = self.registry.require(name) info = self.agents.agent_info_of(thread.name) - usage = AgentRuntimeInfo.optional_context_usage(info) return CoordinationChangedUpdate( ThreadIncarnation(thread.name, thread.created_at), str(self.root.resolve()), @@ -33,5 +31,5 @@ def answer(self, name: str) -> CoordinationChangedUpdate: self.threads.resolve_thread_model(thread.name), ThinkingLevel.optional_name(thread.thinking_level), thread.title or thread.name, - usage, + info.context_usage, ) diff --git a/src/agent_comms/thread_management.py b/src/agent_comms/thread_management.py index eadde21bc2..d8c3198cab 100644 --- a/src/agent_comms/thread_management.py +++ b/src/agent_comms/thread_management.py @@ -549,7 +549,7 @@ def resolve_thread_model(self, name: str, default: str | None = None) -> str | N if thread.session_file and (model := _session_model(Path(thread.session_file))): return "/".join(model) info = self.agents.agent_info_of(thread.name) - return (info.model if info else None) or default + return info.model or default def attach_session(self, original: Thread, session_file: str, *, pid: int | None = None) -> Thread: """Attach authoritative Pi runtime state to an existing thread.""" From 535377010f6be4be30e4604bab2422a09058259c Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Fri, 16 Oct 2026 12:42:41 -0400 Subject: [PATCH 2/8] Require runtime-info states to declare their model answer --- src/agent_comms/runtime_info.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/agent_comms/runtime_info.py b/src/agent_comms/runtime_info.py index 18854eb241..eb7c4a56f8 100644 --- a/src/agent_comms/runtime_info.py +++ b/src/agent_comms/runtime_info.py @@ -22,7 +22,10 @@ class RuntimeInfo(ABC): """A runtime-info read is observed or explicitly unobserved.""" __slots__ = () - model: str | None + + @property + @abstractmethod + def model(self) -> str | None: ... @property @abstractmethod From 7c5fdc27925beee95ed07bdf362d4a80be95119f Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Fri, 16 Oct 2026 12:56:58 -0400 Subject: [PATCH 3/8] Make runtime-info members own labels and configuration projection --- src/agent_comms/acp_extension.py | 10 +----- src/agent_comms/runtime_info.py | 46 +++++++++++++++++++++---- src/agent_comms/thread_configuration.py | 7 ++-- src/agent_comms/thread_management.py | 2 +- tests/test_acp_extension.py | 1 + 5 files changed, 46 insertions(+), 20 deletions(-) diff --git a/src/agent_comms/acp_extension.py b/src/agent_comms/acp_extension.py index 7d4c2e6ed8..9e87b378ec 100644 --- a/src/agent_comms/acp_extension.py +++ b/src/agent_comms/acp_extension.py @@ -466,15 +466,7 @@ class CoordinationChangedUpdate(AgentCommsUpdate): thinking_level: str | None title: str context_usage: ContextUsage | None - - @property - def status_label(self) -> str: - parts = [] - if self.model: - parts.append(self.model) - if self.context_usage is not None: - parts.append(self.context_usage.label) - return " · ".join(parts) + status_label: str @property def source_identity(self): diff --git a/src/agent_comms/runtime_info.py b/src/agent_comms/runtime_info.py index eb7c4a56f8..10511827cb 100644 --- a/src/agent_comms/runtime_info.py +++ b/src/agent_comms/runtime_info.py @@ -3,7 +3,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from collections.abc import Sequence +from collections.abc import Callable, Sequence from dataclasses import dataclass, field, replace from typing import TYPE_CHECKING, ClassVar @@ -14,7 +14,7 @@ if TYPE_CHECKING: from .agent_events import RuntimeInfoObservation - from .acp_extension import ContextUsage + from .acp_extension import ContextUsage, CoordinationChangedUpdate from .threads import Thread @@ -25,19 +25,33 @@ class RuntimeInfo(ABC): @property @abstractmethod - def model(self) -> str | None: ... + def status_label(self) -> str: ... + + @abstractmethod + def model_or(self, default: str | None) -> str | None: ... - @property @abstractmethod - def context_usage(self) -> ContextUsage | None: ... + def configuration_update( + self, create: Callable[[ContextUsage | None, str], CoordinationChangedUpdate] + ) -> CoordinationChangedUpdate: ... class UnobservedRuntimeInfo(RuntimeInfo): """No observation exists; do not invent a model or observation timestamp.""" __slots__ = () - model = None - context_usage = None + + @property + def status_label(self) -> str: + return "" + + def model_or(self, default: str | None) -> str | None: + return default + + def configuration_update( + self, create: Callable[[ContextUsage | None, str], CoordinationChangedUpdate] + ) -> CoordinationChangedUpdate: + return create(None, self.status_label) @dataclass(frozen=True, slots=True) @@ -74,6 +88,24 @@ def context_usage(self) -> ContextUsage | None: return ContextUsage(self.context_used, self.context_size) + @property + def status_label(self) -> str: + parts = [] + if self.model: + parts.append(self.model) + usage = self.context_usage + if usage is not None: + parts.append(usage.label) + return " · ".join(parts) + + def model_or(self, default: str | None) -> str | None: + return self.model or default + + def configuration_update( + self, create: Callable[[ContextUsage | None, str], CoordinationChangedUpdate] + ) -> CoordinationChangedUpdate: + return create(self.context_usage, self.status_label) + @dataclass(frozen=True, slots=True) class RuntimeInfoStore(ThreadOwnedState, LockedStore[dict[str, AgentRuntimeInfo]]): diff --git a/src/agent_comms/thread_configuration.py b/src/agent_comms/thread_configuration.py index 11a03da155..3be5646310 100644 --- a/src/agent_comms/thread_configuration.py +++ b/src/agent_comms/thread_configuration.py @@ -3,6 +3,7 @@ from __future__ import annotations import os +from functools import partial from pathlib import Path from .acp_extension import CoordinationChangedUpdate @@ -23,7 +24,8 @@ def __init__(self, root: Path, registry: Registration, threads: ThreadManagement def answer(self, name: str) -> CoordinationChangedUpdate: thread = self.registry.require(name) info = self.agents.agent_info_of(thread.name) - return CoordinationChangedUpdate( + return info.configuration_update(partial( + CoordinationChangedUpdate, ThreadIncarnation(thread.name, thread.created_at), str(self.root.resolve()), os.getpid(), @@ -31,5 +33,4 @@ def answer(self, name: str) -> CoordinationChangedUpdate: self.threads.resolve_thread_model(thread.name), ThinkingLevel.optional_name(thread.thinking_level), thread.title or thread.name, - info.context_usage, - ) + )) diff --git a/src/agent_comms/thread_management.py b/src/agent_comms/thread_management.py index d8c3198cab..6d896a183a 100644 --- a/src/agent_comms/thread_management.py +++ b/src/agent_comms/thread_management.py @@ -549,7 +549,7 @@ def resolve_thread_model(self, name: str, default: str | None = None) -> str | N if thread.session_file and (model := _session_model(Path(thread.session_file))): return "/".join(model) info = self.agents.agent_info_of(thread.name) - return info.model or default + return info.model_or(default) def attach_session(self, original: Thread, session_file: str, *, pid: int | None = None) -> Thread: """Attach authoritative Pi runtime state to an existing thread.""" diff --git a/tests/test_acp_extension.py b/tests/test_acp_extension.py index 8b53c56d9c..eb8b75c56c 100644 --- a/tests/test_acp_extension.py +++ b/tests/test_acp_extension.py @@ -74,6 +74,7 @@ def test_declared_family_roundtrip_and_strict_boundary(tmp_path): None, "Pilot", None, + "", ), GoalChangedUpdate(None, None), CompactionChangedUpdate(CompactionStart()), From 3e7aabe09558330de5a5865ed1a030d65d57c982 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Fri, 16 Oct 2026 13:49:38 -0400 Subject: [PATCH 4/8] Own context presentation in ContextReading and derive configuration status --- src/agent_comms/acp_extension.py | 20 ++++------ src/agent_comms/context_reading.py | 39 +++++++++++++++++++ src/agent_comms/runtime_info.py | 50 +++++-------------------- src/agent_comms/thread_configuration.py | 7 ++-- src/agent_comms/thread_management.py | 3 +- tests/test_acp_extension.py | 6 +-- 6 files changed, 64 insertions(+), 61 deletions(-) create mode 100644 src/agent_comms/context_reading.py diff --git a/src/agent_comms/acp_extension.py b/src/agent_comms/acp_extension.py index 9e87b378ec..1d2e8fc248 100644 --- a/src/agent_comms/acp_extension.py +++ b/src/agent_comms/acp_extension.py @@ -18,6 +18,7 @@ ) from .agent_events import CompactionEvent from .declared_family import DeclaredFamily +from .context_reading import ContextReading from .input_attempt import ACPInputIdText, InputAttempt from .input_origin import HumanInputOrigin, InputOrigin, UnattributedInputOrigin from .field_codec import FieldCodec @@ -446,27 +447,20 @@ def for_session(self, session_id: str) -> InputStartedUpdate: ) -@dataclass(frozen=True) -class ContextUsage: - used: int - size: int - - @property - def label(self) -> str: - return f"{self.used:,}/{self.size:,} tokens ({self.used / self.size * 100:.1f}%)" - - @dataclass(frozen=True) class CoordinationChangedUpdate(AgentCommsUpdate): thread: ThreadIncarnation wire_root: str owner_pid: int worktree: str - model: str | None + model: str thinking_level: str | None title: str - context_usage: ContextUsage | None - status_label: str + context_usage: ContextReading + + @property + def status_label(self) -> str: + return " · ".join((self.model, *self.context_usage.label_parts())) @property def source_identity(self): diff --git a/src/agent_comms/context_reading.py b/src/agent_comms/context_reading.py new file mode 100644 index 0000000000..d2945a8488 --- /dev/null +++ b/src/agent_comms/context_reading.py @@ -0,0 +1,39 @@ +"""Native context observations own their presentation without nullable readings.""" + +from abc import abstractmethod +from dataclasses import dataclass + +from .declared_family import DeclaredFamily + + +class ContextReading(DeclaredFamily): + @abstractmethod + def label_parts(self) -> tuple[str, ...]: ... + + @abstractmethod + def status_text(self) -> str: ... + + +@dataclass(frozen=True) +class ContextUsage(ContextReading): + used: int + size: int + + @property + def label(self) -> str: + return f"{self.used:,}/{self.size:,} tokens ({self.used / self.size * 100:.1f}%)" + + def label_parts(self) -> tuple[str, ...]: + return (self.label,) + + def status_text(self) -> str: + return self.label + + +@dataclass(frozen=True) +class UnmeasuredContext(ContextReading): + def label_parts(self) -> tuple[str, ...]: + return () + + def status_text(self) -> str: + return "Native owner has not reported context usage" diff --git a/src/agent_comms/runtime_info.py b/src/agent_comms/runtime_info.py index 10511827cb..fb6d03b024 100644 --- a/src/agent_comms/runtime_info.py +++ b/src/agent_comms/runtime_info.py @@ -3,10 +3,11 @@ from __future__ import annotations from abc import ABC, abstractmethod -from collections.abc import Callable, Sequence +from collections.abc import Sequence from dataclasses import dataclass, field, replace from typing import TYPE_CHECKING, ClassVar +from .context_reading import ContextReading, ContextUsage, UnmeasuredContext from .errors import RelationViolationError from .field_codec import FieldCodec from .locked_store import LockedStore @@ -14,7 +15,6 @@ if TYPE_CHECKING: from .agent_events import RuntimeInfoObservation - from .acp_extension import ContextUsage, CoordinationChangedUpdate from .threads import Thread @@ -23,17 +23,11 @@ class RuntimeInfo(ABC): __slots__ = () - @property - @abstractmethod - def status_label(self) -> str: ... - @abstractmethod - def model_or(self, default: str | None) -> str | None: ... + def context_reading(self) -> ContextReading: ... @abstractmethod - def configuration_update( - self, create: Callable[[ContextUsage | None, str], CoordinationChangedUpdate] - ) -> CoordinationChangedUpdate: ... + def model_or(self, default: str) -> str: ... class UnobservedRuntimeInfo(RuntimeInfo): @@ -41,18 +35,12 @@ class UnobservedRuntimeInfo(RuntimeInfo): __slots__ = () - @property - def status_label(self) -> str: - return "" + def context_reading(self) -> ContextReading: + return UnmeasuredContext() - def model_or(self, default: str | None) -> str | None: + def model_or(self, default: str) -> str: return default - def configuration_update( - self, create: Callable[[ContextUsage | None, str], CoordinationChangedUpdate] - ) -> CoordinationChangedUpdate: - return create(None, self.status_label) - @dataclass(frozen=True, slots=True) class AgentRuntimeInfo(RuntimeInfo): @@ -80,32 +68,14 @@ def context_percent(self) -> float | None: return None return self.context_used / self.context_size * 100 - @property - def context_usage(self) -> ContextUsage | None: + def context_reading(self) -> ContextReading: if self.context_used is None or not self.context_size: - return None - from .acp_extension import ContextUsage - + return UnmeasuredContext() return ContextUsage(self.context_used, self.context_size) - @property - def status_label(self) -> str: - parts = [] - if self.model: - parts.append(self.model) - usage = self.context_usage - if usage is not None: - parts.append(usage.label) - return " · ".join(parts) - - def model_or(self, default: str | None) -> str | None: + def model_or(self, default: str) -> str: return self.model or default - def configuration_update( - self, create: Callable[[ContextUsage | None, str], CoordinationChangedUpdate] - ) -> CoordinationChangedUpdate: - return create(self.context_usage, self.status_label) - @dataclass(frozen=True, slots=True) class RuntimeInfoStore(ThreadOwnedState, LockedStore[dict[str, AgentRuntimeInfo]]): diff --git a/src/agent_comms/thread_configuration.py b/src/agent_comms/thread_configuration.py index 3be5646310..f44316b7bf 100644 --- a/src/agent_comms/thread_configuration.py +++ b/src/agent_comms/thread_configuration.py @@ -3,7 +3,6 @@ from __future__ import annotations import os -from functools import partial from pathlib import Path from .acp_extension import CoordinationChangedUpdate @@ -24,8 +23,7 @@ def __init__(self, root: Path, registry: Registration, threads: ThreadManagement def answer(self, name: str) -> CoordinationChangedUpdate: thread = self.registry.require(name) info = self.agents.agent_info_of(thread.name) - return info.configuration_update(partial( - CoordinationChangedUpdate, + return CoordinationChangedUpdate( ThreadIncarnation(thread.name, thread.created_at), str(self.root.resolve()), os.getpid(), @@ -33,4 +31,5 @@ def answer(self, name: str) -> CoordinationChangedUpdate: self.threads.resolve_thread_model(thread.name), ThinkingLevel.optional_name(thread.thinking_level), thread.title or thread.name, - )) + info.context_reading(), + ) diff --git a/src/agent_comms/thread_management.py b/src/agent_comms/thread_management.py index 6d896a183a..2ec870a014 100644 --- a/src/agent_comms/thread_management.py +++ b/src/agent_comms/thread_management.py @@ -32,6 +32,7 @@ from .input_disposition import InputDispositions from .message_bus import MessageBus from .native_arguments import NativeArguments +from .native_settings import NativeThreadDefaults from .native_session_files import NativeSessionFiles from .owner_launch import RestartEnvironment from .owner_lifecycle import OwnerLifecycle @@ -541,7 +542,7 @@ def initialize_native_configuration( self.registry.register(updated, self.registry.status(thread.name)) return updated - def resolve_thread_model(self, name: str, default: str | None = None) -> str | None: + def resolve_thread_model(self, name: str, default: str = NativeThreadDefaults.model) -> str: """Prefer a saved selection, then the resumed session's last model.""" thread = self.registry.require(name) if thread.model is not None: diff --git a/tests/test_acp_extension.py b/tests/test_acp_extension.py index eb8b75c56c..e22e701bfc 100644 --- a/tests/test_acp_extension.py +++ b/tests/test_acp_extension.py @@ -33,6 +33,7 @@ encode_updates, ) from agent_comms.acp_failure import BackendDeliveryFailure +from agent_comms.context_reading import UnmeasuredContext from agent_comms.agent_events import CompactionStart from agent_comms.pi_payloads import McpLiveReceipt from agent_comms.thread_identity import AdmissionIdentity, ThreadIncarnation @@ -70,11 +71,10 @@ def test_declared_family_roundtrip_and_strict_boundary(tmp_path): "/home/ts/wt/pilot", 123, "/home/ts/wt/pilot", - None, + "openai-codex/gpt-6.1-sol", None, "Pilot", - None, - "", + UnmeasuredContext(), ), GoalChangedUpdate(None, None), CompactionChangedUpdate(CompactionStart()), From 4370140a921c58269cceb812a61d6d7132862e60 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Fri, 16 Oct 2026 14:00:26 -0400 Subject: [PATCH 5/8] Project thread context fields through the reading owner --- src/agent_comms/context_reading.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/agent_comms/context_reading.py b/src/agent_comms/context_reading.py index d2945a8488..7c34232fb1 100644 --- a/src/agent_comms/context_reading.py +++ b/src/agent_comms/context_reading.py @@ -13,15 +13,22 @@ def label_parts(self) -> tuple[str, ...]: ... @abstractmethod def status_text(self) -> str: ... + @abstractmethod + def wire_fields(self) -> dict[str, int | float | None]: ... + @dataclass(frozen=True) class ContextUsage(ContextReading): used: int size: int + @property + def context_percent(self) -> float: + return self.used / self.size * 100 + @property def label(self) -> str: - return f"{self.used:,}/{self.size:,} tokens ({self.used / self.size * 100:.1f}%)" + return f"{self.used:,}/{self.size:,} tokens ({self.context_percent:.1f}%)" def label_parts(self) -> tuple[str, ...]: return (self.label,) @@ -29,6 +36,10 @@ def label_parts(self) -> tuple[str, ...]: def status_text(self) -> str: return self.label + def wire_fields(self) -> dict[str, int | float | None]: + return {"context_used": self.used, "context_size": self.size, + "context_percent": self.context_percent} + @dataclass(frozen=True) class UnmeasuredContext(ContextReading): @@ -37,3 +48,6 @@ def label_parts(self) -> tuple[str, ...]: def status_text(self) -> str: return "Native owner has not reported context usage" + + def wire_fields(self) -> dict[str, int | float | None]: + return {"context_used": None, "context_size": None, "context_percent": None} From 8d1b3ef85317609e5c3b12f7c5ec0768bac456db Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Fri, 16 Oct 2026 14:47:29 -0400 Subject: [PATCH 6/8] Delete nullable context wire projection; retain ContextReading presentation --- src/agent_comms/context_reading.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/agent_comms/context_reading.py b/src/agent_comms/context_reading.py index 7c34232fb1..1f5e78c7da 100644 --- a/src/agent_comms/context_reading.py +++ b/src/agent_comms/context_reading.py @@ -13,9 +13,6 @@ def label_parts(self) -> tuple[str, ...]: ... @abstractmethod def status_text(self) -> str: ... - @abstractmethod - def wire_fields(self) -> dict[str, int | float | None]: ... - @dataclass(frozen=True) class ContextUsage(ContextReading): @@ -36,10 +33,6 @@ def label_parts(self) -> tuple[str, ...]: def status_text(self) -> str: return self.label - def wire_fields(self) -> dict[str, int | float | None]: - return {"context_used": self.used, "context_size": self.size, - "context_percent": self.context_percent} - @dataclass(frozen=True) class UnmeasuredContext(ContextReading): @@ -48,6 +41,3 @@ def label_parts(self) -> tuple[str, ...]: def status_text(self) -> str: return "Native owner has not reported context usage" - - def wire_fields(self) -> dict[str, int | float | None]: - return {"context_used": None, "context_size": None, "context_percent": None} From bb607e70282941735dacd48809d61d249b03c685 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Fri, 16 Oct 2026 14:52:41 -0400 Subject: [PATCH 7/8] Include the merged transcript draft member in the ACP codec census --- tests/test_acp_extension.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_acp_extension.py b/tests/test_acp_extension.py index e22e701bfc..0fd2acaec2 100644 --- a/tests/test_acp_extension.py +++ b/tests/test_acp_extension.py @@ -26,6 +26,7 @@ SelectedWriteAcceptedUpdate, TextRouteUpdate, TranscriptChangedUpdate, + TranscriptDraftUpdate, TranscriptSnapshotUpdate, TurnChangedUpdate, UnavailableCursorObservation, @@ -82,6 +83,7 @@ def test_declared_family_roundtrip_and_strict_boundary(tmp_path): TranscriptSnapshotUpdate( read.read(), read.identity ), + TranscriptDraftUpdate(read.identity, None), InputDeliveryChangedUpdate("input"), McpClientReceiptUpdate( "turn", McpLiveReceipt(1, "pi-mcp-client", "a" * 32, "running", "turn", ()) From 2fb445b94fc22ed58a365709f4a873ca53e4a303 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Fri, 16 Oct 2026 14:53:05 -0400 Subject: [PATCH 8/8] Assert strict ACP boundary rejects with its codec validation errors --- tests/test_acp_extension.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_acp_extension.py b/tests/test_acp_extension.py index 0fd2acaec2..b20e17c43e 100644 --- a/tests/test_acp_extension.py +++ b/tests/test_acp_extension.py @@ -107,7 +107,7 @@ def test_declared_family_roundtrip_and_strict_boundary(tmp_path): {"agentComms": {"updates": [{"kind": "unregistered"}]}}, {"agentComms": {"updates": [], "extra": True}}, ): - with pytest.raises(ValueError): + with pytest.raises((TypeError, ValueError)): decode_updates(invalid)