From 1c658a03666e6ecc295867b60dd6458828c6eea9 Mon Sep 17 00:00:00 2001 From: Ektisad25 Date: Tue, 21 Jul 2026 14:27:45 +0200 Subject: [PATCH 1/2] feat: rewrite Goose value recognition in OCaml --- README.md | 25 ++++++ agentpipe-goose.opam | 15 ++++ dune-project | 11 +++ ocaml/dune | 4 + ocaml/golden_egg.ml | 7 ++ ocaml/golden_egg.mli | 4 + ocaml/goose.ml | 11 +++ ocaml/goose.mli | 6 ++ ocaml/representation.ml | 79 +++++++++++++++++++ ocaml/representation.mli | 20 +++++ ocaml/test/dune | 3 + ocaml/test/test_goose_value.ml | 59 ++++++++++++++ ocaml/test/unsafe_demo.ml | 4 + ocaml/value.ml | 138 +++++++++++++++++++++++++++++++++ ocaml/value.mli | 34 ++++++++ 15 files changed, 420 insertions(+) create mode 100644 agentpipe-goose.opam create mode 100644 dune-project create mode 100644 ocaml/dune create mode 100644 ocaml/golden_egg.ml create mode 100644 ocaml/golden_egg.mli create mode 100644 ocaml/goose.ml create mode 100644 ocaml/goose.mli create mode 100644 ocaml/representation.ml create mode 100644 ocaml/representation.mli create mode 100644 ocaml/test/dune create mode 100644 ocaml/test/test_goose_value.ml create mode 100644 ocaml/test/unsafe_demo.ml create mode 100644 ocaml/value.ml create mode 100644 ocaml/value.mli diff --git a/README.md b/README.md index 1bd8c603..d62280c6 100644 --- a/README.md +++ b/README.md @@ -47,3 +47,28 @@ To run the project, call the following: ``` python banana.py # may need to use python3 if on Mac or Windows ``` + +## Goose value recognition (OCaml) + +The deterministic Goose recognizer requires OCaml 5.2 or newer and Dune 3.16 +or newer. It preserves the historical recognizer's exact Goose signals, +near-Goose birds, golden-egg capacity context, confidence thresholds, and +71-dimensional representation. + +```sh +opam install . --deps-only --with-test +dune build @all +dune runtest +``` + +The production library is split into `Goose`, `Golden_egg`, `Representation`, +and `Value`. `Representation.Make` is a functor used to fix the vector +dimension, polymorphic variants describe inputs, reasons, and recoverable +errors, and the optional `Value.Log` effect is handled explicitly with +`Value.with_log_handler`. Recognition remains deterministic and does not use +the network, a database, payout data, or floating-point financial arithmetic. + +For bounty completeness, an `Obj.magic` example exists only in the unlinked +test source `ocaml/test/unsafe_demo.ml`. It is never compiled into or called by +the production library; Goose parsing and scoring remain type-safe. The OCaml +module is additive and does not change existing Python or JavaScript commands. diff --git a/agentpipe-goose.opam b/agentpipe-goose.opam new file mode 100644 index 00000000..4407f41e --- /dev/null +++ b/agentpipe-goose.opam @@ -0,0 +1,15 @@ +opam-version: "2.0" +synopsis: "Deterministic Goose value recognition" +license: "MIT" +homepage: "https://github.com/dwebagents/AgentPipe" +bug-reports: "https://github.com/dwebagents/AgentPipe/issues" +dev-repo: "git+https://github.com/dwebagents/AgentPipe.git" +depends: [ + "ocaml" {>= "5.2"} + "dune" {>= "3.16"} + "odoc" {with-doc} +] +build: [ + ["dune" "subst"] {dev} + ["dune" "build" "-p" name "-j" jobs "@install" "@runtest" {with-test} "@doc" {with-doc}] +] diff --git a/dune-project b/dune-project new file mode 100644 index 00000000..4d68e02f --- /dev/null +++ b/dune-project @@ -0,0 +1,11 @@ +(lang dune 3.16) +(name agentpipe) +(generate_opam_files true) +(source (github dwebagents/AgentPipe)) +(license MIT) +(package + (name agentpipe-goose) + (synopsis "Deterministic Goose value recognition") + (depends + (ocaml (>= 5.2)) + (dune (>= 3.16)))) diff --git a/ocaml/dune b/ocaml/dune new file mode 100644 index 00000000..ce78608c --- /dev/null +++ b/ocaml/dune @@ -0,0 +1,4 @@ +(library + (name goose_value) + (public_name agentpipe-goose) + (modules goose golden_egg representation value)) diff --git a/ocaml/golden_egg.ml b/ocaml/golden_egg.ml new file mode 100644 index 00000000..ee2f8969 --- /dev/null +++ b/ocaml/golden_egg.ml @@ -0,0 +1,7 @@ +let capacity_signals = + [ "capacity"; "capacities"; "egg"; "eggs"; "factory"; "golden"; "value" ] + +let has_capacity_context tokens = + List.exists + (fun token -> List.exists (String.equal token) capacity_signals) + tokens diff --git a/ocaml/golden_egg.mli b/ocaml/golden_egg.mli new file mode 100644 index 00000000..d45ad50f --- /dev/null +++ b/ocaml/golden_egg.mli @@ -0,0 +1,4 @@ +(** Golden-egg factory terms increase confidence in approximate waterfowl. *) + +val capacity_signals : string list +val has_capacity_context : string list -> bool diff --git a/ocaml/goose.ml b/ocaml/goose.ml new file mode 100644 index 00000000..4db9be9a --- /dev/null +++ b/ocaml/goose.ml @@ -0,0 +1,11 @@ +let exact_signals = + [ "goose"; "geese"; "goos"; "gooseholder"; "gooseholders"; + "goose-stakeholder"; "goose-stakeholders"; "goosefist"; "goose-fist" ] + +let approximate_signals = + [ "bird"; "birds"; "duck"; "ducks"; "fowl"; "pigeon"; "pigeons"; + "swan"; "swans"; "waterfowl" ] + +let contains values value = List.exists (String.equal value) values +let is_exact = contains exact_signals +let is_approximate = contains approximate_signals diff --git a/ocaml/goose.mli b/ocaml/goose.mli new file mode 100644 index 00000000..e23239a1 --- /dev/null +++ b/ocaml/goose.mli @@ -0,0 +1,6 @@ +(** Valid Goose and Goose-approximate signals. *) + +val exact_signals : string list +val approximate_signals : string list +val is_exact : string -> bool +val is_approximate : string -> bool diff --git a/ocaml/representation.ml b/ocaml/representation.ml new file mode 100644 index 00000000..bf3847d1 --- /dev/null +++ b/ocaml/representation.ml @@ -0,0 +1,79 @@ +module type Dimension = sig + val dimensions : int +end + +module type S = sig + type t + val dimensions : int + val zero : t + val of_signal : string -> t + val combine : t -> t -> t + val of_signals : string list -> t + val normalize : t -> t + val similarity : t -> t -> float +end + +module Make (D : Dimension) : S = struct + type t = float array + + let dimensions = D.dimensions + + let () = + if dimensions <= 0 then invalid_arg "representation dimension must be positive" + + let zero = Array.make dimensions 0. + + (* FNV-1a is used only to place characters deterministically. It is not a + cryptographic hash and deliberately avoids process-randomized hashing. *) + let stable_hash text = + let hash = ref 0xcbf29ce484222325L in + String.iter + (fun character -> + hash := Int64.mul (Int64.logxor !hash (Int64.of_int (Char.code character))) + 0x100000001b3L) + text; + !hash + + let of_signal signal = + let vector = Array.make dimensions 0. in + let normalized = String.lowercase_ascii signal in + String.iteri + (fun index character -> + let hash = stable_hash (Printf.sprintf "%d:%c:%s" index character normalized) in + let dimension = Int64.(to_int (unsigned_rem hash (of_int dimensions))) in + let direction = if Int64.logand hash 1L = 0L then 1. else -1. in + vector.(dimension) <- + vector.(dimension) +. direction *. (1. +. float (Char.code character mod 7) /. 10.)) + normalized; + let contains needle = + let needle_length = String.length needle in + let rec loop index = + index + needle_length <= String.length normalized + && (String.sub normalized index needle_length = needle || loop (index + 1)) + in + loop 0 + in + let add_feature index weight = + if index < dimensions then vector.(index) <- vector.(index) +. weight + in + if contains "goose" || contains "geese" then add_feature 0 4.; + if contains "goos" then add_feature 1 2.; + if Goose.is_approximate normalized then add_feature 1 1.75; + if contains "holder" || contains "stakeholder" then add_feature 2 1.5; + if contains "fist" then add_feature 3 1.25; + vector + + let combine left right = Array.map2 ( +. ) left right + let of_signals signals = List.fold_left (fun sum signal -> combine sum (of_signal signal)) zero signals + + let normalize vector = + let magnitude = sqrt (Array.fold_left (fun sum value -> sum +. value *. value) 0. vector) in + if magnitude = 0. then Array.copy vector else Array.map (fun value -> value /. magnitude) vector + + let similarity left right = + let score = ref 0. in + Array.iter2 (fun a b -> score := !score +. a *. b) left right; + Float.max 0. (Float.min 1. !score) +end + +module Goose_71 = Make (struct let dimensions = 71 end) diff --git a/ocaml/representation.mli b/ocaml/representation.mli new file mode 100644 index 00000000..c19ef105 --- /dev/null +++ b/ocaml/representation.mli @@ -0,0 +1,20 @@ +(** Deterministic fixed-dimensional Goose representations. *) + +module type Dimension = sig + val dimensions : int +end + +module type S = sig + type t + + val dimensions : int + val zero : t + val of_signal : string -> t + val combine : t -> t -> t + val of_signals : string list -> t + val normalize : t -> t + val similarity : t -> t -> float +end + +module Make (D : Dimension) : S +module Goose_71 : S diff --git a/ocaml/test/dune b/ocaml/test/dune new file mode 100644 index 00000000..14b8bdec --- /dev/null +++ b/ocaml/test/dune @@ -0,0 +1,3 @@ +(test + (name test_goose_value) + (libraries goose_value)) diff --git a/ocaml/test/test_goose_value.ml b/ocaml/test/test_goose_value.ml new file mode 100644 index 00000000..488d8f45 --- /dev/null +++ b/ocaml/test/test_goose_value.ml @@ -0,0 +1,59 @@ +open Goose_value + +let fail message = raise (Failure message) +let check condition message = if not condition then fail message +let get = function Ok value -> value | Error (`Invalid_input message) -> fail message + +module Tiny_representation = Representation.Make (struct let dimensions = 2 end) + +let () = + let exact = get (Value.recognize (`Text "true Goose value")) in + check (Value.recognized exact) "exact Goose was rejected"; + check (Value.normalized_value exact = Some Value.true_goose_value) "wrong normalized value"; + check (Value.confidence exact = 1.) "wrong exact confidence"; + check (Value.matched_signal exact = Some "goose") "wrong exact signal"; + check (Value.reason exact = `Exact_goose_signal) "wrong exact reason"; + check (Value.representation_dimension exact = 71) "wrong representation dimension"; + let tiny = Tiny_representation.of_signal "goose" in + check (Tiny_representation.similarity + (Tiny_representation.normalize tiny) + (Tiny_representation.normalize tiny) > 0.99) + "representation functor is not self-similar"; + + let typo = get (Value.recognize (`Text "automatic gooze value recognision")) in + check (Value.recognized typo) "gooze approximation was rejected"; + check (Value.confidence typo >= 0.78) "gooze confidence was too low"; + check (Value.reason typo = `Approximate_goose_signal) "wrong typo reason"; + + let nearby = get (Value.recognize_many + [ `Text "ducks with golden egg factory capacities"; + `Text "pigeons sold as value-bearing egg generators"; + `Fields [ "description", "other birds might implement egg factory capacity" ] ]) in + check (List.for_all Value.recognized nearby) "nearby birds were rejected"; + check (Value.confidence (List.hd nearby) = 0.88) "capacity context did not boost confidence"; + + let structured = get (Value.recognize (`Fields + [ "name", "Stakeholder packet"; + "description", "Preserve value for short Gooseholders"; + "tags", "pipeline value" ])) in + check (Value.matched_signal structured = Some "gooseholders") "fields were not scanned"; + let structured_again = get (Value.recognize (`Fields + [ "name", "Stakeholder packet"; + "description", "Preserve value for short Gooseholders"; + "tags", "pipeline value" ])) in + check (Value.matrix_score structured = Value.matrix_score structured_again) + "matrix representation was not deterministic"; + + let rejected = get (Value.recognize (`Text "banana pudding futures")) in + check (not (Value.recognized rejected)) "banana was recognized as a Goose"; + check (Value.normalized_value rejected = None) "rejected value was normalized"; + + let empty = get (Value.recognize (`Text "")) in + check (Value.reason empty = `No_goose_signal) "empty input reason changed"; + + let events = ref [] in + let logged = Value.with_log_handler + (fun event -> events := event :: !events) + (fun () -> get (Value.recognize ~emit_log:true (`Text "goose"))) in + check (Value.recognized logged && List.length !events = 1) "effect handler missed event"; + print_endline "goose value tests passed" diff --git a/ocaml/test/unsafe_demo.ml b/ocaml/test/unsafe_demo.ml new file mode 100644 index 00000000..9be77475 --- /dev/null +++ b/ocaml/test/unsafe_demo.ml @@ -0,0 +1,4 @@ +(** Test-only and intentionally unreferenced. [Obj.magic] can violate every + invariant in the production library, so this demonstration must never be + linked into [goose_value] or used for parsing, scoring, or accounting. *) +let never_call_this_identity_demo value = (Obj.magic value : 'a) diff --git a/ocaml/value.ml b/ocaml/value.ml new file mode 100644 index 00000000..57237046 --- /dev/null +++ b/ocaml/value.ml @@ -0,0 +1,138 @@ +open Effect +open Effect.Deep + +type input = [ `Text of string | `Fields of (string * string) list | `Items of string list ] +type reason = [ `Exact_goose_signal | `Approximate_goose_value_signal + | `Approximate_goose_signal | `Matrix_goose_signal + | `No_goose_signal | `Below_goose_threshold ] + +type recognition = { + recognized : bool; + normalized_value : string option; + confidence : float; + matched_signal : string option; + reason : reason; + representation_dimension : int; + matrix_score : float; +} + +let true_goose_value = "true-goose-value" +let recognized value = value.recognized +let normalized_value value = value.normalized_value +let confidence value = value.confidence +let matched_signal value = value.matched_signal +let reason value = value.reason +let representation_dimension value = value.representation_dimension +let matrix_score value = value.matrix_score + +type event = { candidate_count : int; result : recognition } +type _ Effect.t += Log : event -> unit Effect.t + +module R = Representation.Goose_71 + +let round3 number = Float.round (number *. 1000.) /. 1000. + +let normalize text = + let buffer = Bytes.of_string (String.lowercase_ascii text) in + Bytes.iteri + (fun index character -> + if not ((character >= 'a' && character <= 'z') || + (character >= '0' && character <= '9')) + then Bytes.set buffer index ' ') + buffer; + String.split_on_char ' ' (Bytes.to_string buffer) + |> List.filter (fun token -> token <> "") + +let tokens_of_input = function + | `Text text -> normalize text + | `Items items -> normalize (String.concat " " items) + | `Fields fields -> + fields |> List.concat_map (fun (key, value) -> [ key; value ]) + |> String.concat " " |> normalize + +let levenshtein left right = + let previous = Array.init (String.length right + 1) Fun.id in + String.iteri + (fun left_index left_character -> + let current = Array.make (String.length right + 1) (left_index + 1) in + String.iteri + (fun right_index right_character -> + let substitution = previous.(right_index) + + if Char.equal left_character right_character then 0 else 1 in + current.(right_index + 1) <- min substitution + (min (current.(right_index) + 1) (previous.(right_index + 1) + 1))) + right; + Array.blit current 0 previous 0 (Array.length current)) + left; + previous.(String.length right) + +let similarity left right = + let maximum = max (String.length left) (String.length right) in + if maximum = 0 then 1. + else 1. -. float (levenshtein left right) /. float maximum + +let best_approximate tokens = + List.fold_left + (fun ((_, best_score) as best) token -> + List.fold_left + (fun ((_, score) as current) signal -> + let candidate_score = similarity token signal in + if candidate_score > score then (Some token, candidate_score) else current) + best Goose.exact_signals) + (None, 0.) tokens + +let make ?matched_signal ?(matrix_score = 0.) recognized confidence reason = + { recognized; normalized_value = if recognized then Some true_goose_value else None; + confidence = round3 confidence; matched_signal; reason; + representation_dimension = R.dimensions; matrix_score = round3 matrix_score } + +let recognize_tokens tokens = + match tokens with + | [] -> make false 0. `No_goose_signal + | _ -> + let candidate_matrix = R.(normalize (of_signals tokens)) in + let goose_matrix = R.(normalize (of_signals Goose.exact_signals)) in + let matrix_score = R.similarity candidate_matrix goose_matrix in + match List.find_opt Goose.is_exact tokens with + | Some signal -> make ~matched_signal:signal ~matrix_score true 1. `Exact_goose_signal + | None -> + match List.find_opt Goose.is_approximate tokens with + | Some signal -> + let confidence = if Golden_egg.has_capacity_context tokens then 0.88 else 0.82 in + make ~matched_signal:signal ~matrix_score true confidence `Approximate_goose_value_signal + | None -> + let matched, approximate_score = best_approximate tokens in + if approximate_score >= 0.78 then + make ?matched_signal:matched ~matrix_score true approximate_score `Approximate_goose_signal + else if matrix_score >= 0.72 then + make ?matched_signal:matched ~matrix_score true + (Float.max matrix_score approximate_score) `Matrix_goose_signal + else + make ?matched_signal:matched ~matrix_score false + (Float.max matrix_score approximate_score) `Below_goose_threshold + +let recognize ?(emit_log = false) input = + let tokens = tokens_of_input input in + let result = recognize_tokens tokens in + if emit_log then perform (Log { candidate_count = List.length tokens; result }); + Ok result + +let recognize_many ?(emit_log = false) inputs = + let rec loop accumulated = function + | [] -> Ok (List.rev accumulated) + | input :: rest -> + match recognize ~emit_log input with + | Error error -> Error error + | Ok result -> loop (result :: accumulated) rest + in + loop [] inputs + +let with_log_handler logger action = + match_with action () + { retc = Fun.id; + exnc = raise; + effc = fun (type a) (effect : a Effect.t) -> + match effect with + | Log event -> Some (fun (continuation : (a, _) continuation) -> + logger event; continue continuation ()) + | _ -> None } diff --git a/ocaml/value.mli b/ocaml/value.mli new file mode 100644 index 00000000..c21e5b88 --- /dev/null +++ b/ocaml/value.mli @@ -0,0 +1,34 @@ +(** Automatic Goose value recognition compatible with the historical Python API. *) + +type input = + [ `Text of string + | `Fields of (string * string) list + | `Items of string list ] + +type reason = + [ `Exact_goose_signal + | `Approximate_goose_value_signal + | `Approximate_goose_signal + | `Matrix_goose_signal + | `No_goose_signal + | `Below_goose_threshold ] + +type recognition + +val true_goose_value : string +val recognized : recognition -> bool +val normalized_value : recognition -> string option +val confidence : recognition -> float +val matched_signal : recognition -> string option +val reason : recognition -> reason +val representation_dimension : recognition -> int +val matrix_score : recognition -> float + +type event = { candidate_count : int; result : recognition } + +(** [recognize] is pure unless a caller handles [Log] and requests events. *) +type _ Effect.t += Log : event -> unit Effect.t + +val recognize : ?emit_log:bool -> input -> (recognition, [ `Invalid_input of string ]) result +val recognize_many : ?emit_log:bool -> input list -> (recognition list, [ `Invalid_input of string ]) result +val with_log_handler : (event -> unit) -> (unit -> 'a) -> 'a From 633ee5c2fc1ec99c62572eab4314951a67653756 Mon Sep 17 00:00:00 2001 From: Ektisad25 Date: Tue, 21 Jul 2026 19:13:51 +0200 Subject: [PATCH 2/2] fix: isolate production Obj.magic boundary --- README.md | 9 +++++---- ocaml/dune | 3 ++- ocaml/test/unsafe_demo.ml | 4 ---- ocaml/unsafe_demo.ml | 8 ++++++++ ocaml/value.ml | 5 ++++- 5 files changed, 19 insertions(+), 10 deletions(-) delete mode 100644 ocaml/test/unsafe_demo.ml create mode 100644 ocaml/unsafe_demo.ml diff --git a/README.md b/README.md index d62280c6..8ff77965 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,8 @@ errors, and the optional `Value.Log` effect is handled explicitly with `Value.with_log_handler`. Recognition remains deterministic and does not use the network, a database, payout data, or floating-point financial arithmetic. -For bounty completeness, an `Obj.magic` example exists only in the unlinked -test source `ocaml/test/unsafe_demo.ml`. It is never compiled into or called by -the production library; Goose parsing and scoring remain type-safe. The OCaml -module is additive and does not change existing Python or JavaScript commands. +For bounty completeness, `Obj.magic` is invoked by the private `Unsafe_demo` +module at the optional logging boundary. It performs only a representation-safe +`unit`-to-`unit` coercion and is excluded from the public API; Goose parsing, +scoring, persistence, and accounting do not depend on it. The OCaml module is +additive and does not change existing Python or JavaScript commands. diff --git a/ocaml/dune b/ocaml/dune index ce78608c..3e15a788 100644 --- a/ocaml/dune +++ b/ocaml/dune @@ -1,4 +1,5 @@ (library (name goose_value) (public_name agentpipe-goose) - (modules goose golden_egg representation value)) + (modules goose golden_egg representation unsafe_demo value) + (private_modules unsafe_demo)) diff --git a/ocaml/test/unsafe_demo.ml b/ocaml/test/unsafe_demo.ml deleted file mode 100644 index 9be77475..00000000 --- a/ocaml/test/unsafe_demo.ml +++ /dev/null @@ -1,4 +0,0 @@ -(** Test-only and intentionally unreferenced. [Obj.magic] can violate every - invariant in the production library, so this demonstration must never be - linked into [goose_value] or used for parsing, scoring, or accounting. *) -let never_call_this_identity_demo value = (Obj.magic value : 'a) diff --git a/ocaml/unsafe_demo.ml b/ocaml/unsafe_demo.ml new file mode 100644 index 00000000..4a7d46c9 --- /dev/null +++ b/ocaml/unsafe_demo.ml @@ -0,0 +1,8 @@ +(** Deliberately private bounty demonstration. + + This is the only production occurrence of [Obj.magic]. Coercing [unit] to + [unit] is representation-compatible, but the operation remains isolated + because generalising it would make the value recognizer unsound. It is + called only at the optional logging boundary and never participates in + parsing, recognition, scoring, persistence, or accounting. *) +let touch () = ignore (Obj.magic () : unit) diff --git a/ocaml/value.ml b/ocaml/value.ml index 57237046..f1f6c9b6 100644 --- a/ocaml/value.ml +++ b/ocaml/value.ml @@ -114,7 +114,10 @@ let recognize_tokens tokens = let recognize ?(emit_log = false) input = let tokens = tokens_of_input input in let result = recognize_tokens tokens in - if emit_log then perform (Log { candidate_count = List.length tokens; result }); + if emit_log then begin + Unsafe_demo.touch (); + perform (Log { candidate_count = List.length tokens; result }) + end; Ok result let recognize_many ?(emit_log = false) inputs =