feat(divergence): Jev noul restatement check -> harness review nudge (#25)

This commit is contained in:
Smokey committed 2026-10-28 05:37:19 -06:00
1 parent b04aace26b
commit dfe50da320
3 files changed
+121

No files matched your search

+55
View File
@@ -252,6 +252,61 @@ func (j *JevClient) ClassifyKind(ctx context.Context, text string) (string, map[
return a.Choice, a.Probabilities, true return a.Choice, a.Probabilities, true
} }
// divergenceQuestions asks whether a message restates an earlier point. It is
// a noul: JEV_STRATEGIES strategy 3 (crisp yes/no) and noul has no confidence,
// so the probability itself is thresholded (band = threshold).
var divergenceQuestions = map[string]any{
"restates_earlier": map[string]any{
"type": "noul",
"instructions": "Does `this` make a point already made in `earlier`, adding no new angle?",
"criteria": map[string]string{
"true": "It repeats an earlier point without a genuinely new angle.",
"false": "It adds something different from what `earlier` already said.",
},
},
}
// ClassifyDivergence returns the probability that a message restates earlier
// ones, and ok when a value came back (noul always carries one; ok is false
// only on transport error or a missing answer).
func (j *JevClient) ClassifyDivergence(ctx context.Context, state any) (float64, bool) {
if j == nil {
return 0, false
}
answers, _, _, err := j.SystemOne(ctx, state, divergenceQuestions)
if err != nil {
return 0, false
}
a := answers["restates_earlier"]
if a.Noul == nil {
return 0, false
}
return *a.Noul, true
}
// ClassifyDivergence runs the restatement check under the flock guardrails
// (cap, timeout, flight log with the probability recorded).
func (f *Flock) ClassifyDivergence(ctx context.Context, state any, budgets ...*Budget) (float64, bool) {
if f.jev == nil {
return 0, false
}
for _, b := range budgets {
if err := b.Check(); err != nil {
return 0, false
}
}
f.sem <- struct{}{}
defer func() { <-f.sem }()
cctx, cancel := context.WithTimeout(ctx, f.g.CrowTimeout)
defer cancel()
start := time.Now()
p, ok := f.jev.ClassifyDivergence(cctx, state)
if f.log != nil {
f.log.Write(Event{Kind: "divergence", Crow: "Sentry", Model: f.jev.model, Ms: time.Since(start).Milliseconds(), Prob: map[string]float64{"restates_earlier": p}, Note: fmt.Sprint(ok && p >= f.jev.threshold)})
}
return p, ok
}
// ClassifyKind tags a nest message under the same guardrails as any call: // ClassifyKind tags a nest message under the same guardrails as any call:
// flock cap, timeout and flight log, with the probabilities recorded. // flock cap, timeout and flight log, with the probabilities recorded.
func (f *Flock) ClassifyKind(ctx context.Context, text string, budgets ...*Budget) (string, map[string]float64, bool) { func (f *Flock) ClassifyKind(ctx context.Context, text string, budgets ...*Budget) (string, map[string]float64, bool) {
+40
View File
@@ -184,6 +184,10 @@ func (s *Server) postNestMessage(w http.ResponseWriter, r *http.Request) {
} }
} }
if kind == "deliver" {
go s.checkDivergence(nest, record)
}
writeJSON(w, http.StatusCreated, nestRecord(record)) writeJSON(w, http.StatusCreated, nestRecord(record))
} }
@@ -298,6 +302,42 @@ func (s *Server) respondOne(nest Nest, req Message, crow string, lens bool) {
} }
if record, err := s.msgs.Append(reply); err == nil { if record, err := s.msgs.Append(reply); err == nil {
s.emitNestMessage(record) s.emitNestMessage(record)
if record.Kind == "deliver" {
s.checkDivergence(nest, record)
}
}
}
// checkDivergence asks Jev whether this deliver restates earlier delivers that
// answer the same request. Above the band the crow gets a review nudge; the
// deliver itself stays in the log (nudges never silence).
func (s *Server) checkDivergence(nest Nest, m Message, budgets ...*Budget) {
if m.Kind != "deliver" {
return
}
earlier := s.msgs.EarlierDelivers(nest.ID, m.RefersTo, m.ID)
if len(earlier) == 0 {
return
}
if _, nudged := s.msgs.NudgeFor(nest.ID, m.MsgID()); nudged {
return // one nudge per deliver; no loop
}
prob, ok := s.p.Flock.ClassifyDivergence(s.ctx, map[string]any{"this": m.Text, "earlier": earlier}, budgets...)
if !ok || prob < s.p.Flock.jev.threshold {
return
}
nudge := Message{
NestID: nest.ID,
Kind: "nudge",
Author: "harness",
Text: "This reads like a point already made in the thread; review the earlier delivers and add your distinct angle or let it rest.",
To: m.Author,
RefersTo: m.MsgID(),
TaggedBy: "harness",
Timestamp: time.Now().UTC().Format(time.RFC3339),
}
if record, err := s.msgs.Append(nudge); err == nil {
s.emitNestMessage(record)
} }
} }
+26
View File
@@ -132,6 +132,32 @@ func (s *MessageStore) FirstClaim(nestID, referentID string) (string, bool) {
return "", false return "", false
} }
// EarlierDelivers returns the texts of deliver messages answering the same
// referent, oldest first, excluding the one with id excludeID.
func (s *MessageStore) EarlierDelivers(nestID, referentID string, excludeID int) []string {
s.mu.Lock()
defer s.mu.Unlock()
var out []string
for _, m := range s.log[nestID] {
if m.Kind == "deliver" && m.RefersTo == referentID && m.ID != excludeID {
out = append(out, m.Text)
}
}
return out
}
// NudgeFor reports whether a nudge already references a given message.
func (s *MessageStore) NudgeFor(nestID, targetMsgID string) (Message, bool) {
s.mu.Lock()
defer s.mu.Unlock()
for _, m := range s.log[nestID] {
if m.Kind == "nudge" && m.RefersTo == targetMsgID {
return m, true
}
}
return Message{}, false
}
func (s *MessageStore) save(nestID string) error { func (s *MessageStore) save(nestID string) error {
if err := os.MkdirAll(s.dir, 0o755); err != nil { if err := os.MkdirAll(s.dir, 0o755); err != nil {
return err return err