Skip to main content

Conversation

Struct Conversation 

Source
pub struct Conversation {
    pub messages: Vec<Message>,
    pub taint: Taint,
    pub rewritten: Vec<Vec<Message>>,
    pub pressure: ContextTracker,
}
Expand description

A conversation, and what has entered it.

The taint lives here, with the messages, because that is what it is a property of. Tracking it per run meant the lethal trifecta was defeated by pressing Enter: fetch a hostile page on one turn, read a secret and send on the next, and the interlock saw a clean slate both times — while the attacker’s text sat in the model’s context the whole while, still able to steer it. A turn boundary is not a security boundary.

Bundling the two makes the right thing the default rather than something each caller has to remember. Keep the history and you keep the taint; start a new conversation — a batch item, a subagent, an eval case — and you get a clean one, because you built a new Conversation to do it.

Fields§

§messages: Vec<Message>§taint: Taint

What has entered this conversation so far. Grows, never shrinks: there is no way to un-read a page.

§rewritten: Vec<Vec<Message>>

Full states of messages that an in-place rewrite replaced during the current run, oldest first — compaction, eviction, thinning. The loop snapshots the list before each rewrite pass and clears at run start; Session::record_run walks these before the final state, so turns a mid-run rewrite dropped still reach the file. Without this, a run long enough to compact itself lost its own head: the front-end records at run end, and the rewrite record carries only what survived.

On the conversation rather than the outcome for the same reason taint is: it is a fact about what the messages went through, and bundling it with them makes the right thing the default — the recording call receives the conversation and cannot skip what it carries.

§pressure: ContextTracker

What the last requests on this conversation cost, so the next one can be predicted. Here rather than on the run for the reason taint is — see ContextTracker::carry_into, which also explains when it resets.

Implementations§

Source§

impl Conversation

Source

pub fn new() -> Self

Source

pub fn user(text: impl Into<String>) -> Self

Open with one user message.

Source

pub fn resumed(messages: Vec<Message>, taint: Taint) -> Self

Resume a transcript whose taint is known — from a session file that recorded it.

Source

pub fn push(&mut self, message: Message)

Source

pub fn roll_back_failed_turn(&mut self, before: Vec<Message>)

Roll a failed run back to the messages the request found, minus the user message that triggered it — restore the snapshot, then pop, in that order. run_in mutates the list in place and does not roll back on Err, so a bare pop is wrong twice over: after a failure mid-tool-turn the tail is a tool-result message, and popping it orphans the assistant’s tool_use — every later request on the session 400s (“a tool result must exist for every tool_use id”), each failure then eating the user’s newly typed message; and after a mid-run compaction the list is shorter than the snapshot, so the pop keeps the very message it exists to drop.

Here rather than in any one front-end because four of them need it (the chat REPL, the TUI, the web surface, the voice facade), and the fourth was found missing the fix precisely because the first three each carried their own copy. Deliberately touches messages and nothing else: taint stays — a failed turn that read a hostile page still read it.

A caller that writes a transcript must also record the rolled-back state (Session::record_run with the pre-run snapshot expresses it as a rewrite), or the failure survives a resume — the file otherwise keeps the user turn memory just dropped.

The pop is conditional on the tail being the person’s own text (is_plain_user_text), not on its role — because there are two ways a turn begins, and they earn different failure outcomes. A plain submit pushes a user message, and the snapshot ends with it: popped, or the next request resends the dangling trigger. A submit that folded into a tool-round tail (the barge-in shape — see append_user_text’s callers, all of which record the fold at submit and snapshot after it) leaves the snapshot ending with the tool results carrying the folded text: popping would orphan that round’s tool_use, so the utterance survives the failed turn inside an already-valid tail and simply waits for the next attempt. Asymmetric on purpose — a popped trigger prevents a verbatim resend, a kept fold is the owner’s words already on the record inside a turn the next request may legally carry — and one rule serves both, so a caller does not carry a flag from its push site to its error arm.

There is a third shape, and its outcome is chosen, not accidental: a fold into a plain user tail (an interrupt before the first token leaves the previous prompt unanswered; the next submit merges into it, since pushing beside it is the invalid shape). On failure the snapshot’s tail is that merged message — plain user text — so the pop removes both prompts. Deliberate: they were two unanswered requests awaiting the same never-produced reply, and a resend of either without the other misquotes the person. The recorded rewrite removes them from the loadable state only; messages_ever still unions them into the corpus, so recall keeps what was said.

Two costs of that recording, known and accepted: the rewrite carries the whole conversation, so a long-lived surface riding out a flapping provider appends one full history copy per failure — the only way the format can express a rollback, and failures are rare; and the rewrite drops the taint timeline’s earlier checkpoints, so the trailing taint record covers the whole rolled-back list with the run’s cumulative taint — a clean early turn in a session that later read a hostile page and failed classifies untrusted. Over-taint, never under; the safe direction, deliberately.

Source

pub fn is_empty(&self) -> bool

Source

pub fn len(&self) -> usize

Trait Implementations§

Source§

impl Clone for Conversation

Source§

fn clone(&self) -> Conversation

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Conversation

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Conversation

Source§

fn default() -> Conversation

Returns the “default value” for a type. Read more
Source§

impl From<Vec<Message>> for Conversation

Source§

fn from(messages: Vec<Message>) -> Self

Messages with no recorded taint are treated as clean. That is right for a conversation being started and wrong for one being resumed — use Conversation::resumed there, or resuming launders the taint the same way a turn boundary used to.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more