Deprecate EnvStateMessage in favor of Message.info flag#356
Open
jamesbraza wants to merge 2 commits into
Open
Conversation
There was a problem hiding this comment.
Pull request overview
This PR deprecates the EnvStateMessage subclass (which is not preserved through MessagesAdapter serialization) and replaces the env-state “marker” with a wire-safe metadata flag: Message.info["is_env_state"] = True. This allows downstream/remote consumers to reliably identify env-state snapshots after dump/validate round-trips.
Changes:
- Add an
is_env_statemarker contract viaMessage.infoand makeEnvStateMessagea deprecation shim that injects the flag. - Migrate the notebook environment to emit plain
Messageinstances with the info flag instead ofEnvStateMessage. - Add tests to ensure the info flag survives
MessagesAdapter.dump_python(...) -> validate_python(...), and that the shim warns + preserves caller-providedinfo.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/aviary/message.py |
Adds ENV_STATE_INFO_KEY and deprecates EnvStateMessage by injecting the env-state info flag on construction. |
tests/test_messages.py |
Adds unit tests for the deprecation shim and for info-flag round-tripping through MessagesAdapter. |
packages/notebook/src/aviary/envs/notebook/env.py |
Updates get_env_state_msg() to return Message with info={"is_env_state": True}. |
packages/notebook/tests/test_nb_env.py |
Updates notebook env test assertions to check the info flag instead of isinstance(..., EnvStateMessage). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
`EnvStateMessage` carries no data beyond `Message`; its subclass identity is erased by `MessagesAdapter` serialization, so receivers cannot distinguish env-state observations over the wire. Move the marker into `Message.info`, which the server already opts into shipping via `include_info=True`. The subclass keeps working as a shim: its `__init__` injects `info["is_env_state"] = True` and emits a `DeprecationWarning`. A new roundtrip test locks in that the flag survives `MessagesAdapter.dump_python` -> `validate_python`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`NBEnvironment.get_env_state_msg` now returns a plain `Message` with
`info={"is_env_state": True}`, the wire-safe replacement for the
subclass tag. The notebook test's `isinstance(..., EnvStateMessage)`
check is replaced with the info-dict check, which exercises the same
contract a remote consumer would use.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
c4ace12 to
d6efab8
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
EnvStateMessage(Message)carries no fields beyond its parent — the subclass is purely a Python type tag.TaskDatasetServerserializes observations throughMessagesAdapter(union ofMessage | ToolRequestMessage | ToolResponseMessage, noEnvStateMessage), so the subclass identity is erased on the wire. A remote consumer that receives the observation list gets back plainMessageinstances and cannot tell which one is the env-state snapshot, which makes server-side state-replacement (vs. accumulation) impossible to implement downstream.This PR moves the marker from class identity into data:
Message.info["is_env_state"] = Trueidentifies an env-state observation. The server already shipsinfoviaMessagesAdapter.dump_python(obs, context={"include_info": True}), so the flag rides through dump →validate_pythonwithout any schema change.EnvStateMessagebecomes a back-compat shim: its__init__injects the flag intoinfoand emits aDeprecationWarning. Existing external callers keep working; their observations are now wire-safe.NBEnvironment.get_env_state_msg(aviary's only internal caller) is migrated off the subclass toMessage.create_message(..., info={"is_env_state": True}). The notebook test'sisinstancecheck is replaced with the info-dict check — same contract a remote consumer would use.tests/test_messages.py::test_env_state_info_flag_roundtrips_through_messages_adapterlocks in that the flag survivesMessagesAdapter.dump_python→validate_python. A second test asserts the shim warns and that caller-suppliedinfokeys are preserved alongside the injected flag.Design notes
"is_env_state"(unnamespaced). Considered"aviary.env_state_observation"; the unnamespaced key was chosen for terseness — collisions with other futureMessage.infokeys are acceptable to manage by code review.warnings.warn(DeprecationWarning, stacklevel=2)only, notyping_extensions.deprecateddecorator. Python's default filter dedupes by call site, so the warning fires once per caller location.Message.is_env_statehelper: the dict key is the contract; consumers checkmsg.info and msg.info.get("is_env_state")directly.core.pyre-export retained:aviary.core.EnvStateMessagestill imports, so external code can migrate at its own pace within the deprecation window.Literalmessage_typefield + PydanticField(discriminator=...)) would preserve class identity end-to-end, but requires every wire-travelingMessagesubclass to gain a literal field and bumps the JSON schema. The info-flag is non-invasive and round-trips through the existing serialization machinery.Backwards compatibility
infodict.Follow-on (outside this repo)
Downstream consumers that maintain a chat history of observations should filter all entries with
info.get("is_env_state") is Trueexcept the most recent before re-tokenizing the prompt, replacing the current state-accumulation behavior. That work lives in the consumer's observation-to-dict bridge and history-update step, not here.🤖 Generated with Claude Code