protocol

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Aug 24, 2026 License: MIT Imports: 5 Imported by: 0

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

View Source
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.

View Source
const (
	PlanPending = "pending"
	PlanActive  = "active"
	PlanDone    = "done"
	PlanBlocked = "blocked"
)

Plan item statuses.

View Source
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.

View Source
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.

View Source
const Version = "v1"

Version is the protocol version carried in the URL prefix.

Variables

This section is empty.

Functions

func HTTPStatus

func HTTPStatus(code string) int

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"`
	// Unavailable are criteria that could not be run at all. Different from
	// 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

type Error struct {
	Code    string `json:"code"`
	Message string `json:"message"`
}

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.

func AsError

func AsError(err error) (*Error, bool)

AsError extracts a *Error from err, if there is one.

func Errorf

func Errorf(code, format string, args ...any) *Error

Errorf builds an Error with a formatted message.

func (*Error) Error

func (e *Error) Error() string

func (*Error) Status

func (e *Error) Status() int

Status returns the HTTP status this error should be sent with.

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

type MessageDelta struct {
	TurnID string `json:"turn_id"`
	Text   string `json:"text"`
}

MessageDelta is a fragment of model text.

type MessageReasoning

type MessageReasoning struct {
	TurnID string `json:"turn_id"`
	Text   string `json:"text"`
}

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

type SessionCompacted struct {
	FromSeq uint64 `json:"from_seq"`
	ToSeq   uint64 `json:"to_seq"`
}

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 TurnImage

type TurnImage struct {
	MediaType string `json:"media_type"`
	Data      string `json:"data"`
}

TurnImage is one picture on the wire.

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

type TurnSteered struct {
	TurnID string `json:"turn_id"`
	Text   string `json:"text"`
}

TurnSteered is what the person said while the turn was running, and the round it landed at.

type UndoResult

type UndoResult struct {
	Restored []string `json:"restored"`
	Refused  []string `json:"refused"`
}

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.

type Usage

type Usage struct {
	InputTokens      int `json:"input_tokens"`
	OutputTokens     int `json:"output_tokens"`
	CacheReadTokens  int `json:"cache_read_tokens,omitempty"`
	CacheWriteTokens int `json:"cache_write_tokens,omitempty"`
}

Usage is the token accounting for a turn.

Jump to

Keyboard shortcuts

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