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
- Variables
- func AttachedSentence(text string, paths []string) string
- func AttachmentsDir(place session.Place, workspace string) string
- func MachineName() string
- func Pipe() (surface, engine io.ReadWriteCloser)
- func Refuse(out io.Writer, reason string) error
- func Serve(in io.Reader, out io.Writer, opts Options) error
- func ServeAttach(in io.Reader, out io.Writer, opts AttachOptions) error
- type Agent
- func (a *Agent) AnswerLaneOffer(yes bool) bool
- func (a *Agent) Attach() (<-chan session.Event, bool, func())
- func (a *Agent) AttachReplay() ([]session.DisplayEntry, <-chan session.Event, func())
- func (a *Agent) Autonomy() map[session.AskKind]session.Policy
- func (a *Agent) Cancel(id string) (string, error)
- func (a *Agent) Client() *Client
- func (a *Agent) Close() error
- func (a *Agent) Compact(ctx context.Context) error
- func (a *Agent) ContextTokens() int
- func (a *Agent) ConversationEffort() string
- func (a *Agent) Detach() error
- func (a *Agent) EarlierHistory() session.EarlierHistory
- func (a *Agent) EffortSupported() bool
- func (a *Agent) FollowUp(text string) (<-chan session.Event, error)
- func (a *Agent) HandUnverifiedToModel(id uint64) error
- func (a *Agent) HarnessDesigns() <-chan session.Event
- func (a *Agent) HoldQuestion(kind session.QuestionKind, token string)
- func (a *Agent) HoldTask(id uint64)
- func (a *Agent) Interrupt()
- func (a *Agent) InterruptFor(door session.StopDoor)
- func (a *Agent) KeepsFolders() bool
- func (a *Agent) Model() string
- func (a *Agent) NeedsPerson() bool
- func (a *Agent) NoteConnected(service, account string)
- func (a *Agent) OpenQuestions() []session.Question
- func (a *Agent) PendingTasks() []uint64
- func (a *Agent) Places() []session.PlaceRef
- func (a *Agent) ReasoningFor(model string) string
- func (a *Agent) ReasoningLevels() map[string]string
- func (a *Agent) ReferPlace(path string, arrival session.PlaceArrival) (session.PlaceRef, error)
- func (a *Agent) RemovePlace(path string) error
- func (a *Agent) ResolveConflict(id uint64) error
- func (a *Agent) ResolveConnect(id string, approve bool)
- func (a *Agent) ResolveConnectKey(id string, key string)
- func (a *Agent) ResolveConsent(id uint64, allow bool)
- func (a *Agent) ResolveConsentRemember(id uint64, allow bool, scope session.ConsentScope)
- func (a *Agent) ResolveHarness(id uint64, run bool, model string)
- func (a *Agent) ResolveQuestion(answer session.Answer) error
- func (a *Agent) ResolveStanding(id uint64, answer session.StandingAnswer)
- func (a *Agent) ResolveSubharness(id uint64, run bool, input json.RawMessage)
- func (a *Agent) ResolveTask(id uint64, answer session.TaskAnswer)
- func (a *Agent) ResolveUnverified(id uint64, resolution session.TaskResolution, why string) error
- func (a *Agent) ResolvedEffort() string
- func (a *Agent) RetargetTask(id uint64, model string) (session.ModelLanding, error)
- func (a *Agent) RewindAt(index int) ([]session.DisplayEntry, error)
- func (a *Agent) RewindPoints() []session.RewindPoint
- func (a *Agent) SetAutonomy(kind session.AskKind, policy session.Policy) error
- func (a *Agent) SetContextWindow(int)
- func (a *Agent) SetConversationEffort(rung string) bool
- func (a *Agent) SetModel(model string)
- func (a *Agent) SetReasoningFor(model, level string)
- func (a *Agent) SetTaskEffort(id uint64, rung string) error
- func (a *Agent) SettleSupported() bool
- func (a *Agent) ShortTitle() string
- func (a *Agent) StartPlannerRun(ctx context.Context, brief, hint string) (string, string, error)
- func (a *Agent) StartTask(ctx context.Context, brief string, solo bool) (uint64, string, string, error)
- func (a *Agent) Steer(text string) (<-chan session.Event, error)
- func (a *Agent) SteerRepeatKnown() bool
- func (a *Agent) SteerTask(id uint64, line string) (session.SteerReceipt, error)
- func (a *Agent) SteerTaskFrom(id uint64, line string, from session.SteerSource) (session.SteerReceipt, error)
- func (a *Agent) StopWork() error
- func (a *Agent) Submit(ctx context.Context, text string) (<-chan session.Event, error)
- func (a *Agent) SubmitFiles(ctx context.Context, text string, files []WireFile, images []session.Image) (<-chan session.Event, error)
- func (a *Agent) SubmitImage(ctx context.Context, text string, images []session.Image) (<-chan session.Event, error)
- func (a *Agent) SubmitStanding(ctx context.Context, text string) (<-chan session.Event, error)
- func (a *Agent) TakeBackDecision(id uint64) error
- func (a *Agent) TaskEffort(id uint64) string
- func (a *Agent) TaskProposalsPending() ([]uint64, bool)
- func (a *Agent) TaskRoom(id uint64, tail int) (session.TaskRecord, error)
- func (a *Agent) TaskSetupSupported() bool
- func (a *Agent) TaskUpdates() <-chan session.Event
- func (a *Agent) Title() string
- func (a *Agent) TitleChanges() <-chan session.Event
- func (a *Agent) Transcript() []session.DisplayEntry
- func (a *Agent) Typing()
- func (a *Agent) Usage() session.Usage
- func (a *Agent) WatchHarnessDesigns() (<-chan session.Event, func())
- func (a *Agent) WatchQuestions() (<-chan session.Event, func())
- func (a *Agent) WatchTaskUpdates() (<-chan session.Event, func())
- func (a *Agent) WatchTitle() (<-chan session.Event, func())
- func (a *Agent) WorkOutlivesExit() bool
- type ArchiveArgs
- type AttachOptions
- type AutonomyArgs
- type Client
- func (c *Client) Agent() *Agent
- func (c *Client) Archive(dir string, archived bool) error
- func (c *Client) Attached() int
- func (c *Client) CallsMade() uint64
- func (c *Client) ChangedSince(at time.Time) (int, int, error)
- func (c *Client) Close() error
- func (c *Client) DepositFile(name, mime string, data []byte) (string, error)
- func (c *Client) Driver() Driver
- func (c *Client) DriverChanged() <-chan struct{}
- func (c *Client) Err() error
- func (c *Client) Facts() session.Facts
- func (c *Client) FetchFile(path string) (FetchedFile, error)
- func (c *Client) Follow() <-chan Following
- func (c *Client) ForgetMemory(id string) error
- func (c *Client) Held() []HeldQuestion
- func (c *Client) HeldQuestions() ([]HeldQuestion, error)
- func (c *Client) Host() string
- func (c *Client) Ledger(since time.Time) (LedgerReading, error)
- func (c *Client) LinkNote() string
- func (c *Client) ListDir(path string) (DirListing, error)
- func (c *Client) ListMemories(scope string, limit int) ([]store.Memory, error)
- func (c *Client) Live() (uint64, <-chan session.Event)
- func (c *Client) MemoryProvenance(id string) (string, string, time.Time, error)
- func (c *Client) NewSession() (Welcome, error)
- func (c *Client) NewsSilent() bool
- func (c *Client) OpenSession(path string) (Welcome, error)
- func (c *Client) Ping() (time.Duration, error)
- func (c *Client) Recent() []session.Summary
- func (c *Client) RestoreMemory(id string) error
- func (c *Client) SaveStanding(item standing.Item) error
- func (c *Client) SearchConversations(terms string, limit int) ([]store.ConversationHit, error)
- func (c *Client) Snapshot(limit int) (store.MemoryShelves, error)
- func (c *Client) StandingItems(workspace string) ([]standing.Item, error)
- func (c *Client) StandingWatch() (standing.WatchStatus, bool)
- func (c *Client) StatPaths(paths []string) ([]PathFact, error)
- func (c *Client) Take() error
- func (c *Client) TakeNotice() string
- func (c *Client) TaskRecord(uri string, tail int) (session.TaskRecord, error)
- func (c *Client) UpdateMemory(id, title, text string, tags []string) error
- func (c *Client) Welcome() Welcome
- func (c *Client) World() (session.World, error)
- type ConnectArgs
- type ConnectedArgs
- type ConsentArgs
- type DepositedFile
- type Dialer
- type DirEntry
- type DirListing
- type Driver
- type Engine
- type EngineMemory
- type EventWire
- type FactsPush
- type FetchFileArgs
- type FetchedFile
- type Following
- type Frame
- type HarnessArgs
- type HeldQuestion
- type Hello
- type HostSelf
- type InterruptArgs
- type LaneWire
- type Lanes
- type LaunchShape
- type LedgerArgs
- type LedgerReading
- type ListDirArgs
- type Loop
- type MemoryChange
- type MemoryListArgs
- type MemoryOrigin
- type MemoryUpdateArgs
- type Moved
- type Options
- type PathFact
- type PhaseWire
- type PlacesTaskArgs
- type PlannerStartArgs
- type PlannerStarted
- type QuestionArgs
- type QuestionHoldArgs
- type ReasoningArgs
- type ReferArgs
- type Refusal
- type Roaming
- type SearchArgs
- type Session
- type StandingArgs
- type StandingWatchResult
- type StatPathsArgs
- type StreamCursor
- type StreamRef
- type SubharnessResolveArgs
- type SubmitArgs
- type SubmitFilesArgs
- type SubmitImageArgs
- type TaskHoldArgs
- type TaskPending
- type TaskResolveArgs
- type TaskRoomArgs
- type TaskSettleArgs
- type TaskSettled
- type TaskSetupArgs
- type TaskStartArgs
- type TaskStarted
- type TaskSteerArgs
- type TaskSteered
- type TaskStopArgs
- type TaskStopped
- type Turn
- type Welcome
- type WhoIs
- type WireFile
- type WrappedAgent
Constants ¶
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.
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.
const ( // Agent — payloads are the method's own argument struct below; results are // the return values likewise. 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 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 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 // 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) // 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.
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" )
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]). // // IT IS THE ONE DOOR ON THIS WIRE A KEYSTROKE MAY REACH, and it is still not // reached ON a keystroke: the search place arms a quiet interval and asks // only when the words have stopped moving (internal/tui3's place_search.go), // so this is exactly one call per question a person actually asked. There is // no cache behind it for the reason there is one behind every other reading // here — 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.
const ( MethodTaskStart = "Task.Start" MethodPlannerStart = "Task.StartPlanner" MethodTaskRoom = "Task.Room" MethodTaskSteer = "Task.Steer" MethodTaskStop = "Task.Stop" 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.
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 )
const Version = 16
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.
Variables ¶
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.
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.
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 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 ¶
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) 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) Autonomy ¶
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) 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 ¶
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) ContextTokens ¶
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 ¶
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) Detach ¶
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) 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 ¶
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 ¶
FollowUp queues a message for after this turn and returns the stream that turn will run on.
func (*Agent) HandUnverifiedToModel ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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) NeedsPerson ¶
NeedsPerson reads the pushed conversation state without a round trip.
func (*Agent) NoteConnected ¶
NoteConnected tells the session an account is connected.
func (*Agent) OpenQuestions ¶
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 ¶
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 ¶
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) ReasoningFor ¶
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 ¶
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) ReferPlace ¶
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) RemovePlace ¶
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) ResolveConflict ¶
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 ¶
ResolveConnect answers one connect ask.
func (*Agent) ResolveConnectKey ¶
ResolveConnectKey answers one connect ask that arrived with NeedsKey.
func (*Agent) ResolveConsent ¶
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 ¶
ResolveHarness answers one sub-harness offer.
func (*Agent) ResolveQuestion ¶
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 ¶
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) ResolvedEffort ¶
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 ¶
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) 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) SetAutonomy ¶
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 ¶
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 ¶
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 ¶
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 ¶
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) SettleSupported ¶
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 (*Agent) StartPlannerRun ¶
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) Steer ¶
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 ¶
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 ¶
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 ¶
StopWork asks the engine to end all work in this conversation and suppress wakes.
func (*Agent) Submit ¶
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) 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 ¶
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 ¶
TakeBackDecision is that in reverse, and resolves nothing either.
func (*Agent) TaskEffort ¶
TaskEffort is an explicit read; rendering uses the standing task updates.
func (*Agent) TaskProposalsPending ¶
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) TaskRoom ¶
TaskRoom reads the bounded tail of a node whose record URI may not exist yet.
func (*Agent) TaskSetupSupported ¶
func (*Agent) TaskUpdates ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 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 ¶
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 ¶
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) Attached ¶
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 ¶
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) Close ¶
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 ¶
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 ¶
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) 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 ¶
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 (*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) LinkNote ¶
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 (*Client) Live ¶
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 (*Client) NewSession ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 (*Client) SaveStanding ¶
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 (*Client) StandingItems ¶
func (*Client) StandingWatch ¶
func (c *Client) StandingWatch() (standing.WatchStatus, bool)
StandingWatch reads the scheduler on the engine machine.
func (*Client) StatPaths ¶
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 ¶
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 ¶
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 ¶
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) UpdateMemory ¶
func (*Client) 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 ¶
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 ConnectedArgs ¶
type ConsentArgs ¶
type ConsentArgs struct {
ID uint64 `json:"id"`
Allow bool `json:"allow"`
Scope session.ConsentScope `json:"scope,omitempty"`
}
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 {
// 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
// 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 EventWire ¶
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.
type FactsPush ¶
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 {
// 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 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 {
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"`
}
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 {
// 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 at all, for PhaseWire's reason one step further: a sighting is drawn 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 is lost is the pipe's own latency, which on the road this exists for — a surface and an engine host on one machine — is a fraction of a millisecond against ten minutes.
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.
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 ¶
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.
Served <-chan error
// contains filtered or unexported fields
}
Loop is a live client and the engine it is talking to, both in this process.
func Loopback ¶
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 ¶
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 ¶
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.
func (*Loop) Cut ¶
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.
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 ¶
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 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 PlannerStartArgs ¶
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 ¶
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 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 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.
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 ¶
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 ¶
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) Close ¶
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) File ¶
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 ¶
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) RetireIfIdle ¶
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.
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 ¶
StreamCursor is one "I have seen this stream through here".
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 ¶
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 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 ¶
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 ¶
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.
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 Turn ¶
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 {
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"`
// 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.
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"`
// 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"`
// 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"`
}
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 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.
Source Files
¶
- browse.go
- callclass.go
- client.go
- clientlanes.go
- compress.go
- driver.go
- effort.go
- file.go
- held.go
- image.go
- lanes.go
- loopback.go
- news.go
- observe.go
- orderedlane.go
- places.go
- questionlane.go
- redial.go
- replica.go
- server.go
- standinglane.go
- tasklane.go
- tasksettle.go
- tasksetup.go
- typing.go
- wakelane.go
- whois.go
- wire.go
- wire_lanes.go
- wire_places.go
- wire_task.go