the ring reads out over ipc as turn decisions (V-564)
Same shape as TickTrace and RecentEvents: a bounded daemon ring, so the store adapter refuses rather than pretending a table exists. No voice wiring means an empty list and not an error, because a box with no voice path has had no turns to arbitrate.
This commit is contained in:
@@ -350,6 +350,7 @@ func run(args []string) error {
|
|||||||
getMorningStatus: func(ctx context.Context) []ipc.MorningRoutineStatus { return tl.morningStatus(ctx, time.Now()) },
|
getMorningStatus: func(ctx context.Context) []ipc.MorningRoutineStatus { return tl.morningStatus(ctx, time.Now()) },
|
||||||
getDayPlan: func(ctx context.Context) ipc.DayPlan { return tl.dayPlan(ctx, time.Now()) },
|
getDayPlan: func(ctx context.Context) ipc.DayPlan { return tl.dayPlan(ctx, time.Now()) },
|
||||||
getEvents: intakeEventsFn(evBus),
|
getEvents: intakeEventsFn(evBus),
|
||||||
|
getDecisions: turnDecisionsFn(voiceW),
|
||||||
seedStore: seedStoreIfAllowed(st),
|
seedStore: seedStoreIfAllowed(st),
|
||||||
nexus: nexusOf(voiceW),
|
nexus: nexusOf(voiceW),
|
||||||
}
|
}
|
||||||
@@ -619,6 +620,7 @@ func run(args []string) error {
|
|||||||
getMorningStatus: func(ctx context.Context) []ipc.MorningRoutineStatus { return tl.morningStatus(ctx, time.Now()) },
|
getMorningStatus: func(ctx context.Context) []ipc.MorningRoutineStatus { return tl.morningStatus(ctx, time.Now()) },
|
||||||
getDayPlan: func(ctx context.Context) ipc.DayPlan { return tl.dayPlan(ctx, time.Now()) },
|
getDayPlan: func(ctx context.Context) ipc.DayPlan { return tl.dayPlan(ctx, time.Now()) },
|
||||||
getEvents: intakeEventsFn(evBus),
|
getEvents: intakeEventsFn(evBus),
|
||||||
|
getDecisions: turnDecisionsFn(voiceW),
|
||||||
seedStore: seedStoreIfAllowed(st),
|
seedStore: seedStoreIfAllowed(st),
|
||||||
}
|
}
|
||||||
if voiceW != nil && voiceW.handler != nil {
|
if voiceW != nil && voiceW.handler != nil {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ type daemonAPI struct {
|
|||||||
chatFn func(ctx context.Context, conversation, text string) string
|
chatFn func(ctx context.Context, conversation, text string) string
|
||||||
getMCPServers func() []ipc.MCPServerStatus
|
getMCPServers func() []ipc.MCPServerStatus
|
||||||
getEvents func(n int) []ipc.IntakeEvent
|
getEvents func(n int) []ipc.IntakeEvent
|
||||||
|
getDecisions func(n int) []ipc.TurnDecision
|
||||||
// nexus — the identity client, nil when no nexus block is configured. It
|
// nexus — the identity client, nil when no nexus block is configured. It
|
||||||
// is what makes ResolveEntity answerable at all; without it the store
|
// is what makes ResolveEntity answerable at all; without it the store
|
||||||
// adapter's refusal stands, and a surface that wanted an entity id says so
|
// adapter's refusal stands, and a surface that wanted an entity id says so
|
||||||
@@ -118,6 +119,17 @@ func (d *daemonAPI) TickTrace(ctx context.Context) (ipc.TickTrace, error) {
|
|||||||
return toIPCTickTrace(*trace), nil
|
return toIPCTickTrace(*trace), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TurnDecisions — the arbitration records of the last few turns (V-564). Nil
|
||||||
|
// getter means voice was never wired, and that is an empty list rather than an
|
||||||
|
// error: a box with no voice path has had no turns to arbitrate, which is not a
|
||||||
|
// fault and renders as an empty table.
|
||||||
|
func (d *daemonAPI) TurnDecisions(ctx context.Context, n int) ([]ipc.TurnDecision, error) {
|
||||||
|
if d.getDecisions == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return d.getDecisions(n), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *daemonAPI) MorningStatus(ctx context.Context) ([]ipc.MorningRoutineStatus, error) {
|
func (d *daemonAPI) MorningStatus(ctx context.Context) ([]ipc.MorningRoutineStatus, error) {
|
||||||
if d.getMorningStatus == nil {
|
if d.getMorningStatus == nil {
|
||||||
return nil, errors.New("mavend: morning status not available")
|
return nil, errors.New("mavend: morning status not available")
|
||||||
|
|||||||
@@ -836,6 +836,30 @@ type TickTrace struct {
|
|||||||
Rules []RuleTrace `json:"rules"`
|
Rules []RuleTrace `json:"rules"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Turn decision trace DTOs (V-564) ---
|
||||||
|
|
||||||
|
// TurnClaim — one claimant's say on one turn: who, at which stage, what it
|
||||||
|
// would have made the turn, the score it reported if it has one, and what
|
||||||
|
// happened to the claim. Same shape as RuleTrace above and for the same reason:
|
||||||
|
// a winner alone does not explain an arbitration, the losers do.
|
||||||
|
type TurnClaim struct {
|
||||||
|
Stage string `json:"stage"`
|
||||||
|
Claimant string `json:"claimant"`
|
||||||
|
Intent string `json:"intent,omitempty"`
|
||||||
|
Score float64 `json:"score,omitempty"`
|
||||||
|
HasScore bool `json:"has_score,omitempty"`
|
||||||
|
Outcome string `json:"outcome"`
|
||||||
|
Reason string `json:"reason,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TurnDecision — one turn's arbitration, newest first when read as a list.
|
||||||
|
type TurnDecision struct {
|
||||||
|
Ts time.Time `json:"ts"`
|
||||||
|
Utterance string `json:"utterance"`
|
||||||
|
Winner string `json:"winner"`
|
||||||
|
Claims []TurnClaim `json:"claims"`
|
||||||
|
}
|
||||||
|
|
||||||
// MorningRoutineItem — one checklist entry's current state.
|
// MorningRoutineItem — one checklist entry's current state.
|
||||||
type MorningRoutineItem struct {
|
type MorningRoutineItem struct {
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
|
|||||||
@@ -672,6 +672,14 @@ func (c *Client) TickTrace(ctx context.Context) (TickTrace, error) {
|
|||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) TurnDecisions(ctx context.Context, n int) ([]TurnDecision, error) {
|
||||||
|
var d []TurnDecision
|
||||||
|
if err := c.call(ctx, MethodTurnDecisions, nReq{N: n}, &d); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) RecentEvents(ctx context.Context, n int) ([]IntakeEvent, error) {
|
func (c *Client) RecentEvents(ctx context.Context, n int) ([]IntakeEvent, error) {
|
||||||
var e []IntakeEvent
|
var e []IntakeEvent
|
||||||
if err := c.call(ctx, MethodRecentEvents, nReq{N: n}, &e); err != nil {
|
if err := c.call(ctx, MethodRecentEvents, nReq{N: n}, &e); err != nil {
|
||||||
|
|||||||
@@ -169,6 +169,13 @@ type SystemAPI interface {
|
|||||||
// persisted — it's a daemon-level cache).
|
// persisted — it's a daemon-level cache).
|
||||||
TickTrace(ctx context.Context) (TickTrace, error)
|
TickTrace(ctx context.Context) (TickTrace, error)
|
||||||
|
|
||||||
|
// TurnDecisions returns the newest turn arbitration records, newest first
|
||||||
|
// (V-564). Same shape as TickTrace and RecentEvents: a bounded in-memory
|
||||||
|
// ring on the daemon, so the store adapter returns an error rather than
|
||||||
|
// pretending a table exists. Empty is a normal answer — it means no turn
|
||||||
|
// has run since the daemon started.
|
||||||
|
TurnDecisions(ctx context.Context, n int) ([]TurnDecision, error)
|
||||||
|
|
||||||
// RecentEcosystemTraces reads the ecosystem call log, which lives in its
|
// RecentEcosystemTraces reads the ecosystem call log, which lives in its
|
||||||
// own table so machine-rate traces never crowd out human-rate facts.
|
// own table so machine-rate traces never crowd out human-rate facts.
|
||||||
RecentEcosystemTraces(ctx context.Context, n int) ([]EcosystemTrace, error)
|
RecentEcosystemTraces(ctx context.Context, n int) ([]EcosystemTrace, error)
|
||||||
|
|||||||
@@ -583,6 +583,13 @@ var methodTable = map[Method]handlerFunc{
|
|||||||
MethodTickTrace: withoutParams(func(ctx context.Context, api CoreAPI) (TickTrace, error) {
|
MethodTickTrace: withoutParams(func(ctx context.Context, api CoreAPI) (TickTrace, error) {
|
||||||
return api.TickTrace(ctx)
|
return api.TickTrace(ctx)
|
||||||
}),
|
}),
|
||||||
|
MethodTurnDecisions: withParams(func(ctx context.Context, api CoreAPI, p nReq) ([]TurnDecision, error) {
|
||||||
|
d, err := api.TurnDecisions(ctx, p.N)
|
||||||
|
if d == nil {
|
||||||
|
d = []TurnDecision{}
|
||||||
|
}
|
||||||
|
return d, err
|
||||||
|
}),
|
||||||
// MorningStatus intentionally has no nil→[]T{} normalization here — the
|
// MorningStatus intentionally has no nil→[]T{} normalization here — the
|
||||||
// pre-table arm marshaled api.MorningStatus's result as-is (a nil slice
|
// pre-table arm marshaled api.MorningStatus's result as-is (a nil slice
|
||||||
// serializes as JSON null), and this preserves that exact wire shape.
|
// serializes as JSON null), and this preserves that exact wire shape.
|
||||||
|
|||||||
@@ -265,6 +265,12 @@ func (a *storeAPI) TickTrace(ctx context.Context) (TickTrace, error) {
|
|||||||
return TickTrace{}, errors.New("store: tick trace not available via direct store API")
|
return TickTrace{}, errors.New("store: tick trace not available via direct store API")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TurnDecisions — same story as TickTrace: the arbitration record is a daemon
|
||||||
|
// ring, not a table, so there is nothing here to read it from (V-564).
|
||||||
|
func (a *storeAPI) TurnDecisions(ctx context.Context, n int) ([]TurnDecision, error) {
|
||||||
|
return nil, errors.New("store: turn decisions not available via direct store API")
|
||||||
|
}
|
||||||
|
|
||||||
// SeedEvent — same shape as MorningStatus: writing the fact is a store call,
|
// SeedEvent — same shape as MorningStatus: writing the fact is a store call,
|
||||||
// but extraction and detect-and-propose live in mavend, and a seed that wrote
|
// but extraction and detect-and-propose live in mavend, and a seed that wrote
|
||||||
// the fact without running them would be the one thing this seam must not be,
|
// the fact without running them would be the one thing this seam must not be,
|
||||||
|
|||||||
@@ -144,6 +144,9 @@ func (UnimplementedCoreAPI) RevertFact(ctx context.Context, key string) (int64,
|
|||||||
func (UnimplementedCoreAPI) TickTrace(ctx context.Context) (TickTrace, error) {
|
func (UnimplementedCoreAPI) TickTrace(ctx context.Context) (TickTrace, error) {
|
||||||
return TickTrace{}, ErrNotImplemented
|
return TickTrace{}, ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
func (UnimplementedCoreAPI) TurnDecisions(ctx context.Context, n int) ([]TurnDecision, error) {
|
||||||
|
return nil, ErrNotImplemented
|
||||||
|
}
|
||||||
func (UnimplementedCoreAPI) MorningStatus(ctx context.Context) ([]MorningRoutineStatus, error) {
|
func (UnimplementedCoreAPI) MorningStatus(ctx context.Context) ([]MorningRoutineStatus, error) {
|
||||||
return nil, ErrNotImplemented
|
return nil, ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ const (
|
|||||||
MethodAcceptProposedRoutine Method = "accept_proposed_routine"
|
MethodAcceptProposedRoutine Method = "accept_proposed_routine"
|
||||||
MethodRevertFact Method = "revert_fact"
|
MethodRevertFact Method = "revert_fact"
|
||||||
MethodTickTrace Method = "tick_trace"
|
MethodTickTrace Method = "tick_trace"
|
||||||
|
MethodTurnDecisions Method = "turn_decisions"
|
||||||
MethodMorningStatus Method = "morning_status"
|
MethodMorningStatus Method = "morning_status"
|
||||||
MethodMCPServers Method = "mcp_servers"
|
MethodMCPServers Method = "mcp_servers"
|
||||||
MethodDayPlan Method = "day_plan"
|
MethodDayPlan Method = "day_plan"
|
||||||
|
|||||||
Reference in New Issue
Block a user