Documentation
¶
Overview ¶
Package dispatch persists dispatch decisions — the moment a coach (or, later, the auto-dispatch daemon) picks a bead for a session (gm-s47n.6.2, work-planning.md §5 Layer 5).
Each row captures the *prediction*: which bead was picked, which session got it, the affinity score the planner showed at that moment, the conflict edges visible in the ready set, and the set of alternatives that were on the grid. The retrospective (gm-s47n.8.x) joins these rows against scorer_grades on bead_id to grade the planner — "did high-predicted-affinity picks land with low divergence?"
The package is intentionally narrow: a Decision struct + Insert / Get / List on a *sql.DB. Callers (the coach HTTP handler, the auto-dispatch daemon when it lands) materialise the snapshot at pick time and hand it here. No I/O concerns leak out.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ConflictSnapshot ¶
type ConflictSnapshot struct {
Workspace []planner.WorkspaceCollision `json:"workspace,omitempty"`
Semantic []planner.SemanticConflict `json:"semantic,omitempty"`
}
ConflictSnapshot bundles the workspace + semantic conflict edges the planner saw at decision time. Both slices are nil-safe and JSON-encoded into one column.
type Decision ¶
type Decision struct {
// ID is a stable per-row identifier. The store generates one when
// the caller leaves it empty so callers don't have to thread a UUID
// through the dispatch path.
ID string `json:"id,omitempty"`
BeadID core.WorkItemID `json:"bead_id"`
DecidedAt time.Time `json:"decided_at"`
// SessionID + AgentID identify the session that received the
// bead. Empty AgentID is tolerated (the dispatcher may write
// before the session has resolved its agent).
SessionID string `json:"session_id,omitempty"`
AgentID core.AgentID `json:"agent_id,omitempty"`
// DecidedBy is the operator id for coach picks; blank for auto.
DecidedBy string `json:"decided_by,omitempty"`
// Mode = "coach" | "auto". Defaults to ModeCoach when blank.
Mode Mode `json:"mode,omitempty"`
// Affinity carries the planner's predicted score breakdown for
// the picked bead at decision time. AffinityCombined is mirrored
// to its own column for sargable filters; the full breakdown
// rides in affinity_json.
Affinity planner.AffinityScores `json:"affinity"`
// Conflicts captures every conflict edge the planner had visible
// at decision time. Stored verbatim so the retrospective can ask
// "did the coach pick into a known workspace collision?" without
// recomputing the conflict graph from a stale snapshot.
Conflicts ConflictSnapshot `json:"conflicts"`
// ReadySet lists the alternative beads that were on the grid at
// decision time. Required for "of the alternatives, was this
// actually the highest-affinity pick?" — a question the
// retrospective cannot answer without it.
ReadySet []ReadySetEntry `json:"ready_set,omitempty"`
// OperatorReason is the optional one-line explanation a coach-
// mode picker can attach via `gemba dispatch --reason "..."`
// (gm-v5z2.8 §7.5). Free-form; the calibration loop reads it
// alongside (recommended_top_bead, picked_bead, score_delta)
// to surface why an operator overrode the planner's top pick.
// Empty for auto-mode dispatches and for coach picks where
// the operator skipped the prompt.
OperatorReason string `json:"operator_reason,omitempty"`
// CreatedAt is when this row was written; distinct from
// DecidedAt which is when the human pressed the button. The
// store stamps it from now() when zero.
CreatedAt time.Time `json:"created_at"`
}
Decision is the persisted dispatch record. The JSON-encoded snapshot fields capture the planner's view at decision time so the retrospective can grade them later.
type ListFilter ¶
type ListFilter struct {
// BeadID restricts to decisions for this bead. Empty = all
// beads. The retrospective uses this to find every dispatch
// for a bead that has just landed.
BeadID core.WorkItemID
// SessionID restricts to decisions that picked for this
// session. Empty = all sessions.
SessionID string
// Mode restricts to coach or auto. Empty = both modes.
Mode Mode
// MinAffinityCombined keeps rows with affinity_combined >= this
// value. Defaults to 0 (no filter).
MinAffinityCombined float64
// Limit caps the result count. <=0 means no cap; List
// applies a hard ceiling of 1000 to keep the unbounded path
// from accidentally pulling the whole table.
Limit int
}
ListFilter narrows List output. Zero-value = no filtering.
type Mode ¶
type Mode string
Mode names who made the pick. The auto-dispatch daemon (when it lands, gm-s47n.6.3) writes ModeAuto; the coach UI writes ModeCoach.
type ReadySetEntry ¶
type ReadySetEntry struct {
BeadID core.WorkItemID `json:"bead_id"`
AffinityCombined float64 `json:"affinity_combined"`
WorkspaceConflict bool `json:"workspace_conflict,omitempty"`
}
ReadySetEntry is the dispatch-time view of one alternative bead. Only the bead id + the predicted combined affinity for the chosen session is required — that's enough for the retro to ask "could a different bead have predicted higher?"
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store persists dispatch_decisions rows. Mirrors retro.Store: pass nil to disable so callers can `if store != nil` without feature flags.
func NewStore ¶
NewStore wraps the given *sql.DB. Returns nil when db is nil so the dispatch wiring stays branch-free.
func (*Store) EnsureSchema ¶
EnsureSchema applies the embedded planner.SchemaSQL DDL. Safe to call multiple times; idempotent CREATE TABLE IF NOT EXISTS.
func (*Store) Get ¶
Get reads a single Decision by ID. Returns (nil, nil) when no row matches — matches the planner's "absent ≠ error" convention.
func (*Store) Insert ¶
Insert writes one Decision row. Generates an ID when the caller leaves it blank. Returns the stored row's ID so the HTTP handler can surface it back to the SPA for later cross-reference.
CreatedAt is stamped from now() when zero; DecidedAt is required — the dispatcher knows when the pick happened, the store does not.