Formal specification of the Benoit Communication Protocol, Intent Engine, and Contract System.
Named after Benoit Fragniere, who loved science.
The Benoit Protocol is a behavioral communication system for software agents. Instead of transmitting source code, agents exchange mathematical properties, input/output assertions, and algebraic relationships. A receiving agent reconstructs working implementations from these behavioral specifications alone.
The protocol operates across three layers:
- Communication Layer (
protocol.mjs): Encode/decode cycle for transmitting function modules as behavioral descriptions. No source code crosses the wire. - Instruction Layer (
intent.mjs): Behavioral specification of computational intent through examples and property constraints, with synthesis, composition, and negotiation. - Contract Layer (
contract.mjs): Marketplace for behavioral requirements (needs) and implementations (offers), with verification, ranking, and binding.
The protocol version identifier is:
"benoit-protocol-v1"
The encode() function produces a protocol message with the following schema:
{
"protocol": "benoit-protocol-v1",
"functions": [
{
"name": "<string>",
"arity": "<number>",
"assertions": [
{ "input": "<string: e.g. 'add(2, 3)'>", "output": "<string: e.g. '5'>" }
],
"properties": ["<string>"]
}
],
"algebra": {
"equivalenceClasses": [["<string: function name>"]],
"inversePairs": [
{ "f": "<string: function name>", "g": "<string: function name>" }
],
"surprises": [
{ "f": "<string>", "g": "<string>", "type": "<string: composition property type>" }
]
},
"meta": {
"sourceSize": "<number: character count of original source>",
"functionCount": "<number>",
"propertyCount": "<number: total properties across all functions>",
"surpriseCount": "<number>"
}
}Field definitions:
| Field | Type | Description |
|---|---|---|
protocol |
string | Protocol version identifier. Must be "benoit-protocol-v1". |
functions |
array | One entry per function in the source module. |
functions[].name |
string | Function name as declared in source. |
functions[].arity |
number | Number of parameters (0, 1, 2, or 3). |
functions[].assertions |
array | Input/output pairs extracted from inline test assertions. The input field contains the full call expression (e.g. "add(2, 3)"), the output field contains the expected result as a string (e.g. "5"). |
functions[].properties |
string[] | Array of property type identifiers discovered by infer(). See Section 3. |
algebra.equivalenceClasses |
string[][] | Groups of function names that are behaviorally equivalent (same output for the same inputs across the sample space). Only groups with more than one member are included. |
algebra.inversePairs |
object[] | Pairs of unary functions f and g where f(g(x)) = x and g(f(x)) = x for all sampled x. |
algebra.surprises |
object[] | Composition properties that are not derivable from the shared DERIVATION_RULES (Section 5). These must be transmitted explicitly because the receiver cannot reconstruct them from individual function properties alone. |
meta |
object | Message metadata for diagnostics. |
Critical design property: The functions[].properties array contains only property type identifiers (strings), not the evidence. The receiver independently verifies all claimed properties against synthesized implementations.
The encodeIntent() function produces an intent object with the following schema:
{
"type": "intent",
"examples": [
{ "input": "<any>", "output": "<any>" }
],
"properties": ["<string>"],
"constraints": {
"domain": "<string: 'number' | 'array' | 'string' | 'any'>",
"range": "<string: 'number' | 'array' | 'string' | 'any'>"
},
"meta": {
"confidence": "<number | null>",
"synthesized": "<string | null>"
}
}After resolution via resolveIntent(), the object is augmented:
{
"type": "intent",
"examples": [...],
"properties": [...],
"constraints": {...},
"fn": "<Function | null>",
"meta": {
"confidence": "<number: [0, 1]>",
"synthesized": "<string: formula representation>",
"status": "<string: 'resolved' | 'unsolved' | 'composed'>",
"propertiesVerified": {
"satisfied": ["<string>"],
"violated": ["<string>"]
}
}
}After composition via composeIntents():
{
"type": "intent",
"examples": [...],
"properties": [...],
"constraints": {
"domain": "<domain of first intent>",
"range": "<range of second intent>"
},
"fn": "<Function: composed pipeline>",
"meta": {
"confidence": "<number: min(a, b) * 0.95>",
"synthesized": "(<formula_A>) |> (<formula_B>)",
"status": "composed",
"components": ["<formula_A>", "<formula_B>"]
}
}Field definitions:
| Field | Type | Description |
|---|---|---|
type |
string | Always "intent". |
examples |
array | Input/output pairs. Inputs and outputs may be numbers, strings, or arrays. |
properties |
string[] | Behavioral constraints the synthesized function must satisfy. |
constraints.domain |
string | Inferred or specified input type. One of "number", "array", "string", or "any". |
constraints.range |
string | Inferred or specified output type. Same values as domain. |
fn |
Function | The synthesized callable function (only present after resolution). |
meta.confidence |
number | Confidence score in [0, 1]. null before resolution. |
meta.synthesized |
string | Human-readable formula of the synthesized function. |
meta.status |
string | One of "resolved", "unsolved", or "composed". |
meta.propertiesVerified |
object | Lists of satisfied and violated property names. |
Produced by publishNeed():
{
"id": "need-<uuid8>",
"name": "<string>",
"examples": [
{ "input": "<any>", "output": "<any>" }
],
"properties": ["<string>"],
"domain": "<string>",
"range": "<string>",
"createdAt": "<number: Unix timestamp ms>",
"verify": "<Function: (fn) => VerificationReport>"
}Produced by publishOffer():
{
"id": "offer-<uuid8>",
"needId": "<string: need ID>",
"fn": "<Function>",
"source": "<string | null: optional Benoit source>",
"confidence": "<number: [0, 1], default 0.5>",
"createdAt": "<number: Unix timestamp ms>",
"verification": "<VerificationReport | undefined>"
}Produced by bind():
{
"id": "contract-<uuid8>",
"needId": "<string>",
"offerId": "<string>",
"name": "<string>",
"examples": [{ "input": "<any>", "output": "<any>" }],
"properties": ["<string>"],
"domain": "<string>",
"range": "<string>",
"boundAt": "<number: Unix timestamp ms>",
"verification": "<VerificationReport>",
"fn": "<Function>"
}Returned by verify(), need.verify(), and embedded in offers and contracts:
{
"pass": "<boolean>",
"exampleResults": [
{
"input": "<any>",
"expected": "<any>",
"actual": "<any>",
"pass": "<boolean>",
"error": "<string | undefined>"
}
],
"propertyResults": {
"satisfied": ["<string>"],
"violated": ["<string>"]
},
"summary": {
"examplesPassed": "<number>",
"examplesTotal": "<number>",
"propertiesSatisfied": "<number>",
"propertiesViolated": "<number>"
}
}Produced by negotiate(). Each entry is an offer augmented with scoring:
{
"...offer fields",
"verification": "<VerificationReport>",
"score": "<number>",
"rank": "<number: 1-indexed>"
}Scoring formula:
score = (examplesPassed * 10) - (examplesFailed * 20)
+ (propertiesSatisfied * 5) - (propertiesViolated * 10)
+ (offer.confidence * 5)
Offers are ranked descending by score.
The inference engine (infer.mjs) discovers the following properties by probing function behavior over a sample space.
| Property | Formal Definition | Sample Space | Confidence |
|---|---|---|---|
identity |
f(x) = x for all x in S | S = {-10, -3, -2, -1, 0, 1, 2, 3, 5, 10, 42, 100} | 1.0 |
idempotent |
f(f(x)) = f(x) for all x in S, and f is not the identity | S (as above) | 0.95 |
involution |
f(f(x)) = x for all x in S, and f is not the identity | S (as above) | 0.95 |
even_function |
f(-x) = f(x) for all x in S, with at least one x != 0 | S (as above) | 0.95 |
odd_function |
f(-x) = -f(x) for all x in S, with at least one x where f(x) != 0 | S (as above) | 0.95 |
monotonic_increasing |
x1 < x2 implies f(x1) <= f(x2) for all consecutive pairs in sorted S | S (as above), | S |
monotonic_decreasing |
x1 < x2 implies f(x1) >= f(x2) for all consecutive pairs in sorted S, and not also increasing | S (as above), | S |
non_negative |
f(x) >= 0 for all x in S, with at least one x < 0 in S | S (as above) | 0.9 |
fixed_points |
There exist values x in S where f(x) = x, but not all values satisfy this | S (as above) | 1.0 |
| Property | Formal Definition | Sample Space | Confidence |
|---|---|---|---|
commutative |
f(a, b) = f(b, a) for all (a, b) in S x S | S x S, | S |
associative |
f(f(a, b), c) = f(a, f(b, c)) for all (a, b, c) in T | T = {(-1,0,1), (1,2,3), (2,3,5), (0,5,10)} | 0.9 |
right_identity |
f(a, e) = a for all a in S, where e in {0, 1} | S where b = e, | filtered |
left_identity |
f(e, b) = b for all b in S, where e in {0, 1} | S where a = e, | filtered |
absorbing_element |
f(a, 0) = 0 and f(0, b) = 0 for all a, b in S | S where one arg = 0, | filtered |
| Property | Formal Definition | Sample Space | Confidence |
|---|---|---|---|
bounded |
b <= f(x, b, c) <= c for all (x, b, c) in T | T = cross({-5,0,5,10,50,100,200}, {0,10}, {50,100}) | 0.95 |
passthrough_in_bounds |
If b <= x <= c, then f(x, b, c) = x | T (as above), filtered to in-bounds | 0.95 |
The intent and contract systems verify additional properties:
| Property | Definition |
|---|---|
idempotent |
f(f(x)) = f(x) for all provided examples (uses JSON deep equality) |
length_preserving |
For arrays: len(f(x)) = len(x). For strings: len(f(x)) = len(x). |
commutative |
For 2-element array inputs: f([a,b]) = f([b,a]) |
monotonic_increasing |
Sorted numeric inputs produce non-decreasing outputs |
non_negative |
f(x) >= 0 for all examples |
deterministic |
f(x) = f(x) on repeated calls (always true for synthesized functions) |
pure |
Stateless: f(x) produces identical output on repeated invocations |
The solver (solve.mjs) synthesizes Benoit source code from input/output assertion pairs. It operates as a pattern recognizer, not a general solver.
Listed in order of evaluation:
| Pattern | Formula | Confidence | Detection Criterion |
|---|---|---|---|
| Identity | x |
1.0 | All pairs satisfy output = input |
| Constant | c |
0.9 | All outputs identical |
| Linear | a * x + b |
0.95 | Two-point interpolation verified against all pairs (tolerance < 0.001) |
| Quadratic | a * x * x + b * x + c |
0.85 | Three-point system of equations, verified against all pairs |
| Cubic | x * x * x |
0.9 | All pairs satisfy x^3 = output |
| ReLU | Math.max(0, x) |
0.95 | All pairs satisfy max(0, x) = output |
| Floor | Math.floor(x) |
0.9 | All pairs match floor function |
| Sign | Math.sign(x) |
0.9 | All pairs match sign function |
| Absolute value | Math.abs(x) |
0.95 | All pairs match absolute value |
| Factorial | match n -> | 0 => 1 | _ => n * f(n - 1) |
0.9 | f(0) = 1 and factorial recurrence holds. Recursive. |
| Fibonacci | match n -> | 0 => 0 | 1 => 1 | _ => f(n-1) + f(n-2) |
0.95 | f(0) = 0 and Fibonacci recurrence holds. Recursive. |
| Exponential | Math.pow(base, x) for base in {2, 3, e, 10} |
0.9 | All pairs match within tolerance 0.1 |
| Logarithm | Math.log(x), Math.log2(x), Math.log10(x) |
0.9 | Positive-input pairs match within tolerance 0.1 |
| Trigonometric | Math.sin(x), Math.cos(x) |
0.85 | All pairs match within tolerance 0.01 |
| Square root | Math.sqrt(x) |
0.9 | Positive-input pairs match within tolerance 0.01 |
| Round / Ceil | Math.round(x), Math.ceil(x) |
0.9 | Exact match |
| Pattern | Formula | Confidence |
|---|---|---|
| Addition | a + b |
1.0 |
| Multiplication | a * b |
1.0 |
| Subtraction | a - b |
1.0 |
| Maximum | Math.max(a, b) |
0.9 |
| Minimum | Math.min(a, b) |
0.9 |
| Modulo | a % b |
0.9 |
| Power | Math.pow(a, b) |
0.85 |
| Integer division | Math.floor(a / b) |
0.85 |
| GCD (recursive) | match b -> | 0 => a | _ => f(b, a % b) |
0.9 |
| LCM | Math.abs(a * b) / gcd(a, b) |
0.85 |
| Average | (a + b) / 2 |
0.9 |
| Hypotenuse | Math.sqrt(a * a + b * b) |
0.85 |
| Pattern | Formula | Confidence |
|---|---|---|
| Clamp | Math.max(min, Math.min(max, x)) |
0.95 |
When no direct hypothesis matches for a unary function, the solver attempts conditional synthesis by splitting data points according to conditions:
Tested conditions:
x % 2 == 0(even/odd split)x > 0(positive/negative split)x < 0x >= 0
Each branch is independently fitted with a linear function (a * x + b) or a division pattern (x / k for k in {2, 3, 4, 5, 10}). Both branches must have at least 2 data points.
Output format:
condition? -> trueBranch
else? -> falseBranch
For non-numeric assertions, the solver attempts:
- Template patterns: Find consistent prefix/suffix around the input string
- Case transformations:
toUpperCase(),toLowerCase() - Reversal:
split("").reverse().join("") - Length: output = string length
When properties are provided alongside assertions, hypotheses are validated against them. Each property contributes to a score:
| Property | Match bonus | Mismatch penalty |
|---|---|---|
idempotent |
+2 | -3 |
involution |
+2 | -3 |
identity |
+2 | -3 |
even_function |
+1 | -2 |
odd_function |
+1 | -2 |
non_negative |
+1 | -2 |
commutative |
+1 | -2 |
associative |
+1 | -2 |
monotonic_increasing |
+1 | -1 |
monotonic_decreasing |
+1 | -1 |
fixed_points |
0 | 0 |
Hypotheses are sorted by property score first, then by confidence.
The intent engine (intent.mjs) supplements the numeric solver with hypothesis templates for non-numeric domains:
Array operations:
sort:[...x].sort((a, b) => a - b)(confidence: 0.95)filter_even:x.filter(n => n % 2 === 0)(confidence: 0.9)filter_odd:x.filter(n => n % 2 !== 0)(confidence: 0.9)filter_positive:x.filter(n => n > 0)(confidence: 0.9)map_double:x.map(n => n * 2)(confidence: 0.9)map_square:x.map(n => n * n)(confidence: 0.9)map_negate:x.map(n => -n)(confidence: 0.9)reduce_sum:x.reduce((a, b) => a + b, 0)(confidence: 0.95)reduce_product:x.reduce((a, b) => a * b, 1)(confidence: 0.9)reduce_min:Math.min(...x)(confidence: 0.9)reduce_max:Math.max(...x)(confidence: 0.9)reduce_length:x.length(confidence: 0.9)array_reverse:[...x].reverse()(confidence: 0.9)
String operations:
string_upper:x.toUpperCase()(confidence: 0.95)string_lower:x.toLowerCase()(confidence: 0.95)string_reverse:x.split("").reverse().join("")(confidence: 0.85)string_trim:x.trim()(confidence: 0.9)string_length:x.length(confidence: 0.9)
Generic linear map fallback:
When no template matches, the intent engine attempts to fit an element-wise linear map: output[i] = a * input[i] + b across all array examples.
The DERIVATION_RULES in protocol.mjs define the shared "grammar" of function algebra. Both sender and receiver know these rules, so properties derivable from them need not be transmitted.
| Rule ID | Predicts | Condition (on properties of f, g for f . g) |
|---|---|---|
even_odd_even |
even_composition |
f is even_function AND g is odd_function |
even_even_even |
even_composition |
f is even_function AND g is even_function |
any_even_even |
even_composition |
g is even_function |
nonneg_any_nonneg |
non_negative_composition |
f is non_negative |
even_invol_absorb |
absorption |
f is even_function AND g is involution |
any_id_absorb |
absorption |
g is identity |
id_any_transparent |
f_transparent |
f is identity |
invol_self_identity |
composition_identity |
f and g are the same function AND f is involution |
nonneg_absorb_nonneg |
non_negative_composition |
g is non_negative |
even_idempotent_absorb |
absorption |
f is even_function AND g is idempotent AND g is even_function |
nonneg_transparent |
f_transparent |
f is non_negative AND g is non_negative AND f is idempotent |
| Property | Formal Definition | Verification |
|---|---|---|
even_composition |
(f . g)(-x) = (f . g)(x) for all x | Tested over positive samples {1, 3, 5}, verifying f(g(x)) = f(g(-x)) |
non_negative_composition |
(f . g)(x) >= 0 for all x | Tested over samples {-3, -1, 0, 1, 3, 5} |
absorption |
f(g(x)) = f(x) for all x | Verified: for all sample x, f(g(x)) = f(x) |
f_transparent |
f(g(x)) = g(x) for all x | Verified: for all sample x, f(g(x)) = g(x) |
composition_identity |
f(g(x)) = x for all x | Verified: for all sample x, f(g(x)) = x |
During encoding, every composition pair (f, g) where both are unary and f != g is analyzed. For each discovered composition property, the encoder checks whether any derivation rule predicts it. If no rule matches, the property is recorded as a surprise and transmitted in algebra.surprises.
This is a form of differential compression: only transmit what cannot be reconstructed from shared knowledge.
The full encode-decode-verify cycle proceeds as follows:
encode(source) -> message
- Parse source into AST, extract fingerprint (function names, arities, assertions).
- For each function definition, run
infer()to discover properties. - Discover equivalence classes via behavioral hashing.
- Discover inverse pairs among unary functions.
- Analyze all unary composition pairs; record non-derivable surprises.
- Emit protocol message (JSON).
The JSON message is transmitted. It contains:
- Function names, arities, and assertions
- Discovered properties (type identifiers only)
- Algebraic structure (equivalences, inverses, surprises)
- No source code
decode(message) -> result
- Validate protocol version.
- Pass
{ functions }tosynthesize(). - For each synthesized function, transpile the Benoit code to JavaScript and instantiate a callable function.
For each function and each assertion:
- Evaluate the assertion's input expression using all synthesized functions.
- Compare the result to the expected output.
- Record pass/fail.
For each function and each claimed property:
- Invoke
verifyProperty(fn, propType, arity)using the sample space[-10, -5, -3, -1, 0, 1, 3, 5, 10, 42]. - Record pass/fail.
- Derivation rules: For every pair of unary functions, check if any DERIVATION_RULE fires based on their properties. If so, verify the predicted composition property against the synthesized functions.
- Surprises: For each surprise in the message, verify the claimed composition property.
Return verification totals:
{
"assertions": { "passed": N, "total": M },
"properties": { "passed": N, "total": M },
"compositions": { "reconstructed": N, "verified": M }
}An agent publishes a behavioral requirement:
publishNeed({
name: "sorter",
examples: [{ input: [3,1,2], output: [1,2,3] }],
properties: ["idempotent", "length_preserving"],
domain: "array",
range: "array"
})Returns a need object with a unique ID and an attached verify() function.
Another agent offers an implementation:
publishOffer(needId, { fn: mySort, confidence: 0.9 }, need)If the need object is provided, the offer is immediately verified against all examples and properties.
When multiple offers exist for a need:
negotiate(need, [offer1, offer2, offer3])Each offer is scored according to the formula in Section 2.3.5 and ranked descending.
The best offer (or a manually chosen one) is locked into a contract:
bind(need, bestOffer)The contract records the examples as the permanent behavioral interface. Any future implementation must pass these examples.
When an implementation is updated:
verify(contract, newImplementation)Returns a compatibility report indicating whether the new implementation satisfies all contracted examples and properties.
The Registry class provides a centralized marketplace:
publishNeed(spec)-- register a needpublishOffer(needId, impl)-- offer an implementationsearch(properties)-- find needs by property overlapsearchByName(name)-- find needs by substring matchresolve(needId)-- auto-select best offer and bindverify(contractId, newImpl)-- check backward compatibilitygetNeeds(),getOffers(needId),getContracts()-- introspection
encodeIntent(examples, properties, constraints)Creates a behavioral specification. Domain and range are inferred from the first example if not provided:
- Array input ->
"array" - String input ->
"string" - Otherwise ->
"number"
resolveIntent(intent)Synthesis proceeds in order:
- Numeric solver (
solve.mjs): Wraps examples into the assertion format and delegates tosynthesize(). Only applies when all inputs and outputs are numeric. - Hypothesis templates: Tests each built-in template (Section 4.7) against all examples.
- Generic linear map: Fits
output[i] = a * input[i] + bfor array-of-numbers examples.
Candidates are ranked by:
- Property compliance score:
satisfied.length - violated.length * 2 - Confidence (descending)
If the best candidate violates required properties and is the only candidate, its confidence is halved as a penalty.
executeIntent(intent, newInput)Resolves the intent if not already resolved, then applies the synthesized function to the new input. Throws if synthesis failed.
composeIntents(intentA, intentB)Creates a pipeline: output = B(A(input)). Composed examples are derived from A's original inputs. Confidence degrades: min(confA, confB) * 0.95.
negotiateIntent(intent, counterExamples)Adds counter-examples to the intent. If a counter-example's input matches an existing example, it overrides the output (correction). The merged example set is re-encoded and re-resolved.
This implements the "I meant THIS, not THAT" feedback loop.
All composition verifiers (verifyComposition in protocol.mjs) enforce a magnitude cap of 30 on intermediate values. If |g(x)| > 30, the sample is skipped rather than propagated to f. This prevents exponential blowup when composing functions like exponentials or recursive functions.
if Math.abs(g(x)) > 30 then SKIP (return true, do not count as failure)
| Context | Sample Space | Size |
|---|---|---|
| Property inference (unary) | {-10, -3, -2, -1, 0, 1, 2, 3, 5, 10, 42, 100} | 12 |
| Property inference (binary) | S x S | 144 |
| Property inference (ternary) | {-5,0,5,10,50,100,200} x {0,10} x {50,100} | 28 |
| Protocol verification | {-10, -5, -3, -1, 0, 1, 3, 5, 10, 42} | 10 |
| Composition verification | {-3, -1, 0, 1, 3, 5} | 6 |
| Cross-module analysis | {-10, -3, -2, -1, 0, 1, 2, 3, 5, 10, 42} | 11 |
| Solver unknown search | [-1000, 1000] integers | 2001 |
All function evaluations are wrapped in try/catch to prevent runtime errors from propagating. The safe(fn, ...args) pattern is used throughout:
function safe(fn, ...args) {
try { return { ok: true, value: fn(...args) }; }
catch { return { ok: false }; }
}Failed evaluations are silently skipped, never counted as property violations.
The contract and intent systems use JSON.stringify(a) === JSON.stringify(b) for deep equality. This is exact for JSON-serializable values but does not handle:
undefinedvalues in arrays- Circular references
- Functions as values
-0vs+0NaNcomparisons
- Composition reduces confidence:
min(a, b) * 0.95 - Property violations halve confidence:
confidence *= 0.5 - Conditional synthesis caps at 0.85
- String pattern matching caps at 0.85-0.95
The composeModules() function (compose.mjs) merges multiple Benoit source modules into a unified algebra.
If two modules define functions with the same name, the later module's function is renamed to <name>_m<moduleIndex>.
Three types of cross-module relationships are discovered (only between functions from different modules, unary only):
- Equivalences: Functions with identical behavior hashes (same output for all 11 sample values).
- Inverse pairs: Functions f, g where f(g(x)) = x AND g(f(x)) = x for all 11 samples.
- Composition properties: For each cross-module pair (f, g), the composed function f . g is tested for:
composition_identity: f(g(x)) = x for all samplesabsorption: f(g(x)) = f(x) for all samples (and not identity)even_composition: f(g(-x)) = f(g(x)) for all samples with x != 0non_negative_composition: f(g(x)) >= 0 for all samples, with at least one x < 0
The diff(messageA, messageB) function compares two protocol messages:
- New functions (in B but not A)
- Changed functions (same name, different assertions or properties)
- Removed functions (in A but not B)
isCompatible: true if no functions were removed
The solve(assertion, knownFunctions) function resolves unknowns marked with ? in assertions:
add(?, 3) == 5 --> ? = 2
Algorithm: Brute-force search over integers in [-1000, 1000]. For each candidate value, substitute it into the assertion and check if the result matches.
Returns:
{
"assertion": "<original string>",
"unknown": "arg[<index>]",
"solutions": [<number>],
"unique": "<boolean: true if exactly one solution>"
}The query.mjs module treats questions as behavioral specifications with holes.
ask(known, holes, properties?): Creates a query from known examples + input holesanswer(query): Fills holes using synthesischallenge(answeredQuery, corrections): Overrides wrong answers with new evidencecurious(known, options?): Detects gaps in knowledge (extrapolation, edge cases)
quality(examples) measures question quality on 5 axes:
| Axis | Weight | Measure |
|---|---|---|
| Quantity | 0.15 | 5+ examples = max score |
| Resolvability | 0.25 | Can the solver synthesize? |
| Consistency | 0.25 | Does synthesis match ALL examples? |
| Diversity | 0.15 | Sign coverage, input spread, uniqueness |
| Ambiguity | 0.20 | How many alternative functions fit? |
Verdicts: excellent (>= 0.85), good (>= 0.7), adequate (>= 0.5), weak (>= 0.3), insufficient (< 0.3).
reformulate(examples, opts?) auto-improves a bad question:
- Diagnose with
quality() - If low diversity: probe boundaries (0, -1, 1, 10, -10, 100) using current best-fit
- If too few examples: bootstrap from best-fit across input range
- If high ambiguity: test at extreme values where interpretations diverge
- If unsolvable: try structural transforms (e.g., sequential -> recurrence)
Returns { original, reformulated, rounds, improved, history }.
The Dialogue class implements agent-to-agent negotiation:
teach(examples) → add knowledge
ask(holes) → fill holes from accumulated knowledge
negotiate() → "I'm confused here, confirm these inputs"
fulfill(neg, data) → sender answers probes
shouldNegotiate() → cost gate: is a ping-pong worth it?
correct(examples) → override wrong understanding
wonder() → detect gaps in knowledge
understanding() → measure current accuracy
Negotiation flow:
Sender → teach(examples) → Receiver synthesizes
Receiver → shouldNegotiate() → true (few examples or confusion)
Receiver → negotiate() → returns probes + confusion points
Sender → fulfill(neg, answers) → provides clarifications
Receiver → shouldNegotiate() → false (confident)
Receiver → ask(holes) → correct answers
shouldNegotiate rules:
- 0 examples →
critical - < 3 examples →
medium - Model doesn't explain all examples →
high - < 5 examples with formula →
low -
= 5 consistent examples with formula →
false(skip)
Convergence guarantee: Each negotiate round adds examples at maximum-uncertainty points, monotonically reducing the space of plausible interpretations.
The core.mjs module proves all 20 modules reduce to one operation:
given(known).when(hole) → answer
| Operation | Expression |
|---|---|
| Protocol | given(assertions).when(new_input) |
| Intent | given(examples).when(new_input) |
| Query | given(context).when(hole) |
| Correction | given(old).but(new_evidence) |
| Composition | given(f).pipe(given(g)) |
| Diff | asDiff(behavior_a, behavior_b) |
| Self-test | selfTest() — 8/8 pass |
Self-reference: Benoit CAN describe itself. The selfTest() function uses given/when to test given/when, and all 8 tests pass.
| Constant | Value | Location |
|---|---|---|
PROTOCOL_VERSION |
"benoit-protocol-v1" |
protocol.mjs |
| Unary sample space | [-10, -3, -2, -1, 0, 1, 2, 3, 5, 10, 42, 100] |
infer.mjs |
| Binary associativity triples | [(-1,0,1), (1,2,3), (2,3,5), (0,5,10)] |
infer.mjs |
| Composition sample space | [-3, -1, 0, 1, 3, 5] |
protocol.mjs |
| Magnitude cap | 30 | protocol.mjs |
| Unknown search range | [-1000, 1000] | solve.mjs |
| Numeric tolerance | 0.001 (general), 0.01 (trig/sqrt), 0.1 (exp/log) | solve.mjs |
| Need ID prefix | "need-" |
contract.mjs |
| Offer ID prefix | "offer-" |
contract.mjs |
| Contract ID prefix | "contract-" |
contract.mjs |
PROTOCOL_VERSION: stringencode(source: string): ProtocolMessagedecode(message: string | object): DecodedResultexchange(source: string): ExchangeReport
encodeIntent(examples, properties?, constraints?): IntentresolveIntent(intent): ResolvedIntentexecuteIntent(intent, input): anycomposeIntents(intentA, intentB): ComposedIntentnegotiateIntent(intent, counterExamples): ResolvedIntent
publishNeed(spec): NeedpublishOffer(needId, implementation, need?): Offernegotiate(need, offers): RankedOffer[]bind(need, offer): Contractverify(contract, newImplementation): VerificationReportRegistryclass (methods:publishNeed,publishOffer,search,searchByName,resolve,getOffers,getNeeds,getContracts,verify)
composeModules(...sources: string[]): ComposedModulecompose(...messages: ProtocolMessage[]): ComposedModulediff(messageA, messageB): DiffReport
infer(benSrc: string): InferenceResultinferAll(benSrc: string): InferenceResult[]
synthesize(fp: Fingerprint): SynthesisResult[]solve(assertion: string, knownFunctions: object): SolveResult
ask(known, holes, properties?): Queryanswer(query): AnsweredQuerychallenge(answeredQuery, corrections): AnsweredQuerycurious(known, options?): CuriosityReportquality(examples): QualityReportreformulate(examples, opts?): ReformulationResultDialogueclass (methods:teach,ask,negotiate,fulfill,shouldNegotiate,correct,wonder,understanding,summary)
given(known, properties?): ResolverasProtocol(assertions): ResolverasIntent(examples, properties?): ResolverasQuery(context): ResolverasDiff(behaviorA, behaviorB): DiffReportasCompose(behaviorF, behaviorG): ResolverselfTest(): SelfTestResult