package definition import ( "bytes" "encoding/json" ) // SameAgent reports whether two agent definitions mean the same thing. // // This exists because a published version is compared against a new publish to // decide whether the new one is a rewrite. Comparing the raw Markdown makes // that decision on formatting: the authoring UI re-serialises a definition when // somebody saves it — writing `webSearch: false` where the hand-authored file // left the key out, and ordering the frontmatter its own way — so a definition // that nobody meaningfully changed stops a deploy. // // The comparison is deliberately conservative, because the two ways of being // wrong are not equally bad. Reporting a difference that does not exist blocks // a deploy, which is visible and recoverable. Reporting no difference when one // exists lets a changed agent overwrite an approved version silently, which is // the thing versioning is for. So anything not PROVABLY inert counts as a // difference: // // - The body is compared verbatim. It is the system prompt, and Agent.Body // carries `json:"-"`, so marshalling alone would ignore a complete rewrite // of the instructions. // - List ORDER is significant. loader.go resolves Skills in order, so the // order reaches prompt assembly. Two definitions listing the same skills // differently are treated as different, and a deploy that only reorders // one still has to raise its version. That is a deliberate limit, not an // oversight — loosening it needs someone to decide that skill order cannot // matter, and that is not a decision to make inside a comparison function. // // What it does absorb is exactly what the round trip produces: frontmatter key // order, whitespace, and a defaulted value written out explicitly. func SameAgent(stored, incoming string) bool { if stored == incoming { return true } a, err := ParseAgent(stored, Options{}) if err != nil || a == nil { return false } b, err := ParseAgent(incoming, Options{}) if err != nil || b == nil { return false } // Body first: it is the expensive thing to get wrong and the cheap thing // to check. if a.Body != b.Body { return false } ja, err := json.Marshal(a) if err != nil { return false } jb, err := json.Marshal(b) if err != nil { return false } return bytes.Equal(ja, jb) } // SameSkill reports whether two skill definitions mean the same thing. // // The same reasoning as SameAgent, and the same conservatism. It matters less // here — a skill that compares unequal produces a spurious version rather than // a blocked deploy, because skills are numbered by the server and have nothing // to refuse — but a history full of versions that record a reformat is a // history nobody reads. func SameSkill(stored, incoming string) bool { if stored == incoming { return true } a, err := ParseSkill(stored, Options{}) if err != nil || a == nil { return false } b, err := ParseSkill(incoming, Options{}) if err != nil || b == nil { return false } if a.Body != b.Body { return false } ja, err := json.Marshal(a) if err != nil { return false } jb, err := json.Marshal(b) if err != nil { return false } return bytes.Equal(ja, jb) }