Documentation
¶
Overview ¶
Package protocol is the shared vocabulary between the dcode daemon and its clients. It holds no logic and performs no I/O: both sides import it so the wire contract has exactly one definition.
Spec: docs/specs/architecture/client-server-protocol/202608072240-*.
Index ¶
- Constants
- func HTTPStatus(code string) int
- type ApprovalDecision
- type ApprovalRequest
- type ApprovalResolved
- type Completion
- type CreateSessionRequest
- type Error
- type Event
- type EventType
- type MessageDelta
- type MessageReasoning
- type PlanItem
- type PlanUpdated
- type Progress
- type RenameSessionRequest
- type ResolveApprovalRequest
- type Session
- type SessionCompacted
- type SessionRenamed
- type SessionResumed
- type SessionState
- type SteerRequest
- type SubmitTurnRequest
- type ToolCompleted
- type ToolRequested
- type TurnCompleted
- type TurnImage
- type TurnStarted
- type TurnSteered
- type UndoResult
- type Usage
Constants ¶
const ( CodeSessionNotFound = "session_not_found" CodeTurnAlreadyActive = "turn_already_active" // CodeNoActiveTurn answers a correction aimed at a turn that is not // running. Distinct from turn_already_active and its exact mirror: one says // "wait", the other says "there is nothing to correct — send it as a // message". Collapsing them would leave the client guessing which. CodeNoActiveTurn = "no_active_turn" CodeInvalidInput = "invalid_input" CodeApprovalResolved = "approval_already_resolved" CodeApprovalExpired = "approval_expired" CodeEventsExpired = "events_expired" CodeWorkspaceInvalid = "workspace_invalid" CodeMaxSessionsReached = "max_sessions_reached" CodeInternal = "internal" )
Error codes. Section 8 of the planning spec.
const ( PlanPending = "pending" PlanActive = "active" PlanDone = "done" PlanBlocked = "blocked" )
Plan item statuses.
const ( StopDone = "done" StopInterrupted = "interrupted" StopMaxIterations = "max_iterations" StopRepeatLoop = "repeat_loop" StopMaxTokens = "max_tokens" StopError = "error" // StopUnverified and StopIncomplete are RESULTS, not errors. They are the // honest state of work delivered without a check having passed. // // Treating them as failure would create exactly the wrong incentive: the // easy way out becomes switching the checking off. StopUnverified = "unverified" StopIncomplete = "incomplete" )
Turn stop reasons, carried in TurnCompleted.Reason.
const ( // ProgressRounds is the turn against its iteration ceiling. ProgressRounds = "rounds" // ProgressInFlight is how many tool calls are running together, against // the concurrency ceiling this session allows. ProgressInFlight = "in_flight" // ProgressArguments is a tool call still arriving from the model, counted // in bytes of its arguments. // // Bytes rather than lines: what has landed is a fragment of JSON, and // counting lines inside half an escaped string counts something that is // not there yet. It has no total — the model does not say how long the // call will be, and a denominator nobody sent is one somebody would trust. ProgressArguments = "arguments" // ProgressFiles is a scan working through files. Total is known when the // tool had the list before it started and absent when it is still walking. ProgressFiles = "files" )
What a Progress counts. A closed set, so a client can say it in the reader's language instead of printing the daemon's.
Only what something actually emits is declared. A kind nobody writes is a promise on a versioned surface that no code keeps.
const Version = "v1"
Version is the protocol version carried in the URL prefix.
Variables ¶
This section is empty.
Functions ¶
func HTTPStatus ¶
HTTPStatus returns the status for a code, or 500 for an unmapped one.
Types ¶
type ApprovalDecision ¶
type ApprovalDecision string
ApprovalDecision is the user's answer to a boundary crossing.
const ( ApprovalAllow ApprovalDecision = "allow" ApprovalAllowSession ApprovalDecision = "allow_session" ApprovalDeny ApprovalDecision = "deny" // The two answers that outlive the session, because some questions are // about the project rather than about the moment. Asking again next week // about a decision already made is how a prompt becomes something people // dismiss without reading — and a prompt nobody reads protects nobody. // // They are recorded in the USER's config root, never in the workspace: a // grant living inside a project would let a repository arrive // pre-approved, so cloning something would permit it before anyone read a // line of it. ApprovalAllowProject ApprovalDecision = "allow_project" ApprovalAllowAlways ApprovalDecision = "allow_always" )
func (ApprovalDecision) Grants ¶
func (d ApprovalDecision) Grants() bool
Grants reports whether a decision permits the crossing at all.
func (ApprovalDecision) Remembered ¶
func (d ApprovalDecision) Remembered() bool
Remembered reports whether a decision outlives the session, and how widely.
Named rather than compared inline, so the two places that persist a grant and the one that renders it cannot drift into disagreeing about which answers are standing ones.
func (ApprovalDecision) Valid ¶
func (d ApprovalDecision) Valid() bool
Valid reports whether d is a decision the server accepts.
type ApprovalRequest ¶
type ApprovalRequest struct {
ApprovalID string `json:"approval_id"`
TurnID string `json:"turn_id"`
ToolCallID string `json:"tool_call_id"`
Tool string `json:"tool"`
Command string `json:"command,omitempty"`
BoundaryCrossed string `json:"boundary_crossed"`
ExpiresAt time.Time `json:"expires_at"`
// Reason says why in a sentence, and Rule names the pattern that raised the
// question when one did. Consent to a rule nobody can see is consent to
// nothing, and Rule is also what an "allow for the session" answer is
// remembered against.
Reason string `json:"reason,omitempty"`
Rule string `json:"rule,omitempty"`
}
ApprovalRequest is emitted when execution crosses the sandbox boundary. The turn blocks until it is resolved or ExpiresAt passes, which denies.
type ApprovalResolved ¶
type ApprovalResolved struct {
ApprovalID string `json:"approval_id"`
Decision ApprovalDecision `json:"decision"`
}
ApprovalResolved records the decision that unblocked a turn.
type Completion ¶
type Completion struct {
// Verification is the single-criterion seal: clean, passed, failed,
// stale or unavailable.
Verification string `json:"verification"`
// Met and Unmet name the criteria, so a client can show which.
Met []string `json:"met,omitempty"`
Unmet []string `json:"unmet,omitempty"`
// unmet, and shown differently: nothing was learned about them.
Unavailable []string `json:"unavailable,omitempty"`
// TouchedProtected are paths that are part of how the work is measured
// and were written this turn. Never omitted when present.
TouchedProtected []string `json:"touched_protected,omitempty"`
}
Completion is the state of the done criteria when a turn ended.
type CreateSessionRequest ¶
type CreateSessionRequest struct {
Workspace string `json:"workspace"`
Model string `json:"model,omitempty"`
SandboxMode string `json:"sandbox_mode,omitempty"`
// Resume names a recorded session whose conversation this one continues.
//
// A new session either way: the old one ended with the client that ran it,
// and nothing survives what created it. What carries over is the history,
// rebuilt from the record — not the approvals, which were consent given in
// a moment that has passed, and not the background processes, which died
// with the session that started them.
Resume string `json:"resume,omitempty"`
}
CreateSessionRequest opens a session. Workspace must be absolute.
type Error ¶
Error is the wire error. Code is the stable machine identifier; Message is human text in English. Localisation, if it ever exists, hangs off Code and never off Message.
type Event ¶
type Event struct {
Seq uint64 `json:"seq"`
SessionID string `json:"session_id"`
Type EventType `json:"type"`
At time.Time `json:"at"`
Payload json.RawMessage `json:"payload"`
}
Event is the envelope every observable fact travels in. Seq is per session, monotonic from 1, never reused and never gapped.
At is the only non-deterministic field; golden comparisons zero it.
type EventType ¶
type EventType string
EventType identifies the payload shape of an Event.
const ( EventSessionCreated EventType = "session.created" // EventSessionResumed opens the conversation this session continues. // // The events after it happened somewhere else, and a marker is what keeps // the transcript honest about that: without one, a replayed conversation // reads as work this session did. It also carries the only thing the reader // cannot reconstruct from the events themselves — which session they came // from. EventSessionResumed EventType = "session.resumed" EventTurnStarted EventType = "turn.started" EventMessageDelta EventType = "message.delta" // EventMessageReasoning carries the model's thinking, which is not its // answer. It is an event so a client can show it and so replaying a session // looks like having watched it — but it never enters the history the model // is sent, because a model that reads its own reasoning back as something // it said out loud starts defending it. EventMessageReasoning EventType = "message.reasoning" EventToolRequested EventType = "tool.requested" EventApprovalRequired EventType = "tool.approval_required" EventApprovalResolved EventType = "tool.approval_resolved" EventToolCompleted EventType = "tool.completed" // EventTurnSteered is the person correcting a turn already under way. // // Its own event rather than a message delta: a correction the transcript // cannot tell apart from the model's own words is a turn nobody can audit // afterwards, and "why did it change direction" is the first question asked // of any run that went sideways. EventTurnSteered EventType = "turn.steered" EventTurnCompleted EventType = "turn.completed" EventPlanUpdated EventType = "plan.updated" // EventProgress answers "how far along", and it is the only event here // that is not a fact worth replaying on its own. // // One event rather than one per question. A tool counting files and a turn // counting rounds are the same question asked of different subjects, and // adding a versioned surface twice for one kind of question is how it comes // out crooked — the second one always answers slightly differently. // // It joins the log and the record like message.delta does: chatty, batched // rather than flushed, and carrying a Seq. Giving it no Seq would have been // the alternative, and it would have put a gap in the one property the // record is built on. EventProgress EventType = "progress" // EventSessionRenamed is a name a person gave this conversation. // // An event rather than a file beside the record, and the record is where it // belongs for one reason above the others: a name for a conversation that // no longer exists is nothing. Pruning removes the transcript, and a name // stored anywhere else would outlive what it named — a listing full of // titles for sessions nobody can open. // // It also keeps the count at one. A second store beside the log is a second // thing that can disagree with the first, and this protocol's whole shape // is that every observable fact travels the same way. EventSessionRenamed EventType = "session.renamed" EventSessionCompacted EventType = "session.compacted" EventSessionError EventType = "session.error" )
Event types. Payload shapes are defined in section 5.1 of the planning spec.
type MessageDelta ¶
MessageDelta is a fragment of model text.
type MessageReasoning ¶
MessageReasoning is a fragment of the model's thinking.
type PlanItem ¶
type PlanItem struct {
ID int `json:"id"`
Text string `json:"text"`
Status string `json:"status"`
Blocked string `json:"blocked,omitempty"`
}
PlanItem is one entry of the session plan, maintained by the plan tool.
type PlanUpdated ¶
type PlanUpdated struct {
Items []PlanItem `json:"items"`
}
PlanUpdated is the payload of EventPlanUpdated.
type Progress ¶ added in v0.2.0
type Progress struct {
TurnID string `json:"turn_id"`
// ToolCallID names the call this is about; empty means the turn itself.
ToolCallID string `json:"tool_call_id,omitempty"`
// Name is the tool, sent only while a call is still ARRIVING — before
// tool.requested exists to carry it. A subject that does not exist yet
// has to name itself, or the report has nowhere to land.
Name string `json:"name,omitempty"`
Kind string `json:"kind"`
Done int `json:"done"`
Total int `json:"total,omitempty"`
}
Progress is how far along something running has got.
Done without Total is honest: a scan that has not finished enumerating knows how many it has seen and not how many there are. A client shows the count alone rather than inventing a denominator.
Kind comes from the closed set below rather than being a word to print. The daemon's language is not the reader's, and a client that renders the payload's text shows the wrong one to half its users.
Never part of the context the model is sent: it is a person's window, the same rule StartedAt already carries, and a count that changes between two runs of one session is exactly what ADR-03 forbids in a prefix.
type RenameSessionRequest ¶ added in v0.2.0
type RenameSessionRequest struct {
Name string `json:"name"`
}
RenameSessionRequest names a conversation. An empty name restores the title derived from the first question, which is the way back rather than a second command for undoing.
type ResolveApprovalRequest ¶
type ResolveApprovalRequest struct {
Decision ApprovalDecision `json:"decision"`
}
ResolveApprovalRequest answers a pending approval. First writer wins.
type Session ¶
type Session struct {
ID string `json:"id"`
State SessionState `json:"state"`
Workspace string `json:"workspace"`
Model string `json:"model"`
SandboxMode string `json:"sandbox_mode"`
CreatedAt time.Time `json:"created_at"`
LastSeq uint64 `json:"last_seq"`
// ContextWindow is the model's window in tokens. The client needs it to
// turn a token count into the percentage a person can act on; without it,
// "12400 tokens" answers nothing.
ContextWindow int `json:"context_window,omitempty"`
}
Session is the server-owned session record. Clients hold no session state of their own beyond scroll position, panel visibility and their input queue.
type SessionCompacted ¶
SessionCompacted records a context compaction.
type SessionRenamed ¶ added in v0.2.0
type SessionRenamed struct {
Name string `json:"name"`
}
SessionRenamed is the name a person gave, which beats the one derived from the first question. Empty gives the derived title back.
type SessionResumed ¶
type SessionResumed struct {
SourceID string `json:"source_id"`
Turns int `json:"turns"`
StartedAt time.Time `json:"started_at,omitempty"`
}
SessionResumed names the conversation the events after it came from.
Turns is what happened there, counted before the replay rather than by the reader, so a client that joins late reads the same number as one that was there.
type SessionState ¶
type SessionState string
SessionState is the lifecycle state of a session.
const ( SessionStateIdle SessionState = "idle" SessionStateRunning SessionState = "running" SessionStateBlocked SessionState = "blocked" SessionStateClosed SessionState = "closed" )
func (SessionState) Valid ¶
func (s SessionState) Valid() bool
Valid reports whether s is a state the server can be in.
type SteerRequest ¶
type SteerRequest struct {
Text string `json:"text"`
}
TurnStarted announces an accepted input. SteerRequest is what the person says to a turn already running.
type SubmitTurnRequest ¶
type SubmitTurnRequest struct {
Text string `json:"text"`
// Images are pictures shown with this turn, base64 encoded with their
// media type. Sent by value rather than as paths: the daemon may be on
// another machine, and a path only means something where it was typed.
Images []TurnImage `json:"images,omitempty"`
}
SubmitTurnRequest submits user input. Rejected with turn_already_active if a turn is already running: one turn per session.
type ToolCompleted ¶
type ToolCompleted struct {
ToolCallID string `json:"tool_call_id"`
OK bool `json:"ok"`
Output string `json:"output"`
Truncated bool `json:"truncated"`
// What the call did, stated once by the tool that knows.
//
// A client that parses Output to rebuild these numbers breaks silently
// the day the wording changes, and every client has to reimplement the
// same parsing. All optional: a tool reports what applies to it.
Lines int `json:"lines,omitempty"`
Files int `json:"files,omitempty"`
Added int `json:"added,omitempty"`
Removed int `json:"removed,omitempty"`
ExitCode int `json:"exit_code,omitempty"`
HasExit bool `json:"has_exit,omitempty"`
DurationMS int `json:"duration_ms,omitempty"`
// StartedAt and FinishedAt are the REAL execution order (RN-3.3).
//
// A duration cannot answer which of two concurrent calls went first,
// and results arrive in emission order rather than the order they ran.
// A client showing a batch has a person looking at it, and "which of
// these happened first" is a reasonable thing for them to ask.
//
// They live HERE and never in the context sent to the model: a
// timestamp in the prefix would make two runs of the same session
// differ, which ADR-03 forbids. The event is a person's window; the
// context is not.
StartedAt time.Time `json:"started_at,omitempty"`
FinishedAt time.Time `json:"finished_at,omitempty"`
// Diff is the unified diff of a change. Present only for tools that
// modify a file, and never part of what the model was sent.
Diff string `json:"diff,omitempty"`
}
ToolCompleted carries the result of an execution.
type ToolRequested ¶
type ToolRequested struct {
TurnID string `json:"turn_id"`
ToolCallID string `json:"tool_call_id"`
Name string `json:"name"`
Input json.RawMessage `json:"input"`
}
ToolRequested announces a tool call before policy evaluation.
type TurnCompleted ¶
type TurnCompleted struct {
TurnID string `json:"turn_id"`
Reason string `json:"reason"`
// Usage is what the turn cost. Absent when the provider did not report
// it, which is why it is a pointer: zero tokens and unknown tokens are
// different facts and a client shows them differently.
Usage *Usage `json:"usage,omitempty"`
// Completion is what was and was not checked. Absent when the turn had
// no definition of done, which is why it is a pointer: "nothing to
// check" and "checked, all met" are different facts.
//
// It travels on the wire because it is the guarantee that survives a
// model claiming success in prose. The text can lie; this cannot.
Completion *Completion `json:"completion,omitempty"`
}
TurnCompleted ends a turn with one of the Stop* reasons.
type TurnStarted ¶
type TurnStarted struct {
TurnID string `json:"turn_id"`
// Text is what the user asked for.
//
// It rides here because a turn starts BECAUSE of it, and because
// nothing else carried it: the log held the model's side of a
// conversation and none of the questions. A transcript could not be
// read, a session could not be titled, and a client attaching to a
// session already under way saw answers to questions it never saw.
Text string `json:"text,omitempty"`
}
Payloads for the remaining event types.
type TurnSteered ¶
TurnSteered is what the person said while the turn was running, and the round it landed at.
type UndoResult ¶
UndoResult is what putting the last turn back changed, and what it would not touch.
Refused is per file rather than all-or-nothing: seven files changed and one edited by hand should still give six back, and naming the one that stayed is more useful than refusing everything on account of it.