feat: add opt-in feedback barrier #127
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "land/task/santi-tui-shell"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
feat: add opt-in feedback barrier
feat: bind feedback to caller-owned effect
clock: constrain attention tools
tui: render the seat as a three-region shell
Wrap the interactive seat in a full-screen shell of three regions: the
strand stream, a one-line input area, and a statusline carrying the
addressed soul and strand, the context reading, and whether a send is in
flight. The previous blocking line loop owned standard output linearly
and was only alive while it was the operator's turn, so typing during a
turn, scrollback past the entry cap, and ambient status were all absent
at once. Three independently drawn regions resolve them together without
reaching for a new endpoint.
The shell runs only when both standard streams are terminals; otherwise
the original line transcript runs unchanged, so non-interactive use stays
scriptable and the existing session tests continue to drive it directly.
The watched send path is reused whole. Its receipt, gap, duplicate
suppression, ordering and do-not-resend behaviour is unchanged because it
is the same code, writing into a sink that feeds the stream region rather
than standard output.
The entry resume line is removed. It was 121 columns and wrapped
mid-token on any narrower terminal, damaging the one affordance the seat
offered for returning to a relationship. The soul and strand now head the
stream on their own lines, so identity stays unambiguous and nothing
wraps. Folding uses measured display width, and mouse capture stays off
so terminal-native selection still works.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
openai: absorb a stream that failed before it spoke
The router occasionally ends a response stream with an error event within
the first seconds, before any content has been produced. Sampling the
same prompt shape six times reproduced it once, at four seconds with an
empty reply, while the successful attempts ran twenty-four to thirty-six
seconds and produced a thousand characters or more. The failure is not a
timeout, a context length, or a reasoning effort; it arrives early and
its message is not stable, having been observed both as a supplied detail
and as an empty one that fell back to a generic phrase. Error text is
therefore not a usable decision input.
Retry inside the adaptor while the round has said nothing. The gate holds
response-started and in-progress events until the first content event
releases them, so a discarded attempt reaches the runtime as nothing at
all: no duplicate start, no failed turn, no incident. Once any content
has been emitted the window closes and the original failure propagates
unchanged, because speech that has already left the layer cannot be
unsaid.
This is safe against the durable context: the driver never sends
previous_response_id, assembling each round from the strand instead, and
the response id is written to strand state only on the success path, so
an abandoned attempt leaves nothing behind.
The retry is traced rather than recorded as an incident. An absorbed
failure is expected and handled, while an incident means santi met
something it could not handle; conflating them would inflate occurrence
counts that operators rely on. Traces are lifted out before event
dispatch, so this carries no semantic weight. When attempts are
exhausted the turn fails exactly as it does today.
The first open keeps its present semantics: an unauthorized or malformed
request still fails immediately rather than being retried into a second
identical rejection.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
notice: band context pressure and deliver it within the turn
The compact reminder could not reach the soul while it mattered. Its
observation is built inside the round loop, but the queue was drained
only after the turn settled, so a signal about round four arrived after
round sixty-four. A thirty-three minute autonomous turn spent its whole
life blind to its own context, and every compaction that did happen was
triggered by the operator reading the number aloud.
Drain the queue at the top of each round, before the request is
assembled and while no tool call is outstanding, so the notice is placed
and then immediately included in the next assembly. The delay is one
round and cannot be less: the observation is derived from a request that
has already been built.
The banding machinery existed but was never wired. Threshold and band
were constants, so the dedupe key was constant too and the reminder fired
once per strand per process lifetime. Derive both from the share of the
configured input budget, at half, three quarters and nine tenths, so each
crossing speaks once and silence means nothing has changed. Without a
configured budget the previous absolute reference is kept.
The wording stays observational. It reports state, disclaims having
acted, and carries no obligation. A reminder that instructed would train
dependence on the reminder rather than attention to the state.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
notice: tell the reminder what the ceiling is
The banded reminder fired three times inside one turn and changed
nothing, because banding had reached the trigger but never the words.
Every reminder read the same: a byte count beside a threshold it had just
crossed, with no ceiling anywhere in the message. A reader could not tell
whether crossing meant half the room was gone or almost all of it, and
the three notices were textually identical, so the third looked no graver
than the first. The strand reached ninety-seven percent with all three
delivered and read.
Carry the configured ceiling on the observation and render what a
decision needs: the budget, the share of it consumed, and the bytes still
free, alongside the floor of the band just crossed. Name the band so the
notices are distinguishable at a glance, and let the closing sentence
change with it, from an offer at half, to a comparison of costs at three
quarters, to a plain statement at nine tenths that nothing else will
restore room.
The field that used to be called a reference threshold is now the band
floor, which is what it became when banding was wired. Naming it after
its old fixed meaning invited reading a moving line as a constant one.
Without a configured budget the previous absolute wording is kept.
The message still reports and still disclaims having acted. Urgency is
carried by facts that changed, not by instruction.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
compact: let the caller see why a boundary was refused
Every compact boundary failure returned the same opaque internal error.
The service reports these as plain strings, the boundary classifies a
string by matching its prose, and anything unmatched becomes an internal
fault that the caller is not permitted to read. So a soul trying to
compact learned nothing from three different mistakes: a sequence that
addressed a tool record rather than a message, a fabricated message id,
and a mixture of the two parameter styles all arrived as
internal error. The only way to discover the contract was to guess, and the costof that guessing is why compaction has been expensive.
Say what was wrong and what would be right. A sequence that is not a
message now names the distinction it violated, that tool and turn records
carry sequences of their own, and points at the other parameter style.
The mixed-parameter refusal spells out both styles. Classify these as
invalid argument so they reach the caller as such.
Classify a missing resource as missing rather than internal. This reaches
past compaction: any absent resource was previously hidden behind an
opaque five hundred, which helps nobody, while a not-found carrying the
resource name is diagnosable. An internal inconsistency surfacing this
way is now slightly mislabelled, which is the better of the two errors.
The default remains that an unmatched message is internal and unreadable
by its caller, so a new refusal is invisible until someone remembers to
classify it. That default is the reason this defect existed and it is not
addressed here.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
watch: keep the seat's stream alive past the proof ceiling
The ten minute ceiling exists so a scripted watch terminates with a
verdict rather than hanging a caller who is not present. In an
interactive seat the caller is present, watching events arrive, and the
same ceiling only blinds them: a soul working autonomously for half an
hour left the operator dark after ten minutes, with no way back except to
leave and lose the stream entirely.
Apply the ceiling to the watch presentation alone. The seat keeps the
silence boundary, which is the guard that actually matters there — sixty
seconds without any event still reports that something is wrong — and
otherwise follows the turn for as long as it runs.
The scriptable contract is unchanged.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
prompt: state the context limits as fact, the ordering as preference
A soul that wakes with an empty memory has no notion of how its context
is bounded, so it would meet the first refusal with nothing to reason
from and would invent a private discipline that no other soul shares.
Say it in the prompt instead, and say it once, from the same policy the
runtime will enforce: a rendered constant cannot drift from the limit it
describes, and a stated law that disagrees with the enforced one is worse
than no statement at all.
Separate the two registers deliberately. The slot count and the summary
ceiling are facts about the world the soul acts in — exceeding either is
refused, and nothing ordinary proceeds until the timeline fits. Running
the slots from settled to recent, oldest most compressed, is a
suggestion, marked as one, and explicitly free to be replaced when a
different order serves the work better.
What is absorbed when the slots are full, and how the surviving summary
is rewritten, is left to the soul. That is the judgement worth having;
the arithmetic around it is not.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com