Canonical internal model-context messages for goal state and continuation.
Goal context is represented as a HumanMessage so it participates in the
provider's normal turn ordering. Its lc_source marks it as framework-owned
model context rather than conversational user input; transcript, title, and
derived-conversation projections must therefore hide it.
Source for model-only local-context refresh messages.
Prefix for synthetic human messages (e.g. interrupt cancellation notices).
Such messages are written to the messages channel for the agent's benefit on
resume but are not user-authored, so they are filtered out of both the rendered
transcript and a thread's initial prompt. Shared here so the single producer
(textual_adapter) and its consumers (app, sessions) agree on one literal.
Every recognized GoalStatus value, derived so it cannot drift.
Lifecycle status of a TUI-owned goal.
active and blocked are unfinished working states, paused preserves the goal
without driving work, and complete is terminal. A blocked goal is still
considered actionable by the goal-state notice, whereas a paused goal is
unfinished but not actionable.
Declared in this leaf module, not beside the state schema, so both normalizers
can share one vocabulary: resume_state.coerce_goal_status and
goal_state_notice.project_goal_state. The notice path deliberately avoids
resume_state's heavy deepagents import, and this module depends only on
typing, so it is reachable from either side at no startup cost.
Source for framework-owned goal context shown only to the model.
Despite the HumanMessage transport role, messages with this source are never
user transcript content. Keep this source in the internal-message filters when
adding a new transcript or history projection.
Source for the stand-in that replaces an oversized notice in a request.
Deliberately not GOAL_STATE_MESSAGE_SOURCE: is_goal_state_message matches on
that source, so reusing it would let a stand-in win
latest_goal_state_message_index over the notice it was created to yield to.
Canonical goal-message schema version.
Bump this whenever notice content changes in a way that makes an already
checkpointed notice misleading rather than merely stale. goal_state_notice_info
rejects any other version, so a resumed thread's outdated notice stops counting
as authoritative and the next model boundary appends a current one. Version 2
dropped the get_goal/get_rubric references version 1 notices carried, and
version 3 stopped truncating the only model-visible objective and rubric text.
Version 4 rejects oversized new state and supersedes any legacy oversized notice
with bounded recovery guidance. Version 5 counts HTML-escaped embedded text in
that budget, so version 4 notices with escape-heavy text are superseded.
Where a notice's active criteria came from.
Closed rather than str, because this value is hashed into the state
fingerprint: a typo would silently change notice identity and force a fresh
notice every turn, which no test of rendered text would catch.
Validate every user-controlled section of a goal-state notice.
Return ordinary text from a local or serialized message.
Return message metadata from a local or serialized message.
Return a message's lc_source value when present.
Return whether a local or serialized message has the human role.
Return whether a message is a goal-state notice or continuation.
Return whether a message claims to be a goal-state notice.
Return whether the latest human turn carries an unsaved goal fallback.
Return whether a message should be omitted from derived transcripts.
Return whether a message is hidden from user-facing session history.
Build a one-time goal continuation.
Return a valid absolute cutoff index from a summarization event.
This is the canonical explanation of the cutoff rule; the notice predicates
and the /offload accounting point here rather than restating it.
Summarization is non-destructive: it leaves state["messages"] intact and
applies the cutoff only when building a request. Any predicate that scans the
full persisted list must therefore discount messages below this index, or it
treats a notice the model cannot see as authoritative. Every caller that has a
message count in hand should pass it.
A cutoff past message_count is rejected rather than clamped. The SDK
reads that state as "everything was summarized"; here it means the message
list shrank after the summary was written, so the survivors are live turns
and trusting the stale index would discount them as invisible. Rejecting
forces a fresh notice instead. _effective_conversation in app.py makes
the same call for the same reason.
Return the absolute cutoff index of a _summarization_event.
The degrading variant of validated_summarization_cutoff, which documents the
rule and why an out-of-bounds cutoff is rejected rather than clamped. Use this
where 0 — "discount nothing" — is the safe reading of an unusable event, and
log the discard where the collapse changes an outcome.
Record that a restored summarization event was discarded.
Dropping the event also drops its summary_message, so the next request
re-sends the whole untrimmed history. That is a large, silent token and
latency cost whose only symptom is a slow, expensive turn, and the causes
worth chasing — a checkpoint written by another schema, a partial write, a
cutoff recorded against a different message list — all look identical from the
outside. Log it so a repeat is diagnosable.
Shared with the client rather than kept in the middleware: the client is the side that reads possibly-malformed remote snapshot dicts, so it is the more likely place to meet one, and a discard that is loud on one side and silent on the other is worse than either.
Project authoritative channels into deterministic notice state.
Serialize authoritative notice state with canonical JSON formatting.
Return a stable digest for authoritative goal/rubric state.
Return whether state contains a goal or an active rubric.
Select the user-controlled text a notice built from projected embeds.
The objective and status note are withheld unless the goal is actionable, while criteria are embedded whenever a rubric is active — a one-shot rubric stays applicable over a paused goal.
Every caller that validates notice size must project identically to the
renderer, or a size check passes against text the notice does not contain (or
vice versa). One caller deliberately does not: app._resume_goal validates
the state as it will be after the resume, because projecting a still-paused
goal would suppress the objective and note it is about to embed.
Return why state cannot render as a safe notice, or None when it can.
Collapses the project-then-validate sequence its callers each performed
separately. Their correctness depended on all of them projecting exactly as
the renderer does — the fragility notice_text_sections warns about — so one
implementation is the point rather than the brevity.
Build one canonical append-only goal/rubric state notice.
Return validated canonical notice metadata from a message.
Return the newest valid notice and its raw-history index.
Return the newest goal-state source index, including invalid messages.
Return whether embedded goal-state text violates current size limits.
Build a bounded same-index stand-in for an oversized prior notice.
An oversized notice must stop being model-visible, but it cannot be removed
from a model request. The summarizer picks its cutoff from request.messages
and persists that cutoff as an absolute index into state["messages"], which
this middleware never filters. Any removal makes the two lists disagree by the
number of dropped entries, so the persisted cutoff slices the checkpointed
list too early: live turns vanish, and a ToolMessage can outlive the
AIMessage that called it (which the provider rejects).
Replacing in place keeps the length, every later index, and the human/AI/tool
shape identical to the checkpointed list, so the cutoff the summarizer chooses
is valid in both. The stand-in keeps the original id so an add_messages
reducer would overwrite rather than append if one ever saw it.
Goal/rubric text exceeds a model-visible context budget.
Canonical goal/rubric fields used for notices and fingerprints.
The three user-controlled text sections a goal-state notice can embed.
Named rather than a bare tuple[str | None, str | None, str | None]: all five
call sites unpack positionally and immediately re-pass the parts as keyword
arguments to validate_goal_notice_text, where swapping two of them
type-checks cleanly and would validate the wrong text against the wrong
budget. Tuple unpacking still works, so the field names cost nothing.
Metadata extracted from a canonical goal-state notice.