返回 DeepSeek-Reasonix
event.go
根目录 / internal / event / event.go
1 // Package event defines the typed event stream the agent emits as it runs a
2 // turn, and the Sink it emits to. It decouples "what happened" (the model
3 // produced reasoning, a tool was dispatched, a turn used N tokens) from "how to
4 // show it" (ANSI scrollback in a terminal, a card in a webview).
5 //
6 // The agent depends only on Sink; each frontend implements one. The chat TUI
7 // renders events to its scrollback; a headless run renders them to plain ANSI
8 // on stdout; a future GUI/serve transport forwards them to a webview or
9 // websocket. This replaces the old io.Writer contract, where the agent wrote
10 // pre-formatted ANSI and the consumer had to re-derive structure by matching
11 // line prefixes — fragile, and lossy for any frontend richer than a terminal.
12 package event
13
14 import (
15 "encoding/json"
16
17 "reasonix/internal/billing"
18 "reasonix/internal/evidence"
19 "reasonix/internal/nilutil"
20 "reasonix/internal/provider"
21 )
22
23 // Kind tags an Event. Read the field(s) documented for that kind.
24 type Kind int
25
26 const (
27 // TurnStarted marks the start of one top-level Run (one user turn). Sinks
28 // reset any per-turn rendering state on it. Carries no payload.
29 TurnStarted Kind = iota
30 // Reasoning is a thinking-mode reasoning delta (Text). Streamed before the
31 // visible answer; sinks typically render it muted under a "thinking" header.
32 Reasoning
33 // Text is an answer-text delta (Text).
34 Text
35 // Message marks the assistant turn's text as complete: Text holds the full
36 // answer and Reasoning the full chain-of-thought (both already streamed via
37 // the deltas above). A sink may use it to re-render the streamed raw text as
38 // styled markdown; a plain sink can ignore it.
39 Message
40 // ToolDispatch announces a tool call is about to run (Tool: ID/Name/Args/ReadOnly).
41 ToolDispatch
42 // ToolResult reports a finished tool call (Tool: Output/Err/Truncated set).
43 ToolResult
44 // Usage carries per-turn token telemetry (Usage; Pricing optional, for cost).
45 Usage
46 // Notice is an out-of-band message — a warning, truncation, block, or
47 // compaction notice (Level + Text).
48 Notice
49 // Phase marks a coordinator boundary, e.g. planner→executor handoff (Text =
50 // label such as "deepseek · planning").
51 Phase
52 // ApprovalRequest asks the frontend to approve a pending tool call
53 // (Approval: ID/Tool/Subject). The run blocks until the controller's
54 // Approve(ID, …) resolves it; a frontend shows a prompt and answers.
55 ApprovalRequest
56 // AskRequest asks the frontend to put one or more structured multiple-choice
57 // questions to the user (Ask: ID + Questions). The run blocks until the
58 // controller's AnswerQuestion(ID, …) resolves it. Powers the `ask` tool.
59 AskRequest
60 // TurnDone marks the end of one top-level Run (Err non-nil on failure;
61 // nil also for a user cancellation, which is not an error). Always the
62 // last event of a turn.
63 TurnDone
64 // CompactionStarted marks the start of a context-compaction pass (Compaction
65 // payload: Trigger). A frontend shows a "compacting…" placeholder while the
66 // summarizer runs; CompactionDone replaces it. Mirrors ToolDispatch/ToolResult.
67 CompactionStarted
68 // CompactionDone reports a finished compaction pass (Compaction payload:
69 // Trigger/Messages/Summary/Archive). An aborted pass emits this with an empty
70 // Summary so the placeholder still resolves. Replaces the older plain Notice
71 // so a sink can render a distinct, expandable card.
72 CompactionDone
73 // ToolProgress streams a chunk of a still-running tool's combined output
74 // (Tool: ID + Output = the new chunk). Emitted between ToolDispatch and
75 // ToolResult for long tools like bash so a frontend can show live progress.
76 // Appended last to keep the Kind values before it wire-stable.
77 ToolProgress
78 // MCPSurfaceReady fires once per server when its background-loaded surface
79 // (prompts or resources) finishes after startup. Lets UIs refresh /mcp
80 // status without polling. Text carries "<server>: <surface> ready (<count>
81 // items)". Appended last to keep the Kind values before it wire-stable.
82 MCPSurfaceReady
83 // Retrying fires before each backoff sleep while the provider re-attempts the
84 // connection+header phase after a transient failure (RetryAttempt of RetryMax).
85 // A frontend shows a transient "retrying (n/m)" indicator that the next stream
86 // event — or TurnDone — clears. Appended last to keep the Kind values before
87 // it wire-stable.
88 Retrying
89 // Steer fires when a mid-turn steer message is consumed from the queue and
90 // injected as a user message. Text carries the raw steer content (without the
91 // wrapper prefix), so a frontend can display it to the user as confirmation.
92 // Frontends use Steer to know a queued message has been delivered.
93 Steer
94 // GuardianAssessment reports the outcome of a guardian sub-agent safety review.
95 // Carries GuardianResult payload (Outcome, RiskLevel, Rationale, etc.).
96 GuardianAssessment
97 // ExtensionSurface carries a structured UI surface published by an extension
98 // sidecar (Extension payload with one of the Card/Form/Notification
99 // sub-structs set). Appended last to keep the Kind values before it
100 // wire-stable.
101 ExtensionSurface
102 // ExtensionStatus carries a one-line status contribution published by an
103 // extension sidecar (Extension payload with Status set). Appended last to
104 // keep the Kind values before it wire-stable.
105 ExtensionStatus
106 // StreamAttempt marks the local lifecycle of one sampling attempt within a
107 // model round (StreamAttempt payload: begin | discard | commit). IDs are
108 // local transcript identities — persisted in the event ledger, never sent
109 // to the model. Appended last to
110 // keep earlier Kind values wire-stable; older clients ignore unknown kinds.
111 StreamAttempt
112 // ContextMaintenance reports a free tool-result maintenance or a durable
113 // blocked/noop outcome. It is separate from CompactionStarted/Done so UIs do
114 // not render a paid-summary card for a cache-preserving view update.
115 ContextMaintenanceEvent
116 // WorkspaceChanged reports a debounced host-side workspace mutation.
117 WorkspaceChanged
118 // TurnPhase reports a host-side work phase for the active turn (working |
119 // checking | verifying | reviewing). Content-free; Text holds the phase.
120 TurnPhase
121 // CompletionSummary reports a content-free end-of-turn quality summary for
122 // role-setting strategies (preset, verdict, check counts, review status).
123 CompletionSummary
124 // ToolResultPreview reports that a tool has finished locally before its
125 // provider-ordered ToolResult can be emitted. Upsert-capable frontends may
126 // render the successful state early; append-only consumers should ignore it.
127 // The later ToolResult remains the call's only terminal event.
128 ToolResultPreview
129 // TurnStatusChanged is a content-free lifecycle transition such as
130 // waiting_user, cancelling, or returning to in_progress after an answer.
131 TurnStatusChanged
132 // PromptAnswered records that a durable Ask/approval item was answered and the same turn resumed; ItemID carries the stable prompt id and answer content remains in its purpose-built decision receipt.
133 PromptAnswered
134 // MCPInteractionRequest carries a server-initiated MCP elicitation (form
135 // or URL) for the frontend to answer via the MCP interaction resolve call.
136 // Appended last to keep the Kind values before it wire-stable.
137 MCPInteractionRequest
138 // SessionChanged is a content-free Serve routing barrier for all-session clients.
139 SessionChanged
140 // ReadStatus upserts one logical read's delivery state instead of per page.
141 ReadStatus
142 ToolStarted // Persisted after policy/validation and before execution.
143 // UserMessage binds an admitted user bubble to its persisted message ID.
144 // Text is display text; provider-only framing must never be emitted here.
145 UserMessage
146 // KindCount is a sentinel one past the last real Kind. New event kinds must
147 // be inserted above it so completeness tests cover them automatically.
148 KindCount
149 )
150
151 // TurnPhaseName is the machine-readable phase on TurnPhase events.
152 type TurnPhaseName string
153
154 const (
155 TurnPhaseWorking TurnPhaseName = "working"
156 TurnPhaseChecking TurnPhaseName = "checking"
157 TurnPhaseVerifying TurnPhaseName = "verifying"
158 TurnPhaseReviewing TurnPhaseName = "reviewing"
159 )
160
161 // CompletionSummaryInfo is the content-free quality summary on CompletionSummary
162 // events. It never carries user prompts, file contents, command args, or
163 // reviewer reasoning.
164 type CompletionSummaryInfo struct {
165 Preset string // deprecated wire-compat label; pinned to "balanced"
166 Verdict string // complete | partial | blocked | continue
167 Mutations int
168 ChangedFiles int
169 ChecksPassed int
170 ChecksFailed int
171 ChecksSuppressed int
172 Review string // none | passed | warned | failed | unavailable
173 GapKinds []string
174 ConstraintDegraded bool
175 Floor string // standard | delivery; empty on legacy events
176 Attention bool // authoritative when Floor is non-empty
177 }
178
179 // StreamAttemptAction is the lifecycle phase of a local sampling attempt.
180 type StreamAttemptAction string
181
182 const (
183 StreamAttemptBegin StreamAttemptAction = "begin"
184 StreamAttemptDiscard StreamAttemptAction = "discard"
185 StreamAttemptCommit StreamAttemptAction = "commit"
186 )
187
188 // StreamAttemptInfo carries host-local bookkeeping for one sampling attempt.
189 // Reason is a fixed enum (connection_reset | premature_eof | idle_timeout).
190 type StreamAttemptInfo struct {
191 ID string
192 Action StreamAttemptAction
193 Attempt int // 1-based attempt number
194 Max int // total attempts including the first (typically 6)
195 Reason string
196 }
197
198 // Level classifies a Notice so sinks can style or filter it.
199 type Level int
200
201 const (
202 LevelInfo Level = iota
203 LevelWarn
204 )
205
206 // NoticeAudience separates a notice's recipient from its severity. The empty
207 // default preserves the existing contract: ordinary notices are eligible for
208 // every frontend. Operator notices describe local runtime maintenance and must
209 // not be forwarded as end-user chat messages. Local frontends and diagnostics
210 // remain free to surface or quietly record them under their own policy.
211 type NoticeAudience string
212
213 const (
214 NoticeAudienceDefault NoticeAudience = ""
215 NoticeAudienceOperator NoticeAudience = "operator"
216 )
217
218 // Profile carries the subagent model/effort resolved for this call.
219 type Profile struct {
220 Model string
221 Effort string
222 }
223
224 // Tool describes a tool call for ToolDispatch / ToolResult events. On dispatch
225 // ID/Name/Args/ReadOnly and optional preview metadata are set; on result
226 // Output/Err/Truncated are filled in. Args is the raw JSON arguments — a sink
227 // compacts it for display.
228 type Tool struct {
229 RunState provider.ToolRunState
230 Diagnostic json.RawMessage `json:"diagnostic,omitempty"`
231 // Verifying is emitted only once an authorized check actually enters execution.
232 Verifying bool
233 ID string
234 Name string
235 Args string
236 // Todos is the complete semantic todo replacement committed with a
237 // successful todo_write result. TodoWritten distinguishes an empty list
238 // from an older event with no semantic payload.
239 Todos []Todo
240 TodoWritten bool
241 // ResolvedName/CapabilityID describe the real target behind a stable proxy
242 // while Name/Args remain the provider-visible call. They are optional local
243 // display metadata and never enter provider requests.
244 ResolvedName string
245 CapabilityID string
246 Output string // ToolResult: the result text fed to the model
247 Err string // ToolResult: non-empty when the call failed or was blocked
248 PresentedFiles []provider.PresentedFile
249 ReadOnly bool
250 Truncated bool // ToolResult: Output was head+tailed before display/model
251 DurationMs int64 // ToolResult: wall-clock execution time in milliseconds
252 // StartedAt/EndedAt are unix-millisecond execution bounds (ToolResult).
253 // Zero when the call never ran (dependency-skipped, cancelled, synthetic).
254 StartedAt int64
255 EndedAt int64
256 // Partial marks an early ToolDispatch emitted when a call begins (ID/Name set,
257 // Args still streaming) so a frontend can show the card immediately; a second,
258 // full ToolDispatch (Partial false, Args set) follows when the call completes.
259 Partial bool
260 // ArgChars is the cumulative argument characters received so far for a
261 // Partial dispatch — a liveness signal while a large payload streams. Zero
262 // on the initial start dispatch and on full dispatches.
263 ArgChars int
264 // Refreshed marks a repeated full ToolDispatch for the same ID whose file
265 // preview or resolved proxy metadata changed after the initial dispatch.
266 // Frontends that can upsert by ID should replace the existing card;
267 // append-only sinks should ignore it to avoid duplicate tool cards.
268 Refreshed bool
269 // ParentID, when set, is the ID of the tool call that spawned this one — a
270 // sub-agent's calls carry the parent `task` call's ID so a frontend can nest
271 // them under it. Empty for top-level calls.
272 ParentID string
273 // AttemptID is the host-local stream_attempt id that produced a speculative
274 // partial ToolDispatch. Empty for committed/full dispatches and for nested
275 // sub-agent tools. Frontends must only journal partial events whose
276 // AttemptID matches the active stream_attempt begin.
277 AttemptID string
278 FileDiff
279 Profile *Profile // ToolDispatch: subagent model/effort (set for task/skill calls)
280 // Subagent outcome metadata is host/UI-only and never enters provider requests.
281 SubagentRef string
282 SubagentStatus string
283 SubagentErrorCode string
284 SubagentRetryable bool
285 // Execution is optional local shell metadata (ToolResult). Never sent to
286 // model providers; omitempty keeps old wire readers compatible.
287 Execution *ShellExecution
288 // Workspace mutation metadata is host-only and is omitted from eventwire.
289 WorkspaceMutation bool
290 WorkspacePaths []string
291 WorkspaceAllPaths bool
292 }
293
294 // ShellExecution mirrors tool.ShellExecution for event sinks without importing
295 // the tool package (event is a lower-level dependency of tool consumers).
296 type ShellExecution struct {
297 Kind string `json:"kind,omitempty"`
298 Shell string `json:"shell,omitempty"`
299 ShellVersion string `json:"shellVersion,omitempty"`
300 Platform string `json:"platform,omitempty"`
301 SupportsAndAnd bool `json:"supportsAndAnd"`
302 State string `json:"state,omitempty"`
303 FailurePhase string `json:"failurePhase,omitempty"`
304 ExitCode *int `json:"exitCode,omitempty"`
305 OutputTail string `json:"outputTail,omitempty"`
306 MutationRisk string `json:"mutationRisk,omitempty"`
307 Verification string `json:"verification,omitempty"`
308 DurationMs int64 `json:"durationMs,omitempty"`
309 }
310
311 // FileDiff is a previewed change carried on a writer tool's full ToolDispatch
312 // and on its ApprovalRequest, so a frontend can render +/- lines before the
313 // call runs. Diff is the unified diff (empty for read-only tools, binary files,
314 // or no-op changes); Added/Removed are its line tallies.
315 type FileDiff struct {
316 Diff string
317 Added int
318 Removed int
319 }
320
321 // AskOption is one choice the user can pick for an AskQuestion.
322 type AskOption struct {
323 Label string
324 Description string // optional one-line explanation shown under the label
325 }
326
327 // AskQuestion is one structured question the `ask` tool puts to the user.
328 type AskQuestion struct {
329 ID string // stable per-question id, so answers correlate back
330 Header string // short label (the tab title)
331 Prompt string // the question text
332 Options []AskOption
333 Multi bool // allow selecting more than one option
334 }
335
336 // Ask carries an AskRequest: a batch of questions and the ID that correlates the
337 // controller's AnswerQuestion(ID, …) reply.
338 type Ask struct {
339 ID string
340 Questions []AskQuestion
341 TurnID string
342 }
343
344 // MCPInteraction carries one MCPInteractionRequest: a server-initiated
345 // elicitation the frontend must answer with accept/decline/cancel. Mode is
346 // "form" (RequestedSchema is a flat primitive JSON schema) or "url" (URL is a
347 // credential-free HTTP(S) target the user opens explicitly).
348 type MCPInteraction struct {
349 ID string
350 Server string
351 Mode string
352 Message string
353 RequestedSchema json.RawMessage
354 URL string
355 ElicitationID string
356 TurnID string
357 }
358
359 // Compaction carries a context-compaction pass for the CompactionStarted /
360 // CompactionDone events. On CompactionStarted only Trigger is set. On
361 // CompactionDone, Messages/Summary/Archive are filled in (an aborted pass leaves
362 // Summary empty). Trigger is "auto" (the prompt reached the window threshold) or
363 // "manual" (the user ran /compact).
364 type Compaction struct {
365 Trigger string // "auto" | "manual"
366 Messages int // Done: how many messages were folded into the summary
367 Summary string // Done: the briefing the agent keeps relying on
368 Archive string // Done: path the dropped originals were archived to ("" if none)
369 }
370
371 // ContextMaintenance is the typed wire-safe receipt for snip/prune/noop/
372 // blocked operations. Transcript bytes are represented by hashes and counts.
373 type ContextMaintenance struct {
374 Status string `json:"status,omitempty"`
375 Action string `json:"action,omitempty"`
376 Trigger string `json:"trigger,omitempty"`
377 OperationID string `json:"operationId,omitempty"`
378 InputTokens int `json:"inputTokens,omitempty"`
379 ResultTokens int `json:"resultTokens,omitempty"`
380 SavedTokens int `json:"savedTokens,omitempty"`
381 AffectedToolResults int `json:"affectedToolResults,omitempty"`
382 ProjectionVersion uint64 `json:"projectionVersion,omitempty"`
383 CacheBreak bool `json:"cacheBreak,omitempty"`
384 Reason string `json:"reason,omitempty"`
385 }
386
387 // GuardianResult carries the outcome of a guardian sub-agent safety review.
388 // Emitted with Kind=GuardianAssessment after each review completes.
389 type GuardianResult struct {
390 ID string // unique review id
391 Tool string // tool being reviewed (e.g. "bash")
392 Subject string // call subject (e.g. "rm -rf /tmp/build")
393 Outcome string // "allow" | "deny"
394 RiskLevel string // "low" | "medium" | "high" | "critical"
395 UserAuthorization string // "unknown" | "low" | "medium" | "high"
396 Rationale string // one-sentence reason
397 DurationMs int64 // wall-clock review time
398 Usage *provider.Usage // guardian review token telemetry
399 Pricing *provider.Pricing // for cost display (nil = omit cost)
400 }
401
402 // AskAnswer is the user's reply to one AskQuestion: the chosen option label(s)
403 // (a free-typed answer is carried as a single Selected entry).
404 type AskAnswer struct {
405 QuestionID string
406 Selected []string
407 }
408
409 // FinalReadiness carries machine-readable recovery requirements on TurnDone.
410 // Missing values are stable category ids; user-facing detail stays localized in
411 // the frontend instead of scraping the diagnostic error string.
412 type FinalReadiness struct {
413 Attempts int `json:"attempts,omitempty"`
414 Missing []string `json:"missing,omitempty"`
415 }
416
417 const (
418 UsageSourceExecutor = "executor"
419 UsageSourcePlanner = "planner"
420 UsageSourceSubagent = "subagent"
421 UsageSourceCompaction = "compaction"
422 UsageSourceClassifier = "classifier"
423 UsageSourceTitle = "title"
424 UsageSourceCapabilityRouter = "capability-router"
425 UsageSourceRecoveryReviewer = "recovery-reviewer"
426 UsageSourceGoalEvaluator = "goal-evaluator"
427 )
428
429 // Event is one increment in a turn's event stream. Read the field(s) documented
430 // for Kind; the others are zero.
431 type Event struct {
432 Kind Kind
433 MessageID string // local identity shared by streaming and persisted messages
434 AttemptID string // owning sampling attempt; never provider-visible
435 SessionID string // durable display routing, stamped after append
436 RuntimeEpoch string // originating controller incarnation
437 SubmissionID string // exact optimistic submit correlation
438 PromptKind string // interactive prompt kind for lifecycle events
439 InteractionState string // PromptAnswered: answered | rejected | cancelled | unavailable
440 DomainKind string // host-internal state event committed atomically with this lifecycle event
441 DomainPayload json.RawMessage // host-internal payload for DomainKind
442 TurnID string // stable id of the owning top-level turn
443 Sequence uint64 // monotonic session-local event sequence
444 Status TurnStatus // lifecycle state after this event
445 Text string // Reasoning / Text / Message / Notice / Phase
446 ModelRef string // Usage: canonical "provider/model" ref that produced this usage
447 Detail string // Notice: optional diagnostic text for expandable details
448 Code string // Notice: stable id for frontend localization; empty = unmapped
449 Reasoning string // Message: the full reasoning chain
450 MemoryCitations []provider.MemoryCitation // Message: local memory references displayed by rich frontends
451 Tool Tool // ToolDispatch / ToolResult
452 Usage *provider.Usage // Usage
453 Pricing *provider.Pricing // Usage: rate card for quote middleware (nil = omit cost)
454 CostQuote *billing.CostQuote // Usage: host-side quote; sinks must not reprice
455 Source string // optional display/event source (executor, planner, subagent, ...)
456 UsageSource string // Usage: billable call source; empty means executor for compatibility
457 CacheDiagnostics *CacheDiagnostics // Usage: cache-churn attribution (nil = N/A)
458 // SessionHit/SessionMiss carry cumulative cache tokens across the whole
459 // session (Usage events only), so a frontend can show the aggregate hit-rate
460 // — which doesn't crater on a short turn or after compaction — alongside
461 // Usage's single-turn numbers.
462 SessionHit int // Usage: cumulative cache-hit prompt tokens this session
463 SessionMiss int // Usage: cumulative cache-miss prompt tokens this session
464 Level Level // Notice
465 Audience NoticeAudience // Notice: empty = ordinary frontend delivery; operator = no end-user chat forwarding
466 Approval Approval // ApprovalRequest
467 Ask Ask // AskRequest
468 MCPInteraction MCPInteraction // MCPInteractionRequest
469 Extension *ExtensionSurfacePayload // ExtensionSurface / ExtensionStatus (nil for every other kind)
470 Err error // TurnDone: non-nil on failure
471 Cancelled bool // TurnDone: Cancel was requested while the turn was active
472 Outcome string // TurnDone: optional machine-readable recoverable outcome
473 Readiness *FinalReadiness // TurnDone: structured final-readiness recovery state
474 ProtocolRecovery *provider.ProtocolRecoveryAction
475 Diagnostic *provider.FailureDiagnostic
476 RecoveryCheckpoint bool // local durable recovery checkpoint, not a notice
477 Receipt *CompletionReceipt // TurnDone: what the host verified, and what it could not
478 CheckpointTurn *int // TurnDone: authoritative checkpoint for this turn's visible user message
479 Compaction Compaction // Compaction
480 Maintenance *ContextMaintenance // ContextMaintenanceEvent
481 Guardian GuardianResult
482 DecisionReceipt *provider.DecisionReceipt // Notice: durable user decision receipt
483 WriteIntent bool // local write-ahead checkpoint, not a user notice
484 Recovery *RecoveryStatus // optional local recovery details
485 RetryAttempt int // Retrying: 1-based attempt about to be made
486 RetryMax int // Retrying: total attempts before giving up
487 RetryScope RetryScope // Retrying: optional "headers" | "stream"; empty for older emitters
488 StreamAttempt StreamAttemptInfo // StreamAttempt lifecycle
489 ReadStatus *ReadStatusPayload // ReadStatus: one logical read's delivery state
490 ReadPause *provider.ReadPause // TurnDone: durable display-only pause receipt
491 ReadCompletion *provider.ReadCompletion // TurnDone: accepted partial coverage, display-only
492 ItemID string // correlates durable inbox events
493 SessionPath string // routes Serve frames
494 SessionReset bool // SessionChanged came from /new or /clear, not resume/recovery
495 Workspace *WorkspaceChangedPayload // WorkspaceChanged (host-local)
496 // PhaseName is set on TurnPhase events (working|checking|verifying|reviewing).
497 PhaseName TurnPhaseName
498 // Completion is set on CompletionSummary events.
499 Completion *CompletionSummaryInfo
500 // CommittedMessage is the exact provider transcript record paired with a
501 // terminal tool result. It is host-internal and omitted from frontend wire
502 // payloads; the session event store commits it atomically with tool/result
503 // and any todo/write state transition.
504 CommittedMessage *provider.Message
505 }
506
507 type WorkspaceWatchState string
508
509 const (
510 WorkspaceWatchActive WorkspaceWatchState = "active"
511 WorkspaceWatchDegraded WorkspaceWatchState = "degraded"
512 WorkspaceWatchUnavailable WorkspaceWatchState = "unavailable"
513 )
514
515 type WorkspaceRevision struct {
516 Content uint64 `json:"content"`
517 Tree uint64 `json:"tree"`
518 WorkingTree uint64 `json:"workingTree"`
519 GitMeta uint64 `json:"gitMeta"`
520 Session uint64 `json:"session"`
521 }
522
523 type WorkspacePathChange struct {
524 Path string `json:"path"`
525 OldPath string `json:"oldPath,omitempty"`
526 Op string `json:"op"`
527 }
528
529 type WorkspaceChangedPayload struct {
530 Revisions WorkspaceRevision
531 Changes []WorkspacePathChange
532 AllPaths bool
533 Source string
534 WatchState WorkspaceWatchState
535 }
536
537 // ReadinessAuditSink is an optional sink capability. Sinks that do not care
538 // about readiness audit receipts can implement only Sink and will ignore them.
539 type ReadinessAuditSink interface {
540 RecordReadinessAudit(evidence.ReadinessAudit)
541 }
542
543 // AnchorSafetyAudit is a content-free shadow decision for an anchor-based
544 // writer. It contains only bounded enums/counts; paths, anchors, source text,
545 // and digests never leave the host-side observation ledger.
546 type AnchorSafetyAudit struct {
547 Mode string
548 TaskMode string
549 RangeLines int
550 ObservationAge int
551 LegacyAllowed bool
552 ShadowAllowed bool
553 Reason string
554 SameBatchReadRejected bool
555 }
556
557 type AnchorSafetyAuditSink interface {
558 RecordAnchorSafetyAudit(AnchorSafetyAudit)
559 }
560
561 func RecordAnchorSafetyAudit(s Sink, a AnchorSafetyAudit) {
562 if nilutil.IsNil(s) {
563 return
564 }
565 if as, ok := s.(AnchorSafetyAuditSink); ok {
566 as.RecordAnchorSafetyAudit(a)
567 }
568 }
569
570 // TurnCompletionSink is an optional sink capability for synchronous controller
571 // entry points that do not publish a TurnDone UI event. It keeps accounting
572 // independent from frontend event lifecycles without synthesizing an event that
573 // transports may mistake for an interactive completion.
574 type TurnCompletionSink interface {
575 RecordTurnCompletion()
576 }
577
578 // RecordTurnCompletion records one successfully admitted top-level controller
579 // run on sinks that opt into completion accounting.
580 func RecordTurnCompletion(s Sink) {
581 if nilutil.IsNil(s) {
582 return
583 }
584 if ts, ok := s.(TurnCompletionSink); ok {
585 ts.RecordTurnCompletion()
586 }
587 }
588
589 // RecordReadinessAudit forwards a readiness audit receipt to sinks that opt in.
590 func RecordReadinessAudit(s Sink, a evidence.ReadinessAudit) {
591 if nilutil.IsNil(s) {
592 return
593 }
594 if rs, ok := s.(ReadinessAuditSink); ok {
595 rs.RecordReadinessAudit(a)
596 }
597 }
598
599 // ProtocolRecoveryKind is a content-free internal observation about a provider
600 // protocol repair. It is deliberately separate from Event/Notice so recovery
601 // stays invisible in chat transcripts and frontends do not need to understand
602 // provider implementation details.
603 type ProtocolRecoveryKind string
604
605 const (
606 ProtocolRecoveryMissingReasoningDetected ProtocolRecoveryKind = "missing_reasoning_detected"
607 ProtocolRecoveryMissingReasoningRetryAttempted ProtocolRecoveryKind = "missing_reasoning_retry_attempted"
608 ProtocolRecoveryMissingReasoningRetryRecovered ProtocolRecoveryKind = "missing_reasoning_retry_recovered"
609 ProtocolRecoveryMissingReasoningRetryReplaced ProtocolRecoveryKind = "missing_reasoning_retry_replaced_response"
610 ProtocolRecoveryMissingReasoningRetrySuppressed ProtocolRecoveryKind = "missing_reasoning_retry_suppressed"
611 ProtocolRecoveryMissingReasoningFallback ProtocolRecoveryKind = "missing_reasoning_fallback_used"
612 ProtocolRecoveryReasoningOverflowDetected ProtocolRecoveryKind = "reasoning_overflow_detected"
613 ProtocolRecoveryClientToolRejected ProtocolRecoveryKind = "client_tool_rejected_unreplayable_reasoning"
614 ProtocolRecoveryServerSearchSalvaged ProtocolRecoveryKind = "server_search_history_salvaged"
615 ProtocolRecoveryHistoryRepaired ProtocolRecoveryKind = "unreplayable_history_repaired"
616 ProtocolRecoveryReasoningReplay400Detected ProtocolRecoveryKind = "reasoning_replay_400_detected"
617 ProtocolRecoveryReasoningReplay400Recovered ProtocolRecoveryKind = "reasoning_replay_400_recovered"
618 )
619
620 type ProtocolRecoveryAudit struct {
621 Kind ProtocolRecoveryKind
622 }
623
624 // ContractShadowAudit is the shadow task-contract's end-of-turn summary:
625 // counts and enums only, never requirement text. Shadow means observed, not
626 // enforced — the old control logic still decides behavior.
627 type ContractShadowAudit struct {
628 Intent string
629 Requirements int
630 RequirementsSatisfied int
631 Checks int
632 ChecksSatisfied int
633 Epoch uint64
634 Verdict string
635 Complete bool
636 ReadyToFinalize bool
637 }
638
639 // ContractShadowAuditSink is an optional sink capability; implementations
640 // must keep it content-free, like every other audit channel.
641 type ContractShadowAuditSink interface {
642 RecordContractShadow(ContractShadowAudit)
643 }
644
645 // RecordContractShadow forwards the shadow contract summary only to sinks
646 // that explicitly opt in. Ordinary UI sinks receive nothing.
647 func RecordContractShadow(s Sink, a ContractShadowAudit) {
648 if nilutil.IsNil(s) {
649 return
650 }
651 if cs, ok := s.(ContractShadowAuditSink); ok {
652 cs.RecordContractShadow(a)
653 }
654 }
655
656 // CompletionReportAudit is the host-authored completion report's end-of-turn
657 // summary: counts, enums, and gap kinds only, never paths or command text.
658 // The gap counters carry the point — what the turn left unproven.
659 type CompletionReportAudit struct {
660 Verdict string
661 Risk string
662 Criteria int
663 CriteriaSatisfied int
664 Changes int
665 ChangesUnreviewed int
666 Verifications int
667 VerificationsFailed int
668 VerificationsStale int
669 Gaps int
670 GapKinds []string
671 // ClaimsVerified counts the turn's own asserted verifications;
672 // ClaimsUnbacked is how many of them the ledger did not support.
673 ClaimsVerified int
674 ClaimsUnbacked int
675 }
676
677 // CompletionReportAuditSink is an optional sink capability; implementations
678 // must keep it content-free, like every other audit channel.
679 type CompletionReportAuditSink interface {
680 RecordCompletionReport(CompletionReportAudit)
681 }
682
683 // RecordCompletionReport forwards the completion summary only to sinks that
684 // explicitly opt in. Ordinary UI sinks receive nothing.
685 func RecordCompletionReport(s Sink, a CompletionReportAudit) {
686 if nilutil.IsNil(s) {
687 return
688 }
689 if cs, ok := s.(CompletionReportAuditSink); ok {
690 cs.RecordCompletionReport(a)
691 }
692 }
693
694 // MemoryRecallAudit summarizes one automatic-recall decision: identifiers,
695 // scores, and budget numbers only — never the query or fact text.
696 type MemoryRecallAudit struct {
697 Hits []MemoryRecallHit
698 UsedChars int
699 Omitted int
700 Suppressed string // reason recall stayed silent; "" when hits were injected
701 // Shadow is the Retrieval V2 ranking (telemetry only, never served).
702 Shadow []MemoryRecallHit
703 }
704
705 // MemoryRecallHit is one recalled fact's content-free fingerprint.
706 type MemoryRecallHit struct {
707 ID string
708 Revision int
709 Scope string
710 Type string
711 Freshness string
712 Score float64
713 }
714
715 // MemoryRecallSink is an optional sink capability; implementations must keep
716 // it content-free, like every other audit channel.
717 type MemoryRecallSink interface {
718 RecordMemoryRecall(MemoryRecallAudit)
719 }
720
721 // RecordMemoryRecall forwards a recall decision only to sinks that explicitly
722 // opt in. Ordinary UI sinks receive nothing.
723 func RecordMemoryRecall(s Sink, a MemoryRecallAudit) {
724 if nilutil.IsNil(s) {
725 return
726 }
727 if mr, ok := s.(MemoryRecallSink); ok {
728 mr.RecordMemoryRecall(a)
729 }
730 }
731
732 // DelegationAdmissionAudit is the shadow admission verdict for one expensive
733 // delegation call: tool name and enums only, never the query or prompt text.
734 // Shadow means observed, not enforced — no call is blocked.
735 type DelegationAdmissionAudit struct {
736 Tool string
737 Verdict string // "allow" | "deny"
738 Reason string // e.g. "local_fix_no_external_need"
739 Intent string // compatibility field; no longer classified from prompt text
740 }
741
742 // DelegationAdmissionSink is an optional sink capability; implementations
743 // must keep it content-free, like every other audit channel.
744 type DelegationAdmissionSink interface {
745 RecordDelegationAdmission(DelegationAdmissionAudit)
746 }
747
748 // RecordDelegationAdmission forwards a shadow admission verdict only to sinks
749 // that explicitly opt in. Ordinary UI sinks receive nothing.
750 func RecordDelegationAdmission(s Sink, a DelegationAdmissionAudit) {
751 if nilutil.IsNil(s) {
752 return
753 }
754 if da, ok := s.(DelegationAdmissionSink); ok {
755 da.RecordDelegationAdmission(a)
756 }
757 }
758
759 // OutcomeProgressSink is an optional sink capability for the shadow outcome
760 // scorer's per-round samples: counts only, never paths or commands. Shadow
761 // means observed, not enforced — the novelty guard still decides behavior.
762 type OutcomeProgressSink interface {
763 RecordOutcomeProgress(evidence.OutcomeSample)
764 }
765
766 // RecordOutcomeProgress forwards a shadow outcome sample only to sinks that
767 // explicitly opt in. Ordinary UI sinks receive nothing.
768 func RecordOutcomeProgress(s Sink, sample evidence.OutcomeSample) {
769 if nilutil.IsNil(s) {
770 return
771 }
772 if op, ok := s.(OutcomeProgressSink); ok {
773 op.RecordOutcomeProgress(sample)
774 }
775 }
776
777 // ProtocolRecoveryAuditSink is an optional sink capability. Implementations
778 // must keep it content-free; prompts, responses, endpoints, model names, and
779 // tool arguments do not belong in this audit channel.
780 type ProtocolRecoveryAuditSink interface {
781 RecordProtocolRecovery(ProtocolRecoveryAudit)
782 }
783
784 // RecordProtocolRecovery forwards a content-free recovery observation only to
785 // sinks that explicitly opt in. Ordinary UI sinks receive nothing.
786 func RecordProtocolRecovery(s Sink, a ProtocolRecoveryAudit) {
787 if nilutil.IsNil(s) {
788 return
789 }
790 if rs, ok := s.(ProtocolRecoveryAuditSink); ok {
791 rs.RecordProtocolRecovery(a)
792 }
793 }
794
794 lines GO