Documentation
¶
Index ¶
- Variables
- func Canonicalize(msg string) string
- func CompactDigestText(p PingPayload) string
- func NextPingDelay(occurrence int, delays PingDelays) time.Duration
- func PingPayloadSize(p PingPayload) int
- type ActivityDetector
- type BlobRef
- type BlobStore
- type BucketConfig
- type Bus
- type ChannelNotifyFn
- type Coalescer
- type Context
- type DedupEntry
- type Deduplicator
- type FlowController
- type Inbox
- func (inbox *Inbox) Cursor() time.Time
- func (inbox *Inbox) GC()
- func (inbox *Inbox) Ingest(entry *InboxEntry) InboxDelta
- func (inbox *Inbox) LoadCritical(path string) error
- func (inbox *Inbox) MarkRead(fingerprints []string, advanceCursor bool)
- func (inbox *Inbox) Query(filter QueryFilter) ([]InboxEntry, Stats)
- func (inbox *Inbox) SaveCritical(path string) error
- func (inbox *Inbox) Stats() Stats
- func (inbox *Inbox) Subscribe() (<-chan InboxDelta, func())
- type InboxDelta
- type InboxEntry
- type IncidentEvent
- func FromAlertMatch(m *overlay.AlertMatch, processID string) IncidentEvent
- func FromFrontendError(fe proxy.FrontendError, proxyID string) IncidentEvent
- func FromHTTPEntry(he proxy.HTTPLogEntry, proxyID string) (IncidentEvent, bool)
- func FromHookStopFailure(sessionID, errMsg, errDetails string) IncidentEvent
- func FromPortConflict(port, pid int, processName string) IncidentEvent
- func FromProcessExit(processID, reason string, exitCode int, stderrTail string) IncidentEvent
- func FromProxyDiagnostic(d proxy.ProxyDiagnostic, proxyID string) (IncidentEvent, bool)
- func FromShutdown(reason string) IncidentEvent
- func NewIncidentEvent(src Source, sev Severity, category, msg string, ctx Context, store *BlobStore) IncidentEvent
- type IncidentView
- type MCPNotifyFn
- type MPSCBus
- func (b *MPSCBus) AddSession(sessionID string, mcpNotify MCPNotifyFn, channelNotify ChannelNotifyFn, ...)
- func (b *MPSCBus) Close()
- func (b *MPSCBus) Dropped() int64
- func (b *MPSCBus) Fire(ev *IncidentEvent)
- func (b *MPSCBus) HasSession(sessionID string) bool
- func (b *MPSCBus) MarkReadSession(sessionID string, fingerprints []string, advanceCursor bool)
- func (b *MPSCBus) Publish(ev IncidentEvent)
- func (b *MPSCBus) QuerySession(sessionID string, filter QueryFilter) ([]InboxEntry, Stats)
- func (b *MPSCBus) RemoveSession(sessionID string)
- type MessageType
- type NopBus
- type PTYInjectFn
- type PingConfig
- type PingDelays
- type PingEmitter
- type PingPayload
- type PingStats
- type QueryFilter
- type Remediation
- type Severity
- type Source
- type Stats
- type SwallowDetector
- type ToolSuggestion
- type TopEntry
Constants ¶
This section is empty.
Variables ¶
var DefaultBucketConfigs = map[Severity]BucketConfig{
SeverityError: {Size: 20, RefillRate: 10},
SeverityWarning: {Size: 10, RefillRate: 5},
SeverityInfo: {Size: 5, RefillRate: 2},
}
DefaultBucketConfigs are the production token-bucket parameters. Critical severity is not listed — it is always allowed (infinite bucket).
var DefaultPingDelays = PingDelays{ Initial: 500 * time.Millisecond, Max: 10 * time.Second, ResetAfter: 30 * time.Second, }
DefaultPingDelays are the production backoff parameters.
var ErrBlobEvicted = errors.New("blob evicted from store")
ErrBlobEvicted is returned by Read when the requested blob was evicted.
Functions ¶
func Canonicalize ¶
Canonicalize strips volatile parts of msg (timestamps, addresses, UUIDs, line numbers on non-app frames) so that two occurrences of the same logical error produce the same fingerprint regardless of when or where they fired.
func CompactDigestText ¶ added in v0.13.16
func CompactDigestText(p PingPayload) string
CompactDigestText renders the one-line digest summary for cross-process transport (daemon → STREAM-EVENTS → consumer). Exported wrapper over the internal compact formatter.
func NextPingDelay ¶
func NextPingDelay(occurrence int, delays PingDelays) time.Duration
NextPingDelay computes the ping delay for the nth occurrence of a fingerprint. occurrence is 1-based. The sequence doubles from Initial each occurrence until Max.
func PingPayloadSize ¶
func PingPayloadSize(p PingPayload) int
PingPayloadSize returns the JSON-marshalled byte count of a payload for size assertions in tests.
Types ¶
type ActivityDetector ¶
type ActivityDetector struct {
// contains filtered or unexported fields
}
ActivityDetector tracks agent hook activity to defer non-critical pings while the agent is busy making tool calls. When the agent goes idle (no hook events for idleWindow), onIdle is called so pending pings can flush.
Critical-severity events bypass the detector entirely — callers should not gate critical pings on IsActive().
func NewActivityDetector ¶
func NewActivityDetector(activeWindow, idleWindow time.Duration, onIdle func()) *ActivityDetector
NewActivityDetector creates a detector. activeWindow is how long after the last hook event the agent is considered active (default 500ms). idleWindow is how long without activity before onIdle fires (default 3s). onIdle must not block.
func (*ActivityDetector) IsActive ¶
func (ad *ActivityDetector) IsActive() bool
IsActive returns true if a hook event was seen within the last activeWindow.
func (*ActivityDetector) RecordHook ¶
func (ad *ActivityDetector) RecordHook()
RecordHook records a hook event (pre-tool-use, post-tool-use, etc.). Resets the idle timer so onIdle fires idleWindow from now.
func (*ActivityDetector) Stop ¶
func (ad *ActivityDetector) Stop()
Stop cancels the idle timer without firing it. Call on shutdown.
type BlobRef ¶
type BlobRef struct {
Hash string // sha256 hex of raw payload
Size int
MIME string // "text/plain", "application/json", "text/html"
}
BlobRef points to a payload stored in a BlobStore.
type BlobStore ¶
type BlobStore struct {
// contains filtered or unexported fields
}
BlobStore is a bytes-bounded content-addressed in-memory store with LRU eviction. Write computes the sha256 hash, deduplicates identical payloads, and evicts the least-recently-used blob when the budget is exceeded.
Writes are dispatched via a bounded channel (256 entries) to a background goroutine so callers never block on lock contention. Close drains the queue and stops the goroutine.
WriteAsync returns a BlobRef immediately (hash computed synchronously) and defers the actual storage. If the async queue is full the content may never land — readers fall back to IncidentEvent.Summary in that case.
func NewBlobStore ¶
NewBlobStore creates a BlobStore limited to maxBytes of payload storage. Pass 0 for the default (16MB).
func (*BlobStore) Close ¶
func (bs *BlobStore) Close()
Close stops the background goroutine after draining pending writes.
func (*BlobStore) Read ¶
Read retrieves the payload for hash. Returns ErrBlobEvicted if the blob was evicted since writing.
func (*BlobStore) Write ¶
Write stores content and returns a BlobRef. Identical content (same sha256) is stored only once. If the store is over budget the oldest entry is evicted. Write never blocks the caller: it is enqueued to a background goroutine. The returned channel receives the result when the write completes.
func (*BlobStore) WriteAsync ¶
WriteAsync computes the sha256 hash synchronously and returns a BlobRef immediately, then enqueues the actual content write to a background goroutine. If the async queue is full the ref is still returned but the content may never land — readers should fall back to IncidentEvent.Summary in that case.
type BucketConfig ¶
type BucketConfig struct {
Size float64 // maximum tokens (burst capacity)
RefillRate float64 // tokens added per second
}
BucketConfig configures a token bucket for one severity level.
type Bus ¶
type Bus interface {
Publish(IncidentEvent)
}
Bus is the central sink for all incident events. MPSCBus is the production implementation; NopBus discards events.
type ChannelNotifyFn ¶
type ChannelNotifyFn func(content string, payload PingPayload) error
ChannelNotifyFn sends a claude/channel notification. content is compact text.
type Coalescer ¶
type Coalescer struct {
// contains filtered or unexported fields
}
Coalescer schedules ping emissions with exponential backoff per fingerprint. The first occurrence fires after Initial; repeats are delayed more. Severity escalation (e.g. warning → error on the same fingerprint) immediately cancels the pending timer and fires the ping.
func NewCoalescer ¶
func NewCoalescer(delays PingDelays, pingFn func(IncidentEvent)) *Coalescer
NewCoalescer creates a Coalescer that calls pingFn when a ping should emit. pingFn must not block.
func (*Coalescer) ForceFlush ¶
ForceFlush immediately emits a pending ping for fp, if any, and resets the slot.
func (*Coalescer) Schedule ¶
func (c *Coalescer) Schedule(ev IncidentEvent)
Schedule registers an event for coalesced ping emission. If a pending ping exists for the same fingerprint, its timer is rescheduled (potentially with a longer delay) unless the new event's severity is higher — in that case the pending timer is cancelled and the ping fires immediately.
type Context ¶
type Context struct {
ProcessID string
ProxyID string
SessionID string
ProjectPath string
URL string
PID int
PGID int
Port int
}
Context carries identifiers for the resources involved in the incident.
type DedupEntry ¶
type DedupEntry struct {
First IncidentEvent
Last IncidentEvent
Count int
// contains filtered or unexported fields
}
DedupEntry tracks merged occurrences of a recurring incident fingerprint within a sliding deduplication window.
type Deduplicator ¶
type Deduplicator struct {
// contains filtered or unexported fields
}
Deduplicator merges repeated IncidentEvents that share a fingerprint into one DedupEntry, incrementing Count on each duplicate. The window is sliding: each duplicate resets the expiry. A fingerprint seen after its window expires is treated as a new occurrence.
func NewDeduplicator ¶
func NewDeduplicator(window time.Duration) *Deduplicator
NewDeduplicator creates a Deduplicator with the given sliding window. Use 30s in production; tests can use shorter values.
func (*Deduplicator) Ingest ¶
func (d *Deduplicator) Ingest(sessionID string, ev IncidentEvent) (merged bool, entry *DedupEntry)
Ingest adds ev to the deduplicator. Returns (true, existing entry) when ev deduplicates against a live entry; returns (false, new entry) on first occurrence or after the window has expired.
func (*Deduplicator) Len ¶
func (d *Deduplicator) Len() int
Len returns the number of currently tracked fingerprints.
func (*Deduplicator) Trim ¶
func (d *Deduplicator) Trim()
Trim removes expired entries, reclaiming memory.
type FlowController ¶
type FlowController struct {
// contains filtered or unexported fields
}
FlowController is a per-severity token bucket that rate-limits ping emissions. Critical severity is never throttled. Over-budget events should still be inboxed — only their ping emission is suppressed.
func NewFlowController ¶
func NewFlowController(configs map[Severity]BucketConfig) *FlowController
NewFlowController creates a FlowController using the provided per-severity configs. Any severity not in configs (including SeverityCritical) is unthrottled.
func (*FlowController) TryPing ¶
func (fc *FlowController) TryPing(sev Severity) bool
TryPing returns true if a ping for the given severity is allowed. Critical always returns true. Other severities consume a token; if the bucket is empty TryPing returns false (caller should inbox but not ping).
type Inbox ¶
type Inbox struct {
SessionID string
// contains filtered or unexported fields
}
Inbox is a bounded per-session incident inbox with 4 severity bands (critical, error, warning, info). Concurrent-safe.
func (*Inbox) GC ¶
func (inbox *Inbox) GC()
GC removes Read entries older than staleReadAge from all bands.
func (*Inbox) Ingest ¶
func (inbox *Inbox) Ingest(entry *InboxEntry) InboxDelta
Ingest adds or merges an InboxEntry into the appropriate severity band. If the fingerprint already exists in any band, Count is incremented and LastSeenAt updated; severity escalation moves the entry to a higher band. Returns the resulting InboxDelta.
func (*Inbox) LoadCritical ¶
LoadCritical loads previously saved critical entries. Silently ignores missing file (first session).
func (*Inbox) Query ¶
func (inbox *Inbox) Query(filter QueryFilter) ([]InboxEntry, Stats)
Query returns entries matching filter. Results are sorted newest first.
func (*Inbox) SaveCritical ¶
SaveCritical persists unread critical entries to path as JSON.
func (*Inbox) Subscribe ¶
func (inbox *Inbox) Subscribe() (<-chan InboxDelta, func())
Subscribe returns a buffered channel delivering InboxDelta on each Ingest. Slow consumers receive dropped silently. Call cancel when done.
type InboxDelta ¶
type InboxDelta struct {
Entry *InboxEntry
IsNew bool // false = fingerprint-merge
Escalated bool // severity moved entry to a higher band
}
InboxDelta is delivered to Subscribe subscribers on each Ingest.
type InboxEntry ¶
type InboxEntry struct {
Fingerprint string `json:"fingerprint"`
FirstSeenAt time.Time `json:"first_seen_at"`
LastSeenAt time.Time `json:"last_seen_at"`
Count int `json:"count"`
Sample *IncidentEvent `json:"sample,omitempty"`
SampleURLs []string `json:"sample_urls,omitempty"` // up to maxSampleURLs distinct
DistinctURLs int `json:"distinct_urls,omitempty"` // distinct URLs seen, capped
Severity Severity `json:"severity"`
Read bool `json:"read"`
// contains filtered or unexported fields
}
InboxEntry is a deduplicated, severity-banded record of an incident fingerprint.
type IncidentEvent ¶
type IncidentEvent struct {
ID string
Fingerprint string // sha256(source|category|canonical_msg|location)[:16]
Type MessageType
ReceivedAt time.Time
Source Source
Severity Severity
Category string // finer-grained, e.g. "TypeError", "ECONNREFUSED"
Summary string // ≤200 bytes, single line
PayloadRef *BlobRef
Ctx Context
Remediation Remediation
}
IncidentEvent is the canonical envelope for all 11 signal sources. ID is a time-ordered UUID v7 string — monotonic, sortable, cursor-friendly. Summary is capped at 200 bytes; oversized payloads live in BlobStore via PayloadRef.
func FromAlertMatch ¶
func FromAlertMatch(m *overlay.AlertMatch, processID string) IncidentEvent
FromAlertMatch converts a pattern-matched line from the AlertScanner into an IncidentEvent. Build-failure patterns (webpack, vite, nextjs, rebuild, or any pattern whose description contains "build"/"compile") use SourceBuildFail; all others use SourceProcessAlert.
func FromFrontendError ¶
func FromFrontendError(fe proxy.FrontendError, proxyID string) IncidentEvent
FromFrontendError converts a browser JS error captured by the proxy into an IncidentEvent. The error type name is extracted as the category.
func FromHTTPEntry ¶
func FromHTTPEntry(he proxy.HTTPLogEntry, proxyID string) (IncidentEvent, bool)
FromHTTPEntry converts a logged HTTP request/response into an IncidentEvent. Returns (event, true) for 4xx/5xx responses; returns (zero, false) for 2xx/3xx which do not warrant incidents.
func FromHookStopFailure ¶
func FromHookStopFailure(sessionID, errMsg, errDetails string) IncidentEvent
FromHookStopFailure creates an IncidentEvent for a Claude Code StopFailure hook event (the agent turn ended due to an API error). Uses plain primitives to avoid importing the daemon package.
sessionID is the Claude Code session that failed. errMsg is the short error label. errDetails carries the full error body (may be empty).
func FromPortConflict ¶
func FromPortConflict(port, pid int, processName string) IncidentEvent
FromPortConflict converts a port-conflict detection record into an IncidentEvent. Uses plain primitives to avoid importing the daemon package.
port is the blocked port number. pid is the blocking process's OS PID. processName is a human-readable label for the blocking process (may be empty).
func FromProcessExit ¶
func FromProcessExit(processID, reason string, exitCode int, stderrTail string) IncidentEvent
FromProcessExit converts a process death record into an IncidentEvent. Uses plain primitive params so the incident package stays free of daemon imports (avoiding a cycle when daemon wires the bus in L8).
reason is "stopped" | "crash" | "signal". exitCode is the OS exit code. stderrTail is the last N lines of stderr captured on exit (may be empty).
func FromProxyDiagnostic ¶
func FromProxyDiagnostic(d proxy.ProxyDiagnostic, proxyID string) (IncidentEvent, bool)
FromProxyDiagnostic converts a proxy diagnostic event into an IncidentEvent. Info-level diagnostics are filtered out (returns zero, false). Transport-layer events (connection refused/timeout) use SourceTransportErr; all others use SourceProxyDiag.
func FromShutdown ¶
func FromShutdown(reason string) IncidentEvent
FromShutdown creates an IncidentEvent signalling daemon/session shutdown.
func NewIncidentEvent ¶
func NewIncidentEvent(src Source, sev Severity, category, msg string, ctx Context, store *BlobStore) IncidentEvent
NewIncidentEvent builds an IncidentEvent from the raw message and optional full payload. If payload is non-nil and longer than 1KB it is stored in store (if non-nil) and referenced via PayloadRef; otherwise it is truncated into Summary.
type IncidentView ¶
type IncidentView struct {
ID string `json:"id"`
Fingerprint string `json:"fingerprint"`
FirstSeen time.Time `json:"first_seen"`
LastSeen time.Time `json:"last_seen"`
Count int `json:"count"`
Severity Severity `json:"severity"`
Source Source `json:"source"`
Category string `json:"category,omitempty"`
Summary string `json:"summary,omitempty"`
Payload *string `json:"payload,omitempty"` // hydrated when detail="full"
Ctx Context `json:"context,omitempty"`
Remediation Remediation `json:"remediation,omitempty"`
Read bool `json:"read"`
}
IncidentView is the pull-side projection of an InboxEntry, enriched with remediation guidance. Used by get_incidents output and AggregateRemediation.
type MCPNotifyFn ¶
type MCPNotifyFn func(level string, payload PingPayload) error
MCPNotifyFn sends an MCP Log notification. level is "error", "warning", or "info".
type MPSCBus ¶
type MPSCBus struct {
// contains filtered or unexported fields
}
MPSCBus is a backpressure-safe, non-blocking event bus. Multiple producers call Fire or Publish; a single dispatch goroutine fans events to all active session pipelines. If the inbound channel is full, the newest event is dropped and the drop counter incremented. Every 100th drop synthesises one meta:bus_overflow warning incident so the AI agent sees bus pressure.
Session lifecycles:
- AddSession: creates sessionPipeline and starts its dispatch goroutine.
- RemoveSession: signals the goroutine to stop and waits up to 2s.
- Close: removes all sessions and stops the bus.
func NewMPSCBus ¶
func NewMPSCBus(pingConfig *PingConfig) *MPSCBus
NewMPSCBus creates a ready-to-use MPSCBus. pingConfig may be nil for a headless bus (no pinger). The bus starts the central dispatch goroutine immediately.
func (*MPSCBus) AddSession ¶
func (b *MPSCBus) AddSession(sessionID string, mcpNotify MCPNotifyFn, channelNotify ChannelNotifyFn, ptyInject PTYInjectFn)
AddSession registers a new session pipeline. It is idempotent — calling again with the same sessionID replaces the old pipeline (the old one is torn down first).
mcpNotify, channelNotify, and ptyInject may all be nil (pinger channels are skipped). pingConfig may be nil; DefaultPingConfig() is used in that case.
func (*MPSCBus) Close ¶
func (b *MPSCBus) Close()
Close shuts down the bus and all session pipelines.
func (*MPSCBus) Fire ¶
func (b *MPSCBus) Fire(ev *IncidentEvent)
Fire is the single non-blocking entry point for signal producers. It must complete in <1μs even when the bus is saturated.
func (*MPSCBus) HasSession ¶ added in v0.13.3
HasSession reports whether a pipeline exists for the given session.
func (*MPSCBus) MarkReadSession ¶ added in v0.13.3
MarkReadSession marks entries as read in the given session's inbox. No-op if the session has no pipeline.
func (*MPSCBus) Publish ¶
func (b *MPSCBus) Publish(ev IncidentEvent)
Publish implements the Bus interface. It wraps Fire for callers that own the event value (not a pointer).
func (*MPSCBus) QuerySession ¶ added in v0.13.3
func (b *MPSCBus) QuerySession(sessionID string, filter QueryFilter) ([]InboxEntry, Stats)
QuerySession queries the incident inbox for the given session. Returns nil entries and empty stats if no pipeline exists for the session.
func (*MPSCBus) RemoveSession ¶
RemoveSession tears down the session pipeline for sessionID and waits up to sessionDrainWindow for in-flight events to finish.
type MessageType ¶ added in v0.13.16
type MessageType string
MessageType partitions the agent-inbound queue into per-type lanes. The error lane is severity-banded; drawing/comment lanes are FIFO. New types are added here plus a lane config — the gate/digest machinery is type-agnostic.
const ( // MessageError is the lane for diagnostics, HTTP errors, crashes, etc. MessageError MessageType = "error" // MessageDrawing is the lane for sketch-mode wireframes. MessageDrawing MessageType = "drawing" // MessageComment is the lane for floating-panel user messages. MessageComment MessageType = "comment" )
type NopBus ¶
type NopBus struct{}
NopBus discards every event published to it.
func (NopBus) Publish ¶
func (NopBus) Publish(IncidentEvent)
type PTYInjectFn ¶
PTYInjectFn injects a single-line status string into PTY stdin.
type PingConfig ¶
type PingConfig struct {
MCPNotifications bool
ChannelEnabled bool
PTYInjection bool
MaxTopFPs int // 0 → defaultMaxTopFingerprints
IncludeSummary bool // include short summary text per top entry
Delays PingDelays // coalesce backoff; zero → DefaultPingDelays
}
PingConfig controls which channels emit pings and their content density.
type PingDelays ¶
type PingDelays struct {
Initial time.Duration // first ping delay (default 500ms)
Max time.Duration // backoff ceiling (default 10s)
ResetAfter time.Duration // inactivity window before counter resets (default 30s)
}
PingDelays configures the exponential backoff for coalesced ping emission. Initial is the first-occurrence delay; each repeat doubles it up to Max. After ResetAfter of no activity on a fingerprint the counter resets to Initial.
type PingEmitter ¶
type PingEmitter struct {
// contains filtered or unexported fields
}
PingEmitter subscribes to an Inbox, applies flow + activity gating, and fans pings out to up to 3 channels (MCP log, claude/channel, PTY stdin).
Critical events bypass the activity deferral and coalesce timer. Non-critical events are debounced via a session-level Coalescer slot.
func NewPingEmitter ¶
func NewPingEmitter( inbox *Inbox, config PingConfig, flow *FlowController, activity *ActivityDetector, mcpNotify MCPNotifyFn, channelNotify ChannelNotifyFn, ptyInject PTYInjectFn, ) *PingEmitter
NewPingEmitter creates and starts a PingEmitter. Stop() must be called to release the subscription goroutine. Any of mcpNotify, channelNotify, or ptyInject may be nil (that channel is skipped).
activity.onIdle is not set by NewPingEmitter — the caller should wire it with: `ForceFlush(sessionPingFP)` on the returned emitter's coalescer if activity-triggered flushing is desired (typically in L8 wiring).
func (*PingEmitter) ForceFlushCoalesce ¶
func (pe *PingEmitter) ForceFlushCoalesce()
ForceFlushCoalesce immediately emits any pending coalesced ping. Intended for activity-idle callbacks wired by L8.
func (*PingEmitter) Stop ¶
func (pe *PingEmitter) Stop()
Stop unsubscribes from the inbox and cancels all pending pings.
type PingPayload ¶
type PingPayload struct {
Type string `json:"type"` // "agnt.incident_ping"
Version int `json:"version"` // 1
Session string `json:"session"`
Summary PingStats `json:"summary"`
Top []TopEntry `json:"top,omitempty"`
}
PingPayload is the stable schema for all channels. Target size < 2KB.
type PingStats ¶
type PingStats struct {
Critical int `json:"critical"`
Error int `json:"error"`
Warning int `json:"warning"`
Info int `json:"info"`
New int `json:"new"` // unread entries at time of ping
}
PingStats is the compact summary inside PingPayload.
type QueryFilter ¶
type QueryFilter struct {
Severities []Severity // empty = all
Since time.Time // zero = no lower bound
UnreadOnly bool
Limit int // 0 = all
}
QueryFilter narrows which entries Query returns.
type Remediation ¶
type Remediation struct {
PrimaryTool string `json:"primary_tool,omitempty"`
PrimaryArgs map[string]any `json:"primary_args,omitempty"`
FallbackTool string `json:"fallback_tool,omitempty"`
FallbackArgs map[string]any `json:"fallback_args,omitempty"`
SkillHint string `json:"skill_hint,omitempty"`
}
Remediation hints which MCP tool and skill should address this incident. Populated by the L7 routing table; zero value means "no hint".
func Resolve ¶
func Resolve(ev *IncidentEvent) Remediation
Resolve returns remediation guidance for ev. Never returns a zero Remediation.
type Source ¶
type Source string
Source identifies where the incident originated.
const ( SourceBrowserJS Source = "browser_js" SourceHTTP5xx Source = "http_5xx" SourceHTTP4xx Source = "http_4xx" SourceTransportErr Source = "transport_err" SourceProxyDiag Source = "proxy_diag" SourceProcessAlert Source = "process_alert" SourceProcessOutput Source = "process_output" SourceProcessCrash Source = "process_crash" SourceBuildFail Source = "build_fail" SourcePortConflict Source = "port_conflict" SourceShutdown Source = "shutdown" SourceHookStopFail Source = "hook_stop_failure" SourceChaosSwallow Source = "chaos_swallowed_error" )
type Stats ¶
type Stats struct {
Critical int `json:"critical"`
Error int `json:"error"`
Warning int `json:"warning"`
Info int `json:"info"`
Dropped int64 `json:"dropped"`
OldestUnread time.Time `json:"oldest_unread,omitempty"`
Cursor time.Time `json:"cursor,omitempty"`
}
Stats is a point-in-time snapshot of inbox health.
type SwallowDetector ¶ added in v0.13.20
type SwallowDetector struct {
// contains filtered or unexported fields
}
SwallowDetector implements the chaos "swallowed error" heuristic: when the chaos engine injects an error-status fault into a proxied response and the application produces NO app-side error signal (a frontend JS error on the same proxy) within a window, the fault was silently eaten — a real defect in the app's error handling that the chaos run exists to surface.
The detector is a pure correlation state machine driven by three calls:
- RecordFault — an injected fault was logged (pending until proven handled)
- RecordAppError — an app-side error arrived; clears recent pending faults on that proxy (the app surfaced something, so it did not swallow)
- Sweep — pending faults older than the window with no matching app error become swallowed-error incidents
Time is passed in explicitly (no internal clock) so the daemon can drive it with a ticker and tests can drive it deterministically.
Correlation is per-proxy and time-windowed, not per-URL: a frontend JS error rarely names the request that triggered it, so any app error within the window after an injected fault counts as "handled". This is a heuristic by construction — it trades precision for catching the silent-failure case that nothing else can observe.
func NewSwallowDetector ¶ added in v0.13.20
func NewSwallowDetector(window time.Duration) *SwallowDetector
NewSwallowDetector returns a detector with the given correlation window. A non-positive window falls back to two seconds.
func (*SwallowDetector) RecordAppError ¶ added in v0.13.20
func (d *SwallowDetector) RecordAppError(proxyID string, now time.Time)
RecordAppError records that the app produced an error on proxyID. It clears every pending fault on that proxy whose injection time is within the window ending at now — the app surfaced an error in response, so those faults were handled, not swallowed.
func (*SwallowDetector) RecordFault ¶ added in v0.13.20
func (d *SwallowDetector) RecordFault(proxyID, url string, status int, now time.Time)
RecordFault registers an injected error-status fault as pending. It becomes a swallowed-error incident on a later Sweep unless RecordAppError clears it first.
func (*SwallowDetector) Sweep ¶ added in v0.13.20
func (d *SwallowDetector) Sweep(now time.Time) []IncidentEvent
Sweep returns swallowed-error incidents for every pending fault older than the window (no app error cleared it in time) and drops them from the pending set. Faults still inside the window are retained for a future sweep.
type ToolSuggestion ¶
type ToolSuggestion struct {
Tool string `json:"tool"`
Args map[string]any `json:"args,omitempty"`
Rationale string `json:"rationale"`
}
ToolSuggestion is a pre-filled tool call with a one-line rationale.
func AggregateRemediation ¶
func AggregateRemediation(incidents []IncidentView) (skill string, tools []ToolSuggestion)
AggregateRemediation picks the dominant skill+tools across a slice of incidents, weighted by occurrence count.