remote

package
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 30, 2026 License: Apache-2.0 Imports: 29 Imported by: 0

Documentation

Overview

Package remote is the wire between a surface on one machine and an engine on another. The surface half dials `ssh <host> codeaf engine …` and speaks this protocol over the pipes; the engine half wraps an ordinary *session.Agent and answers. Both halves import THIS file and nothing of each other.

THE CONTRACT IS THE ENVELOPE, NOT THE PAYLOADS. Payloads are the session package's own types carried as JSON — both ends compile against internal/session, so a field added there travels without a wire change. The one exception is EventWire, because error does not survive encoding/json.

Frames are JSON, one per line (a journal's own framing, for a journal's own reason: a torn write is one lost line, not a lost stream).

Index

Constants

View Source
const (
	HeldConsent  = "consent"
	HeldStanding = "standing"
	HeldHarness  = "harness"
	HeldConnect  = "connect"
)

The four kinds a HeldQuestion can carry, which are the four resolve-doors this wire has. They are spelled once here so the engine and the surface cannot disagree about a string; wire.go's HeldQuestion.Kind documents why an unknown one is skipped rather than refused.

View Source
const (
	MethodObserve   = "Observe"
	MethodUnobserve = "Unobserve"
)

Observation is connection-local. It never announces a new turn, writes the journal, or shares a consumer with Submit. Reopening therefore reads the engine's atomic transcript/backlog boundary without executing anything again.

View Source
const (
	// Agent — payloads are the method's own argument struct below; results are
	// the return values likewise.
	MethodSubmitBash      = "SubmitBash"      // SubmitArgs → StreamRef, then shell output events
	MethodSubmit          = "Submit"          // SubmitArgs → StreamRef, then "event" frames
	MethodSubmitImage     = "SubmitImage"     // SubmitImageArgs → StreamRef, then "event" frames
	MethodSubmitFiles     = "SubmitFiles"     // SubmitFilesArgs → StreamRef, then "event" frames
	MethodFollowUp        = "FollowUp"        // SubmitArgs → StreamRef, then "event" frames
	MethodQuestionReplace = "ReplaceQuestion" // QuestionArgs → StreamRef
	MethodSteer           = "Steer"           // SubmitArgs → StreamRef, then "event" frames
	// MethodTyping is a person having started writing, and it is the only frame
	// on this wire that nobody waits for ([Agent.Typing]).
	//
	// IT IS ONE OF THE TWO HALVES THAT WERE MISSING FROM THE PROBE.
	// internal/session's [session.Agent.Typing] buys a measurement of the two
	// machines the next turn is most likely to use, and the second thing it
	// buys is a WARM CONNECTION, so the real request's first token is not also
	// paying for a handshake (internal/provider's probe.go says so in its own
	// header). The surface asks for it through an optional interface, and until
	// this door existed the assertion simply failed on the default road — which
	// is every launch that is not `--no-host`. The other half was inside the
	// engine and is mended in the same change (typing.go's header): the
	// completer wrapper was swallowing the probing door, so the measurement had
	// never been bought in process either.
	//
	// IT RIDES VERSION 14 RATHER THAN MOVING THE NUMBER, under the rule stated
	// on [Version]: an engine that does not know it answers "no such method",
	// the surface drops the answer it was never waiting for, and what is lost is
	// a pre-warm nobody can see. Nothing goes dark, so nothing is refused at the
	// door.
	MethodTyping          = "Typing"                 // nothing → nothing, and nothing waits
	MethodStopWork        = "StopWork"               // nothing → nothing; stop this conversation, retaining history
	MethodInterrupt       = "Interrupt"              // InterruptArgs, or nothing → nothing
	MethodCompact         = "Compact"                // nothing → nothing (error carries the failure)
	MethodClose           = "Close"                  // nothing → nothing
	MethodModel           = "Model"                  // nothing → string
	MethodSetModel        = "SetModel"               // string → nothing
	MethodSetSpendRail    = "SetSpendRail"           // dollars → nothing
	MethodSetContext      = "SetContextWindow"       // legacy version-5 hint; current remote surfaces do not send it
	MethodReasoningFor    = "ReasoningFor"           // string → string
	MethodSetReasoningFor = "SetReasoningFor"        // ReasoningArgs → nothing
	MethodConsent         = "ResolveConsent"         // ConsentArgs → nothing
	MethodConsentRemember = "ResolveConsentRemember" // ConsentArgs → nothing
	MethodStandingResolve = "ResolveStanding"        // StandingArgs → nothing
	// MethodQuestionResolve is the ONE door for an answer to any question, over
	// the wire (docs/design/questions/DESIGN.md). It carries
	// [session.Answer] whole — the lane, the token, the keys, the words beside
	// them, the notes on parts, the exchanges, the blanks, the dial, the scope —
	// and the engine reads the lane off it and applies it through that lane's own
	// resolver ([session.Agent.ResolveQuestion]).
	//
	// IT IS ONE FRAME AND NOT ELEVEN because the object it carries already says
	// which lane it belongs to. The per-lane frames above stay exactly as they
	// are: they are what an older window on the other end of this wire sends, and
	// this one is what a window that has the whole object sends.
	MethodQuestionResolve = "ResolveQuestion" // QuestionArgs → nothing (or a refusal)
	// MethodQuestionHold is the OTHER thing a key on a question can mean: stop
	// the clock, do not answer. A question with a deadline takes the asker's own
	// pick when it runs out ([session.PolicyRecommendThenAuto]), and a person
	// reading it has to be able to stop that without deciding anything —
	// [MethodTaskHold] is the same act for the one lane that had it first, and
	// this is the door for every lane, named the way an answer is named: the lane
	// and the lane's own token.
	//
	// IT NEVER MAKES A KEY WAIT. The surface sends it and carries on; the engine
	// says the question again with its deadline gone, so every window stops
	// counting from the same frame rather than from its own guess.
	MethodQuestionHold = "Question.Hold" // QuestionHoldArgs → nothing
	// MethodQuestionWatch is the surface saying it draws questions, and it buys
	// exactly what [MethodTaskWatch] and [MethodDesignWatch] buy: "question"
	// frames from here on, including everything already open replayed the
	// moment the subscription opens ([session.Agent.WatchQuestions]). It is sent
	// once per conversation the surface takes up, never on a frame.
	//
	// IT IS THE OTHER HALF OF THE DOOR ABOVE. An answer with no way for the
	// question to arrive is a key nobody will ever press; internal/tui3 asserts
	// the lane and the answer as ONE interface for that reason, and a wire
	// carrying one of them leaves a turn stopped on a question no screen shows.
	MethodQuestionWatch = "Question.Watch" // nothing → nothing, then "question" frames
	// MethodSetAutonomy is `D`: let the engine answer every question of one SHAPE
	// from now on ([session.Agent.SetAutonomy]). The setting is kept per project
	// on the engine's side, which is why it is a call and not a local file: a
	// window attached over `--host` is setting the dial on the machine the work
	// is happening on.
	MethodSetAutonomy = "SetAutonomy" // AutonomyArgs → nothing (or a refusal)
	// MethodAutonomy IS THE OTHER HALF OF THE DOOR ABOVE, and it was missing.
	// A surface could WRITE one of these rules over the wire and never read one
	// back, so every window on the ordinary road — the surface talks to its own
	// engine process through exactly this client — asked the question and got
	// "this conversation has no project to keep question rules in", whatever
	// project it was in. `/autonomy` printed that sentence on a machine with the
	// rules sitting in `.codeaf/autonomy.json`, and the settings rows that read
	// the same door drew nothing at all.
	MethodAutonomy      = "Autonomy"          // nothing → map[AskKind]Policy
	MethodHarness       = "ResolveHarness"    // HarnessArgs → nothing
	MethodConnect       = "ResolveConnect"    // ConnectArgs → nothing
	MethodConnectKey    = "ResolveConnectKey" // ConnectArgs → nothing
	MethodNoteConnected = "NoteConnected"     // ConnectedArgs → nothing
	MethodTitle         = "Title"             // nothing → string
	MethodUsage         = "Usage"             // nothing → session.Usage
	MethodContextTokens = "ContextTokens"     // nothing → int
	MethodTranscript    = "Transcript"        // nothing → []session.DisplayEntry
	MethodEarlier       = "EarlierHistory"    // nothing → session.EarlierHistory
	MethodRewindPoints  = "RewindPoints"      // nothing → []session.RewindPoint
	MethodRewindAt      = "RewindAt"          // int → []session.DisplayEntry
	// MethodPlanSpend CARRIES THE RUN'S SPEND-BY-SEAT ACROSS THE WIRE, and it
	// is the spend page's other reading beside the machine's own ledger above.
	// A conversation that seeded a plan writes its workers' calls into the plan
	// store's spend ledger, and the page draws those rolled up by SEAT
	// ([session.PlanSpendLine]); over a connection that store lives on the
	// engine's disk, so a surface that could not ask the engine drew no block
	// at all ([session.Agent.PlanSpend]).
	//
	// IT RIDES [Version] RATHER THAN MOVING IT, under the rule stated there: an
	// engine that does not know it answers "no such method", the surface reads
	// that as the block being absent HERE — which is exactly what it drew before
	// this door existed — and the emptiness law is kept. Nothing that was drawn
	// goes dark, so nothing is refused at the door.
	MethodPlanSpend    = "PlanSpend"    // PlanSpendArgs → []session.PlanSpendLine
	MethodPlanTasks    = "PlanTasks"    // nothing → []session.PlanTaskRow
	MethodPlanTaskPage = "PlanTaskPage" // PlanTaskPageArgs → PlanTaskPageResult
	// MethodPlanTaskWork is the task room's work tab: the difference in the
	// run's working copy. It rides this version rather than moving it, for
	// [MethodPlanSpend]'s reason — an engine that does not know it answers
	// "no such method", and the tab draws the absence sentence it already
	// drew for an engine with no door.
	MethodPlanTaskWork      = "PlanTaskWork"      // PlanTaskArgs → PlanTaskWorkResult
	MethodPlanNote          = "PlanNote"          // PlanTextArgs → nothing
	MethodPlanPause         = "PlanPause"         // PlanTaskArgs → nothing
	MethodPlanResume        = "PlanResume"        // PlanTaskArgs → nothing
	MethodPlanCancel        = "PlanCancel"        // PlanTaskArgs → nothing
	MethodPlanAmend         = "PlanAmend"         // PlanTextArgs → nothing
	MethodPlanPriority      = "PlanPriority"      // PlanPriorityArgs → nothing
	MethodPlanRunSummary    = "PlanRunSummary"    // PlanRunSummaryArgs → PlanRunSummaryResult
	MethodRefreshRunSummary = "RefreshRunSummary" // RefreshRunSummaryArgs → PlanRunSummaryResult
	// The conversation's own place on the thinking ladder (internal/session's
	// effort.go). Three doors and not one, because the stored rung and the
	// resolved rung are two different answers: the dial DRAWS the resolved one
	// and a person opening it CHOSE the stored one, and a wire that carried only
	// one of them would make the surface derive the other.
	//
	// The resolved rung also rides [session.Facts] unasked, which is what a frame
	// reads; these are the keystroke's doors (effort.go).
	MethodEffort         = "Effort"         // nothing → string (the stored rung, "" for none)
	MethodResolvedEffort = "ResolvedEffort" // nothing → string (the rung the next turn asks for)
	MethodSetEffort      = "SetEffort"      // string → bool (false when the word is not a rung)

	// The skills a person puts in front of this conversation by hand, and the
	// shelf they are chosen from (internal/session's skillattach.go, and
	// skills.go here). The attachment is the SESSION'S — it is held beside the
	// conversation and read on every message it sends — so a surface on the
	// other end of a socket reaches it through these doors rather than holding
	// a copy of its own.
	MethodAttachSkills   = "AttachSkills"   // []string → []string (the set as it now stands)
	MethodDetachSkill    = "DetachSkill"    // string → bool (whether it was on)
	MethodAttachedSkills = "AttachedSkills" // nothing → []string
	MethodClearSkills    = "ClearSkills"    // nothing → int (how many were on)
	MethodSkillShelf     = "SkillShelf"     // SkillShelfArgs → []store.Fact

	// The conversation's own posture on the tool gate (internal/session's
	// approvalposture.go), the dial above one door over: the resolved posture
	// rides [session.Facts] unasked for the frame, and these are the keystroke's
	// doors (approval.go). The set answers the refusal as a sentence rather than
	// a bool because the local door answers an error and the surface prints it.
	MethodResolvedApproval = "ResolvedApproval" // nothing → string (the posture in force)
	MethodSetApproval      = "SetApproval"      // string → string ("" took, else the refusal)

	// MethodAnswerLaneOffer answers the one question the phase seam can raise:
	// the machine a person PINNED has gone quiet, there is somewhere else to
	// go, and a pin is asked rather than overridden ([provider] offer.go). The
	// surface presses `y` and this is the road that keystroke takes home.
	//
	// IT EXISTS BECAUSE THE PHASE CROSSED. Until "phase" frames did, a hosted
	// engine had nobody reading phases at all and BORROWED the other lane
	// without asking — which was right, since a question nobody can hear is a
	// wait that never ends. With the phase on the wire the question is asked
	// and drawn, so the answer needs the same road back or the surface would be
	// showing `switch to auto? (y)` over a key that does nothing.
	//
	// It takes the ANSWER and not the question: which lane the rescue goes to
	// was settled when the offer was raised, because the moment a rescue is
	// wanted is the worst possible moment to start choosing one. False means
	// there was nothing to answer, which is a real answer and not a failure.
	MethodAnswerLaneOffer = "AnswerLaneOffer" // bool → bool

	// Session doors.
	MethodSessionsRecent = "Sessions.Recent" // nothing → []session.Summary
	MethodSessionNew     = "Session.New"     // nothing → Welcome (the engine swaps to a fresh session)
	MethodSessionOpen    = "Session.Open"    // string (path) → Welcome (the engine swaps to that session)

	// Standing doors. They are in the Session group and not the Agent one
	// because they are about the ENGINE MACHINE'S STORE rather than about the
	// conversation: a local surface opens internal/standing on its own disk and
	// a remote one cannot, which is the same reason Sessions.Recent exists. The
	// items belong to the machine that runs them, so a session swap leaves them
	// exactly where they were.
	MethodStandingItems = "Standing.Items" // string (workspace) → []standing.Item
	MethodStandingSave  = "Standing.Save"  // standing.Item → nothing (the error carries a refused write)
	MethodStandingWatch = "Standing.Watch" // nothing → StandingWatchResult

	// MethodPlacesWorld is the walk of the engine machine's places root: every
	// project, every conversation in it, and the work each of those ran
	// ([session.ReadWorld]). It is the reading FIVE of the surface's seven
	// places are built from — home lists it, tasks reads the task rows inside
	// it, standing walks its projects to ask what else keeps an eye on that
	// machine, spend joins its titles onto the ledger's ids, and search opens
	// the conversation behind a hit out of it — so one door answers all five.
	MethodPlacesWorld = "Places.World" // nothing → session.World
	// MethodPing is one empty frame out and one empty frame back. The surface
	// times that round trip on its own machine; a timestamp carried between two
	// machines would mix clocks that need not agree and would not measure the
	// path the person is actually waiting on.
	MethodPing = "Ping" // nothing → nothing

	// MethodPlacesTask is ONE ROW of that record, read deeper than the walk
	// reads it: the last thing that piece of work said, out of the journal it
	// left on the engine machine's disk ([session.TaskRecord]).
	//
	// IT IS A SECOND DOOR AND NOT A FIELD ON THE WORLD, for what it costs. The
	// walk is taken on a beat and answers five places; a report is a forward scan
	// of a whole session journal and is wanted for exactly one row — the one
	// somebody just pressed. Putting it on the walk would read four hundred
	// journals to draw a list that shows none of them.
	//
	// AND IT IS NOT [MethodFetchFile]. That door answers under the two-roots law
	// — this conversation's workspace and this conversation's own folder — and a
	// task's journal is in ANOTHER conversation's folder under the state root, so
	// a fetch of it is refused, correctly. This one answers under its own root,
	// the places root, and hands back the sentence rather than the file: a
	// transcript is megabytes and the card draws one paragraph of it.
	MethodPlacesTask = "Places.Task" // PlacesTaskArgs → session.TaskRecord

	// MethodDetach is a surface LEAVING ON PURPOSE, and it is the one method
	// whose whole value is the difference between it and silence.
	//
	// Version 1 had no way to say this, so a closed window and a dead pipe were
	// the same event and the engine had to treat both as an interrupt. That was
	// the right reading of a closed window and the wrong reading of a dropped
	// connection, and the person could not tell the two apart either — they
	// closed a laptop lid and lost a running turn.
	//
	// Version 2 splits them. Detach says "this surface is going; the turn is
	// yours to finish", and the engine keeps working, keeps the events, and
	// holds any question it raises ([HeldQuestion]). A pipe that simply dies
	// means the same thing — the engine assumes the surface will be back — and
	// the deliberate END of a conversation is what [MethodClose] has always
	// been. So the three roads out finally read as three different things.
	MethodDetach = "Detach" // nothing → nothing

	// MethodFetchFile is the reverse of an attachment: the surface asking for
	// the bytes of a file the ENGINE holds, by a path on the engine's disk.
	//
	// IT IS WHAT MAKES `/export` AND A DOWNLOADED DELIVERABLE HONEST. Version 1
	// had no door for moving a byte from the engine machine to the surface's,
	// so /export assembled what the surface happened to be holding and said
	// `· on this machine`, and a file the session MADE could not be brought
	// here at all. The path is never resolved on this side — it is the engine's
	// path, the way every path on a "result" already is.
	MethodFetchFile = "Fetch.File" // FetchFileArgs → FetchedFile

	// MethodHeldQuestions is what a surface asks the moment it attaches: the
	// questions this session raised while nobody was looking. See
	// [HeldQuestion] for why they wait rather than expire.
	MethodHeldQuestions = "Held.Questions" // nothing → []HeldQuestion

	// MethodListDir is one directory of the engine's, as a listing rather than
	// as bytes: what the browse view and the surface's file picker over a
	// connection read. It answers under the SAME two-roots law as
	// [MethodFetchFile] (file.go's handOver): the workspace and the session's
	// own folder, and nothing outside them crosses.
	MethodListDir = "List.Dir" // ListDirArgs → DirListing

	// MethodStatPaths is the honesty rule of tui3's pathlink.go carried over
	// the wire: nothing on a hosted session becomes a link until the ENGINE
	// says the path exists, because a stat is a fact about the other machine.
	// It is batched — one call per burst of new rows, never one per word.
	MethodStatPaths = "Stat.Paths" // StatPathsArgs → []PathFact

	// MethodDepositFile is [MethodFetchFile] walked backwards: a file going
	// from the surface's machine to the engine's, and NOT AS A MESSAGE.
	//
	// IT EXISTS BECAUSE THE BROWSE PAGE HAS A DRAG-DROP LANE AND THE WIRE HAD
	// NOWHERE TO PUT WHAT LANDED ON IT. [MethodSubmitFiles] already writes a
	// person's files into a session's attachments, but it is a MESSAGE: the
	// engine keeps the bytes and then opens a turn on them. A file dropped on
	// a web page is not a sentence anybody said, so submitting it would start a
	// turn nobody at this end asked for, spend somebody's money on it, and
	// stream its answer into a channel that page is not reading.
	//
	// SO THIS METHOD KEEPS AND DOES NOTHING ELSE. The bytes land in the far
	// session's attachments/ folder — the same place [SubmitFilesArgs]'s files
	// land, under the same name law, and NOWHERE ELSE; an arbitrary path on the
	// engine's disk is not a thing this wire will ever write to. No turn opens,
	// no event is sent, nothing reaches the transcript: a deposit is a FACT ON
	// DISK, and the conversation learns of it only when a person mentions it.
	// The lane for a person's own message with a file on it is still /attach.
	MethodDepositFile = "Deposit.File" // WireFile → DepositedFile

	// MethodTake is a surface asking for the keyboard back, and it is the whole
	// of what a watcher can do besides watch.
	//
	// IT IS ONE ROUND TRIP AND NEVER A RECONNECT. A person who walked back to a
	// machine and pressed enter must be typing a moment later, not waiting on a
	// handshake — so taking the keyboard moves one field on the engine and fans
	// one frame out to the room. The connection underneath it never moved.
	//
	// The ENGINE decides, and it is the only thing that does: it answers this,
	// it tells every surface what changed ([Driver]), and it refuses a Submit
	// from a surface that is not the driver. A surface that decided locally that
	// it was now driving would be the second authority on a fact that can only
	// have one, and the failure would be two windows both believing they had the
	// keyboard.
	MethodTake = "Take" // nothing → nothing
)

The methods, one per door. The Agent group mirrors tui3.Agent exactly (plus the rewind pair rewind.go type-asserts for); the Session group is the doors only a remote surface needs, because a local one reads the disk directly.

View Source
const (
	// MethodTeamsDefaults is the engine profile's five `teams.` defaults.
	MethodTeamsDefaults = "Teams.Defaults" // struct{} → teamstore.Defaults
	// MethodTeamsApplyDefault writes one of those rows, the same way the
	// settings tab writes it locally (config's ApplyTeamDefault). The answer
	// is the five defaults after the write. [Welcome.TeamSettings] says an
	// engine has the door; an engine without it leaves the tab read-only.
	MethodTeamsApplyDefault = "Teams.ApplyDefault" // TeamDefaultArgs → teamstore.Defaults
	// MethodTeamsPackets is the packets waiting on a scope, or word that the
	// packet files have not moved since the stamp the window holds.
	MethodTeamsPackets = "Teams.Packets" // PacketsArgs → PacketsReading
	// MethodTeamsRaise records a new packet and answers it as written.
	MethodTeamsRaise = "Teams.Raise" // teamstore.Packet → teamstore.Packet
	// MethodTeamsDecide records a decision on a packet.
	MethodTeamsDecide = "Teams.Decide" // DecideArgs → teamstore.Packet
	// MethodTeamsEscalate sends a packet up.
	MethodTeamsEscalate = "Teams.Escalate" // EscalateArgs → teamstore.Packet
	// MethodTeamsSpend is one team's spend on a day, or word that neither the
	// teams file nor the ledger moved since the stamp the window holds.
	MethodTeamsSpend = "Teams.Spend" // SpendArgs → SpendReading
	// MethodTeamsDelete forgets a closed team and its files.
	MethodTeamsDelete = "Teams.Delete" // DeleteTeamArgs → DeleteTeamReply
)

── DELEGATION ACROSS THE WIRE ──────────────────────────────────────────────

The delegation store (internal/teams' decision.go, spend.go, lifecycle.go and the four `teams.` defaults) lives in the engine's profile for wire_teams.go's reason: the far session's team tools read and write it there. So the teams page over --host asks for it through these doors, answered from Engine.ProfileDir and the engine machine's usage ledger, and never from this laptop.

THEY ARE SHAPED FOR A CLOCK, like the teams doors. The two reads a page repeats, the open packets and a team's spend, carry the stamp the window last got and are answered `{"stamp":"…","same":true}` when nothing moved, which the engine learns from stats (a directory listing and a stat per team for the packets, two stats for the spend). The writes are single calls: a packet write is an append under the engine's lock, validated there, and needs no compare-and-swap because an append cannot undo another writer.

THEY ARE ADDITIVE, and Welcome.Delegation says an engine has them. A window facing an engine without it leaves the delegation seam doors nil and says the teams page's inbox and spend are not available over that connection; it never reads the laptop's packet files, which the far manager never sees. Closing and reopening a team are edits to the teams file and cross by MethodTeamsUpdate like any other; only deleting, which removes the team's Traffic and packet files too, needs its own door.

View Source
const (
	MethodTeamsWrapUp        = "Teams.WrapUp"        // WrapUpArgs → struct{}
	MethodTeamsAcceptClosing = "Teams.AcceptClosing" // AcceptClosingArgs → AcceptClosingReply
)

THE WRAP-UP'S TWO DOORS (Welcome.WrapUp). Traffic is the channel between the interface and the session, and over --host the window reads it (MethodTeamsTraffic) but has no door to write it: so the one line the person's `Wrap up first` writes crosses by a door of its own, which appends exactly teamstore.WrapUpRequest to the engine's log and nothing else; and accepting a closing report, which closes the team and logs it, crosses by teamstore.AcceptClosing on the engine. Neither is a general Traffic writer: a window cannot put words in a member's mouth through them.

View Source
const (
	// MethodDesignWatch is the surface saying it draws the harness lane, and it
	// buys exactly what [MethodTaskWatch] buys: "design" frames from then on.
	// It is sent once per conversation the surface takes up, never on a frame.
	MethodDesignWatch = "Design.Watch"

	// MethodTitleWatch is the surface saying it draws the far conversation's
	// name, and it buys exactly what the two above buy: "title" frames from then
	// on, including the name already minted replayed the moment the
	// subscription opens (internal/session's [Agent.WatchTitle]). That replay is
	// the whole reason this is a lane and not a fact push — a conversation names
	// itself while nobody is attached to it, and a surface that came back would
	// otherwise wait for a name that was chosen an hour ago.
	MethodTitleWatch = "Title.Watch"
	// MethodSubharnessResolve answers one intake card — whether the saved
	// program runs, and on which input. The design card's own answer has been
	// [MethodHarness] since version 1; this is the OTHER question that arrives
	// on the same subscription, and without it the card would be a page whose
	// keys pressed nothing.
	MethodSubharnessResolve = "Subharness.Resolve"
)
View Source
const (
	// MethodPlacesLedger is the spend place's reading of THE ENGINE MACHINE'S
	// usage ledger, and it is BOUNDED BY A FLOOR because the ledger is a file
	// that grows by a line per model call, forever.
	//
	// A DOOR THAT ANSWERED THE WHOLE LEDGER WOULD CARRY A YEAR OF A BUSY
	// MACHINE'S ROWS ACROSS AN SSH PIPE TO DRAW A FORTNIGHT. So the surface says
	// how far back it is looking and the engine answers only that far back — and
	// on the beat after, the surface asks from the newest instant it already
	// holds, so the second call and every call after it carries the handful of
	// lines written since. That is [session.UsageCache]'s own tail-read law with
	// a wire in the middle of it (cmd/codeaf's [hostLedger]).
	MethodPlacesLedger = "Places.Ledger" // LedgerArgs → LedgerReading

	// MethodPlacesSearch is one full-text query over every message the ENGINE
	// machine has kept ([store.Store.SearchConversations]).
	//
	// THE SURFACE NO LONGER ASKS IT, and the engine keeps answering it anyway.
	// The Search place that sent this query was removed (#1650), so a current
	// surface never calls it; an older surface attached to a newer engine still
	// has that place, and this door is what keeps its box finding anything.
	// There is no cache behind it — a query is not a beat, it is an answer
	// somebody's enter key is waiting for.
	MethodPlacesSearch  = "Places.Search"  // SearchArgs → []store.ConversationHit
	MethodPlacesArchive = "Places.Archive" // ArchiveArgs → nothing

	// ── the folders a person attaches ───────────────────────────────────────
	//
	// THE PATH IS THE ENGINE MACHINE'S AND WAS ALWAYS GOING TO BE. A folder
	// attached to a conversation is a folder the WORK can reach, and the work
	// runs where the engine runs — so these two doors carry a path and the far
	// end is the one that stats it, snaps it to its repository root and writes
	// it down. That is not a concession to the remote case: the ordinary local
	// launch goes through this same wire to this machine's own session host
	// (cmd/codeaf's chatv3_local.go), and before these methods existed the
	// surface's picker asserted a door onto the agent, found a wire client that
	// had none, and said `folder · <path>` over a conversation that had gained
	// nothing.
	//
	// THERE IS NO METHOD FOR READING THEM, and that absence is the design. The
	// set rides [session.Facts] and comes down unasked on every push
	// (replica.go), because the folder indicator is drawn on a frame and a frame
	// may not wait on a network. These two are INTENT — a person attaching, a
	// person removing — which is exactly what still goes up.
	MethodPlacesRefer = "Places.Refer" // ReferArgs → session.PlaceRef
	// MethodPlacesRemove takes one folder back off the conversation. It carries
	// the path and nothing else, and the engine refuses a path its conversation
	// is not about rather than answering a silent nothing.
	MethodPlacesRemove = "Places.Remove" // string (path) → nothing

	// MethodMemorySnapshot is everything remembered on the engine machine,
	// shelved and counted, in the two statements the place draws from.
	MethodMemorySnapshot = "Memory.Snapshot" // int (row cap) → store.MemoryShelves
	// MethodMemoryChanged is how many memories were learned and how many were
	// let go of since an instant — the two figures the memory tab's number is
	// made of. A zero instant answers zeros, which the store already promises.
	MethodMemoryChanged = "Memory.ChangedSince" // time.Time → MemoryChange
	// MethodMemoryList is the flat list behind `/memories` and behind the undo,
	// which has to re-read after a restore.
	MethodMemoryList = "Memory.List" // MemoryListArgs → []store.Memory
	// MethodMemoryUpdate is `e` on a line: the wording, fixed.
	MethodMemoryUpdate = "Memory.Update" // MemoryUpdateArgs → nothing
	// MethodMemoryForget is `f` on a line, and MethodMemoryRestore is the undo
	// that takes it back. Both carry the id and nothing else.
	MethodMemoryForget  = "Memory.Forget"  // string (id) → nothing
	MethodMemoryRestore = "Memory.Restore" // string (id) → nothing
	// MethodMemoryProvenance is where and when one memory was learned, asked for
	// the ONE id whose card is open rather than for every row on the page.
	MethodMemoryProvenance = "Memory.Provenance" // string (id) → MemoryOrigin
)

── THE THREE PLACES THAT COULD NOT CROSS ───────────────────────────────────

MethodPlacesWorld answered five of the surface's seven places out of one walk, and stopped there because the other three are not that walk: spend adds up an append-only LEDGER, search asks a full-text INDEX a question, and memory reads — and WRITES — a store of what the machine has learned. None of those is a listing of the projects root, so none of them could ride the world door, and each one drew a dim sentence in place of its rows (internal/tui3's host.go).

These are their doors. They are additive to version 4, which is deliberate and is the whole reason they are shaped this way: an engine that predates them answers `engine: no such method` (server.go's fallthrough), the surface reads that as the reading being absent HERE, and the place says the sentence it has always said instead of hanging on a call nobody will answer. Nothing about the old wire moved, so a new surface against an old engine is exactly the program that shipped, and an old surface against a new engine never asks.

ONE METHOD PER READING, which is the law MethodPlacesWorld already states: a single door answering everything would tie a page that wants the ledger on a three-second beat to a full-text search nobody typed.

View Source
const (
	// MethodDelegateList and MethodDelegateStart are the program door
	// (internal/session's delegate_door.go): the programs the ENGINE machine's
	// build carries, and handing a brief to one. They belong to the engine side
	// for the reason the task door does — the program runs on that machine and
	// the run spends that machine's money — so a hosted surface lists the far
	// build's programs and its `/<name> <brief>` starts work there.
	MethodDelegateList  = "Delegate.List"
	MethodDelegateStart = "Delegate.Start"
	MethodTaskStart     = "Task.Start"
	// MethodTaskRedoStronger runs the newest task again with every seat nobody
	// pinned one step stronger ([session.Agent.RedoStronger]).
	MethodTaskRedoStronger = "Task.RedoStronger"
	MethodPlannerStart     = "Task.StartPlanner"
	MethodTaskRoom         = "Task.Room"
	MethodTaskSteer        = "Task.Steer"
	MethodTaskStop         = "Task.Stop"
	MethodTaskRetry        = "Task.Retry"
	MethodTaskModel        = "Task.Model"
	MethodTaskEffort       = "Task.Effort"
	MethodTaskSetEffort    = "Task.SetEffort"
	// MethodTaskWatch is the surface saying it draws tasks, and it is the only
	// one of these that asks for nothing back: what it buys is the engine
	// pushing "task" frames from then on (tasklane.go). It is sent once per
	// conversation the surface takes up, never on a frame.
	MethodTaskWatch = "Task.Watch"
	// MethodTaskResolve answers one task proposal. It is the fourth thing a
	// hosted rail needs and the one nothing carried: the card drew, the keys
	// worked, and `y` went nowhere — so the whole task seam failed to assert on
	// a hosted surface and the rail was never even subscribed.
	MethodTaskResolve = "Task.Resolve"
	// MethodTaskHold removes one proposal's admission clock while keeping the
	// question open. It travels separately from Resolve because typing is not an
	// answer and a deleted draft must leave the hold in force.
	MethodTaskHold = "Task.Hold"
	// MethodTaskSettle answers a landing that came home as the person's call:
	// accept it, say it is not finished, spend one more merge round on a branch
	// that would not fasten, hand the question to the model, or take it back.
	//
	// FIVE ACTS AND ONE METHOD, because they are one question being answered and
	// the surface draws them as one row (internal/tui3's tasksettle.go). Which act
	// is asked for is a field of [TaskSettleArgs] rather than five method names,
	// so an engine that speaks this method speaks all of it — a half-answered card
	// is the shape this whole door exists to end.
	MethodTaskSettle = "Task.Settle"
	// MethodTaskPending is the proposals the far engine is still waiting on. A
	// surface asks it where the local one reads [session.Agent.PendingTasks] —
	// when a turn ends with a proposal card still on screen — because a card
	// about a question nobody is asking any more has to stop asking it.
	MethodTaskPending = "Task.Pending"
)

These are the task-command questions whose answers belong to the engine machine. The surface sends intent; sizing, shaping, admission and spending remain with the session agent that owns the conversation.

View Source
const (
	// MethodTeamsRead is the engine's teams file, or word that it has not
	// moved since the stamp the window holds.
	MethodTeamsRead = "Teams.Read" // TeamsReadArgs → TeamsReading
	// MethodTeamsUpdate writes the whole list back IF the file is still at the
	// stamp the window read it at, and answers Stale when it is not. The window
	// then reads again, makes its change again and retries (cmd/codeaf's
	// hostTeams), so a manager the far session set in between is never undone.
	MethodTeamsUpdate = "Teams.Update" // TeamsUpdateArgs → TeamsReading
	// MethodTeamsTraffic is one team's log after a cursor, at most a page: the
	// Ledger's Since pattern, so every call after the first carries only the
	// lines written since.
	MethodTeamsTraffic = "Teams.Traffic" // TeamsTrafficArgs → TeamsTraffic
)

── TEAMS ACROSS THE WIRE ───────────────────────────────────────────────────

A team's file and its Traffic logs live in the profile of the machine the SESSION runs on, because the team tools a model calls (internal/session) read and write them there. Over --host the window is on the laptop and the session is on the far machine, so a window that kept teams in its own profile would draw a list the manager never reads and a rail that is an empty log drawn as the team's. These three doors let the window read and write the ENGINE's teams, answered from Engine.ProfileDir.

THEY ARE SHAPED FOR A CLOCK OVER SSH. The window asks about a managed team about once a second while it holds one of its conversations, and never at any other time. So every question carries what the window already has (the file's stamp, the log's cursor), and an answer about nothing new is a few bytes: `{"stamp":"…","same":true}` for the file and `{"stamp":"…"}` for a log. The engine stats before it reads (teamstore.Watch), so a quiet second costs it a stat per file too.

THEY ARE ADDITIVE TO THIS VERSION, and Welcome.Teams is how a window knows they are there: an engine from before them sends no field, and the window turns teams off over that connection with the sentence it has always said rather than falling back to the laptop's file (internal/tui3's host.go).

View Source
const (
	// MethodTeamsName asks the engine's naming role for one short name for a
	// group of conversations, given their titles.
	MethodTeamsName = "Teams.Name" // TeamNameArgs → string
	// MethodTeamsPropose asks the engine's naming role which teams the
	// conversations offered could form and which existing teams more of them
	// belong in.
	MethodTeamsPropose = "Teams.Propose" // TeamProposeArgs → session.TeamProposal
)

── AND THE TWO ASKS THE WALL MAKES OF THE ENGINE'S MODEL ───────────────────

A team's suggested name and Organize's proposals are one cheap call each on the naming role (internal/session's teamname.go and teampropose.go), made by the ENGINE because the model, its key and its bill are on the engine's machine. Over --host the wall is on the laptop, so without these two doors the default road never asked: the card kept the word it opened with and Organize showed the folder pass alone, in every terminal and in no test.

THEY CARRY A BUDGET, NOT A DEADLINE, for RefreshRunSummaryArgs.Budget's reason: the engine's clock is not this one's. The wall bounds each ask (internal/tui3's teamNameWait and organizeWait), and the engine bounds the model call by the same span so it stops when the wall stops listening.

Welcome.TeamAsk says the engine answers them. An engine from before them sends no field, and this end refuses at once rather than spend a round trip on a refusal, which is exactly the failure the wall already turns into the word it holds and the folder pass alone.

View Source
const (
	// RoamWindow is how long a dropped link is redialled before the connection
	// is declared gone. It is the honest bound: five minutes covers the things
	// that actually interrupt a connection while somebody is still sitting
	// there — a wifi handover, a tunnel, a VPN reconnect, a lid closed over a
	// walk to another desk — and stops well short of pretending a laptop shut
	// for the night is still attached to anything.
	//
	// GIVING UP COSTS NOTHING BUT THE WINDOW, which is why the number can be
	// this modest: the engine journals every turn as it happens, so the
	// conversation is on the far machine's disk either way and the same command
	// opens it again. Roaming buys the person not having to type it.
	RoamWindow = 5 * time.Minute
)
View Source
const Version = 20

Version is the protocol's version. The hello and the welcome both carry it, and a mismatch is a refusal at the door — two builds that might disagree about a frame must not guess at each other.

VERSION 2 IS THE PERSISTENT ENGINE. Version 1 married a conversation to a pipe: the engine was `ssh … codeaf engine`, it read frames on stdin, and when the pipe died so did the turn in flight. Version 2 separates the two — a session lives on the engine machine and a surface ATTACHES to it — and the four things that separation needs are the whole of the delta:

  • Frame.Seq numbers every event of a stream, and Hello.Resume says which ones a returning surface already has, so a reattach replays the gap instead of the conversation.
  • MethodDetach tells the engine a surface is leaving ON PURPOSE, which is the fact version 1 could not express: a torn pipe and a closed window were one event, so both had to interrupt the turn to be safe.
  • SubmitFilesArgs and MethodFetchFile carry a person's attachments both ways, generalizing the one payload version 1 already remade on arrival (image.go).
  • HeldQuestion lets a card raised with nobody attached WAIT rather than expire, which is what turns half the --host refusals from "the card would land in an empty room" into an answered question.

EVERY VERSION-2 FIELD IS ADDITIVE AND OMITEMPTY, so a version-2 frame read by a version-1 decoder is a version-1 frame. That does not make the versions compatible — the door still refuses a mismatch, and it must, because a version-1 engine would silently interrupt a turn the surface believed was detached — but it does mean this file stayed a superset rather than becoming a second protocol. VERSION 4 IS THREE THINGS THAT LANDED IN ONE WAVE, AND THEY SHARE A NUMBER because nobody ever ran a build with only one of them.

THE PLACES FOLLOW THE SESSION'S MACHINE. A place is a listing of one machine's disk — the conversations, the work they ran, what was learned, what it cost — and every one of them was read under the SURFACE's process, which over a connection is the laptop while the conversation lives on the server. Version 4 adds the doors that let the surface ask the machine that owns the work instead (MethodPlacesWorld and its neighbours), and one field on the welcome saying which state root those answers were read under.

AND THE ROOM HAS ONE KEYBOARD. Version 2 let several surfaces attach to one conversation and version 3 left it at that: every one of them could type, and the only arbiter was the engine's own "a turn is already running" refusal — so two windows on one conversation raced, and neither screen said the other existed. Version 4 names a DRIVER, and the whole delta is three things:

  • Hello.Surface carries the surface machine's short name, so a screen can say WHICH window has the keyboard rather than that some window does.
  • Welcome.Driver and the "driver" frame say who holds it, told to each surface as that surface should read it, and the engine is the only thing that decides.
  • MethodTake moves it here in one round trip, and Hello.Back is how a surface that merely lost its link says "I am not a new window" — because the keyboard follows the newest ARRIVAL, and a redial in the background must not steal it from a machine the person has actually walked to.

AND IT MAKES THE OTHER WINDOWS WINDOWS. Turn tells every surface that did NOT start a turn that one has started, because a surface only draws a stream it knows about: the events have fanned out to the whole room since version 2 and a watching window had nowhere to put them, so it sat on a still frame while the work went on in front of somebody else.

AND INTENT GOES UP, FACTS COME DOWN. Versions 1 to 3 made every fact a QUESTION: a surface drew a status line by asking the engine what the model was, what had been spent, what the conversation weighed and how hard it was being asked to think — four round trips over an ssh pipe, on a frame the person expected to be instant. Version 4 turns that around. The engine states those facts, unasked, whenever they move; the surface keeps a replica and reads it from memory. What still travels UP is intent — a message, a key, an answer — because intent is the one thing the far end cannot know on its own.

The delta is two additions and no removals:

  • FactsPush is the whole fact set with a revision number, and Welcome.Facts is the one a surface arrives holding.
  • the "facts" frame carries later ones. It belongs to the CONNECTION and not to a stream, so a fact that moves between turns still lands.

Both are additive and omitempty, exactly as version 2's were, and the door still refuses a mismatch: a version-3 engine states nothing, so a version-4 surface reading a replica off it would draw a status line frozen at whatever the welcome said.

VERSION 4 ADDS MethodPing. It changes no session state and carries no payload in either direction; the version still moves because a method one half may send and the other half cannot answer is a protocol difference, and Decision 3 in docs/REMOTE.md says those differences are refused at the door.

VERSION 5 ADDS MethodPlacesTask. Version 4 moved the PLACES onto the machine that owns the work (MethodPlacesWorld), and the tasks place duly listed the far machine's four hundred pieces of work — but the CARD behind one of those rows still read the last thing that work said off THIS process's disk, at a path that only exists on the other one. The door that ends it is one. The method also carries the bounded journal tail a hosted task room needs, so both halves must agree on its meaning at the version door.

AND THE OTHER PLACES METHODS RIDE THE SAME NUMBER. Places.Task moved the door once; the ledger, search, memory and archive methods in wire_places.go stay on that same version because an older engine's no-such-method answer has an explicit honest fallback on the surface.

VERSION 6 IS TWO LANES' ONE BUMP, the same way version 4 was: steering and the task door landed together and a number that moved twice for one release would refuse engines for no reason.

It adds MethodSteer. A local surface could put words into a running turn, but the client agent did not expose that verb and a hosted surface therefore hid the key entirely. Steering is another stream-opening intent: the returned stream is the running turn from the correction onward, exactly as session.Agent.Steer defines it.

And it adds THE TASK DOOR — MethodTaskStart, MethodPlannerStart and `Task.Judge`, which version 16 retired (wire_task.go). Every place method before it was a READING, which is why they could ride version 5 behind an honest fallback: an engine that cannot answer one leaves a page drawing the sentence it has always drawn. These calls are not reading. They COMMISSION WORK on the far machine and spend that machine's money doing it, so there is no sentence a surface could draw instead of an answer — either the far end starts the task or nothing happened. A surface that sent MethodTaskStart to an engine which does not know the method would have told a person their work was under way while the far machine refused a name it had never heard, and that is precisely the guess Decision 3 refuses to let two builds make three turns into a conversation. So the number moves and the mismatch is refused at the door, in the same sentence naming the same fix.

VERSION 7 opens a running task's room by id and carries its steer and stop verbs. A running node has no record URI yet, so version 5's Places.Task door cannot name it; the node id belongs to the current engine conversation and exists from admission onward. The room read is bounded and the two writes preserve the session agent's own answers.

VERSION 8 PUSHES THE TASK LANE, and it is the last half of version 6's task door. Version 6 let a hosted surface COMMISSION work on the far machine and left it with no way to watch what it had commissioned: a node's life — queued, running, done — is emitted on the session's STANDING task subscription, which no frame carried, so `/task solo …` over a connection answered "started", ran to completion on the far machine, and never put a row on the rail of the person who typed it. A model's proposal looked like it worked only because a proposal happens inside a turn and the turn's stream carried its updates by accident of where it was raised.

The delta is one intent up and one fact down, on the shape version 4 named:

  • MethodTaskWatch is the surface saying it draws tasks. The engine opens one standing subscription per surface that asks, which REPLAYS THE WHOLE ROSTER before its first live event (session's Agent.WatchTaskUpdates), so a window that attached an hour into the work still learns every row.
  • the "task" frame carries each of that lane's events onward. It belongs to the CONNECTION and not to a stream, exactly as "facts" does, because a node's landing happens when no turn is running and there is no stream left for it to land on.
  • MethodTaskPending answers the one question the lane cannot: which proposals are still open. It is asked only while a card is on screen and a turn has just ended, never on a frame and never on a pointer.
  • MethodTaskResolve carries the answer to a proposal, and it is the door whose absence broke everything else. The surface asserts the task seam as ONE interface — the lane, the pending reading, and this — so a wire holding three of the four left a hosted rail unsubscribed rather than partly working, with nothing on any screen saying why. internal/tui3's DrawsTasks is that assertion made checkable, and cmd/codeaf makes it.

The number moves rather than riding version 7 for MethodTaskStart's reason: an engine that does not know Task.Watch would answer the surface's one subscription with "no such method" and leave the rail permanently empty with nothing on the screen saying so.

VERSION 9 CARRIES THE FOREGROUND-COMMAND CLOCK IN Welcome. The clock is armed from the engine machine's profile, while a hosted surface has a different profile of its own. Leaving the field out would make the row draw a deadline no process on the far machine was following. The number moves because an older same-version engine would otherwise be accepted and answer the new field with zero, which is itself a real and different posture.

VERSION 10 CARRIES MethodTaskHold. A version-9 engine would reject the first rune's hold while its surface already showed "waiting on you", then admit the task on the deadline the person believed had stopped.

VERSION 11 CARRIES THE HARNESS LANE. A design card and a subharness intake card are raised on a subscription that outlives the turn (internal/session's emitHarness), and only a running turn's stream crossed this wire — so cmd/codeaf built every hosted session with the designer nilled and the cards off, and said so in prose. The delta is one subscription up (MethodDesignWatch), its frames down ("design"), and the intake card's answer (MethodSubharnessResolve); the design card's own answer has been MethodHarness since version 1. A lane without its answer door puts a question on a screen that nothing can close, which is the fault version 8 found in the task rail.

The adaptive run lane is deliberately NOT part of this delta; lanes.go states exactly what is missing from it.

The number moves rather than riding version 10 for MethodTaskWatch's reason: an older engine answers the new subscription with "no such method" and leaves a lane permanently dark with nothing on the screen saying so.

VERSION 12 CARRIES Hello.Join AND Hello.Watch, AND THE NUMBER IS THE ENFORCEMENT. Both are SAFETY fields — one says "never start a conversation", the other says "never give me the keyboard" — and both are omitempty booleans, which is exactly the shape a version-11 engine DISCARDS in silence. That engine would then do the two things the fields exist to prevent, before the surface ever sees a welcome to check: boot a whole conversation to answer a question about work that is running, and hand a reader the keyboard off the window that owns the work. Neither is recoverable by a check afterwards.

So the guarantee is the door's, not the flag's. The version is compared before AttachOptions.Open is called and before [Session.attach] runs, by BOTH builds — and a version-11 engine enforces it against a version-12 surface using code that has been there since version 1. That is the only mechanism in this protocol an old peer can be trusted to run.

VERSION 13 IS THE MOVE, AND IT IS A RULING ABOUT WHAT A SECOND WINDOW MEANS. Versions 4 to 12 let several surfaces sit in one conversation and arbitrated between them with a keyboard (Driver): the newest arrival typed, the others watched. The ruling is that a person opening a conversation in the terminal they are standing at MEANS TO BE IN IT, and the window they walked away from should say so and step back — one conversation, one window, and the way back is the same keystroke from the other side.

The delta is one frame down, and nothing goes up:

  • the "moved" frame carries a Moved to every OTHER surface in the room when a window arrives that is neither a watcher (Hello.Watch) nor a link coming back (Hello.Back). The surface hearing it DETACHES — the engine holds the conversation and the work never stops — and lands on home with that row under the cursor.

THE NUMBER MOVES BECAUSE THE OLD BEHAVIOUR WAS A BEHAVIOUR AND NOT A GAP. A version-12 surface joined by a version-13 one never hears the frame and stays attached, watching, exactly as it did before — which is not broken, and is precisely why an engine may not be left to guess: a version-12 ENGINE would leave two windows both believing they are the one in the conversation, and only the door can tell those two builds apart. VERSION 14 CARRIES THE QUESTIONS LANE, and it is the last of the standing lanes to cross. A question is one decision handed to a person with its evidence attached (docs/design/questions/DESIGN.md), and internal/session speaks every one of them on a subscription of its own that outlives the turn — session.Agent.WatchQuestions, which replays everything still open the moment a surface attaches. That subscription had no frame here, so a hosted surface asserted the questions half of its agent, found no Agent.WatchQuestions on it, and drew nothing: an `ask` on the road a plain `codeaf` takes stopped the turn with no block, no chip and no row on any screen, for as long as the person left it. Measured at three minutes.

The delta is one intent up and one fact down, on the shape versions 8 and 11 named:

  • MethodQuestionWatch is the surface saying it draws questions. The engine opens one subscription per surface that asks, which REPLAYS WHAT IS STILL OPEN before its first live event, so a window that attached an hour into the wait still learns the question.
  • the "question" frame carries each of that lane's events onward — session.EventQuestion, session.EventQuestionWithdrawn and session.EventQuestionAnswered, each with the whole object on it. It belongs to the CONNECTION and not to a stream, because most questions outlive the turn that raised them and many never had one.

THE ANSWER'S OWN DOOR WAS ALREADY HERE and is unchanged: MethodQuestionResolve has carried session.Answer whole since it landed. That is what made the gap so quiet — the half a person presses worked perfectly and the half that puts the question on the screen did not exist.

The number moves rather than riding version 13 for MethodTaskWatch's reason, and the reason is the whole of the discipline here: a version-13 engine answers this subscription with "no such method" and leaves the lane permanently dark, with nothing on the screen saying why. Refused at the door, a person is told their engine is an older codeaf; accepted, they would be told nothing at all and their turn would simply stop. NEVER TO SILENCE. VERSION 15 IS AN ANSWER THAT IS A MESSAGE (docs/design/questions/DESIGN.md). A question the model asks no longer exists only for as long as the call that asked is parked on it: the answer is delivered to the conversation, which is what lets the model carry on while somebody decides, lets a clock take the pick on a question nobody is waiting on, and lets a decision be CHANGED afterwards. Two of those cross this wire:

  • session.Answer.Revises says an answer is a person changing their mind about a settled question rather than a second click on one somebody else has already answered. It rides MethodQuestionResolve, which has always carried the answer whole.
  • MethodQuestionHold stops a question's clock without answering it.

THE NUMBER MOVES BECAUSE BOTH FAIL AS SILENCE ON AN OLDER ENGINE. A version-14 engine reads `revises` as a field it does not know, applies answers.go's own law — a late answer is ignored and nothing says so — and the person watches their change do nothing; and it answers `Question.Hold` with no such method while its clock goes on counting, so the pick is taken under the hand of somebody who pressed a key to stop exactly that. NEVER TO SILENCE.

AND VERSION 15 CARRIES THE READ SIDE OF THE QUESTION RULES, which is the other half of the same number and travelled with it rather than moving it again. MethodSetAutonomy crossed alone: this wire could WRITE a project's rules and never read them back. That was not a remote-only fault — the ordinary launch talks to its own engine through this client — so `/autonomy` and the settings rows beside it answered `this conversation has no project to keep question rules in` on every machine, whatever project it was in, because the door they asserted did not exist. MethodAutonomy is the missing half and carries `map[session.AskKind]session.Policy` down.

AND THE NUMBER MOVES FOR THE SAME REASON VERSION 14'S DID, which is why this could not ride 14. A version-14 engine answers this call with "no such method" and the client can only report that it could not read the rules; ridden silently, an empty answer is INDISTINGUISHABLE FROM A PROJECT THAT KEEPS NO RULES, and every row on the settings page would say `ask me` while the engine was quietly on `decide`. A wrong account of what may happen without a person is the one thing this lane must never give, so the refusal is at the door and the nil that comes back is drawn as "not read" rather than as "nothing set" (client.Autonomy, and settingsautonomy.go's own reading).

VERSION 16 IS THE TASK DOOR THAT STOPPED WAITING (issue #936). `/task` used to be two calls in series — `Task.Judge`, then MethodTaskStart held open for the engine's shaper — and the engine now admits the work at once and reads its width and writes its brief beside the worker. So `Task.Judge` is gone, TaskStartArgs carries the one fact the engine cannot know (`solo`), and the start is an ordinary call with the ordinary deadline. The number moves because a version-15 engine would still hold the start behind its shaper for up to half a minute while this surface had stopped waiting at ten seconds: the person would be told their work did not start while it did. NEVER TO A SENTENCE THAT IS FALSE. VERSION 17 separates clarification from answering a pending question and adds ReplaceQuestion. It also carries whether a caller has no approval resolver. Older peers must refuse before a question or an unwatched tool can run under semantics the other side does not understand.

VERSION 19 CARRIES THE DELEGATE DOOR — MethodDelegateList and MethodDelegateStart (wire_task.go). The number moves for MethodTaskStart's reason: `Delegate.Start` COMMISSIONS WORK on the far machine and spends its money, so a version-18 engine answering "no such method" would leave a person told their work was under way while nothing had started. The list rides the same number because a surface generates its command rows from it before its first frame, and a row for a program the engine cannot start is a command that lies.

VERSION 18 IS THE CREW PICKED PER TASK. TaskStartArgs carries the one-task effort word (`/task --best`, `/task --cheap`), and MethodTaskRedoStronger runs the last task again on a stronger crew. The number moves because both fail as silence on an older engine: a version-17 engine reads `effort` as a field it does not know and starts the task on the crew it would have had, and the person is never told their word did nothing. NEVER TO SILENCE. Version 20 adds the explicit human shell door and streamed shell output. Older peers must refuse rather than treat a shell command as model input.

Variables

View Source
var ErrJoinedGone = errors.New("engine: that conversation is not open here any more")

ErrJoinedGone is a joined connection finding that the conversation it joined is not the one this session is running any more. It is a fact and not a failure: the window that owns the session opened something else in it, and a reader bound to the old one has nothing left to read.

View Source
var ErrLate = errors.New("the engine did not answer in time")

ErrLate is what a call that outlived this end's patience matches with errors.Is. The engine may still be doing the work, so a surface that can say "still running" rather than "failed" reads it through this.

View Source
var ErrNoHostThere = errors.New("remote: the far end did not answer what build it is")

ErrNoHostThere is a far end that answered the question with a refusal, which is what EVERY BUILD FROM BEFORE THIS EXCHANGE EXISTED does: an unknown first frame has always been `engine: the first frame was %q, not a hello`. It is the most useful thing a silence could have said — the process holding that socket is not this build, and it cannot be asked anything else either.

View Source
var WatchFor = watchGrace

WatchFor is that grace as [Session.watchedLocked] applies it, and it is [watchGrace] everywhere the product runs. It is a var for the reason internal/enginehost's sessionIdle is one: a test asking what the IDLE POLICY decides is not asking about a stalled road, and it must be able to take this span out of the question rather than wait it out. Nothing in the product writes it.

Functions

func AttachedSentence

func AttachedSentence(text string, paths []string) string

AttachedSentence is what the MODEL is told about the files on a message, and it is a PATH rather than a payload — see this file's header for why.

IT IS EXPORTED BECAUSE THE LOCAL SURFACE COMPOSES THE SAME SENTENCE. Over a connection the engine writes the files down and says this; on a local session there is nothing to write down — the file is already on the machine the session runs on — and the surface says it instead (internal/tui3's attach.go). The two must be one sentence, because a model that met a different phrasing depending on which machine it was running on would have learned two things.

The words are plain on purpose. This is not a person's line; it is the part of the message that tells a model where something is, and anything decorative in it is a thing the model has to decide whether to repeat.

func AttachmentsDir

func AttachmentsDir(place session.Place, workspace string) string

AttachmentsDir is where a file a person attached lands on the engine machine.

IT IS BESIDE THE TRANSCRIPT AND NOT AMONG THE DELIVERABLES, and that is the one place it parts from session.ImagesDir. A deliverable is something the harness MADE and somebody may want back, so it lands where the person will look for it (internal/session's landing.go states that law). An attachment is the opposite claim: it is the person's own INPUT, they already have it, and a borrowed session that copied every log file somebody dropped into the repo it was lent would be littering. So it lands in the session's own folder, which is where everything a session holds ABOUT ITSELF lives — one folder per session is one gesture to delete them (docs/CHAT-V3.md, Decision 26).

It is NOT under session.Place.Logs, which is the other thing in that folder that is not a deliverable, because the droppings there carry the sweep's 7-day TTL. A journal that references an attachment by path must go on being readable long after that, so the file may not be swept.

A session with no folder at all — the legacy flat layout — puts them under the workspace's own dot directory, the same fallback every other landing takes.

func MachineName

func MachineName() string

MachineName is what this machine calls itself on the wire: its host name with any domain trimmed off, so a person reads `spark` rather than `spark.local.example.com` in a sentence about which window is typing.

IT IS A LABEL AND NOT AN IDENTITY (see Hello.Surface). The empty string is a machine that could not answer, and the empty string is what every screen downstream draws as nothing at all rather than as "unknown" — the emptiness law, applied to a name.

internal/pair's ThisMachineLabel reads the same fact for the pairing lane and is deliberately NOT called here: the two packages are siblings glued together by the door in cmd/codeaf, neither imports the other, and pairing's label is a device's durable name where this is one connection's passing one.

func Pipe

func Pipe() (surface, engine io.ReadWriteCloser)

Pipe is two connected halves of one connection: what the surface would have got from an ssh child's pipes, without the ssh child.

It is net.Pipe rather than an os.Pipe pair because net.Pipe is synchronous and in-memory — no file descriptors, no buffering to make a test's timing lie, and a close on either half is seen immediately by the other, which is exactly the event this protocol's dead-connection handling turns on.

func Refuse

func Refuse(out io.Writer, reason string) error

Refuse writes one refusal onto a wire nobody has said hello on yet and hands back the same Refusal a handshake's own refusals do.

IT EXISTS FOR THE ONE REFUSAL THAT COMES BEFORE THERE IS A SERVER. `codeaf engine` decides whether it may splice this connection onto a host before it reads a byte of stdin (cmd/codeaf's engine.go), and a reason found there has the same audience and travels the same road as any other: the surface is holding the terminal, it is waiting for a welcome, and a fatal frame is the sentence it prints unchanged.

func Serve

func Serve(in io.Reader, out io.Writer, opts Options) error

Serve runs one engine on one pipe until the pipe dies or the protocol does. It returns nil for an ordinary hang-up and an error for everything the surface broke, and the caller turns that into an exit code.

THE CONVERSATION IS THIS PIPE'S WHOLE LIFE, which is what the false below says: a bare `codeaf engine` with no host behind it is a legitimate version-2 engine, it simply ends when the connection does. The welcome says so (Welcome.Persistent) so that no surface promises a lifetime this shape does not have.

func ServeAttach

func ServeAttach(in io.Reader, out io.Writer, opts AttachOptions) error

ServeAttach runs one connection against whatever conversation AttachOptions opens for its hello — a fresh one, or one that has been running since before this surface existed.

Types

type AcceptClosingArgs

type AcceptClosingArgs struct {
	ID string `json:"id"`
}

AcceptClosingArgs names a decided closing packet.

type AcceptClosingReply

type AcceptClosingReply struct {
	Closed bool   `json:"closed"`
	Stamp  string `json:"stamp"`
}

AcceptClosingReply says whether this call closed the team, and the teams file's stamp after.

type Agent

type Agent struct {
	// contains filtered or unexported fields
}

Agent is the engine's session as internal/tui3 sees it: every method of tui3.Agent, plus the rewind pair that surface type-asserts for (internal/tui3's rewind.go). It holds no state of its own — it is a handle on whichever session the engine currently has open, which is why /new and /resume keep using the same one.

func (*Agent) AnswerLaneOffer

func (a *Agent) AnswerLaneOffer(yes bool) bool

AnswerLaneOffer answers the question a stalled PINNED lane raises: the machine this person named has gone quiet, there is somewhere else to go, and a pin is asked rather than overridden. The `y` they pressed takes this road home (wire.go's MethodAnswerLaneOffer).

FALSE IS A REAL ANSWER AND NOT A FAILURE — the lane came good while the person was reaching for the key, the request finished, or the question aged out ([provider.AnswerOffer] states it) — so a call that could not be made at all reads as false too, and the surface draws nothing either way. That is what lets this door ride a wire version that predates it: an older engine answers "no such method" and the key does what it did before the door existed, which is nothing.

func (*Agent) ApprovalDial added in v0.4.0

func (a *Agent) ApprovalDial() bool

ApprovalDial answers for THE MACHINE AT THE OTHER END, off what it said at the door.

func (*Agent) Attach

func (a *Agent) Attach() (<-chan session.Event, bool, func())

Attach gives the keeper its own reader. Leaving it stops observation only.

func (*Agent) AttachReplay

func (a *Agent) AttachReplay() ([]session.DisplayEntry, <-chan session.Event, func())

AttachReplay leaves the turn's partial output in its stream, so the transcript and the replay cannot draw the same model output twice.

func (*Agent) AttachSkills

func (a *Agent) AttachSkills(names ...string) []string

AttachSkills puts names in front of the far conversation and answers the set as it now stands there.

func (*Agent) AttachedSkills

func (a *Agent) AttachedSkills() []string

AttachedSkills is the set as the far conversation last stated it, in attachment order, read off the replica and never asked for.

func (*Agent) Autonomy

func (a *Agent) Autonomy() map[session.AskKind]session.Policy

Autonomy is this project's question rules, read back over the same wire Agent.SetAutonomy writes them down. It is the half of the pair that was missing, and its absence was not a remote-only fault: the ordinary launch talks to its own engine through this client, so `/autonomy` and the settings rows that read it answered "no project" on every machine.

A CONNECTION THAT CANNOT ANSWER RETURNS NIL, which is the same answer every other read on this agent gives and the honest one: rules that cannot be fetched are not drawn as rules that are.

func (*Agent) Cancel

func (a *Agent) Cancel(id string) (string, error)

Cancel asks the engine to stop the prefixed work id and keeps its sentence.

func (*Agent) ClearAttachedSkills

func (a *Agent) ClearAttachedSkills() int

ClearAttachedSkills takes every name back off and says how many were on.

func (*Agent) Client

func (a *Agent) Client() *Client

Client is the connection under this agent, for a door that needs the session seams as well as the conversation ones.

func (*Agent) Close

func (a *Agent) Close() error

Close flushes the REMOTE session file and leaves this connection standing.

That is the whole difference between it and Client.Close, and it is a difference the surface depends on: /new and /resume both close the agent they are holding before asking for the next one (internal/tui3's app.go and welcome.go), and a Close that hung up the ssh process would make the second conversation impossible. The connection is the DOOR; the session is what is behind it, and only cmd/codeaf shuts the door.

func (*Agent) Compact

func (a *Agent) Compact(ctx context.Context) error

Compact runs a compaction pass on the far side.

IT WAITS AS LONG AS A PASS CAN TAKE, not [callDeadline]. A pass may ask the model for a summary, which on a slow model is longer than ten seconds, and a surface that gave up sooner said "did not answer in time" about a pass that landed a moment later. The surface asks from a command rather than from its update loop, so the longer wait is a line saying "compacting…", never a terminal that stops drawing. It runs beside the ordered lane (callclass.go's [classWork]), so nothing the person sends meanwhile queues behind it.

func (*Agent) ContextTokens

func (a *Agent) ContextTokens() int

ContextTokens is what the conversation weighs right now, read from memory. It is stated at a turn end and after a compaction — the two moments the figure moves — which is exactly where internal/tui3 asks for it (app.go's measureContext, which is written never to ask on the frame clock).

func (*Agent) ConversationEffort

func (a *Agent) ConversationEffort() string

ConversationEffort is the rung THIS conversation was set to, "" when nobody has set one.

IT IS AN EXPLICIT READ, and it is allowed to be one because nothing draws it: it answers the question "what did I choose", asked by a person opening the dial, where the seam's own word is always the resolved rung (Agent.ResolvedEffort). A dead link or an engine with no dial answers "", which is the same string the local door gives for a conversation nobody has set — so a caller cannot tell them apart, and must ask Agent.EffortSupported first if it needs to.

func (*Agent) DefaultEffort added in v0.4.0

func (a *Agent) DefaultEffort() string

DefaultEffort is the far install's row as the welcome carried it — a memory read, for Agent.ResolvedEffort's reason: the draft on home draws it on every frame, and a View over --host issues zero far calls.

func (*Agent) Delegates

func (a *Agent) Delegates() session.DelegateReport

Delegates is the programs the engine machine's build carries, as the surface draws its rows from them (internal/session's delegate_door.go). A failed read is the zero report — no rows — because a list is a reading and never worth a refusal at the door.

func (*Agent) Detach

func (a *Agent) Detach() error

Detach lets go of this VIEW of the conversation, which is what a terminal closing means: the window is gone and the work need not be.

Against a session host it sends MethodDetach — nothing interrupted, nothing closed, the connection ended by the far side's reader loop with the turn left to finish — because MethodClose would end a running task on behalf of somebody who only shut a window. Against a one-shot engine, whose whole life is this pipe, leaving IS ending, so the interrupt and the flush stand.

Every call below is bounded ([Client.call] carries callDeadline) and safe on a dead connection, so this cannot hold a quit open.

func (*Agent) DetachSkill

func (a *Agent) DetachSkill(name string) bool

DetachSkill takes one name back off and says whether it was there.

func (*Agent) EarlierHistory

func (a *Agent) EarlierHistory() session.EarlierHistory

EarlierHistory is the conversation above the session's latest compaction and where the pass's rewritten copy of it ends, which is what the surface scrolls back into. Empty for a session that has never been compacted, and empty for a connection that has dropped — the same answer as every other read on this agent, and the honest one either way: what cannot be fetched cannot be drawn, and an empty region leaves the transcript drawn exactly as it always was.

func (*Agent) EffortSupported

func (a *Agent) EffortSupported() bool

EffortSupported answers for THE MACHINE AT THE OTHER END, off what it said at the door — a fact this end could not otherwise learn without setting a rung and reading the refusal, which is after the person has pressed the key.

func (*Agent) FollowUp

func (a *Agent) FollowUp(text string) (<-chan session.Event, error)

FollowUp queues a message for after this turn and returns the stream that turn will run on.

func (*Agent) HandUnverifiedToModel

func (a *Agent) HandUnverifiedToModel(id uint64) error

HandUnverifiedToModel gives one landing's decision to the model. Nothing is resolved by it: what moves is who is holding the question.

func (*Agent) HarnessDesigns

func (a *Agent) HarnessDesigns() <-chan session.Event

HarnessDesigns is the same lane for a caller with no way to leave it, which is the shape internal/tui3's designAgent asks for. Every door in this build takes the leavable road above.

func (*Agent) HoldQuestion

func (a *Agent) HoldQuestion(kind session.QuestionKind, token string)

HoldQuestion stops one question's clock on the far machine without making a key wait for the connection — Agent.HoldTask's shape, for every lane. There is no answer to carry back: what a person sees is the question said again with its countdown gone, on the questions lane.

func (*Agent) HoldTask

func (a *Agent) HoldTask(id uint64)

HoldTask stops one proposal clock on the far machine without making a key wait for the connection. The engine broadcasts the zero-deadline proposal back to every surface after it accepts the hold.

func (*Agent) Interrupt

func (a *Agent) Interrupt()

Interrupt cancels the in-flight turn. IT DOES NOT WAIT and it reports nothing: the interface says so, and a key that is pressed to stop something must not itself become a thing that blocks. A dead connection swallows it, which is exactly what a dead connection does to the turn as well.

func (*Agent) InterruptFor

func (a *Agent) InterruptFor(door session.StopDoor)

InterruptFor is the same stop with the door on it, for the machinery stops that are not a person. An engine too old to read the argument sees the stop it always saw.

func (*Agent) KeepsFolders

func (a *Agent) KeepsFolders() bool

KeepsFolders answers FOR THE MACHINE AT THE OTHER END, off what it said at the door (Welcome.Folders) — the fact a surface needs BEFORE it opens a picker, and the one its own type assertion cannot give it: this type always has the three methods below, whatever is behind the pipe.

AND IT IS RE-READ RATHER THAN REMEMBERED, exactly as Agent.SteerRepeatKnown is: /new, /resume and a reconnect all replace the welcome, and the engine behind it can change with them.

func (*Agent) Model

func (a *Agent) Model() string

Model is the model the next request will use.

IT IS A MEMORY READ. The engine states this at the door and again whenever it moves, so the status line asks nothing (replica.go states the whole law, and PERF.md pins it: a View over --host issues zero far calls).

func (*Agent) NameTeam

func (a *Agent) NameTeam(ctx context.Context, titles []string) (string, error)

NameTeam asks the engine's naming role for a short name for a group of conversations, given their titles: the wall's teamNamer door, over the wire. An engine without the ask is refused here, before anything is written.

THE WALL'S DEADLINE BOUNDS THE CALL as well as [callDeadline] does: the wall waits teamNameWait and no longer, so the call gives up with it and the engine is told the same span, rather than answering a card that has already kept its word.

func (*Agent) NeedsPerson

func (a *Agent) NeedsPerson() bool

NeedsPerson reads the pushed conversation state without a round trip.

func (*Agent) NoteConnected

func (a *Agent) NoteConnected(service, account string)

NoteConnected tells the session an account is connected.

func (*Agent) OpenQuestions

func (a *Agent) OpenQuestions() []session.Question

OpenQuestions is every decision the far conversation is waiting on somebody for, oldest first, answered from what the lane has already said (the file header says why that is a reading and not a call).

A SURFACE THAT NEVER OPENED THE LANE IS TOLD NOTHING RATHER THAN NOTHING TRUE: the list is empty, exactly as it is for a conversation with no questions, because a window that does not draw questions has not been sent any and has nothing to report about them.

func (*Agent) PendingTasks

func (a *Agent) PendingTasks() []uint64

PendingTasks is the same reading for [tui3.taskAgent], whose shape has no room for the second answer. IT IS DELIBERATELY THE PESSIMISTIC HALF: an engine that did not answer is reported as having nothing outstanding, which is why the surface asserts Agent.TaskProposalsPending first and only falls back here.

func (*Agent) Places

func (a *Agent) Places() []session.PlaceRef

Places is the folders this conversation is about, newest first — A MEMORY READ THAT NEVER TOUCHES THE WIRE.

The set rides the fact push for replica.go's stated reason: the folder indicator is drawn on a frame, and a frame is not allowed to wait on a network. What is answered is what the engine last said, which for a connection that has dropped is the last true picture rather than a list that emptied itself because a pipe closed.

func (*Agent) PlanAmend added in v0.4.0

func (a *Agent) PlanAmend(id, text string) error

func (*Agent) PlanCancel added in v0.4.0

func (a *Agent) PlanCancel(id string) error

func (*Agent) PlanNote added in v0.4.0

func (a *Agent) PlanNote(id, text string) error

func (*Agent) PlanPause added in v0.4.0

func (a *Agent) PlanPause(id string) error

func (*Agent) PlanPriority added in v0.4.0

func (a *Agent) PlanPriority(id string, priority int) error

func (*Agent) PlanResume added in v0.4.0

func (a *Agent) PlanResume(id string) error

func (*Agent) PlanRunSummary added in v0.4.0

func (a *Agent) PlanRunSummary(rootID string) (session.RunPlanSummary, bool)

PlanRunSummary reads the last engine-side summary; an unavailable link keeps nothing.

func (*Agent) PlanSpend added in v0.4.0

func (a *Agent) PlanSpend(since time.Time) []session.PlanSpendLine

func (*Agent) PlanTaskPage added in v0.4.0

func (a *Agent) PlanTaskPage(id string) (session.PlanTaskPage, bool)

PlanTaskPage reads one complete task page from the engine.

func (*Agent) PlanTaskWork

func (a *Agent) PlanTaskWork(id string) (session.PlanTaskWork, bool)

PlanTaskWork reads the run's working copy over the wire. An engine that has no such door answers "no such method", which is [PlanTaskWork.NoDoor]: the work tab draws its absence sentence, the same one it draws for an agent that was never given the door.

func (*Agent) PlanTasks added in v0.4.0

func (a *Agent) PlanTasks() []session.PlanTaskRow

PlanSpend is the run's spending rolled up by seat, over the wire. It is the engine door internal/tui3's spend page asserts (session.Agent.PlanSpend), carried here so the block draws on a remote conversation exactly as it does on a local one.

THE EMPTINESS LAW DECIDES ITS ERROR, and it is the whole of why this returns a slice and no error. The seat block is DRAWN FROM the lines it is handed, so a conversation with no plan and a link that cannot answer must both read as the same thing: nothing drawn. An engine older than this door answers "no such method", which lands here as a nil slice — the block is simply absent, which is what a remote conversation drew before the door existed. PlanTasks reads this conversation’s complete plan rows from the engine.

func (*Agent) ProposeTeams

ProposeTeams asks the engine's naming role which teams the conversations in in could form: the wall's teamProposer door, over the wire, refused here for an engine without it as Agent.NameTeam is.

func (*Agent) ReadPlanTaskPage

func (a *Agent) ReadPlanTaskPage(id string) (session.PlanTaskPage, bool, error)

ReadPlanTaskPage is Agent.PlanTaskPage with the engine's refusal kept.

A READING WINDOW NEEDS THE REFUSAL. A page opened onto another conversation's program task reads nothing but this, and the one way it learns the conversation under it was replaced is the engine's own sentence (ErrJoinedGone) — which the plan capability's (page, found) shape has nowhere to put (internal/tui3's [tui3.TaskOwnerView.TaskPage]).

func (*Agent) ReasoningFor

func (a *Agent) ReasoningFor(model string) string

ReasoningFor is how hard one model is asked to think.

IT IS THE READ THAT MADE THIS WHOLE FILE NECESSARY. internal/tui3's view.go asks it on every frame it draws — the level rides the model segment of the status row — so as a round trip it set the repaint rate of the terminal to the round-trip time of the link. The engine pushes the whole level map, keyed the way internal/session keys it, and this is a lookup in it.

func (*Agent) ReasoningLevels

func (a *Agent) ReasoningLevels() map[string]string

ReasoningLevels is every level this conversation holds, in one answer.

IT IS THE DOOR THAT MAKES THE SURFACE'S OWN TABLE COMPLETE AT BOOT (internal/tui3's reasoninglevel.go). Asked one model at a time, a picker drawing a three-hundred-row catalog had three hundred questions to get through; the engine states the whole map instead, so this is one memory read of the replica and the surface has nothing left to discover.

func (*Agent) RedoStronger

func (a *Agent) RedoStronger(ctx context.Context, row uint64) (uint64, string, error)

RedoStronger runs a task again on the engine machine with a stronger crew (`/redo stronger`); row 0 is the newest task the conversation started.

func (*Agent) ReferPlace

func (a *Agent) ReferPlace(path string, arrival session.PlaceArrival) (session.PlaceRef, error)

ReferPlace attaches one folder to the conversation on the ENGINE machine.

IT IS A ROUND TRIP AND IT HAS TO BE. The engine is what stats the path, snaps it to its repository root, writes it onto the conversation's meta.json and puts it in front of the model — none of which this end can do or check, and all of which is the difference between a folder attached and a line drawn. The session.PlaceRef that comes back is THE ENGINE'S ANSWER, root-snapped and canonical, so a caller reporting what was attached reports what landed rather than what it asked for.

func (*Agent) RefreshRunSummary added in v0.4.0

func (a *Agent) RefreshRunSummary(ctx context.Context, rootID string, lastLook time.Time) (session.RunPlanSummary, bool)

RefreshRunSummary asks the engine to refresh within the caller deadline.

func (*Agent) RemovePlace

func (a *Agent) RemovePlace(path string) error

RemovePlace takes one folder back off the conversation on the engine machine, and carries the engine's own refusal back for a folder it is not about.

func (*Agent) ReplaceQuestion added in v0.4.0

func (a *Agent) ReplaceQuestion(ctx context.Context, answer session.Answer) (<-chan session.Event, error)

ReplaceQuestion starts a revised request after retiring the pending turn.

func (*Agent) ReplayCovers

func (a *Agent) ReplayCovers(ev session.Event) bool

ReplayCovers is checked again on the surface loop: a Follow message can already be in flight when its atomic replay finishes on another goroutine.

func (*Agent) ReplayCoversStream

func (a *Agent) ReplayCoversStream(ch <-chan session.Event) func() bool

ReplayCoversStream also supplies ownership for locally queued follow-ups, whose channel can wait on the surface while a reconnect refreshes history.

func (*Agent) ResolveConflict

func (a *Agent) ResolveConflict(id uint64) error

ResolveConflict spends one more merge round on a branch that would not fasten. It is the conflict card's `[a] resolve it`, and it takes nothing as done on the way past.

func (*Agent) ResolveConnect

func (a *Agent) ResolveConnect(id string, approve bool)

ResolveConnect answers one connect ask.

func (*Agent) ResolveConnectKey

func (a *Agent) ResolveConnectKey(id string, key string)

ResolveConnectKey answers one connect ask that arrived with NeedsKey.

func (*Agent) ResolveConsent

func (a *Agent) ResolveConsent(id uint64, allow bool)

ResolveConsent answers one approval question for this call only.

func (*Agent) ResolveConsentRemember

func (a *Agent) ResolveConsentRemember(id uint64, allow bool, scope session.ConsentScope)

ResolveConsentRemember answers one and says how long the answer lasts.

func (*Agent) ResolveHarness

func (a *Agent) ResolveHarness(id uint64, run bool, model string)

ResolveHarness answers one sub-harness offer.

func (*Agent) ResolveQuestion

func (a *Agent) ResolveQuestion(answer session.Answer) error

ResolveQuestion answers ONE QUESTION OF ANY LANE, whole, over the wire.

IT IS THE METHOD THAT MAKES A QUESTION ANSWERABLE FROM A SURFACE AT ALL, and Agent.ResolveStanding's note above says why in the older case: internal/tui3 asserts an OPTIONAL interface on whatever agent it is holding and draws a page that can be READ and not answered for one that does not implement it. Every local chat surface holds this type — the engine runs in its own process even on this machine — so without this the question page was a page nobody could answer anywhere.

THE ERROR COMES BACK. Every other resolver here drops it, because their answers cannot be refused: an approval either applies or the question is already gone. A question CAN be refused with something a person needs to read — the work it was about finished, somebody else answered it first — and the page draws exactly that sentence where its foot was.

func (*Agent) ResolveStanding

func (a *Agent) ResolveStanding(id uint64, answer session.StandingAnswer)

ResolveStanding answers one standing card: set it up, set it up once, or a correction in the person's own words.

IT IS THE METHOD THAT MAKES A STANDING CARD ANSWERABLE OVER A CONNECTION. internal/tui3's standing.go asserts an OPTIONAL interface on whatever agent it is holding ([standingAgent]) and draws no chips at all for one that does not implement it, so a remote handle without this would have shown the person a proposal they could look at and could not answer. Adding it here is the whole of the difference.

func (*Agent) ResolveSubharness

func (a *Agent) ResolveSubharness(id uint64, run bool, input json.RawMessage)

ResolveSubharness answers one intake card on the far machine.

IT IS A CALL WITH NOTHING COMING BACK, as the other resolve doors on this wire are: what happens next is a program starting, and that arrives as events on the task rail. It is made off the update loop so the keystroke that pressed `y` does not wait on a round trip before the card comes down.

func (*Agent) ResolveTask

func (a *Agent) ResolveTask(id uint64, answer session.TaskAnswer)

ResolveTask answers one proposal on the far machine.

IT IS A CALL WITH NOTHING COMING BACK, exactly as the four other resolve doors on this wire are: what happens next is a turn resuming or a task starting, and both of those arrive as events. It is made off the update loop for the reason every write on this wire is — the keystroke that pressed `y` must not wait on an ssh round trip before the row on screen changes.

func (*Agent) ResolveUnverified

func (a *Agent) ResolveUnverified(id uint64, resolution session.TaskResolution, why string) error

ResolveUnverified spends the person's answer on one landing.

IT WAITS FOR THE ENGINE, unlike the proposal doors beside it (Agent.ResolveTask answers nothing and returns nothing). The card reads the error: a question somebody else has already answered stops asking and says `already answered`, and anything else keeps the choices and says one dim line, so an answer that went nowhere may not be reported as one that landed.

func (*Agent) ResolvedApprovalPosture added in v0.4.0

func (a *Agent) ResolvedApprovalPosture() string

ResolvedApprovalPosture is the posture the far gate is standing at. IT IS A MEMORY READ, for Agent.ResolvedEffort's reason: the seam draws it on every frame, and a View over --host issues zero far calls.

func (*Agent) ResolvedEffort

func (a *Agent) ResolvedEffort() string

ResolvedEffort is the rung the next turn will actually ask for.

IT IS A MEMORY READ. The seam draws it on every frame, and PERF.md's law is that a View over --host issues zero far calls (replica.go states the whole reason).

func (*Agent) RetargetTask

func (a *Agent) RetargetTask(id uint64, model string) (session.ModelLanding, error)

RetargetTask changes only the task in the conversation the surface has open.

AND IT CARRIES BACK WHEN THE PICK LANDED, because the room on this side says it out loud (internal/tui3's roomModelTiming). An engine too old to answer sends nothing, and nothing unmarshals as the landing every live node has when no request is out — which is the safe half of the sentence to say when we cannot know.

func (*Agent) RetryTask added in v0.4.0

func (a *Agent) RetryTask(id uint64) error

RetryTask binds the task number to the conversation shown by this connection.

func (*Agent) RetryTaskIn added in v0.4.0

func (a *Agent) RetryTaskIn(id uint64, conversation string) error

RetryTaskIn retains the page's owner even if the connection changes before the command runs.

func (*Agent) RewindAt

func (a *Agent) RewindAt(index int) ([]session.DisplayEntry, error)

RewindAt cuts at one of them. Its error is SHOWN — the mode stays up and prints the sentence — so a dead connection lands there like any other refusal.

func (*Agent) RewindPoints

func (a *Agent) RewindPoints() []session.RewindPoint

RewindPoints is every place the conversation can be cut. It is half of the OPTIONAL pair internal/tui3's rewind.go type-asserts for, and this agent implements it so a remote session rewinds exactly like a local one.

func (*Agent) SetApprovalPosture added in v0.4.0

func (a *Agent) SetApprovalPosture(posture string) error

SetApprovalPosture moves the far conversation's posture and says why not — the local door's contract exactly (internal/session's session.Agent.SetApprovalPosture), so the surface's one path from the chord, the press and the command cannot mean two things depending on which machine the conversation is on.

IT READS THE RESOLVED POSTURE BACK BEFORE IT RETURNS, on Agent.SetConversationEffort's terms: the caller says the new word in the very next statement, and the engine's own push is still on the wire.

func (*Agent) SetAutonomy

func (a *Agent) SetAutonomy(kind session.AskKind, policy session.Policy) error

SetAutonomy is `D`: it says which shape of question may be answered without asking, from now on, in this project. It carries the refusal back for Agent.ResolveQuestion's reason — "clarification always waits for an answer" and "this conversation has no project" are both sentences a person has to read.

func (*Agent) SetContextWindow

func (a *Agent) SetContextWindow(int)

SetContextWindow is deliberately a no-op here. The surface's catalog belongs to the laptop; SetModel makes the engine consult its own catalog and move its own compaction point. The method remains on the interface for local agents and on the version-5 wire for compatibility with builds already in flight.

func (*Agent) SetConversationEffort

func (a *Agent) SetConversationEffort(rung string) bool

SetConversationEffort sets this conversation's rung and reports whether the word was one — the local door's contract exactly (internal/session's session.Agent.SetConversationEffort), so the surface's one path from the chord and the menu (internal/tui3's setEffortRung) cannot mean two things depending on which machine the conversation is on.

IT READS THE RESOLVED RUNG BACK BEFORE IT RETURNS, and that is not a convenience. The caller asks what the rung resolved to in the very next statement — to say "thinking stays high" when a level on the model is winning — and the engine's own push is still on the wire at that moment. A read that waited for the push would report the rung from before the keystroke.

func (*Agent) SetModel

func (a *Agent) SetModel(model string)

SetModel swaps it, and the surface's own copy moves with it.

THE LOCAL WRITE IS NOT A SECOND AUTHORITY. The engine announces the change to every surface on this conversation, with a revision that lands over the top of what is assumed here; the assumption only covers the round trip, which is the gap in which a person who pressed a key is looking at the row it changed.

func (*Agent) SetReasoningFor

func (a *Agent) SetReasoningFor(model, level string)

SetReasoningFor sets it, moving the surface's own copy on Agent.SetModel's terms — the picker's ctrl+t reads the level back the moment it sets one.

func (*Agent) SetSpendRail

func (a *Agent) SetSpendRail(usd float64) error

SetSpendRail waits for the engine to bind the new conversation limit before a setting receipt can claim that the open chat has it.

func (*Agent) SetTaskEffort

func (a *Agent) SetTaskEffort(id uint64, rung string) error

func (*Agent) SettleSupported

func (a *Agent) SettleSupported() bool

SettleSupported is the surface's own question, answered from the welcome rather than from a type assertion.

THE ASSERTION CANNOT SEE ACROSS THE WIRE — every *Agent has these methods, on every connection, whatever the machine at the other end is — which is Welcome.Folders's stated reason for existing and applies here word for word. internal/tui3 asks this before it draws a chip, so a window talking to an older engine draws the card with no answers, exactly as it does against an engine that has no task doors at all: absent, not broken.

func (*Agent) ShortTitle

func (a *Agent) ShortTitle() string

ShortTitle is a compatibility alias; old remote labels never override the full name.

func (*Agent) SkillFacts

func (a *Agent) SkillFacts(status string, limit int) ([]store.Fact, error)

SkillFacts is the far conversation's skill shelf, as that session reads it.

func (*Agent) SkillsSupported

func (a *Agent) SkillsSupported() bool

SkillsSupported answers for THE MACHINE AT THE OTHER END, off what it said at the door.

func (*Agent) StandingApprovalPosture added in v0.4.0

func (a *Agent) StandingApprovalPosture() string

StandingApprovalPosture is the far install's standing word as the welcome carried it — a memory read, on Agent.DefaultEffort's terms.

func (*Agent) StartDelegate

func (a *Agent) StartDelegate(ctx context.Context, name, brief string) (uint64, string, string, error)

StartDelegate hands the brief to the named program on the engine machine and returns the same receipt StartTask does. It is an ordinary call with the ordinary deadline: the engine admits the run at once.

func (*Agent) StartPlannerRun

func (a *Agent) StartPlannerRun(ctx context.Context, brief, hint string) (string, string, error)

StartPlannerRun opens the adaptive form on the engine machine.

func (*Agent) StartTask

func (a *Agent) StartTask(ctx context.Context, brief string, solo bool) (uint64, string, string, error)

StartTask commissions the work on the engine machine and returns its receipt: id, title, and the engine's line about where the work stands (empty on every ordinary start).

IT IS AN ORDINARY CALL WITH THE ORDINARY DEADLINE. It used to be given the engine's shaper window and five seconds more, because the engine held the command while a model wrote the brief; the engine admits at once now and the brief is written beside the work (internal/session's task_shape.go), so there is nothing on the far side worth a longer wait.

func (*Agent) StartTaskEffort

func (a *Agent) StartTaskEffort(ctx context.Context, brief string, solo bool, effort string) (uint64, string, string, error)

StartTaskEffort is Agent.StartTask with the one-task effort word said (`/task --best`, `/task --cheap`); the engine's router reads it for this task and nothing after it.

func (*Agent) Steer

func (a *Agent) Steer(text string) (<-chan session.Event, error)

Steer puts words into the running turn on the engine machine and returns the same live tail the local agent returns. It is its own method because FollowUp promises a later turn while Steer promises the next step boundary of this one; making the far end infer which was meant would erase the person's intent at the wire.

func (*Agent) SteerRepeatKnown

func (a *Agent) SteerRepeatKnown() bool

SteerRepeatKnown answers for THE MACHINE AT THE OTHER END, off what it said at the door (Welcome.SteerRepeat) — a fact this end could not otherwise know until it had already asked twice.

AND IT IS RE-READ RATHER THAN REMEMBERED. /new, /resume and a reconnect all replace the welcome, and the engine behind it can change with them.

func (*Agent) SteerTask

func (a *Agent) SteerTask(id uint64, line string) (session.SteerReceipt, error)

SteerTask carries a correction to the engine's node and keeps its whole receipt: delivered, delivered-and-woke, or held on the task's record while its work is being checked (internal/session's session.SteerReceipt).

It is the unnamed send — nothing about it can be recognised if it is sent twice — and it stays because callers that have no way to number their sends still have to be able to steer. Agent.SteerTaskFrom is the one a surface uses.

func (*Agent) SteerTaskFrom

func (a *Agent) SteerTaskFrom(id uint64, line string, from session.SteerSource) (session.SteerReceipt, error)

SteerTaskFrom carries the same correction WITH THE SURFACE'S OWN NAME FOR THE SEND on it, so that a crossing this end never heard the answer to can be asked again without the worker being corrected twice (TaskSteerArgs states the whole law).

A SEND WITH NO IDENTITY TAKES THE UNNAMED DOOR, exactly as the local engine's does: an empty session.SteerSource is a caller saying it cannot name this send, and inventing one here would be this end promising a guarantee its caller cannot keep. THE CONVERSATION IT WAS WRITTEN FOR CROSSES WITH IT and is checked there (TaskSteerArgs.Session), because this handle keeps pointing at the engine after /resume or /new have changed which conversation is open behind it.

func (*Agent) StopWork

func (a *Agent) StopWork() error

StopWork asks the engine to end all work in this conversation and suppress wakes.

func (*Agent) Submit

func (a *Agent) Submit(ctx context.Context, text string) (<-chan session.Event, error)

Submit runs one turn and streams its events.

THE CONTEXT BOUNDS THE CALL AND NOT THE TURN. Locally, cancelling the context handed to Submit cancels the work; here it can only cancel the round trip that STARTS the work, because the work is on another machine. The surface's own cancel is Agent.Interrupt, which is a frame of its own and travels, so nothing a person can press is lost — but a caller reading this method's signature should know which of the two it is holding.

func (*Agent) SubmitBash

func (a *Agent) SubmitBash(ctx context.Context, text string) (<-chan session.Event, error)

SubmitBash explicitly runs the person's command; ordinary Submit never interprets message content as executable shell syntax.

func (*Agent) SubmitFiles

func (a *Agent) SubmitFiles(ctx context.Context, text string, files []WireFile, images []session.Image) (<-chan session.Event, error)

SubmitFiles is Submit with files attached, and pictures with them where the message carried both.

THE BYTES ARE READ ON THE MACHINE THE PERSON IS SITTING AT, which is the only machine the path they typed means anything on — the same fact Agent.SubmitImage turns on, and the reason this method takes bytes it did not open a file for: the surface reads them at the moment enter is pressed, so the message is assembled from what was on disk when the person sent it.

The name is reduced to a name HERE, with this machine's own idea of what a separator is. A surface on Windows holding `C:\logs\run.txt` knows that `run.txt` is the name of it and the engine, which may be a Unix box where a backslash is an ordinary character, does not. The engine refuses a path regardless ([attachmentName]) — that is the boundary and it stays one — but the refusal it would make is not a thing anybody should have to see for a path this side could read correctly.

func (*Agent) SubmitImage

func (a *Agent) SubmitImage(ctx context.Context, text string, images []session.Image) (<-chan session.Event, error)

SubmitImage is Submit with pictures. THE BYTES ARE READ HERE, on the machine the person is sitting at, because that is the only machine the path means anything on: /image points at a file on their laptop and the engine has no way to open it. The same two ceilings the local lane applies are applied here (internal/session's image.go), for the same reason and one more — an unchecked path would put a multi-gigabyte file through an ssh pipe before anybody discovered it was too big.

func (*Agent) SubmitStanding

func (a *Agent) SubmitStanding(ctx context.Context, text string) (<-chan session.Event, error)

SubmitStanding is Submit for a draft the person marked as something to keep true. It rides the same method as an ordinary send with one flag on it, for the reason SubmitArgs.Standing states: the two turns differ only in what the ENGINE puts in front of the sentence, which is not a thing a wire can carry halfway.

func (*Agent) TakeBackDecision

func (a *Agent) TakeBackDecision(id uint64) error

TakeBackDecision is that in reverse, and resolves nothing either.

func (*Agent) TaskEffort

func (a *Agent) TaskEffort(id uint64) string

TaskEffort is an explicit read; rendering uses the standing task updates.

func (*Agent) TaskProposalsPending

func (a *Agent) TaskProposalsPending() ([]uint64, bool)

TaskProposalsPending is the far engine's open proposals, AND WHETHER IT SAID.

IT IS ASKED AND NOT REPLICATED because of when it is asked: a turn ending with a proposal card still on screen, which is neither a frame nor a pointer and happens once per turn at most (internal/tui3's [app.syncTaskAsk]).

THE SECOND ANSWER IS THE WHOLE POINT OF THE PAIR. The surface reads a missing id as "nobody is asking this any more" and writes a verdict on the card, so a call that timed out or a link that died would retire a question that is still open on the far machine. False is "this engine did not say", and the surface leaves the card exactly as it found it.

func (*Agent) TaskRetrySupported added in v0.4.0

func (a *Agent) TaskRetrySupported() bool

func (*Agent) TaskRoom

func (a *Agent) TaskRoom(id uint64, tail int) (session.TaskRecord, error)

TaskRoom reads the bounded tail of a node whose record URI may not exist yet.

func (*Agent) TaskSetupSupported

func (a *Agent) TaskSetupSupported() bool

func (*Agent) TaskUpdates

func (a *Agent) TaskUpdates() <-chan session.Event

TaskUpdates is the same lane for a caller that has no way to leave it. It exists because [tui3.taskAgent] asks for this shape and the leavable one is asserted on top of it; every door in this build takes the leavable road.

func (*Agent) Title

func (a *Agent) Title() string

Title is the name the session gave itself, read from memory. The engine states it when the naming errand settles, which is the only moment it ever changes — so a surface that has one has the one the session earned, and one that has none is looking at a conversation that has not earned one yet.

func (*Agent) TitleChanges

func (a *Agent) TitleChanges() <-chan session.Event

TitleChanges is the same lane for a caller with no way to leave it, which is the shape internal/tui3's own door asks for.

func (*Agent) Transcript

func (a *Agent) Transcript() []session.DisplayEntry

Transcript is the conversation so far, shaped for display.

func (*Agent) Typing

func (a *Agent) Typing()

Typing tells the engine somebody is writing. IT DOES NOT WAIT, it reports nothing, and a connection that cannot carry it swallows it — which is the same contract session.Agent.Typing has and the reason the surface may call it on every character (internal/tui3's [app.laneTyping]).

func (*Agent) Usage

func (a *Agent) Usage() session.Usage

Usage is the session's running total, read from memory. The engine states it at every turn end, ahead of the EventTurnDone that the surface settles on (server.go's emit), so the figures a settle reads are that turn's.

func (*Agent) WatchHarnessDesigns

func (a *Agent) WatchHarnessDesigns() (<-chan session.Event, func())

WatchHarnessDesigns is this surface's subscription to the far conversation's harness lane: the design being written, the card that asks whether to keep it, and the intake card of a saved program the session is offering.

func (*Agent) WatchQuestions

func (a *Agent) WatchQuestions() (<-chan session.Event, func())

WatchQuestions is this surface's subscription to every question the far conversation raises, withdraws or has answered, with the whole object on each event. It hands back the lane and the way out of it.

WHAT IS OPEN IS FORGOTTEN FIRST. The engine replays it onto the subscription this call opens, so the replica is rebuilt from the engine's own account — and a surface taking up a SECOND conversation must not carry the first one's questions into it.

func (*Agent) WatchTaskUpdates

func (a *Agent) WatchTaskUpdates() (<-chan session.Event, func())

WatchTaskUpdates is this surface's standing subscription to the far conversation's task lane, and the way out of it.

IT IS A FRESH CHANNEL EVERY TIME, replacing the one before it. The surface opens a lane per conversation it takes up (internal/tui3's [app.watchTasks]), and handing back the same channel twice would leave two pumps reading one channel — each event delivered to whichever won the race, which is a rail that silently drops half of what it is told.

func (*Agent) WatchTitle

func (a *Agent) WatchTitle() (<-chan session.Event, func())

WatchTitle is this surface's subscription to the far conversation's name.

THE REPLICA IS MOVED BEFORE THE EVENT IS DELIVERED, and that is done at the frame rather than here (client.go's reader): a surface woken by this lane draws Agent.Title on the very next frame, and a cached name a beat behind the event announcing it is the whole defect this lane exists to avoid.

func (*Agent) WorkOutlivesExit

func (a *Agent) WorkOutlivesExit() bool

WorkOutlivesExit says whether this conversation keeps working once the view goes. It is Welcome.Persistent — the engine's own statement of its lifetime, which is the only honest source: a conversation hosted by the daemon on this laptop names no machine at all, so nothing about the transport or the host name can be read for it.

type ArchiveArgs

type ArchiveArgs struct {
	Dir      string `json:"dir"`
	Archived bool   `json:"archived"`
}

type AttachOptions

type AttachOptions struct {
	Open func(Hello) (*Session, error)

	// Host answers the version exchange (whois.go): which build is holding this
	// socket, whether it has work in flight, and whether it will retire.
	//
	// NIL IS "NOTHING IS HOLDING A CONVERSATION HERE" and it is the honest
	// answer for a pipe engine, which is why [Serve] leaves it unset. A pipe's
	// conversation cannot outlive its connection, so it can never be the stale
	// middle half this exchange exists to find.
	Host func(WhoIs) HostSelf
}

AttachOptions is ServeAttach's one function: which conversation this hello is attaching to. It differs from Options in exactly the way a host differs from a pipe — the answer may be a conversation that was already running when this connection dialled, and the same Session may be handed to several connections at once.

type AutonomyArgs

type AutonomyArgs struct {
	Kind   session.AskKind `json:"kind"`
	Policy session.Policy  `json:"policy"`
}

AutonomyArgs is one shape of question and what may answer it from now on.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is one connection to one engine. It is safe for concurrent use, which it has to be: the surface asks synchronous getters from its update loop while a turn's events are arriving on the reader.

func Dial

func Dial(conn io.ReadWriteCloser, host string, hello Hello) (*Client, error)

Dial performs the handshake on an already-open pipe pair and returns the live client. It is separate from spawning ssh on purpose: the spawning belongs to the door (cmd/codeaf, which owns processes and flags), and a test drives this over an io.Pipe with no ssh anywhere.

IT BLOCKS UNTIL THE ENGINE HAS ANSWERED, and that is the whole point of the order the door runs things in: the handshake happens while the terminal is still the person's, so ssh's own passphrase and host-key questions, and the sentence below about a version mismatch, are plain text on a plain screen.

func Roam

func Roam(host string, hello Hello, roam Roaming) (*Client, error)

Roam dials the engine and returns a client that redials for itself.

It is Dial with a dialer instead of a pipe, and the first attempt is NOT roamed: a machine that cannot be reached at all, a codeaf that is not installed there, a version that does not match — those are things the door has to be able to say plainly on a terminal that is still the person's (the prompt law in cmd/codeaf's chatv3_host.go), and quietly retrying them for five minutes would replace an answerable sentence with a hang.

func (*Client) Agent

func (c *Client) Agent() *Agent

Agent is the handle onto the engine's current session.

func (*Client) Archive

func (c *Client) Archive(dir string, archived bool) error

func (*Client) Attached

func (c *Client) Attached() int

Attached is how many OTHER surfaces are on this session, as the engine counted them at the door.

IT IS A FACT A PERSON MUST BE ABLE TO LEARN. Two windows on one conversation — two people, or one person and their own forgotten laptop — is a thing that changes what typing into it means, and a screen that hid it would be the one place codeaf kept a secret about who is in the room. Zero draws nothing, by the emptiness law.

func (*Client) CallsMade

func (c *Client) CallsMade() uint64

CallsMade is how many calls this client has put on the wire since it was dialled, and it is here for ONE reason: the laws that say a frame and a pointer cost nothing on the far machine are counts of round trips, and PERF.md's doctrine forbids proving such a thing with a clock. It counts calls and never stream frames, because a turn's events are the work a person asked for and the getters are the work nobody did.

func (*Client) ChangedSince

func (c *Client) ChangedSince(at time.Time) (int, int, error)

func (*Client) Close

func (c *Client) Close() error

Close ends the connection, which ends the ssh process. It is NOT what the surface's /new and /resume call — see Agent.Close, which flushes the remote session file and leaves the connection standing.

func (*Client) DepositFile

func (c *Client) DepositFile(name, mime string, data []byte) (string, error)

DepositFile puts one file in the far session's attachments folder WITHOUT saying anything: the browse page's drag-drop lane. It answers with the path the bytes landed at, on the ENGINE's disk.

THE NAME CROSSES AS THE PAGE GAVE IT AND IS JUDGED OVER THERE. That is the one place this parts from Agent.SubmitFiles, which reduces a path to a name on this side because the person typed it here on a machine that knows what its own separator is. This name came off a browser upload, so there is no local knowledge to apply to it and nothing to gain by pre-empting the boundary: the engine refuses a name that is a path ([attachmentName]), and the refusal is the engine's sentence with nothing softening it, exactly as Client.FetchFile's is.

func (*Client) Driver

func (c *Client) Driver() Driver

Driver is who holds the keyboard on this conversation right now, as the engine last said. It answers from memory with nothing on the wire behind it, because the surface asks it on the draw path (internal/tui3's watcher line).

A CLIENT WITH NO ENGINE BEHIND IT YET SAYS `Yours`. That is the honest default for the one instant it covers — before the first welcome there is no room to be a watcher in — and every road after it is an answer the engine gave.

func (*Client) DriverChanged

func (c *Client) DriverChanged() <-chan struct{}

DriverChanged is closed the next time the answer to Client.Driver moves.

IT IS HOW A HAND-OVER REACHES A SCREEN WITH NOBODY TOUCHING THE KEYBOARD. The other machine took the keyboard; nothing happened on this one; and the surface still has to stop drawing a composer this instant. So the wait is a channel the surface can sit in a select on, replaced rather than reused so a waiter that arrives late gets the NEXT change and never a stale one.

func (*Client) Err

func (c *Client) Err() error

Err is why this connection stopped, or nil while it is alive.

func (*Client) Facts

func (c *Client) Facts() session.Facts

Facts is what this connection last heard the engine say about itself. It is a memory read and it never touches the wire.

A DOOR THAT WANTS THE WHOLE SET WANTS THIS ONE. The five getters on Agent are the tui3.Agent interface's own shape and each answers one field off this same value; anything else asking about a remote conversation should ask here, because a caller that asked four getters would be reading four independent photographs of a set that is published whole.

func (*Client) FetchFile

func (c *Client) FetchFile(path string) (FetchedFile, error)

FetchFile asks the engine for the bytes of a file it holds, by a path on the ENGINE's disk. The path is never resolved here — it came off something the engine already said, and this side has no directory to resolve it against.

THE ERROR IS THE ENGINE'S SENTENCE AND NOTHING SOFTENS IT. A file this session will not hand over is a decision taken on the machine that owns the file, and a surface that redrew that refusal in its own words would be guessing at somebody else's boundary (the same bargain Client.SaveStanding makes with a store that refused a write).

func (*Client) Follow

func (c *Client) Follow() <-chan Following

Follow is the turns started by some other window on this conversation.

IT IS WHAT MAKES A SECOND WINDOW A WINDOW AND NOT A DEAD FRAME. A surface that is not holding the keyboard is still watching the work, and the work is a turn somebody started somewhere else.

func (*Client) ForgetMemory

func (c *Client) ForgetMemory(id string) error

func (*Client) Held

func (c *Client) Held() []HeldQuestion

Held is the questions this session raised while nobody was attached, as they arrived in the welcome. They are already here on the first frame a returning surface draws — see HeldQuestion for why they waited rather than expired.

func (*Client) HeldQuestions

func (c *Client) HeldQuestions() ([]HeldQuestion, error)

HeldQuestions is what this session asked while nobody was attached, asked for over the wire rather than read off the welcome.

THERE ARE TWO DOORS ONTO THE SAME LIST BECAUSE THERE ARE TWO MOMENTS. The welcome carries them so the first frame a returning surface draws already has them (Client.Held); this asks again, which is what a surface wants after it has answered one, after a session swap, or when it has been sitting attached for a while and something was raised on another surface's watch.

AN ERROR IS AN ERROR HERE and not an empty list, for Client.StandingItems' reason: "nothing is waiting" and "the far end did not answer" are different facts, and a surface that drew the second as the first would be quietly telling a person there is nothing to answer.

func (*Client) Host

func (c *Client) Host() string

Host is the ssh destination this client dialled.

func (*Client) Ledger

func (c *Client) Ledger(since time.Time) (LedgerReading, error)

func (*Client) LinkNote

func (c *Client) LinkNote() string

LinkNote is the quiet true sentence about the connection right now, and the empty string whenever there is nothing to say — which is almost always, and is what the emptiness law asks a status line to draw as nothing at all.

THE LOUD SENTENCE IS NOT THIS ONE. A link that has merely dropped is being redialled and says `reconnecting to devbox…`; the sentence about a connection that is gone belongs to a client that has stopped trying, and Client.Err is where that one lives.

func (*Client) ListDir

func (c *Client) ListDir(path string) (DirListing, error)

ListDir asks the engine for one directory, by a path on the ENGINE's disk. The path is never resolved here, for the reason Client.FetchFile gives. The error is the engine's sentence and nothing softens it.

func (*Client) ListMemories

func (c *Client) ListMemories(scope string, limit int) ([]store.Memory, error)

func (*Client) Live

func (c *Client) Live() (uint64, <-chan session.Event)

Live is the turn that was already running when this surface arrived, and the channel its events come out of. It answers zero and nil when the session was idle, which is the ordinary case.

THE CHANNEL IS THE SAME KIND OF CHANNEL A SUBMIT ANSWERS WITH, on purpose: a surface that reattaches mid-turn should draw that turn with the code that draws every turn, and the only thing it lacks is the StreamRef it would have got from opening it. This hands that back.

func (*Client) MemoryProvenance

func (c *Client) MemoryProvenance(id string) (string, string, time.Time, error)

func (*Client) NewSession

func (c *Client) NewSession() (Welcome, error)

NewSession asks the engine to swap to a fresh session, and answers with the facts about it. It is /new, and the Welcome it returns replaces the one Dial got — the session file changed, and everything on screen that names it has to name the new one.

func (*Client) NewsSilent

func (c *Client) NewsSilent() bool

NewsSilent reports whether the engine at the far end has given no sign that it sends the status line's news: its welcome did not say so (Welcome.News) and not one "phase" or "lane" frame has arrived on this connection.

IT IS A READING, NOT A VERDICT, and the surface asks it only once a whole answer has come back. An engine with the news posts a phase on every request it makes, so an answer that arrived with none beside it came from an engine built before the frames existed — the one case where the live rate and the machine are missing for a reason nothing on the screen would otherwise name.

It answers from two fields already held, with nothing on the wire behind it, because the surface may ask it on the update loop.

func (*Client) OpenSession

func (c *Client) OpenSession(path string) (Welcome, error)

OpenSession asks the engine to swap to a session it already has, by transcript path. It is /resume, and the path came off Client.Recent, so it is a path on the ENGINE's disk and is never resolved here.

func (*Client) Ping

func (c *Client) Ping() (time.Duration, error)

Ping measures one empty call to the engine and back.

THE CLOCK STAYS ON THIS MACHINE. Two hosts need not agree about the time, while the elapsed time around one call is exactly the path a keystroke and its answer use. A reconnecting client refuses before [Client.call], so the gentle meter on the surface never adds traffic to a link already trying to find its way back.

func (*Client) Recent

func (c *Client) Recent() []session.Summary

Recent is this workspace's past conversations, as the engine's disk holds them. It is the remote answer to the welcome box's right column and the /resume picker's rows.

AN ERROR IS AN EMPTY LIST, which is what the local door does with an unreadable session directory (cmd/codeaf's v3RecentSessions says why): this answers a list a person may never look at, and the one thing it must not do is take a keystroke away.

func (*Client) RestoreMemory

func (c *Client) RestoreMemory(id string) error

func (*Client) SaveStanding

func (c *Client) SaveStanding(item standing.Item) error

SaveStanding writes one item back to the engine machine's store — the pause and the stop keys, and nothing else on this surface.

THE REFUSAL TRAVELS. internal/tui3's StandingSeam.Save returns the write's error and home prints it rather than swallowing it, because a row that redrew as paused over a store that refused the write would be the screen lying about somebody else's disk. So the engine's error comes back as this call's error and nothing is invented here.

func (*Client) SearchConversations

func (c *Client) SearchConversations(terms string, limit int) ([]store.ConversationHit, error)

func (*Client) Snapshot

func (c *Client) Snapshot(limit int) (store.MemoryShelves, error)

func (*Client) StandingItems

func (c *Client) StandingItems(workspace string) ([]standing.Item, error)

func (*Client) StandingWatch

func (c *Client) StandingWatch() (standing.WatchStatus, bool)

StandingWatch reads the scheduler on the engine machine.

func (*Client) StatPaths

func (c *Client) StatPaths(paths []string) ([]PathFact, error)

StatPaths asks the engine which of these candidate paths exist under its two-roots law. One call per burst of new rows — the batching is the whole reason this is not a per-word round trip.

func (*Client) Take

func (c *Client) Take() error

Take asks for the keyboard. It is one round trip and it does not refuse — see MethodTake for why a person pressing enter on their own work is not a thing the engine weighs.

func (*Client) TakeNotice

func (c *Client) TakeNotice() string

TakeNotice is one sentence the surface should show once and then forget, and the empty string when there is none. IT DRAINS: the sentence is a piece of news about something that just happened to this connection, not a condition that stays true, so a second reading answers nothing.

Only a redial writes one, and only for the two things a redial can discover that a person must not be left to work out from the screen: the engine did not keep the turn, and the engine came back with a different conversation open.

func (*Client) TaskRecord

func (c *Client) TaskRecord(uri string, tail int) (session.TaskRecord, error)

TaskRecord is ONE ROW of the engine machine's record, read deeper than Client.World reads it: the last thing that piece of work said, and whether its journal is still on that machine's disk (MethodPlacesTask).

THE ERROR IS ANSWERED AND NOT SWALLOWED, for Client.World's reason narrowed to one card: a record that came back empty is a piece of work that said nothing at the end, and a call that failed is a card that has not been told yet. The surface draws a different line for each, and only an error can carry the second.

func (*Client) TeamsAcceptClosing

func (c *Client) TeamsAcceptClosing(id string) (AcceptClosingReply, error)

TeamsAcceptClosing closes the team a decided closing packet reports on, on the engine.

func (*Client) TeamsApplyDefault

func (c *Client) TeamsApplyDefault(key, raw string) (teamstore.Defaults, error)

TeamsApplyDefault writes one `teams.` row on the engine and answers the five defaults as they stand after the write.

func (*Client) TeamsDecide

func (c *Client) TeamsDecide(id, by, decision, reason string) (teamstore.Packet, error)

TeamsDecide records a decision on the engine.

func (*Client) TeamsDefaults

func (c *Client) TeamsDefaults() (teamstore.Defaults, error)

TeamsDefaults is the engine profile's `teams.` defaults.

func (*Client) TeamsDelete

func (c *Client) TeamsDelete(team string) (DeleteTeamReply, error)

TeamsDelete forgets a closed team on the engine.

func (*Client) TeamsEscalate

func (c *Client) TeamsEscalate(id, by, to, reason string) (teamstore.Packet, error)

TeamsEscalate sends a packet up on the engine.

func (*Client) TeamsPackets

func (c *Client) TeamsPackets(scope, stamp string) (PacketsReading, error)

TeamsPackets is the packets waiting on scope, or Same when the packet files are still at stamp.

func (*Client) TeamsRaise

func (c *Client) TeamsRaise(p teamstore.Packet) (teamstore.Packet, error)

TeamsRaise records p on the engine and answers it as written.

func (*Client) TeamsRead

func (c *Client) TeamsRead(stamp string, reserved []float64) (TeamsReading, error)

TeamsRead asks for the engine machine's teams file, telling it the stamp this window holds ("" for none); a reading with Same set carries no teams.

func (*Client) TeamsSpend

func (c *Client) TeamsSpend(team, day, stamp string) (SpendReading, error)

TeamsSpend is team's spend on day ("" the engine's today), or Same when nothing moved since stamp.

func (*Client) TeamsTraffic

func (c *Client) TeamsTraffic(team, after string, limit int) (TeamsTraffic, error)

TeamsTraffic reads one team's log on the engine machine after a cursor.

func (*Client) TeamsUpdate

func (c *Client) TeamsUpdate(base string, teams []teamstore.Team) (TeamsReading, error)

TeamsUpdate writes teams as the engine machine's whole teams file while it is still at stamp base. A reading with Stale set wrote nothing.

func (*Client) TeamsWrapUp

func (c *Client) TeamsWrapUp(team, text string) error

TeamsWrapUp asks the engine's manager of team to wrap up.

func (*Client) UpdateMemory

func (c *Client) UpdateMemory(id, title, text string, tags []string) error

func (*Client) Welcome

func (c *Client) Welcome() Welcome

Welcome is what the engine said at the door: the workspace it resolved, the session file it opened, and whether it found that file or made it.

func (*Client) World

func (c *Client) World() (session.World, error)

StandingItems is the engine machine's standing items for one workspace: the far half of what a local surface reads straight off its own disk (cmd/codeaf's [v3StandingSeam]).

IT ANSWERS AN ERROR RATHER THAN AN EMPTY LIST, which is the one place it differs from Client.Recent, and the difference is what the caller does with it: this list is asked for again and again on a beat, so a caller that keeps the last good answer must be able to tell "there is nothing here" from "the round trip failed" — a fault redrawn as an empty band would be the screen saying the person's watches had gone away. World is the engine machine's places root, walked: what home lists, what the tasks place reads its rows out of, and what the standing, spend and search places each take one fact from (MethodPlacesWorld).

THE ERROR IS ANSWERED AND NOT SWALLOWED, unlike Client.Recent next door, and the difference matters: a recent-sessions list that came back empty is a picker with no rows, which is a small wrong. A WORLD that came back empty is every place on the surface saying this machine has nothing on it — so the caller has to be able to tell "the engine has no world door" and "the call failed" from "there is genuinely nothing there", and only an error can carry the first two (cmd/codeaf's [hostWorld] is what does the telling).

type ConnectArgs

type ConnectArgs struct {
	ID      string `json:"id"`
	Approve bool   `json:"approve,omitempty"`
	Key     string `json:"key,omitempty"`
}

type ConnectedArgs

type ConnectedArgs struct {
	Service string `json:"service"`
	Account string `json:"account"`
}

type ConsentArgs

type ConsentArgs struct {
	ID    uint64               `json:"id"`
	Allow bool                 `json:"allow"`
	Scope session.ConsentScope `json:"scope,omitempty"`
}

type DecideArgs

type DecideArgs struct {
	ID       string `json:"id"`
	By       string `json:"by"`
	Decision string `json:"decision"`
	Reason   string `json:"reason,omitempty"`
}

DecideArgs is a decision on one packet.

type DelegateStartArgs

type DelegateStartArgs struct {
	Name  string `json:"name"`
	Brief string `json:"brief"`
}

DelegateStartArgs carries the program's name and the person's brief, both as typed: the name is resolved against the engine machine's build there.

type DeleteTeamArgs

type DeleteTeamArgs struct {
	Team string `json:"team"`
}

DeleteTeamArgs names the closed team to forget.

type DeleteTeamReply

type DeleteTeamReply struct {
	Gone  []string `json:"gone"`
	Stamp string   `json:"stamp"`
}

DeleteTeamReply is every team id forgotten and the teams file's stamp after.

type DepositedFile

type DepositedFile struct {
	Path string `json:"path"`
}

DepositedFile is the engine's word on a file it just kept: the path ON THE ENGINE'S DISK where the bytes landed.

IT IS THE ENGINE'S ANSWER AND IS NEVER DERIVED HERE, the same law FetchedFile.Name and DirListing.Path state from their own directions. The surface named the file and the ENGINE chose the directory, stamped the name and made it unique ([writeAttachment]), so the only machine that can say where the thing now is is the one it is now on — a surface that guessed would be showing a person a path that is nearly right.

type Dialer

type Dialer func() (io.ReadWriteCloser, error)

Dialer opens ONE fresh transport to the engine. It is the seam the redial loop turns on: the spawning of ssh belongs to the door (cmd/codeaf, which owns processes and flags) and this package must be able to ask for it again without knowing what it is.

type DirEntry

type DirEntry struct {
	Name    string `json:"name"`
	Dir     bool   `json:"dir,omitempty"`
	Size    int64  `json:"size,omitempty"`
	ModTime int64  `json:"mtime,omitempty"`
	MIME    string `json:"mime,omitempty"`
}

DirEntry is one row of a listing. ModTime is unix seconds because a listing is drawn, not computed with, and a whole time.Time per row is frame weight.

type DirListing

type DirListing struct {
	Path      string     `json:"path"`
	Entries   []DirEntry `json:"entries"`
	Truncated bool       `json:"truncated,omitempty"`
}

DirListing is the engine's answer: the path AS THE ENGINE RESOLVED IT — the surface must never derive it — and the entries, directories first, then files, each half sorted by name. Truncated says the cap (listDirMax, file.go) cut the tail rather than the directory ending there.

type Driver

type Driver struct {
	// Yours says the surface reading this frame is the one that drives. It is
	// the ordinary case and the only one that draws nothing.
	Yours bool `json:"yours,omitempty"`
	// Machine is the driver's machine name as [Hello.Surface] gave it, empty
	// when that surface sent none. It is sanitized ([machineLabel]) because it
	// is drawn.
	Machine string `json:"machine,omitempty"`
	// Here says the driver is another window on THIS surface's own machine,
	// which is the case a person reads as a window they forgot rather than as a
	// machine they walked away from.
	Here bool `json:"here,omitempty"`
}

Driver is who holds the keyboard on one conversation, as told to ONE surface.

IT CARRIES THE FACT AND THE READING, and that is why it is per-recipient rather than one broadcast fact. "The driver is macbook" means two different sentences depending on who hears it: to the window sitting on macbook beside it, the honest word is the one codeaf already uses at home — `another window` — and to a surface on spark it is the machine's name. Only the engine knows both names, so only the engine can answer that; and the SURFACE still owns the words, because the rest of the line it goes in is about keys on this keyboard (Decision 6: the engine machine is the authority, the surface owns the screen).

type Engine

type Engine struct {
	// Headless records the approval context the agent was built for. Reusing an
	// interactive agent must not silently give a headless caller its default gate.
	Headless bool
	// Agent is the conversation the surface starts on. Required.
	Agent WrappedAgent
	// RefreshModelSources updates the engine's own agents from its own profile
	// immediately before a model-set call. It is nil for engines with no
	// profile behind them and changes no wire shape: the existing SetModel call
	// is the notification that a local surface has already written the row.
	RefreshModelSources func()
	// RefreshApprovals updates this conversation's running gate from the engine's
	// own profile immediately before a rule-scoped consent answer is applied.
	// It is nil for engines with no profile behind them and changes no wire
	// shape: the existing consent answer is the notification that a local
	// surface has already written the rule.
	RefreshApprovals func()
	// Closed is called once for each conversation this engine shut down, with
	// the agent that was closed, so the door that built it can forget it.
	//
	// IT IS THE PAIR OF WHATEVER REGISTERED THE AGENT, and it exists because
	// [Engine.RefreshModelSources] reaches the conversations its door is
	// holding: a door that registers and never forgets grows that list for as
	// long as the process lives, and an engine process outlives every
	// conversation in it. Nil for a door that retains nothing. It changes no
	// wire shape — a conversation ending is already the end of its stream.
	Closed func(agent WrappedAgent)
	// ProfileDir is the profile directory this engine process resolved. It is
	// carried in Welcome so a linked-local surface writes every local row back
	// to the profile the running conversation actually reads. A remote surface
	// does not use the path for local writes.
	ProfileDir        string
	UnreadProfileKeys []string
	// Workspace is the directory the engine resolved and works in — the answer
	// to the path the hello asked for, which the welcome carries back.
	Workspace string
	// SessionFile is the transcript being written, and Resumed says it was
	// picked up rather than created.
	SessionFile string
	Resumed     bool

	// Place is the session folder on THIS machine (session's place.go, Decision
	// 26). It is here for one reason: a picture arrives on this wire as bytes
	// and has to be written down before it can be journaled (image.go), and
	// where it lands is a question about the engine's disk that only the
	// engine's own session folder can answer. The zero Place is the legacy
	// layout. A pasted picture earns no row in the deliverables index — it is
	// the person's input, not something made for them — so the index path
	// itself is not carried here.
	Place session.Place
	// Note is the one sentence worth showing once: "session open elsewhere —
	// started a new one" travels here, the same words the local door puts on
	// the surface's entry notice.
	Note string
	// Launch is the shape this conversation was built with, echoed on every
	// welcome so a surface can tell the conversation it asked for from the one
	// it joined ([LaunchShape]). Nil is the engine's own defaults.
	Launch *LaunchShape

	// ApprovalMode is this machine's own answer to "does a tool run without
	// asking", read once at boot off the profile the boot closure resolved
	// against, and carried unchanged through a session swap (the profile
	// belongs to the machine, not to the file that happens to be open). It is
	// what turns the YOLO badge honest again over --host (host.go's approvalPosture,
	// tui3.go's ApprovalMode option).
	ApprovalMode string
	// BashBackgroundAfterSeconds is the foreground-command handoff clock this
	// engine armed. It travels for ApprovalMode's reason: a remote surface's
	// profile belongs to another machine, and zero is a meaningful off posture.
	BashBackgroundAfterSeconds int

	// Fresh builds a replacement agent on the same config with a new session
	// file, and returns it with that file's path. It is what Session.New calls,
	// and it is the local surface's /new closure by another name. Nil makes the
	// method fail rather than pretend.
	Fresh func() (WrappedAgent, string, error)

	// Open builds a replacement agent on the same config pointed at an existing
	// transcript, and says whether that file was found. It is Session.Open, and
	// it is the picker's Resume closure by another name.
	Open func(file string) (WrappedAgent, bool, error)

	// Recent lists the conversations this workspace has had. It is the one door
	// that exists only because the surface is remote: a local one reads the
	// session directory off its own disk, and a remote one cannot see it.
	Recent func() []session.Summary

	// StandingItems and StandingSave are this MACHINE'S ambient side, for the
	// same reason Recent is here: the store is a directory of documents under
	// the engine's own state root, a local surface opens it directly, and a
	// remote one has no way to. The workspace is a path on THIS disk, which is
	// the only kind of path an item's own Workspace field ever holds.
	//
	// Nil is the ambient side off for this engine, and it is answered as a
	// refusal rather than as an empty list — a capability that cannot work is
	// absent, and the surface keeps the difference between "no items" and "no
	// door" (internal/remote's Client.StandingItems says what it does with it).
	StandingItems func(workspace string) ([]standing.Item, error)
	// StandingSave writes one item back. THE STORE'S OWN REFUSAL IS THE ERROR:
	// internal/standing validates what it is asked to write, and a surface that
	// redrew a row as paused over a rejected write would be lying about this
	// disk, so nothing here softens it.
	StandingSave func(item standing.Item) error
	// StandingWatch reads this machine's scheduler. Nil means this engine has no
	// scheduler to ask, which the surface renders as no line.
	StandingWatch func() (standing.WatchStatus, bool)

	// World is the walk of this machine's places root: every project, every
	// conversation in it, and the work each of those ran. It is the reading five
	// of the surface's seven places are built from ([MethodPlacesWorld] names
	// them), which is why one door serves all five.
	World func() session.World

	// Ledger is the spend place's reading: the priced lines of THIS machine's
	// usage ledger at or after a floor the surface named, and whether the file
	// holds any priced line at all outside it ([LedgerReading] says why the
	// second fact cannot be derived from the first). Nil is a refusal, which the
	// surface draws as the sentence spend has always said.
	Ledger func(since time.Time) LedgerReading

	// Search is one full-text query over every message THIS machine has kept.
	// Nil is a refusal for the same reason, and it is the ordinary state of an
	// engine whose memory row is off: the index and the memory store are one
	// database today, and a machine that is not remembering has neither.
	Search func(terms string, limit int) ([]store.ConversationHit, error)

	// Memory is THIS machine's memory store, readings and writes together.
	//
	// IT IS ONE FIELD FOR SEVEN METHODS AND THAT IS THE POINT. The memory place
	// is the only place on the surface that WRITES, so a wire that carried its
	// readings and not its writes would hand a person a page of the far
	// machine's memories whose `e` and `f` keys edited this laptop's. The store
	// crosses whole or it does not cross, and nil is memory off over there —
	// which the surface says in those words rather than in this session's own
	// ([EngineMemory]).
	Memory  EngineMemory
	Archive func(dir string, archived bool) error
	// PlacesRoot is the directory World walked, carried on the welcome so the
	// surface can put THIS conversation back into a walk taken before it existed
	// ([Welcome.PlacesRoot] holds the argument). Empty says nothing about the
	// world door; a build that answers a world and no root simply cannot adopt.
	PlacesRoot string
	// TaskRecord is ONE ROW of that record read deeper than the walk reads it:
	// the last thing that piece of work said, out of the journal it left here
	// ([MethodPlacesTask]). nil is the same absence World's nil is — the door is
	// answered as a refusal and the card says so, rather than the surface reading
	// a path on its own disk that only exists on this one.
	TaskRecord func(uri string, tail int) (session.TaskRecord, error)
}

Engine is one opened conversation and the doors that replace it. The door that builds it (cmd/codeaf) owns config resolution, session-file resolution and the locked-file fallback; this package owns nothing about how an agent is made and everything about how one is spoken to.

type EngineMemory

type EngineMemory interface {
	Snapshot(limit int) (store.MemoryShelves, error)
	ChangedSince(t time.Time) (learned, letGo int, err error)
	ListMemories(scope string, limit int) ([]store.Memory, error)
	UpdateMemory(id, title, text string, tags []string) error
	ForgetMemory(id string) error
	RestoreMemory(id string) error
	MemoryProvenance(id string) (sessionID, sessionTitle string, writtenAt time.Time, err error)
}

EngineMemory is the ENGINE MACHINE'S memory store as this wire needs it, and it is deliberately the same seven methods internal/tui3's MemoryStore asks for — one interface, satisfied by the same adapter at both ends, so that a method added to the place cannot land here as a method the far end silently does not have (cmd/codeaf's [v3Brain] satisfies both by construction).

NIL IS MEMORY OFF ON THAT MACHINE, and it is answered as a refusal rather than as an empty store — the same reading Engine.World and Engine.StandingItems already ask for. The surface keeps the difference, because "that machine remembers nothing" and "that machine is not remembering" are two sentences and only one of them is about a setting.

type EscalateArgs

type EscalateArgs struct {
	ID     string `json:"id"`
	By     string `json:"by"`
	To     string `json:"to"`
	Reason string `json:"reason,omitempty"`
}

EscalateArgs sends one packet to To: a team above, or teamstore.Person.

type EventWire

type EventWire struct {
	session.Event
	Err string `json:"Err,omitempty"`
}

EventWire is a session.Event that survives JSON. Err is an interface and marshals to nothing, so the string rides beside it and shadows it on the wire; [EventWire.Event] restores the one field that needs restoring.

func WireEvent

func WireEvent(ev session.Event) EventWire

WireEvent wraps an event for sending.

func (EventWire) Unwire

func (w EventWire) Unwire() session.Event

Unwire unwraps a received event.

type FactsPush

type FactsPush struct {
	Rev   uint64        `json:"rev"`
	Facts session.Facts `json:"facts"`
}

StreamRef is the result of the three stream-opening calls: the id every "event" frame of that turn carries. The stream ends with a "closed" frame bearing the same id, which is the channel close. FactsPush is one statement of the whole fact set, and the revision that orders two of them.

IT IS THE WHOLE SET AND NEVER A DELTA. A push naming only what changed would be smaller and would be wrong the first time one went missing: a surface that had lost a frame would carry a stale field forever with nothing able to tell it so. The set is five short fields and a tiny map — smaller than one line of a reply — so every push is complete and the newest one is always the truth.

REV IS WHY IT CAN BE READ OUT OF ORDER SAFELY. Two facts can move at almost the same instant on the engine, and the two pushes race to the writer; the number is minted where the order is decided (under the session's own lock), so a surface keeps the highest it has seen and drops anything older. Without it a late push would overwrite a newer one and the status line would go backwards, which is the one thing a live row must never do.

type FetchFileArgs

type FetchFileArgs struct {
	Path string `json:"path"`
}

FetchFileArgs is the surface asking for a file the engine holds.

THE PATH IS THE ENGINE'S AND IS NEVER RESOLVED HERE, which is the same law every path on this wire obeys. It comes off something the engine already said — a deliverable's row, a tool result, the session file itself — and the engine is free to refuse a path outside what this session may hand over. THE REFUSAL IS THE ENGINE'S TO MAKE: a surface cannot know that machine's boundaries, and a client-side check would be a permission decision taken on the wrong machine.

type FetchedFile

type FetchedFile struct {
	// Name is what the surface should call it when it writes it down. It is the
	// base name of the engine's path, and it is the engine's answer rather than
	// something this side derives, for the reason [WireFile.Name] states in the
	// other direction.
	Name string `json:"name"`
	MIME string `json:"mime,omitempty"`
	// Size and Hash describe the whole file the bytes came from. Hash is the
	// lowercase hex SHA-256 of Bytes — the same digest internal/cas keys on —
	// so a surface that caches by content can ask "do I already have this"
	// before it writes anything down.
	Size  int64  `json:"size,omitempty"`
	Hash  string `json:"hash,omitempty"`
	Bytes []byte `json:"bytes"`
}

FetchedFile is one file coming back the other way.

type Following

type Following struct {
	Replay  bool
	Covered func() bool
	// Said is the message that opened the turn, empty when the transcript
	// already has it — see [Turn.Said].
	Said string
	// Events is that turn, arriving.
	Events <-chan session.Event
}

Following is a turn this surface did not start: the sentence that opened it, where the engine sent one, and the channel its events arrive on.

THE CHANNEL IS THE SAME KIND OF CHANNEL A SUBMIT ANSWERS WITH, on purpose and for Client.Live's reason: a window watching somebody else's turn should draw it with the code that draws every turn, and the only thing it lacks is the StreamRef it would have got from opening it.

type Frame

type Frame struct {
	// Kind says what this frame is: "hello", "welcome", "call", "result",
	// "event", "closed", "facts", "task", "design", "question", "phase", "lane",
	// "turn", "driver", "moved", "fatal".
	//
	// "facts", "task", "design", "question", "phase" and "lane" are the KINDS
	// THAT ANSWER NOTHING. The third and fourth are version 11's harness lane
	// and version 14's questions lane, and each carries one [EventWire],
	// exactly as "task" does (standinglane.go); the last two carry the phase
	// clock and the lane sighting (news.go). Every other
	// frame from the engine either replies to a call or belongs to a stream a
	// call opened; these are the engine saying something the surface did
	// not ask for on that frame, because the whole point of them is that the
	// surface never has to ask. "facts" carries a [FactsPush]; "task" carries
	// one [EventWire] off the standing task lane (tasklane.go); "phase" carries
	// a [PhaseWire] and "lane" a [LaneWire], both off the news desk this engine
	// keeps for every conversation it is running (news.go). None of them has an
	// ID, and a build that does not know the kind ignores it, which is what the
	// reader in client.go already does with every kind it has no case for.
	Kind string `json:"kind"`
	// ID correlates a call with its result, and an event with the Submit that
	// opened its stream. The client mints call ids; the server mints stream ids
	// and names the stream in the call's result.
	ID uint64 `json:"id,omitempty"`
	// Method is the call's name, on "call" frames only — a [Method] constant.
	Method string `json:"method,omitempty"`
	// Payload is the frame's body, shaped by Kind and Method.
	Payload json.RawMessage `json:"payload,omitempty"`
	// Encoding and Data carry a large payload after both ends agreed on the
	// encoding at the version door. Payload stays the ordinary representation
	// so an older peer sees exactly the frames it has always seen; Data is only
	// emitted to a peer whose hello named this encoding.
	Encoding string `json:"encoding,omitempty"`
	Data     []byte `json:"data,omitempty"`
	// Error is a call that failed, on "result" frames, and the reason on
	// "fatal" frames.
	Error string `json:"error,omitempty"`

	// Seq is this event's position in its stream, counting from 1, on "event"
	// and "closed" frames only.
	//
	// IT EXISTS SO A REATTACH CAN ASK FOR THE GAP AND NOT FOR THE
	// CONVERSATION. A surface that dropped mid-turn has already drawn some of
	// that turn; the transcript door would give it the finished shape of what
	// it half-has, and the journal does not hold a partial reply's deltas at
	// all. So the engine keeps the turn's events in memory while it runs, the
	// returning surface says how far it got ([Hello.Resume]), and the engine
	// sends what came after. Numbering from 1 makes zero mean "nothing of this
	// stream has been seen", which is the state a surface attaching for the
	// first time is in.
	//
	// A "closed" frame carries the seq of the LAST event it follows, so a
	// surface can tell a stream that ended from one it lost the tail of.
	Seq uint64 `json:"seq,omitempty"`
}

Frame is one line on the wire, either direction.

type HarnessArgs

type HarnessArgs struct {
	ID    uint64 `json:"id"`
	Run   bool   `json:"run"`
	Model string `json:"model,omitempty"`
}

type HeldQuestion

type HeldQuestion struct {
	// Kind is which resolve-door answers this: "consent", "standing",
	// "harness", "connect". It is a string rather than an enum because the
	// envelope is the contract and a newer engine holding a kind this build
	// does not draw must not be a broken conversation — an unknown kind is
	// SKIPPED by the surface, which leaves the question waiting for a build
	// that knows it, exactly as it was.
	Kind string `json:"kind"`
	// Event is the frame that raised it, verbatim, so the surface draws the
	// card it would have drawn live.
	Event EventWire `json:"event"`
	// Stream is the turn it belongs to, so a surface reattaching mid-turn puts
	// the card back where it was rather than at the end of the room.
	Stream uint64 `json:"stream,omitempty"`
	// Since is when it was raised. THE SCREEN SHOULD SAY HOW LONG SOMETHING HAS
	// WAITED: a consent card from four hours ago is a different thing to answer
	// than one from four seconds ago, and only the engine knows which it is.
	Since time.Time `json:"since"`
}

HeldQuestion is a card this session raised while nobody was attached.

THE EMPTY ROOM BECOMES A WAITING ROOM, and that is the single change that unlocks most of what --host could not do. Version 1's refusals — no harness design, no adaptive run, no "always" on a consent card — all had the same root: the answer to those questions travels on a lane a connection did not carry, so a question raised with nobody there would sit in an empty room and expire. A persistent engine is exactly the machine that CAN hold one: the card is asked, nothing proceeds, and the next surface to attach is handed it with the time it has been waiting.

It is deliberately NOT a copy of each card's own type. The card itself already crosses as an ordinary event (EventWire) and the surface already knows how to draw every kind of card there is; what a returning surface lacks is the KNOWLEDGE THAT ONE IS OUTSTANDING and the event that raised it. So this carries the raw event and the identity needed to answer it, and the surface replays it through the same door a live one goes through.

type Hello

type Hello struct {
	// SessionInstance scopes Resume cursors to the live owner that issued them.
	SessionInstance string `json:"sessionInstance,omitempty"`

	// Headless says this caller cannot answer approval questions. The engine
	// requires explicit approval settings instead of the interactive default.
	Headless  bool   `json:"headless,omitempty"`
	Version   int    `json:"version"`
	Workspace string `json:"workspace,omitempty"`
	// Session is an explicit session file to open, empty for the workspace's
	// latest-or-new (the same meaning the --session flag has locally).
	Session string `json:"session,omitempty"`

	// Model and Level are --model and --reasoning, carried in the frame that
	// BUILDS the session rather than applied to it a millisecond later.
	//
	// They retire a stub. Version 1 had no room for them, so the door set them
	// immediately after the handshake (cmd/codeaf's applyHostChoices), which
	// worked for every turn the person could type but left the session file's
	// first line naming the model the session was BORN on rather than the one
	// they asked for. Nobody on the screen could see the difference; the
	// journal could, and the journal is the record.
	Model string `json:"model,omitempty"`
	Level string `json:"level,omitempty"`

	// Launch is how the conversation should be BUILT, when this hello is the
	// one that opens it. Nil asks for the engine's own defaults, which is what
	// every remote surface sends: these are settings of the machine the session
	// runs on, and cmd/codeaf refuses them over --host and --at by name.
	Launch *LaunchShape `json:"launch,omitempty"`

	// Encodings are the optional frame payload encodings this surface can read.
	// They are negotiated INSIDE one protocol version because absence means the
	// old JSON payload and every added field is omitempty: an older engine
	// ignores this offer, and a newer engine sends it nothing encoded.
	Encodings []string `json:"encodings,omitempty"`

	// Resume is how far this surface got before it went away, one entry per
	// stream it still cares about. Empty is a surface that has seen nothing,
	// which is every first attach.
	//
	// THE ENGINE ANSWERS THE GAP AND NOTHING ELSE. Each cursor names a stream
	// and the last [Frame.Seq] this surface actually drew; the engine replays
	// from the one after it and then carries on live. A stream the engine no
	// longer holds — it finished long ago, or this is a different engine — is
	// answered with nothing rather than with an error: the transcript is the
	// authority on a finished turn, and the surface reads that anyway.
	Resume []StreamCursor `json:"resume,omitempty"`

	// Surface is this machine's short name — `macbook`, `spark` — as
	// [MachineName] reads it off os.Hostname.
	//
	// IT EXISTS SO A SCREEN CAN NAME THE WINDOW THAT HAS THE KEYBOARD. "somebody
	// else is typing" is a sentence that makes a person hunt; "typing from spark
	// now" is one they can act on, because they know where spark is. Two windows
	// on ONE machine send the same name, which is how the engine can tell the
	// far desk from the forgotten terminal behind this one.
	//
	// IT IS A LABEL AND NEVER AN IDENTITY. Nothing is authorized by it — a
	// connection is already whatever ssh or a pinned device key made it — and
	// the engine sanitizes it before it is drawn ([machineLabel]) because it is
	// text one machine sends for another machine's screen.
	Surface string `json:"surface,omitempty"`

	// Back says this surface has been in this conversation before and is coming
	// back from a link that dropped, rather than arriving for the first time.
	//
	// IT IS THE ONE THING THAT KEEPS "THE NEWEST WINDOW DRIVES" HONEST. A redial
	// is an attach the person did not make: they closed a laptop lid in one city
	// and started typing in another, and the lid's machine reconnecting in the
	// background half an hour later must not take the keyboard off the machine
	// they are sitting at. So a returning surface drives only if the keyboard is
	// going spare, and a NEW one always drives.
	Back bool `json:"back,omitempty"`

	// Join says this hello wants a conversation THAT IS ALREADY OPEN and will
	// take nothing else. [Session] names the transcript to look for, and a host
	// that is not running it answers an error rather than starting it.
	//
	// IT EXISTS BECAUSE "OPEN OR CREATE" IS THE WRONG VERB FOR A SECOND VIEW. A
	// surface that wants to READ one task of a conversation running next door is
	// asking about work that exists; booting a whole session so that the question
	// has an answer would start a model, take the transcript's lock away from
	// nobody, and hand back a conversation with none of the work in it. So the
	// two intentions are two flags rather than one hopeful one.
	//
	// AND IT IS MATCHED ON THE TRANSCRIPT AND NOT ON THE KEY. A host keys its
	// conversations by whatever the FIRST hello said — which for the ordinary
	// launch is the empty string, meaning "this workspace's latest" — so a second
	// surface naming the same conversation by its file would miss it and be given
	// a new one. The file is the identity every other part of this program uses
	// for a conversation, so it is the one a join is answered on.
	Join bool `json:"join,omitempty"`

	// New says this hello MINTS A CONVERSATION OF ITS OWN and will not be given
	// one that is already open. It is the exact opposite of [Join], and the two
	// are separate flags for the same reason Join is separate from an ordinary
	// hello: "open or create" is the wrong verb for both intentions.
	//
	// IT IS WHAT MAKES A SECOND TAB A SECOND CONVERSATION. An ordinary hello
	// naming no session means "this workspace's latest-or-new", so two surfaces
	// that both say nothing are asking for the SAME conversation — which is the
	// whole of "sit down somewhere else and be in it" and exactly wrong for a
	// window opening another chat beside the one it already has. Without this
	// flag the only way to mint one was [MethodSessionNew], which SWAPS the
	// conversation on the connection that asked and ends the one it replaced
	// (internal/remote's Session.swap): one connection, one conversation, and the
	// sentence a person read on screen.
	//
	// THE ENGINE CHOOSES THE FILE. This hello carries no session, because the
	// transcript a new conversation lands on is a question about the engine's own
	// disk; the welcome names what it opened, and a host keys the conversation by
	// that answer so a later window can name it and join.
	//
	// A REDIAL NEVER SAYS IT TWICE. [Client.helloNow] clears it once a welcome is
	// in hand, alongside the session and [Back] it already rewrites — a link that
	// dropped is coming back to the conversation it minted, not asking for
	// another one.
	New bool `json:"new,omitempty"`

	// Watch says this surface is HERE TO READ and must never be given the
	// keyboard — not on arrival, not when the driver leaves, not ever.
	//
	// [Back] IS NOT THIS, and reading it as this is the bug that made the flag
	// necessary. A returning surface takes the keyboard when it is going spare,
	// which is right for a redial and wrong for a second view somebody opened to
	// look at one piece of work: the window that owns the conversation may simply
	// have detached for a moment, and it would come back to find a reader driving
	// it. A watcher is refused the keyboard even when there is no driver at all,
	// so the conversation is left with none rather than with the wrong one.
	//
	// IT IS THE SURFACE'S OWN DECLARATION AND THE ENGINE STILL DECIDES. Nothing
	// here is a permission — the engine enforces it, in the one place that owns
	// who drives (driver.go) — and a watcher that tries to type anyway is refused
	// with a sentence rather than dropped.
	Watch bool `json:"watch,omitempty"`
}

Hello is the client's first frame ("hello"). Workspace is the path AS TYPED after the colon — empty means the engine's own home — and the engine answers with the path it resolved.

type HostSelf

type HostSelf struct {
	// Version is the protocol this build speaks — [Version], filled in by the
	// answering side rather than by whoever built the struct, because ONE
	// SOURCE OF TRUTH about a build's protocol is the constant compiled into
	// it.
	Version int `json:"version"`
	// Build identifies the running executable even when its protocol is unchanged.
	// It is the source the executable was built from, so rebuilding the same commit
	// answers with the same word and only a real source change reads as another
	// build. An empty stamp identifies a host predating this check, not a matching
	// build.
	Build string `json:"build,omitempty"`
	// Busy is work in flight: a surface attached, a turn running, or a question
	// waiting for somebody to come back and answer it.
	Busy bool `json:"busy,omitempty"`
	// Retiring says the host took the stand-down and is on its way out. The
	// asker waits for the socket to stop answering and then starts a fresh one.
	Retiring bool `json:"retiring,omitempty"`
	// Workspace is the directory this host holds, for a sentence that has to
	// name it.
	Workspace string `json:"workspace,omitempty"`

	// PID is the host process.
	PID int `json:"pid,omitempty"`
	// Binary is the file the host was started from, as it resolved at start.
	Binary string `json:"binary,omitempty"`
	// Revision is the build as a person reads it — the source revision and
	// when it was built — which [HostSelf.Build] is not written for.
	Revision string `json:"revision,omitempty"`
	// Started is when the host process began holding the workspace.
	Started time.Time `json:"started,omitzero"`
	// BuiltAt is the moment the host's binary was built: the stamp `make
	// build` links in, or the binary file's own modification time when there
	// is none. IT IS WHAT DECIDES WHICH OF TWO BUILDS IS THE OLDER ONE, and so
	// which of them gives up the workspace (cmd/codeaf's takeover rule): the
	// source identity says two builds differ and never which came first.
	BuiltAt time.Time `json:"builtAt,omitzero"`
	// Surfaces is how many windows are attached right now, not counting the
	// connection asking.
	Surfaces int `json:"surfaces,omitempty"`
	// Conversations is how many conversations the host is holding open.
	Conversations int `json:"conversations,omitempty"`
}

HostSelf is the answer: what this process is, and what it did with the request.

func AskHost

func AskHost(conn io.ReadWriter, ask WhoIs) (HostSelf, error)

AskHost puts the question on an already-open connection and reads the answer. It owns the connection for the length of the exchange and nothing else uses it afterwards: the answer is the whole of what this connection was for.

type InterruptArgs

type InterruptArgs struct {
	Door string `json:"door,omitempty"`
}

SubmitArgs carries Submit and FollowUp. InterruptArgs names the door a stop came through, so that a hosted engine can write down what ended a turn and say one sentence about a reply that never arrived (internal/session's stopcause.go).

AN EMPTY DOOR IS A PERSON'S OWN STOP, which is what an older surface that sends no arguments at all means and what it always meant. That is the one direction this may fail in that costs nothing: a stop is still a stop.

type LaneWire

type LaneWire struct {
	// AgeMS is how long before this frame was sent the answer landed — zero
	// for a sighting that is crossing as it happens.
	AgeMS int64 `json:"ageMs,omitempty"`
	// Model is the model the answer came back on. A news with no model belongs
	// to nobody and is dropped on both sides of the wire.
	Model string `json:"model"`
	// Lane is the machine the request WENT TO and Winner the one that finished
	// it; they differ only when a rescue landed, which is the whole of what a
	// surface means by "rescued". Alt is whichever of the pair was not asked
	// first.
	Lane   string `json:"lane,omitempty"`
	Alt    string `json:"alt,omitempty"`
	Winner string `json:"winner,omitempty"`
	// TTFTMS is the wait before the first token in milliseconds, and Rate how
	// fast the answer was written.
	TTFTMS int64   `json:"ttftMs,omitempty"`
	Rate   float64 `json:"rate,omitempty"`
	// Hedged says a second request went out for this answer; Trying says one is
	// out RIGHT NOW and nobody has committed yet.
	Hedged bool `json:"hedged,omitempty"`
	Trying bool `json:"trying,omitempty"`
	// Reason is why the rescue went out, in the transport's own two words, and
	// Failed WITHDRAWS a claim already made: the lane in Alt was what a
	// `trying X…` was about and it has now failed.
	Reason string `json:"reason,omitempty"`
	Failed bool   `json:"failed,omitempty"`
	// Role is who the answer was for. It crosses unfiltered for [PhaseWire.Role]'s
	// reason.
	Role string `json:"role,omitempty"`
	// Subject is which piece of work the sighting is about, and it crosses for
	// [PhaseWire.Subject]'s reason and with its reading of absence: empty is the
	// conversation. It is spelled the same on both wire shapes because they are
	// twins, and a surface keys both desks with one function.
	Subject string `json:"subject,omitempty"`
	// Session is whose sighting it is, for [PhaseWire.Session]'s reason and with
	// its reading of absence: an older peer sends none, and the sighting is
	// filed under its model as it always was.
	Session string `json:"session,omitempty"`
}

LaneWire is one finished far answer's lane story: which machine it went to, which one finished it, and whether a rescue went out while somebody was waiting (session.LaneNews).

It carries no moment, for PhaseWire's reason one step further: the sheet's `served` row draws a sighting for ten minutes after it was taken (internal/tui3's servedWindow) and the only clock that reading can be taken against is the surface's own, so the surface stamps it when the frame lands. What it carries instead is an AGE, and only when it has one: a sighting replayed to a window that arrived after the answer (news.go's [Session.watchNews]) says how long ago the answer landed, so the surface files it as the old sighting it is. A live sighting's age is nothing, and an older peer that does not know the field reads it as nothing.

type Lanes

type Lanes struct {
	// Designs is the harness lane (standinglane.go): the design card, the notes
	// around it, and the subharness intake card that shares the subscription. It
	// is complete — the events cross, ResolveHarness has answered the design
	// card since version 1, and internal/session replays a card that is still
	// standing onto every new subscription, so a detached window comes back to
	// it.
	Designs bool
	// Cards is the intake card's own answer door, [MethodSubharnessResolve]. It
	// is a separate bit because the two cards share a subscription and not an
	// answer.
	Cards bool
	// Runs is the adaptive run lane, and it is FALSE in this build. The work
	// that is left is exact:
	//
	//  1. internal/session's WatchOrchestrations replays nothing, unlike
	//     WatchHarnessDesigns. A run's fuel gate raised while every window is
	//     away would be lost on reconnect, and a gate nobody can answer stops
	//     the run in silence at its cap.
	//  2. internal/tui3's orchAgent needs four doors, not one. Snapshot and
	//     SteerOrchestrate would cross as they stand; OrchestrateNodeJournal
	//     answers a PATH ON THE ENGINE'S DISK, which the run page reads locally
	//     (roomorch.go's session.ReadTranscript), so it needs a content-carrying
	//     door and a seam in that package before it can be honest here.
	//
	// Nothing is lost by the wait: no command, tool or cue opens an adaptive run
	// in this build, on this machine or a far one, so a conversation built
	// without the runner is the same conversation either way.
	Runs bool
}

Lanes says which standing capabilities this build carries end to end.

func StandingLanes

func StandingLanes() Lanes

StandingLanes is what this build carries.

type LaunchShape

type LaunchShape struct {
	Yolo      bool    `json:"yolo,omitempty"`
	NoCompact bool    `json:"noCompact,omitempty"`
	OneModel  bool    `json:"oneModel,omitempty"`
	MaxHours  float64 `json:"maxHours,omitempty"`
	MaxCost   float64 `json:"maxCost,omitempty"`
	// Interactive says the surface that opened this conversation is a screen
	// somebody is typing into, so the session is steered rather than run to a
	// goal of its own. Only the local dial fills it; a --once probe leaves it
	// unset, and the engine maps it onto the session's own steering fact.
	Interactive bool `json:"interactive,omitempty"`
}

LaunchShape is the handful of flags that describe how a session is built rather than what is said in it: --yolo, --no-compact, --one-model and the two unattended ceilings. They are one struct because they are one decision — the shape a conversation was opened with — and because comparing two of them is how a surface tells "the engine built what I asked for" from "I joined somebody else's conversation".

func (*LaunchShape) Same

func (l *LaunchShape) Same(other *LaunchShape) bool

Same reports whether two shapes describe the same conversation. A nil shape is the engine's defaults, so nil and a zero shape are the same thing.

type LedgerArgs

type LedgerArgs struct {
	Since time.Time `json:"since,omitempty"`
}

LedgerArgs is how far back the spend place is looking.

SINCE IS A FLOOR AND NOT A WINDOW. The page's own window has two ends and moves under the arrow keys at key-repeat rate; this carries only the earlier one, so a person paging BACK widens what the surface holds by one call and a person paging forward within it makes none at all. A zero Since is the whole ledger, which is what a caller with no window yet means and what a test means.

type LedgerReading

type LedgerReading struct {
	// Lines are every priced call at or after [LedgerArgs.Since], in the order
	// the ledger holds them.
	Lines []session.UsageLine `json:"lines,omitempty"`
	// Held is whether the ledger holds ANY priced line at all, at any depth —
	// the fact that tells a machine which has never spent anything from a window
	// paged onto a quiet fortnight. The spend page draws its own three sentences
	// for the first and the window header over nothing for the second
	// (internal/tui3's [spendPage.held]), and a bounded read cannot tell them
	// apart on its own: no lines since a floor is both.
	Held bool `json:"held,omitempty"`
}

LedgerReading is the answer: the lines at or after the floor, and the one fact about the rest of the file that a bounded read cannot carry.

type ListDirArgs

type ListDirArgs struct {
	Path string `json:"path"`
}

ListDirArgs names the directory the surface wants to read. A relative path is resolved against the workspace, exactly as FetchFileArgs.Path is.

type Loop

type Loop struct {
	// Client is the surface's half — the real [Client], dialled and handshaken.
	Client *Client
	// Served is the error [Serve] finished with, readable after Close. It is a
	// channel rather than a field because the engine goroutine outlives the
	// call that started it, and a test that wants to know how the far end died
	// has to be able to wait for it. Close never receives from it, so the
	// answer is still there for whoever asks after.
	Served <-chan error
	// contains filtered or unexported fields
}

Loop is a live client and the engine it is talking to, both in this process.

A CLOSED LOOP HAS NO ENGINE LEFT RUNNING. Loop.Close returns only once the engine goroutine has finished Serve and everything Serve does on the way out — the conversation's close, which flushes the journal and takes the presence file away — because the caller of Close is almost always a test whose TempDir cleanup runs the instant Close returns. An engine still leaving then is a writer inside a folder that is being removed, and that is how TestHostedWelcomeCarriesUnreadProfileKeysToSurface failed under load (#1647).

func Loopback

func Loopback(hello Hello, opts Options) (*Loop, error)

Loopback dials a real client against a real engine over an in-memory pipe.

The engine runs on its own goroutine, exactly as it does under ssh, so everything about the ordering — a call answered while a turn's events are arriving, a stream that outlives the call that opened it — behaves the way it does on a real link. What it does not reproduce is LATENCY, and no test should read a pass here as evidence about a slow connection; the deadline laws in client.go are about a link that has stopped answering, and a test for those hands Dial a half that never writes.

func (*Loop) CallsMade

func (l *Loop) CallsMade() uint64

CallsMade is how many calls this loop's surface has put on the wire — the reading every one of PERF.md's connection laws is counted against (Client.CallsMade), forwarded here because a test driving a Loop holds the loop and not the client.

func (*Loop) Close

func (l *Loop) Close() error

Close ends the connection from the surface's side, which is the ordinary way a link dies: the pipe shuts and the engine's reader sees EOF. It is idempotent because a test that closes in a defer and again on the happy path is a test that should not have to care.

AND IT RETURNS ONLY WHEN THE ENGINE HAS FINISHED LEAVING (Loop's law). The wait is on the engine goroutine and not on a clock: a pipe that has shut is an EOF the engine has already been handed, so what is waited for is the conversation's own close, which is bounded by that close's own graces. The second and later calls wait too, so no caller is told the engine is gone while it is not.

func (*Loop) Cut

func (l *Loop) Cut() error

Cut kills the link WITHOUT the surface saying goodbye, which is the event every reconnect story is actually about: a laptop that slept, a network that went away, an ssh that was killed.

IT IS DELIBERATELY NOT Loop.Close. Close sets the client's own `closing` flag, so the EOF that follows is read as a door being shut and the surface says "this connection is closed". Cut sets nothing, so the same EOF is read as a connection that was lost and the surface says the sentence a person needs — which is the difference the version-2 detach frame exists to make visible, and therefore the difference a test of it must be able to stage.

Cut does not wait for the engine, because the surface under test is meant to be alive and reconnecting while the far end reads its EOF. A later Close still joins the engine, without turning the lost link into a goodbye.

type MemoryChange

type MemoryChange struct {
	Learned int `json:"learned,omitempty"`
	LetGo   int `json:"letGo,omitempty"`
}

MemoryChange is MethodMemoryChanged's pair of figures. It is a struct because the call answers two numbers and a JSON array of two ints is a shape nobody reading a frame with `head` could name.

type MemoryListArgs

type MemoryListArgs struct {
	Scope string `json:"scope,omitempty"`
	Limit int    `json:"limit,omitempty"`
}

MemoryListArgs is the flat list, by scope and bounded.

type MemoryOrigin

type MemoryOrigin struct {
	Session string    `json:"session,omitempty"`
	Title   string    `json:"title,omitempty"`
	At      time.Time `json:"at,omitempty"`
}

MemoryOrigin is where and when one memory was learned: the conversation's own id, its title, and the instant. It is the store's three return values with names on them, for MemoryChange's reason.

type MemoryUpdateArgs

type MemoryUpdateArgs struct {
	ID    string   `json:"id"`
	Title string   `json:"title,omitempty"`
	Text  string   `json:"text,omitempty"`
	Tags  []string `json:"tags,omitempty"`
}

MemoryUpdateArgs is one memory's wording, fixed. Every field travels whole — the store's Update replaces rather than patches, and a wire that carried only what changed would have to know which of the four a person touched.

type Moved

type Moved struct {
	// Machine is the arriving surface's machine name as [Hello.Surface] gave
	// it, sanitized ([machineLabel]) because it is drawn. Empty is a surface
	// that sent none.
	Machine string `json:"machine,omitempty"`
	// Here says the window that took it is on THIS surface's own machine.
	Here bool `json:"here,omitempty"`
}

Moved is the engine telling ONE surface that another window has opened this conversation and is now the one in it.

IT IS A FACT ABOUT THE ROOM AND NOT AN INSTRUCTION. The engine goes on holding the conversation, running whatever turn is in flight and keeping every task on its feet; what has changed is who is sitting in front of it. The surface that hears this detaches, which is the road that leaves the work alone (internal/tui3's movedAway), and a surface that ignores the kind is left attached and reading — the honest floor for a build that predates the frame.

IT NAMES THE MACHINE FOR Driver's REASON, in Driver's words: `another window` is the true and weaker claim when the name is missing or is this surface's own, and the name is what a person needs when the conversation walked to a different computer.

type Options

type Options struct {
	Boot func(Hello) (*Engine, error)
}

Options is what Serve needs, which is one function: how to open the conversation the hello asked for. The engine cannot assemble it before the handshake, because the workspace and the session file are things the surface says.

type PacketsArgs

type PacketsArgs struct {
	Scope string `json:"scope,omitempty"`
	Stamp string `json:"stamp,omitempty"`
}

PacketsArgs is a scope (a team id, teamstore.Person, or "" for every waiting packet) and the stamp the window last got, "" for none.

type PacketsReading

type PacketsReading struct {
	Stamp   string             `json:"stamp"`
	Same    bool               `json:"same,omitempty"`
	Packets []teamstore.Packet `json:"packets,omitempty"`
}

PacketsReading is the waiting packets and their stamp; Same says the stamp is the one asked about, and then Packets is absent.

type PathFact

type PathFact struct {
	Path   string `json:"path"`
	Exists bool   `json:"exists,omitempty"`
	Dir    bool   `json:"dir,omitempty"`
	// Size is the file's byte count as this machine sees it. A directory
	// reports its own, which is a filesystem artifact rather than a fact about
	// what is inside it — the surface reads it for files and nothing else.
	Size int64 `json:"size,omitempty"`
	// ModTime is the last modification in whole seconds since the epoch, which
	// is the resolution every filesystem and every archive format agrees on.
	// It is a NUMBER and not a time.Time because it is compared and never
	// drawn: a surface asking "is this the file I already have" wants equality,
	// not a moment in a person's timezone.
	ModTime int64 `json:"mtime,omitempty"`
}

PathFact is the engine's word on one candidate: it exists under the two-roots law, whether it is a directory, and HOW THE FILE STANDS RIGHT NOW. A path outside the roots reports Exists false — to a surface deciding whether to draw a door, a file that will refuse to open IS absent.

SIZE AND MODTIME ARE HERE SO THAT A CACHE CAN BE WRONG AND FIND OUT. A surface holding a copy of a far file has exactly one cheap way to learn that the file was rewritten under it: ask this machine what the file is now and compare. Without these two numbers the only honest answers are "fetch the whole thing again every time" and "serve the old bytes forever", and the second is the one a cache keyed by path quietly becomes. They cost nothing — the stat that answers Exists already has them in hand — and they turn a freshness question into one small frame instead of a transfer.

type PhaseWire

type PhaseWire struct {
	// Phase is [provider.Phase] — the person's own word for what is happening.
	// An empty phase is the end of the story and is carried as such, because a
	// stale clock left running is the defect the phase seam exists to fix.
	Phase string `json:"phase,omitempty"`
	// Model is the model this request is on and Role who it is for
	// ([lane.Role]). EVERY ROLE CROSSES AND THE SURFACE DECIDES: a task node's
	// phases are drawn where a node is drawn and a conversation's on the status
	// row, and that filter is already written on the surface (internal/tui3's
	// phase.go). An engine that filtered here would be a second opinion about
	// the same question.
	Model string `json:"model,omitempty"`
	Role  string `json:"role,omitempty"`
	// Subject is which piece of work this news is about: empty for the
	// conversation, and one node's own name for a node
	// ([session.NewsSubject]). AN OLDER PEER SENDS NONE, WHICH READS AS THE
	// CONVERSATION — the same absence every producer that predates the field
	// means, so a surface talking to a build without it behaves exactly as it
	// always did.
	Subject string `json:"subject,omitempty"`
	// Session is WHOSE news this is — the conversation, in the engine's own
	// spelling ([session.Agent.NewsKey]) — and the name the surface files the
	// conversation's own news under first (internal/tui3's newsDeskKeys).
	//
	// IT CROSSES BECAUSE THE MODEL IS NOT AN IDENTITY. Without it the surface
	// filed the conversation's phases under the model id, and the model is
	// exactly the thing that moves between an engine and a window: a pick made
	// mid-turn, a stream-cut hop onto another model, a change made from another
	// window. Each put the live rate and the machine under a name that window
	// was not asking for.
	//
	// AN OLDER PEER SENDS NONE, and the surface files what it gets under the
	// model alone, which is what it always did — so no version moves for it.
	Session string `json:"session,omitempty"`
	// Lane is the machine answering when one has named itself, and Rate how
	// fast it is writing in tokens a second. Zero for both is "not measured",
	// never "nothing" — the emptiness law, carried across the wire intact.
	Lane string  `json:"lane,omitempty"`
	Rate float64 `json:"rate,omitempty"`
	// Door is the billing road in use. It stays separate from Lane so a direct
	// service is never presented as a router's serving machine.
	Door string `json:"door,omitempty"`
	// Detail is the phase's own noun, already in a person's words, and Then
	// what will be done about the wait when a deadline is real.
	Detail string `json:"detail,omitempty"`
	Then   string `json:"then,omitempty"`
	// SinceMS is how long this phase had lasted when the engine said so, and
	// DeadlineMS how long was left before something is done about it. Both in
	// milliseconds; zero DeadlineMS is no deadline, which is the honest answer
	// wherever no alternative lane exists.
	SinceMS    int64 `json:"sinceMs,omitempty"`
	DeadlineMS int64 `json:"deadlineMs,omitempty"`
}

PhaseWire is one moment of one far turn's life: what the request is doing right now, on which machine, and how fast it is writing ([provider.PhaseNews]).

IT CARRIES NO CONVERSATION AND NO OFFER TOKEN. The engine files its news by conversation to decide WHICH connection each piece goes down (news.go), and once it is on a connection the connection IS the conversation — a name on the frame would be a second answer to a question already settled. The offer token stays behind for the same reason: the token is the engine's own bookkeeping, and the surface answers by pressing `y` at the conversation it is sitting in (MethodAnswerLaneOffer), never by naming a token it was handed.

IT DOES CARRY THE SUBJECT, AND THAT IS NOT THE CONVERSATION SAID TWICE. The conversation is WHOSE this news is, which the connection answers; the subject is WHAT IT IS ABOUT — the conversation itself, or one task node inside it — which nothing on this side of the pipe can answer. A surface holds one desk for every window it draws, so a node's phase and its parent conversation's arrive down one connection and have to be told apart at the desk (internal/tui3's phase.go). Until this field crossed, a node's room over a connection could draw no clock at all, and two nodes on one model id overwrote each other's.

THE MOMENTS ARE ELAPSED TIMES AND NEVER WALL CLOCKS. A surface ages a phase out fifteen seconds after it was said ([provider.PhaseWindow]) and counts a clock up from when it began, both against ITS OWN now — so a wall clock from another machine, even a few seconds out, would either drop every phase on arrival or draw one that had been running since before it started. SinceMS is how long the phase had already lasted when the engine said so and DeadlineMS how long was left; the surface adds both to the moment the frame landed (client.go's [Client.phaseFrame]).

type PlacesTaskArgs

type PlacesTaskArgs struct {
	Transcript string `json:"transcript"`
	Tail       int    `json:"tail,omitempty"`
}

PlacesTaskArgs names the row of the record a card was opened on.

IT IS THE URI OFF THE ROW, WHICH THE ENGINE ITSELF WROTE. The row travelled here on the world walk carrying the journal's own address on that machine (session.TaskIndexEntry.TranscriptURI), and this hands it straight back — the same law every path on this wire obeys, and the same reason FetchFileArgs does not resolve one either. The engine checks it against its own places root before it opens anything (session.ReadTaskRecordUnder), because a boundary the surface asserted would be a permission decision taken on the wrong machine.

IT IS A STRUCT AND NOT A BARE STRING so the card can learn to ask for a second fact about the same row without a second door and without a second version.

type PlanPriorityArgs added in v0.4.0

type PlanPriorityArgs struct {
	ID       string `json:"id"`
	Priority int    `json:"priority"`
}

PlanPriorityArgs carries the task and its new scheduling priority.

type PlanRunSummaryArgs added in v0.4.0

type PlanRunSummaryArgs struct {
	RootID string `json:"root_id"`
}

PlanRunSummaryArgs names the run whose stored summary is read.

type PlanRunSummaryResult added in v0.4.0

type PlanRunSummaryResult struct {
	Summary session.RunPlanSummary `json:"summary"`
	OK      bool                   `json:"ok"`
}

PlanRunSummaryResult preserves both the summary and whether one exists.

type PlanSpendArgs added in v0.4.0

type PlanSpendArgs struct {
	Since time.Time `json:"since,omitempty"`
}

PlanSpendArgs is how far back the spend page's seat rollup is looking.

SINCE IS THE SAME FLOOR LedgerArgs.Since IS, and it is a plain time.Time for the same reason: it is a moment the wire already knows how to encode, cut in Go on the far side against the ledger's own RFC3339Nano stamps (session.Agent.PlanSpend). A zero Since is the whole rollup, which is what a caller with no window yet means and what a test means.

type PlanTaskArgs added in v0.4.0

type PlanTaskArgs struct {
	ID string `json:"id"`
}

PlanTaskArgs names one task for a steering verb.

type PlanTaskPageArgs added in v0.4.0

type PlanTaskPageArgs struct {
	ID string
}

PlanTaskPageArgs names the task whose complete page is requested.

type PlanTaskPageResult added in v0.4.0

type PlanTaskPageResult struct {
	Page session.PlanTaskPage
	OK   bool
}

PlanTaskPageResult preserves both the page and whether the task belongs to the plan.

type PlanTaskWorkResult

type PlanTaskWorkResult struct {
	Work session.PlanTaskWork
	OK   bool
}

PlanTaskWorkResult preserves both the working copy and whether the task belongs to the plan.

type PlanTextArgs added in v0.4.0

type PlanTextArgs struct {
	ID   string `json:"id"`
	Text string `json:"text"`
}

PlanTextArgs carries the task and prose for note and amend.

type PlannerStartArgs

type PlannerStartArgs struct {
	Brief string `json:"brief"`
	Hint  string `json:"hint,omitempty"`
}

PlannerStartArgs also carries the sizing hint used by the adaptive form.

type PlannerStarted

type PlannerStarted struct {
	ID    string `json:"id,omitempty"`
	Title string `json:"title,omitempty"`
}

PlannerStarted is the receipt the existing adaptive-task note draws.

type QuestionArgs

type QuestionArgs struct {
	Answer session.Answer `json:"answer"`
}

QuestionArgs is one answer, whole. It carries session.Answer rather than a flattened set of fields for the reason the object exists at all: the fields on it are what a person's intent looks like, and a wire that carried only the key would be the wire deciding that the notes, the exchange and the words beside the pick are not part of the answer.

type QuestionHoldArgs

type QuestionHoldArgs struct {
	Kind  session.QuestionKind `json:"kind"`
	Token string               `json:"token"`
}

QuestionHoldArgs names one question whose clock a person has stopped, the way an answer names it: the lane, and the lane's own token (session.Question.Token).

type ReasoningArgs

type ReasoningArgs struct {
	Model string `json:"model"`
	Level string `json:"level"`
}

type ReferArgs

type ReferArgs struct {
	Path    string               `json:"path"`
	Arrival session.PlaceArrival `json:"arrival,omitempty"`
}

ReferArgs is one folder, attached. The arrival travels because it is the whole of the difference between the two roads onto a place — somebody's own act, which never expires, and a ground the work resolved, which decays (internal/session's places.go) — and an engine that assumed one of them would be deciding what a person meant on the other end of a pipe.

type RefreshRunSummaryArgs added in v0.4.0

type RefreshRunSummaryArgs struct {
	RootID   string    `json:"root_id"`
	LastLook time.Time `json:"last_look,omitempty"`
	// Budget is HOW LONG the caller will wait, never the instant it stops
	// waiting: the engine may be on another machine whose clock is not this
	// one's, and an instant read against a clock a minute ahead is a refresh
	// that is cut before it starts. Zero is a caller with no deadline.
	Budget time.Duration `json:"budget,omitempty"`
}

RefreshRunSummaryArgs carries the refresh window and caller deadline to the engine.

type Refusal

type Refusal struct{ Reason string }

Refusal is a handshake the engine turned away, with the reason ALREADY on the wire. A caller that holds one has nothing left to say: the surface has been told, in these words, and the only thing still owed is a non-zero exit.

func (*Refusal) Error

func (r *Refusal) Error() string

type Roaming

type Roaming struct {
	// Dial opens the next link. Required.
	Dial Dialer
	// Window is how long to keep trying, and zero is [RoamWindow].
	Window time.Duration
}

Roaming is the policy a client redials by.

type SearchArgs

type SearchArgs struct {
	Terms string `json:"terms"`
	Limit int    `json:"limit,omitempty"`
}

SearchArgs is one question for the far machine's index.

type Session

type Session struct {
	// contains filtered or unexported fields
}

Session is one conversation on the engine machine: the agent, its streams, and everybody currently watching it.

IT IS THE UNIT THAT OUTLIVES A CONNECTION. A host holds one per open session and hands the same one to every surface that asks for it; a bare engine makes one, gives it to its single pipe, and closes it when the pipe ends. Nothing below knows which of those it is except through [Session.persistent], and that flag decides exactly one thing — what a torn pipe means.

func NewSession

func NewSession(engine *Engine, persistent bool) *Session

NewSession wraps an opened engine as a conversation. Persistent says the engine outlives its connections, which is the fact Welcome.Persistent carries and the fact a torn pipe is read against.

func (*Session) Attached

func (sess *Session) Attached() int

Attached is how many surfaces are watching right now.

func (*Session) Close

func (sess *Session) Close() error

Close ends the conversation: the turn in flight stops where it is and keeps its partial reply, and the agent is closed, which flushes the journal.

THE JOURNAL IS THE SAFETY. An engine is the only writer of its session file and no surface holds anything that is not in it, so the whole of "did this conversation survive" is whether this ran. It is idempotent because every road out of a connection may call it and a host may call it again on the way down. AND THE DOOR IS READ FROM PRESENCE, because this is the road the whole machine takes when it goes away — internal/enginehost's shutdown, which is `codeaf engine --stop`, a signal, and a stale build letting go. A window still in the room means the engine went out from under somebody, and that is not the unattended door however the host was asked. An empty room is.

func (*Session) Ended

func (sess *Session) Ended() bool

func (*Session) File

func (sess *Session) File() string

Ended reports that this conversation is over — somebody said goodbye through MethodClose, or the pipe that was its whole life went away. A host reaps one that says so. File is the transcript this conversation is writing.

IT IS THE ONE IDENTITY A CONVERSATION HAS OUTSIDE THIS PACKAGE. A host keys its sessions by whatever the first hello said, which is a launch's word and not the conversation's own; the file is what the world scan, the presence files, the task index and the surface all name a conversation by. So a caller answering "is the conversation in this file already open here" asks this (Hello.Join is that caller).

It is read under the session's own lock because a swap writes it ([Session.swap]).

func (*Session) IdleSince

func (sess *Session) IdleSince() time.Time

IdleSince is when this conversation went quiet, and the zero time when it has not. Five things make it busy: a surface attached, a turn still streaming, a question waiting to be answered, WORK THE CONVERSATION HANDED OFF, and a window that acted inside [watchGrace].

The fourth was missing and it had a clock on it. A task, an adaptive run and a background job outlive the turn that started them; the turn ends, its ring is dropped, and the session read as idle while the workers ran on — thirty minutes later the sweep closed it and took the work. So the agent is asked, and session.Agent.WorkingNow is the authoritative reading: tasks, runs and jobs together.

The agent is asked off this lock, because that walk takes locks of its own and holding sess.mu across it would put the sweep in front of every event a turn records. The answer can age while it is read, so the state is re-checked afterwards and a conversation that changed under the reading is reported busy for this pass: one sweep too many costs a minute of memory, one too few costs somebody's work. Session.RetireIfIdle is where that re-check becomes a decision.

func (*Session) ReleaseForTakeover

func (sess *Session) ReleaseForTakeover() error

ReleaseForTakeover closes this conversation because another window asked for it. It is the ordinary close under the takeover's own door, so the stop is recorded as a move rather than as the engine going away.

func (*Session) RetireIfIdle

func (sess *Session) RetireIfIdle(olderThan time.Duration) bool

RetireIfIdle ends this conversation if it has been idle for longer than the policy allows, and answers whether it is now over.

IT EXISTS FOR THE GAP BETWEEN ASKING AND ACTING. A caller that read Session.IdleSince and then called Close would be deciding on a photograph: a surface can attach, a turn can open and a card can be raised in between, and closing then would take down a conversation somebody had just come back to. Here the last look and the decision to end it happen under ONE acquisition of the session lock, so anything that goes through that lock either arrives before the look — and cancels the retirement — or finds the conversation already ended and is handed a fresh one (internal/enginehost's open).

WHAT IS STILL A PHOTOGRAPH is the work reading inside IdleSince, which cannot be taken under this lock (it walks the graph). The guarantee is therefore: nothing that reaches this session's own state can be lost, and a background worker that starts something new in the microseconds after the walk is interrupted with its journal flushed, exactly as a person's own /quit would. The window is that walk, not the whole idle span.

func (*Session) TakeoverAsked

func (sess *Session) TakeoverAsked() bool

TakeoverAsked reports that another window on this machine has asked for this conversation and it has not been let go of yet. A closed conversation has nothing left to let go of and answers false.

func (*Session) Workspace

func (sess *Session) Workspace() string

Workspace is the directory this conversation works in — the answer a host files it under and the one a person reads in a listing.

type SkillShelfArgs

type SkillShelfArgs struct {
	Status string `json:"status,omitempty"`
	Limit  int    `json:"limit,omitempty"`
}

SkillShelfArgs asks for one reading of the conversation's skill shelf, on store.Store.SkillFacts's own two arguments.

type SpendArgs

type SpendArgs struct {
	Team  string `json:"team"`
	Day   string `json:"day,omitempty"`
	Stamp string `json:"stamp,omitempty"`
}

SpendArgs is one team's day ("" is the engine's today) and the stamp the window last got.

type SpendReading

type SpendReading struct {
	Stamp string           `json:"stamp"`
	Same  bool             `json:"same,omitempty"`
	Spend *teamstore.Spend `json:"spend,omitempty"`
}

SpendReading is the team's spend and its stamp; Same says nothing moved and then Spend is absent.

type StandingArgs

type StandingArgs struct {
	ID     uint64                 `json:"id"`
	Answer session.StandingAnswer `json:"answer"`
}

StandingArgs carries ResolveStanding: which card, and what the person said to it. It is the standing lane's ConsentArgs — one id and one answer — and the answer travels WHOLE rather than field by field, because session.StandingAnswer is the engine's own type and a field added there must arrive without a wire change (this file's header states that bargain).

KEEPWATCH'S THIRD STATE IS LOAD-BEARING AND SURVIVES BECAUSE IT IS A POINTER. The field answers a question that is only ever ASKED of a person's first standing item, so nil means nobody was asked, and encoding/json writes a nil pointer as null and reads null back as nil. A bool would have turned "never asked" into "said no" on the far machine.

type StandingWatchResult

type StandingWatchResult struct {
	Status standing.WatchStatus `json:"status"`
	Known  bool                 `json:"known"`
}

StandingWatchResult keeps "not installed" distinct from "could not read".

type StatPathsArgs

type StatPathsArgs struct {
	Paths []string `json:"paths"`
}

StatPathsArgs is a bounded batch of candidate paths, relative ones meaning the workspace. Over statPathsMax (file.go) the engine refuses the call rather than trimming it silently.

type StreamCursor

type StreamCursor struct {
	Stream uint64 `json:"stream"`
	Seq    uint64 `json:"seq"`
}

StreamCursor is one "I have seen this stream through here".

type StreamRef

type StreamRef struct {
	Stream uint64 `json:"stream"`
}

type SubharnessResolveArgs

type SubharnessResolveArgs struct {
	ID    uint64          `json:"id"`
	Run   bool            `json:"run"`
	Input json.RawMessage `json:"input,omitempty"`
}

SubharnessResolveArgs is one person's answer to one intake card. Input is the card as it was settled, or nil for the card exactly as it was raised — which is what internal/session's ResolveSubharness reads a nil as.

type SubmitArgs

type SubmitArgs struct {
	Text string `json:"text"`
	// Standing says the person MARKED this draft as something to keep true
	// (internal/session's standing_mark.go), so the engine opens the turn
	// through SubmitStanding rather than Submit.
	//
	// IT IS A FIELD RATHER THAN A METHOD OF ITS OWN because the two differ in
	// what the engine puts in front of the sentence and in nothing a wire can
	// see: same argument, same StreamRef, same event frames. An older engine
	// that does not read it runs the ordinary turn, which is the one direction
	// this may fail in that leaves the person's words intact.
	Standing bool `json:"standing,omitempty"`
}

type SubmitFilesArgs

type SubmitFilesArgs struct {
	Text  string     `json:"text"`
	Files []WireFile `json:"files"`
	// Images ride along so ONE MESSAGE IS ONE CALL. A person who pastes a
	// screenshot and drops a log file has sent one message, and splitting it
	// into two submits would open two turns.
	Images []session.Image `json:"images,omitempty"`
}

SubmitFilesArgs carries SubmitFiles: a message with ordinary files attached.

IT IS image.go's LAW, GENERALIZED, and the generalization is the point. A picture already travels as BYTES and is remade on the engine's disk, because the surface read it off a disk the engine cannot see. Every other thing a person drops into the chat — a log, a CSV, a PDF, a stack trace saved to a file — has exactly the same problem and had no answer at all in version 1: the path was typed here and meant nothing there.

So the contract is one sentence: WHAT A PERSON PUTS INTO THE CHAT IS THE SURFACE'S TO READ AND THE ENGINE'S TO KEEP. The bytes ride the message, the engine writes them where that session keeps such things, and what reaches the journal is a path that is true on the machine that owns the journal — which is the same bargain internal/session's image.go already struck, for the same reason (a reference is only worth writing if it names a file that exists on the machine that wrote it).

The model is TOLD THE PATH rather than the contents: an attached file is a file, and the session already has a `read` tool. That keeps a 4MB CSV out of the context window until something actually wants it.

type SubmitImageArgs

type SubmitImageArgs struct {
	Text   string          `json:"text"`
	Images []session.Image `json:"images"`
}

SubmitImageArgs carries SubmitImage. Images travel with their bytes filled in — the engine has no way to read a path on the surface's disk — and the engine writes them to its own image store before submitting, so the journal holds references the way it always does.

type TaskHoldArgs

type TaskHoldArgs struct {
	ID uint64 `json:"id"`
}

TaskHoldArgs names the proposal whose first typed rune stopped its clock.

type TaskPending

type TaskPending struct {
	IDs []uint64 `json:"ids"`
}

TaskPending is the open proposals, oldest id first — session.Agent.PendingTasks as it crosses. An empty list is the honest answer that nothing is waiting, and it is why the field is not omitempty: "no proposals" and "this engine did not say" have to stay two different readings on the way back.

type TaskRedoArgs

type TaskRedoArgs struct {
	ID uint64 `json:"id,omitempty"`
}

TaskRedoArgs names the task to run again stronger; 0 is the newest one.

type TaskResolveArgs

type TaskResolveArgs struct {
	ID       uint64 `json:"id"`
	Approved bool   `json:"approved,omitempty"`
	Redirect string `json:"redirect,omitempty"`
	Model    string `json:"model,omitempty"`
}

TaskResolveArgs is one person's answer to one proposal, in the engine's own three fields (session.TaskAnswer): whether it may run, the correction to append if they redirected it, and the model they picked where the card offered a choice.

type TaskRoomArgs

type TaskRoomArgs struct {
	ID   uint64 `json:"id"`
	Tail int    `json:"tail,omitempty"`
}

TaskRoomArgs names a node in the engine's current conversation. Unlike a record URI, the id exists before the node has written its first journal line.

type TaskSettleArgs

type TaskSettleArgs struct {
	ID uint64 `json:"id"`
	// Merge spends one more merge round on a branch that clashed. It is the
	// conflict card's yes, and it takes nothing as done.
	Merge bool `json:"merge,omitempty"`
	// Hand gives this one decision to the model. Back takes it away again.
	Hand bool `json:"hand,omitempty"`
	Back bool `json:"back,omitempty"`
	// Resolution is `accept`, `reaudit` or `refute` — [session.TaskResolution] as
	// it crosses — and Why is the sentence a caller may put on the record with it.
	Resolution string `json:"resolution,omitempty"`
	Why        string `json:"why,omitempty"`
}

TaskSettleArgs is one person's answer to one landing.

THE THREE FLAGS ARE NOT RESOLUTIONS AND ARE KEPT APART FROM ONE. `accept` and `not right` are the engine's own words (session.TaskResolution) and travel in Resolution; the merge round, the hand-over and the take-back RESOLVE NOTHING — each moves the question rather than answering it — so folding them into the same field would put three acts that settle no task into the vocabulary of the two that do.

An engine reads at most one of them: the flags are tested before Resolution, in the order below, and an argument with none of them set is a resolution.

type TaskSettled

type TaskSettled struct {
	Decided bool `json:"decided,omitempty"`
}

TaskSettled is the answer, and its ONE FIELD IS THE ONE THING A SENTENCE CANNOT CARRY: whether the question was already gone.

A landing can be settled from four places at once — this card, another window, the model's own `tasks … resolve`, a check that finally answered — and whichever answer arrives second finds nothing to spend. That is a card catching up rather than a fault, and the surface draws it as one (session.ErrTaskDecided). Every other refusal stays the engine's own sentence and arrives as the call's error.

type TaskSetupArgs

type TaskSetupArgs struct {
	ID      uint64 `json:"id"`
	Session string `json:"session"`
	Value   string `json:"value,omitempty"`
}

TaskSetupArgs binds a setup change to the conversation whose task was drawn.

type TaskStartArgs

type TaskStartArgs struct {
	Brief  string `json:"brief"`
	Solo   bool   `json:"solo,omitempty"`
	Effort string `json:"effort,omitempty"`
}

TaskStartArgs carries the person's brief without interpreting it locally, and whether they said the work is one worker's (session.Agent.StartTask's solo): that is the one thing the surface knows and the engine cannot, because the word and the standing answer are both read on the surface's side.

Effort is the one-task effort word the person said (`best`, `cheap`), read on the surface's side like solo, and empty on every ordinary start.

type TaskStarted

type TaskStarted struct {
	ID    uint64 `json:"id,omitempty"`
	Title string `json:"title,omitempty"`
	Note  string `json:"note,omitempty"`
}

TaskStarted is the receipt the existing single-task note draws. Note is the engine's one line about where the work stands when the ground ladder moved it (internal/session's taskstands.go), and empty on every ordinary start.

type TaskSteerArgs

type TaskSteerArgs struct {
	ID   uint64 `json:"id"`
	Text string `json:"text"`
	// Scope and Seq are the send's identity, absent on a surface that has no way
	// to mint one and on every client written before this.
	Scope string `json:"scope,omitempty"`
	Seq   uint64 `json:"seq,omitempty"`
	// Said is when the person pressed enter, which is what the node's record
	// orders its corrections by. It travels because a send may be repeated
	// minutes later and the instant that decides which correction is the later
	// one is the one they said it at, not the one the wire delivered it on.
	Said time.Time `json:"said,omitzero"`
	// Session is the conversation the surface believed it was addressing, and it
	// is checked at the engine against the one actually open.
	//
	// THE HANDLE DOES NOT CHANGE WHEN THE CONVERSATION DOES. `Session.Open` and
	// `Session.New` swap the engine's conversation behind the same client and the
	// same agent (cmd/codeaf's chatv3_host.go returns that agent unchanged), so a
	// send held over a swap — queued behind another, or retried after one — would
	// otherwise reach whatever task 7 means in the conversation that replaced it.
	// Empty is a surface making no claim.
	Session string `json:"session,omitempty"`
}

TaskSteerArgs carries one person's correction to a running node.

── AND THE NAME OF THE SEND, SO A LOST ANSWER IS NOT A LOST SENTENCE ──

The id and the text alone cannot survive the one failure that matters over a wire: the engine TAKES the words and the answer never gets back — the link died, the deadline ran out. The surface then holds a sentence it can neither report as delivered nor send again, because a second send with nothing to recognise it by is a second correction on the worker's queue.

So the surface's own name for the send crosses with it (session.SteerSource — a scope naming one life of one window, and a number counting its sends), and an engine that keeps them answers a repeat with the receipt already on the record, delivering nothing (Again below). It is the SEND's identity and never a hash of the words: two intentional sends of one sentence carry two Seqs and are two directions.

BOTH ENDS DEGRADE HONESTLY. An engine that predates this ignores the two fields and steers exactly as it always did — which is why the surface asks whether the far machine keeps them (Welcome.SteerRepeat) BEFORE it sends, rather than discovering it by having asked twice.

type TaskSteered

type TaskSteered struct {
	Waiting   bool   `json:"waiting,omitempty"`
	Held      bool   `json:"held,omitempty"`
	Direction uint64 `json:"direction,omitempty"`
	Landing   string `json:"landing,omitempty"`
	// Again says this node already held these words FROM THIS SAME SEND, so
	// nothing was delivered a second time and Direction is the receipt the first
	// one was written down as. It is the answer a surface asking again for a
	// crossing nobody answered is hoping for, and it is why asking again is safe
	// at all ([TaskSteerArgs]).
	Again bool `json:"again,omitempty"`
	// Elsewhere says the conversation the surface named ([TaskSteerArgs.Session])
	// is not the one open here, so NOTHING WAS DELIVERED. It is a field rather
	// than an error string because the surface has to recognise it exactly: the
	// send stays unresolved and is never re-aimed at the task with that number in
	// the conversation that replaced it (internal/session's
	// [session.ErrNotThatConversation]).
	Elsewhere bool `json:"elsewhere,omitempty"`
	// Uncertain says the engine could not tell whether this correction was kept:
	// it reached the record and could not be taken back off the disk again
	// (internal/session's [session.ErrSendUnanswered] wearing an engine's reason
	// rather than a dead link's). It is a field for [Elsewhere]'s reason — the
	// surface has to recognise it exactly, keep the send under the name it has,
	// and offer to ask again rather than hand the words back to be renamed.
	Uncertain bool `json:"uncertain,omitempty"`
}

TaskSteered carries the local door's whole receipt (internal/session's session.SteerReceipt) rather than only its waiting fact.

HELD IS WHY IT GREW. A line said while the engine is checking a task's work is TAKEN — it goes on the task's record and the landing may not publish over it — and that is a success with a different sentence, not an error. Carried as an error it would have arrived here as bare text, so a hosted room could not tell "kept, and it will be read" from "refused, say it somewhere else"; the person furthest from the work would have been the one told least about it.

An engine that predates the receipt fills Waiting and nothing else, which is exactly what this type meant before: absent fields read as false, and a room then draws the delivery it always drew.

type TaskStopArgs

type TaskStopArgs struct {
	ID string `json:"id"`
}

TaskStopArgs uses the session's already-prefixed work id unchanged.

type TaskStopped

type TaskStopped struct {
	Line string `json:"line,omitempty"`
}

TaskStopped carries the engine's person-facing sentence without rewriting it.

type TeamDefaultArgs

type TeamDefaultArgs struct {
	Key string `json:"key"`
	Raw string `json:"raw"`
}

TeamDefaultArgs is one `teams.` row, as the settings tab would apply it: Key is the registry key, Raw is what was typed (on, off, a number, or blank).

type TeamNameArgs

type TeamNameArgs struct {
	Titles []string      `json:"titles"`
	Budget time.Duration `json:"budget,omitempty"`
}

TeamNameArgs is the titles a group is named from, and how long the wall will wait. Zero Budget uses the engine's ceiling; a negative Budget has already expired and never starts a model call.

type TeamProposeArgs

type TeamProposeArgs struct {
	In     session.TeamProposalInput `json:"in"`
	Budget time.Duration             `json:"budget,omitempty"`
}

TeamProposeArgs is everything one Organize ask is about, and how long the wall will wait. Zero uses the engine's ceiling, and a negative Budget has already expired and never starts a model call.

type TeamsReadArgs

type TeamsReadArgs struct {
	Stamp    string    `json:"stamp,omitempty"`
	Reserved []float64 `json:"reserved,omitempty"`
}

TeamsReadArgs is what the window holds of the file. An empty Stamp is a window that has not read it yet and is always answered with the file. Reserved is the window's palette's reserved hues: a team the file left without a colour is coloured around them and the colour written back, as a local load does (teamstore.LoadHued).

type TeamsReading

type TeamsReading struct {
	Stamp string           `json:"stamp"`
	Same  bool             `json:"same,omitempty"`
	Stale bool             `json:"stale,omitempty"`
	Teams []teamstore.Team `json:"teams,omitempty"`
}

TeamsReading is the file as the engine holds it. Same says it is still at the stamp asked about, and then Teams is absent. Stale is only ever set by MethodTeamsUpdate: the file moved since the base, nothing was written, and Stamp is where it is now.

type TeamsTraffic

type TeamsTraffic struct {
	Entries []teamstore.Entry `json:"entries,omitempty"`
	Stamp   string            `json:"stamp,omitempty"`
}

TeamsTraffic is what came after the cursor, oldest first, and the log's stamp when it was read.

type TeamsTrafficArgs

type TeamsTrafficArgs struct {
	Team  string `json:"team"`
	After string `json:"after,omitempty"`
	Limit int    `json:"limit,omitempty"`
}

TeamsTrafficArgs is one team's log after a cursor: After "" is the tail, the last Limit entries; an id pages forward from it.

type TeamsUpdateArgs

type TeamsUpdateArgs struct {
	Base  string           `json:"base"`
	Teams []teamstore.Team `json:"teams"`
}

TeamsUpdateArgs is the whole list the window wants written, and the stamp of the file it made that list from.

type Turn

type Turn struct {
	Stream uint64 `json:"stream"`
	Said   string `json:"said,omitempty"`
}

Turn is a turn that has just started in this conversation, told to every surface that did NOT start it.

IT IS WHAT MAKES A SECOND WINDOW A WINDOW ONTO THE WORK RATHER THAN A DEAD FRAME. The events of a turn have always fanned out to everybody attached, but a surface only draws a stream it knows about — the one its own Submit named, or Welcome.Live at the door — so a turn started on ANOTHER machine after this surface arrived went past it in silence. The room could not watch itself.

IT IS SENT BY THE ENGINE AND NEVER INFERRED HERE, because the engine is the only thing that knows who called Submit. A surface that guessed from "an event on a stream I have not seen" would race its own submit's result and draw its own turn twice.

Said is the sentence that opened it, and it is carried for the one moment the transcript cannot answer: a surface that has already read the transcript and is sitting there watching. A turn already running when a surface ATTACHES needs none of it — that message is in the journal the surface reads on its way in — so Welcome.Live carries no sentence and this does.

type Welcome

type Welcome struct {
	// SessionInstance identifies this live conversation owner, not its durable
	// transcript. A replaced engine can reopen the same file with stream IDs
	// starting over, so a returning surface must forget the previous owner IDs.
	// Empty preserves compatibility with engines predating this optional field.
	SessionInstance string `json:"sessionInstance,omitempty"`

	Version     int    `json:"version"`
	Workspace   string `json:"workspace"`
	SessionFile string `json:"sessionFile"`
	Resumed     bool   `json:"resumed"`
	Model       string `json:"model"`
	Build       string `json:"build,omitempty"`
	Title       string `json:"title,omitempty"`
	ShortTitle  string `json:"shortTitle,omitempty"`
	// Note is a sentence worth showing once — "session open elsewhere, started
	// a new one" travels here.
	Note string `json:"note,omitempty"`
	// ApprovalMode is the engine machine's own answer to "does a tool run
	// without asking" (internal/config's ToolApprovalModeAt) — "allow" or
	// empty. A REMOTE YOLO BADGE MUST NAME THE ENGINE'S POSTURE, NOT THIS
	// LAPTOP'S: the gate that decides whether a tool runs unattended is read
	// from the profile on the machine that runs it, and drawing the badge from
	// the surface's own settings would be a safety claim about a machine
	// nobody consulted.
	ApprovalMode string `json:"approvalMode,omitempty"`
	// BashBackgroundAfterSeconds is the foreground-command clock the ENGINE
	// armed for this session. A HOSTED COUNTDOWN MUST READ THIS MACHINE'S
	// POSTURE, NOT THE SURFACE'S PROFILE: zero is a real off answer, so absence
	// cannot be filled from a local default without inventing a deadline.
	BashBackgroundAfterSeconds int `json:"bashBackgroundAfterSeconds,omitempty"`
	// ProfileDir is the engine process's resolved profile directory. A plain
	// linked-local surface uses it for writes because the daemon may predate the
	// terminal's current profile override. An older peer sends none and the
	// surface falls back to its own resolved directory; linked-local launches
	// retire a daemon whose build differs, so that compatibility reading is
	// theoretical on the road that consumes it. No protocol version moves.
	ProfileDir        string   `json:"profileDir,omitempty"`
	UnreadProfileKeys []string `json:"unreadProfileKeys,omitempty"`
	// Encoding is the one frame payload encoding selected from Hello.Encodings,
	// or empty when this connection stays on ordinary JSON payloads.
	Encoding string `json:"encoding,omitempty"`

	// PlacesRoot is the engine machine's own state root — the directory
	// [MethodPlacesWorld] walked, on the disk it walked it on.
	//
	// IT TRAVELS BECAUSE A WORLD IS A SET OF PATHS AND A PATH NEEDS ITS DISK.
	// [session.World.Adopt] puts the conversation THIS WINDOW is sitting in back
	// into a walk that was taken too early to see it, and it needs the root that
	// walk was taken under to work out which bucket the conversation belongs to.
	// Handing it this laptop's root would file a session that lives on the server
	// under a project on the laptop. Empty is an engine that answers no world,
	// which is version 3 and every build before it.
	PlacesRoot string `json:"placesRoot,omitempty"`

	// Live is the stream still running when this surface arrived, or zero.
	//
	// IT IS THE WHOLE POINT OF THE PERSISTENT ENGINE, said in one number: a
	// person asked for a long refactor from a café, closed the laptop, and sat
	// down somewhere else — and this field is how the new surface learns there
	// is a turn in flight to reattach to rather than an idle session to type
	// at. The events of that turn arrive as ordinary "event" frames from
	// [Hello.Resume]'s cursor onward, so nothing about drawing it is special.
	Live uint64 `json:"live,omitempty"`

	// Attached is how many OTHER surfaces are on this session right now.
	//
	// It is carried because a surface that is not alone must be able to say so:
	// two people (or one person and their own forgotten window) sharing a
	// conversation is a fact about that conversation, and a screen that hid it
	// would be the one place codeaf lied about who is in the room. Zero is the
	// ordinary case and draws nothing, by the emptiness law.
	Attached int `json:"attached,omitempty"`

	// Held is the questions this session raised while nobody was attached,
	// carried in the welcome so the first frame a returning surface draws
	// already has them. See [HeldQuestion].
	Held []HeldQuestion `json:"held,omitempty"`

	// Facts is the fact set this surface arrives holding — the model, the name,
	// the spending, the weight, the reasoning levels — so the FIRST frame it
	// draws is drawn from memory and not from four round trips.
	//
	// It is a pointer so that "this engine states nothing" is a thing a decoder
	// can see. Nothing on the surface has to handle that case today (the door
	// refuses a version mismatch before the screen exists), but a nil here and a
	// zero-valued fact set are different facts, and a replica filled from the
	// second would draw a conversation with no model and nothing spent.
	Facts *FactsPush `json:"facts,omitempty"`

	// Persistent says the far end is a session HOST — the engine outlives this
	// connection — rather than version 2's other honest shape, a one-shot
	// engine on a pipe.
	//
	// A SURFACE MUST NOT PROMISE A LIFETIME THE ENGINE DOES NOT HAVE. Both
	// shapes speak this protocol and both are legitimate: `codeaf engine`
	// started by hand on a machine with no host is still a conversation, it
	// simply ends when the pipe does. The screen's word for detaching, and
	// whether "close the lid, it keeps going" is true, both hang off this
	// single fact, so it is stated rather than assumed from the transport.
	Persistent bool `json:"persistent,omitempty"`

	// Launch is the shape the conversation actually has, as the engine built
	// it. It is an ECHO and not a confirmation: a hello that asked for one
	// shape and joined a conversation somebody else had already opened gets
	// that conversation's shape here, and the surface is expected to notice.
	Launch *LaunchShape `json:"launch,omitempty"`

	// Driver is who holds the keyboard the moment this surface arrived, told
	// the way this surface should read it. A first attach is always the driver;
	// a [Hello.Back] one may not be.
	//
	// IT IS CARRIED ON THE WELCOME AND NOT LEFT TO THE FIRST "driver" FRAME,
	// because a frame is only sent when the answer CHANGES and a returning
	// surface can arrive into an answer that did not. A surface that assumed it
	// drove until told otherwise would draw a composer somebody's keystrokes
	// would then be refused into.
	Driver Driver `json:"driver,omitzero"`

	// SteerRepeat says this engine RECOGNISES A SEND IT HAS ALREADY TAKEN: a
	// correction sent into a task's page again under the same identity
	// ([TaskSteerArgs]) answers the receipt already on the record and delivers
	// nothing.
	//
	// IT IS CARRIED BECAUSE THE QUESTION IS ASKED BEFORE ANYTHING IS SENT, and
	// it has to be. A surface holding a send it got no answer to has exactly two
	// moves — ask again, or keep the words and say so — and which one is honest
	// depends on a fact about the far machine that no failed call can report:
	// the call that would have told it is the one that stopped answering.
	//
	// ABSENCE IS false AND false IS THE SAFE READING. An engine that predates
	// this ignores the identity on a send, so a repeat there would be a second
	// correction; the surface therefore keeps the person's words instead of
	// asking twice, which is the degradation that costs a keystroke rather than
	// the one that corrects a worker twice.
	SteerRepeat bool `json:"steerRepeat,omitempty"`

	// SteerOwner says this engine CHECKS THE CONVERSATION A SEND WAS WRITTEN FOR
	// ([TaskSteerArgs.Session]) against the one it actually has open, and refuses
	// rather than delivering to the task with that number over here.
	//
	// IT IS A SEPARATE FACT FROM SteerRepeat and may not be inferred from it: an
	// engine can keep send identities and still have been built before this check
	// existed, and it would then read Session as an unknown field and deliver.
	//
	// ABSENCE IS false, AND false MEANS THE SURFACE MUST NOT SEND A BOUND
	// CORRECTION AT ALL. An unenforced claim is worse than no claim: the surface
	// would believe the engine was guarding something nobody is guarding.
	SteerOwner bool `json:"steerOwner,omitempty"`
	// TaskSetup advertises task-scoped model and thinking controls.
	// TaskRetry advertises retrying incomplete tasks in place.
	TaskRetry bool `json:"taskRetry,omitempty"`
	TaskSetup bool `json:"taskSetup,omitempty"`
	// TaskSettle says this engine can be ASKED TO DECIDE A LANDING — accept, not
	// right, one more merge round, the hand-over and the take-back
	// ([MethodTaskSettle]).
	//
	// IT IS CARRIED FOR [Welcome.Folders]'S REASON, and the cost of not carrying
	// it was measured: internal/tui3 asserts these doors on the agent it holds and
	// draws no answers row at all where the assertion fails, so a window on an
	// engine host drew a landing card with its reason and nothing to press (#706).
	// Every *Agent has the methods; only the welcome knows whether the machine at
	// the far end does.
	TaskSettle bool `json:"taskSettle,omitempty"`

	// Effort says this engine HAS A DIAL ON THE CONVERSATION'S OWN THINKING —
	// that its agent answers [MethodEffort], [MethodResolvedEffort] and
	// [MethodSetEffort] rather than refusing them (effort.go).
	//
	// IT IS CARRIED FOR [Welcome.Folders]'S REASON, WHICH IS THE ONE THAT MATTERS
	// MOST HERE. A surface at this end holds a *remote.Agent, which ALWAYS has
	// the three methods on it, so the type assertion a local surface uses to tell
	// a dial from no dial answers yes for every connection and says nothing about
	// the far machine. And the honest reading cannot be taken from the ANSWER
	// either: "" is a real rung on this ladder — a conversation asking for no
	// thinking at all — so silence and absence are the same string, and the flag
	// is the only thing that separates them.
	//
	// ABSENCE IS false AND false IS THE SAFE READING: A CAPABILITY THAT CANNOT
	// WORK IS ABSENT, NOT BROKEN, so the seam draws no rung, the chord does
	// nothing, and nothing on the screen offers to move a knob the far engine
	// has never heard of.
	Effort bool `json:"effort,omitempty"`

	// Approval says this engine HAS A DIAL ON THE CONVERSATION'S OWN POSTURE ON
	// THE TOOL GATE — that its agent answers [MethodResolvedApproval] and
	// [MethodSetApproval] rather than refusing them (approval.go). It is
	// carried for [Welcome.Effort]'s reason, and false is the safe reading for
	// the same reason: the surface then draws the chip as a reading of
	// [Welcome.ApprovalMode] and says the far machine's rules decide.
	Approval bool `json:"approval,omitempty"`

	// DefaultEffort is the far install's own `thinking` row, and
	// StandingApproval is what a conversation nobody has touched opens at on
	// that install (the rows as they stand, or the launch's `--yolo`). Both
	// are carried ONCE, at the door, because they are facts about the install
	// and not about any conversation: the draft on home and the other places
	// draws them as the rung and the gate the NEXT conversation on that machine
	// would run at (internal/tui3's boxseam.go), and a draft is drawn on every
	// frame. An empty DefaultEffort means auto when Effort is true; Effort
	// itself distinguishes an engine without the control. An empty
	// StandingApproval means there is no gate and draws no approval cell.
	DefaultEffort    string `json:"defaultEffort,omitempty"`
	StandingApproval string `json:"standingApproval,omitempty"`

	// Folders says this engine CAN HOLD THE FOLDERS A CONVERSATION IS ABOUT —
	// that its agent answers [MethodPlacesRefer] and [MethodPlacesRemove] rather
	// than refusing them (wire_places.go).
	//
	// IT IS CARRIED BECAUSE THE QUESTION IS ASKED BEFORE ANYTHING IS CHOSEN, and
	// that is [Welcome.SteerRepeat]'s reason exactly. A surface at this end holds
	// a *remote.Agent, which ALWAYS has the three methods on it — so the type
	// assertion a local surface uses to tell a capable agent from an incapable
	// one answers yes for every connection and says nothing about the machine at
	// the far end. Without this flag the only honest reading arrives as the
	// refusal to the call, which is after the person has already picked a folder
	// out of a list and pressed enter.
	//
	// ABSENCE IS false AND false IS THE SAFE READING: an engine that predates
	// these doors sends no field, and a surface that believed it could attach
	// would open a picker whose every row ends in an error.
	Folders bool `json:"folders,omitempty"`

	// Teams says this engine ANSWERS THE TEAMS DOORS ([MethodTeamsRead],
	// [MethodTeamsUpdate], [MethodTeamsTraffic]) from its own profile, which
	// is where its team tools keep the teams and their Traffic.
	//
	// IT IS CARRIED FOR [Welcome.Folders]' REASON: the window decides at the
	// door whether it has teams over this connection, before anything is
	// drawn. ABSENCE IS false, and false turns teams off over the connection
	// with the sentence the window has always said; it never sends the window
	// back to the laptop's own teams file, which the far session cannot see.
	Teams bool `json:"teams,omitempty"`

	// Delegation says this engine ANSWERS THE DELEGATION DOORS
	// ([MethodTeamsDefaults], [MethodTeamsPackets], [MethodTeamsRaise],
	// [MethodTeamsDecide], [MethodTeamsEscalate], [MethodTeamsSpend],
	// [MethodTeamsDelete]) from its own profile, beside the teams doors.
	//
	// IT IS CARRIED FOR [Welcome.Teams]' REASON, and it is a second flag
	// because an engine can have the first without it: one built between the
	// two answers the teams file and its Traffic and not the packets, the
	// spend or a delete. ABSENCE IS false, and false leaves those seam doors
	// nil, which the window reads as "not over this connection" and says so
	// rather than reading this laptop's files.
	Delegation bool `json:"delegation,omitempty"`

	// TeamSettings says this engine ANSWERS [MethodTeamsApplyDefault]: the
	// settings tab can change the five `teams.` defaults on this machine.
	//
	// IT IS A FLAG OF ITS OWN beside [Welcome.Delegation] for that flag's
	// reason. An engine can read the defaults and still have no door that
	// writes them. ABSENCE IS false, and false leaves the Teams tab read-only
	// over the connection, said as such, rather than writing this laptop's file.
	TeamSettings bool `json:"team_settings,omitempty"`

	// WrapUp says this engine ANSWERS THE WRAP-UP'S TWO DOORS
	// ([MethodTeamsWrapUp], [MethodTeamsAcceptClosing]). A third flag for
	// [Welcome.Delegation]'s reason: an engine built between the two has the
	// packets and not these. ABSENCE IS false, and false leaves the window
	// with `Close now` only over that connection, said as such.
	WrapUp bool `json:"wrap_up,omitempty"`

	// TeamAsk says this engine ANSWERS THE WALL'S TWO MODEL ASKS
	// ([MethodTeamsName], [MethodTeamsPropose]): its agent names a group of
	// conversations and proposes teams on its own naming role.
	//
	// IT IS CARRIED FOR [Welcome.Folders]' REASON: a *remote.Agent always has
	// NameTeam and ProposeTeams on it, so the wall's type assertion answers yes
	// for every connection and says nothing about the far machine. ABSENCE IS
	// false, and false is refused at this end before anything is written, which
	// the wall reads as it reads any failed ask: the word it already holds, and
	// Organize's folder pass alone.
	TeamAsk bool `json:"teamAsk,omitempty"`

	// News says this engine SENDS THE STATUS LINE'S NEWS — the "phase" and
	// "lane" frames the live rate and the `via <machine>` rider are drawn from
	// (news.go) — for the conversation this surface arrived in.
	//
	// IT MAKES AN ABSENCE KNOWABLE WITHOUT REFUSING ANYBODY. The news frames
	// rode an existing version on purpose ([Version]'s note says why: a status
	// line must not turn a live conversation away), which left a surface
	// attached to an engine from before them drawing no rate and no machine and
	// no way to say why — and a busy older engine on the same version is
	// attached to rather than retired (cmd/codeaf's clearStaleEngineHost). A
	// surface reads this, or a news frame arriving, as the engine having the
	// news; neither after a whole answer is an older engine, and the surface
	// says so once ([Client.NewsSilent]).
	//
	// ABSENCE IS false, and it is not by itself proof of an old engine: every
	// build between the news frames and this flag sends them without saying so,
	// which is why a frame arriving counts as the same answer.
	News bool `json:"news,omitempty"`

	// Skills says this engine's conversation CAN CARRY SKILLS PUT IN FRONT OF
	// IT BY HAND and can list the shelf they come from — that its agent
	// answers [MethodAttachSkills], [MethodDetachSkill], [MethodAttachedSkills],
	// [MethodClearSkills] and [MethodSkillShelf] rather than refusing them
	// (skills.go).
	//
	// IT IS CARRIED FOR [Welcome.Folders]'S REASON: a surface at this end holds
	// a *remote.Agent, which ALWAYS has the doors on it, so the assertion the
	// picker makes says nothing about the far machine. ABSENCE IS false, and
	// false keeps the picker's own sentence for a conversation that cannot
	// carry attached skills rather than a list whose every choice goes nowhere.
	Skills bool `json:"skills,omitempty"`
}

Welcome is the server's answer ("welcome"): the facts a surface needs before its first frame, which are the same facts newApp reads off a local agent.

type WhoIs

type WhoIs struct {
	// StandDown asks the host to retire: close its conversations, flush their
	// journals, drop the socket and exit, so the next connection starts a host
	// from whatever binary is on disk now. It is refused while there is work in
	// flight, and the answer says which happened.
	StandDown bool `json:"standDown,omitempty"`
	// Anyway asks for the retirement even with work in flight, and there is
	// exactly one caller: a person typing `codeaf engine --stop` on the machine
	// itself, who has been told what is running and said stop anyway. A turn
	// caught by it stops where it is and keeps its partial reply — the same
	// thing ctrl+c does locally, by the same road ([Session.Close]).
	Anyway bool `json:"anyway,omitempty"`
}

WhoIs is the question, and it is asked on a connection's FIRST frame in place of a hello.

type WireFile

type WireFile struct {
	// Name is the file's own name as the surface saw it, and NEVER a path: the
	// engine joins it to a directory of the engine's choosing, so a "name" that
	// walked out of that directory would be this wire handing a remote machine
	// an arbitrary write. The engine sanitizes it regardless — a boundary that
	// trusts its input is not a boundary — but the field is documented as a
	// name so that nothing on this side is tempted to send a path.
	Name string `json:"name"`
	// MIME is what the surface believed this was, empty when it could not tell.
	// It is a hint for the engine's naming and nothing is refused for lacking
	// it — unlike an image, whose type the provider genuinely needs.
	MIME string `json:"mime,omitempty"`
	// Bytes is the file itself. The frame ceiling (server.go's frameCap) is the
	// only limit this wire imposes; the SIZE the person is allowed to attach is
	// a surface question, asked on the surface, in the surface's own words —
	// the same division images already use.
	Bytes []byte `json:"bytes"`
}

WireFile is one attachment travelling with its bytes.

type WrapUpArgs

type WrapUpArgs struct {
	Team string `json:"team"`
	Text string `json:"text,omitempty"`
}

WrapUpArgs is the team to wrap up and the person's words, "" for the standard ones.

type WrappedAgent

type WrappedAgent interface {
	Submit(ctx context.Context, text string) (<-chan session.Event, error)
	SubmitStanding(ctx context.Context, text string) (<-chan session.Event, error)
	SubmitImage(ctx context.Context, text string, images []session.Image) (<-chan session.Event, error)
	FollowUp(text string) (<-chan session.Event, error)
	Steer(text string) (<-chan session.Event, error)
	Interrupt()
	// InterruptFor is the stop with the door it came through on it, for the
	// machinery stops that are not a person (internal/session's stopcause.go).
	InterruptFor(door session.StopDoor)
	Compact(ctx context.Context) error
	Close() error
	Model() string
	SetModel(model string)
	SetContextWindow(tokens int)
	ReasoningFor(model string) string
	// ReasoningLevels is every model somebody has dialled and the level it
	// holds. It is here rather than left to ReasoningFor above because a
	// SURFACE ASKS THAT QUESTION ABOUT MODELS IT HAS NOT SWITCHED TO — a picker
	// row, a ctrl+t on that row — and one round trip per row is exactly the
	// shape [session.Facts] exists to end. The whole map is a handful of short
	// strings and travels in one push.
	ReasoningLevels() map[string]string
	SetReasoningFor(model, level string)
	ResolveConsent(id uint64, allow bool)
	ResolveConsentRemember(id uint64, allow bool, scope session.ConsentScope)
	ResolveStanding(id uint64, answer session.StandingAnswer)
	ResolveHarness(id uint64, run bool, model string)
	ResolveConnect(id string, approve bool)
	ResolveConnectKey(id string, key string)
	NoteConnected(service, account string)
	Title() string
	Usage() session.Usage
	ContextTokens() int
	Transcript() []session.DisplayEntry
	EarlierHistory() session.EarlierHistory
	RewindPoints() []session.RewindPoint
	RewindAt(index int) ([]session.DisplayEntry, error)
}

WrappedAgent is the slice of *session.Agent an engine serves. It is the union of tui3.Agent and the rewind pair that surface type-asserts for, because a REMOTE SURFACE MUST NOT BE A LESSER SURFACE: whatever the local one can ask its agent, this one answers over the wire, and a method missing here would be a feature that quietly works at home and quietly does not away.

It is named WrappedAgent rather than Agent because remote.Agent is already the OTHER end's name: the client's implementation of tui3.Agent, which a surface holds. This is the engine's own view of the same shape, declared here rather than imported from internal/tui3 for the reason wire.go states about the two halves: the engine knows nothing of the surface package and never will. It is an interface rather than the concrete agent for the reason session.Completer is one — the tests below drive a scripted agent and never open a socket.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL