Benoit Protocol Specification v0.8.0

April 11, 2026 · View on GitHub

Formal specification of the Benoit Communication Protocol, Intent Engine, and Contract System.

Named after Benoit Fragniere, who loved science.


1. Overview

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:

  1. Communication Layer (protocol.mjs): Encode/decode cycle for transmitting function modules as behavioral descriptions. No source code crosses the wire.
  2. Instruction Layer (intent.mjs): Behavioral specification of computational intent through examples and property constraints, with synthesis, composition, and negotiation.
  3. 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"

2. Wire Format

2.1 Function Message (Protocol Message)

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:

FieldTypeDescription
protocolstringProtocol version identifier. Must be "benoit-protocol-v1".
functionsarrayOne entry per function in the source module.
functions[].namestringFunction name as declared in source.
functions[].aritynumberNumber of parameters (0, 1, 2, or 3).
functions[].assertionsarrayInput/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[].propertiesstring[]Array of property type identifiers discovered by infer(). See Section 3.
algebra.equivalenceClassesstring[][]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.inversePairsobject[]Pairs of unary functions f and g where f(g(x)) = x and g(f(x)) = x for all sampled x.
algebra.surprisesobject[]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.
metaobjectMessage 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.

2.2 Intent Message

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:

FieldTypeDescription
typestringAlways "intent".
examplesarrayInput/output pairs. Inputs and outputs may be numbers, strings, or arrays.
propertiesstring[]Behavioral constraints the synthesized function must satisfy.
constraints.domainstringInferred or specified input type. One of "number", "array", "string", or "any".
constraints.rangestringInferred or specified output type. Same values as domain.
fnFunctionThe synthesized callable function (only present after resolution).
meta.confidencenumberConfidence score in [0, 1]. null before resolution.
meta.synthesizedstringHuman-readable formula of the synthesized function.
meta.statusstringOne of "resolved", "unsolved", or "composed".
meta.propertiesVerifiedobjectLists of satisfied and violated property names.

2.3 Contract Messages

2.3.1 Need Object

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>"
}

2.3.2 Offer Object

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>"
}

2.3.3 Contract Object

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>"
}

2.3.4 Verification Report

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>"
  }
}

2.3.5 Negotiation Result

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.


3. Property Taxonomy

The inference engine (infer.mjs) discovers the following properties by probing function behavior over a sample space.

3.1 Unary Function Properties

PropertyFormal DefinitionSample SpaceConfidence
identityf(x) = x for all x in SS = {-10, -3, -2, -1, 0, 1, 2, 3, 5, 10, 42, 100}1.0
idempotentf(f(x)) = f(x) for all x in S, and f is not the identityS (as above)0.95
involutionf(f(x)) = x for all x in S, and f is not the identityS (as above)0.95
even_functionf(-x) = f(x) for all x in S, with at least one x != 0S (as above)0.95
odd_functionf(-x) = -f(x) for all x in S, with at least one x where f(x) != 0S (as above)0.95
monotonic_increasingx1 < x2 implies f(x1) <= f(x2) for all consecutive pairs in sorted SS (as above),S
monotonic_decreasingx1 < x2 implies f(x1) >= f(x2) for all consecutive pairs in sorted S, and not also increasingS (as above),S
non_negativef(x) >= 0 for all x in S, with at least one x < 0 in SS (as above)0.9
fixed_pointsThere exist values x in S where f(x) = x, but not all values satisfy thisS (as above)1.0

3.2 Binary Function Properties

PropertyFormal DefinitionSample SpaceConfidence
commutativef(a, b) = f(b, a) for all (a, b) in S x SS x S,S
associativef(f(a, b), c) = f(a, f(b, c)) for all (a, b, c) in TT = {(-1,0,1), (1,2,3), (2,3,5), (0,5,10)}0.9
right_identityf(a, e) = a for all a in S, where e in {0, 1}S where b = e,filtered
left_identityf(e, b) = b for all b in S, where e in {0, 1}S where a = e,filtered
absorbing_elementf(a, 0) = 0 and f(0, b) = 0 for all a, b in SS where one arg = 0,filtered

3.3 Ternary Function Properties

PropertyFormal DefinitionSample SpaceConfidence
boundedb <= f(x, b, c) <= c for all (x, b, c) in TT = cross({-5,0,5,10,50,100,200}, {0,10}, {50,100})0.95
passthrough_in_boundsIf b <= x <= c, then f(x, b, c) = xT (as above), filtered to in-bounds0.95

3.4 Intent/Contract Property Verification

The intent and contract systems verify additional properties:

PropertyDefinition
idempotentf(f(x)) = f(x) for all provided examples (uses JSON deep equality)
length_preservingFor arrays: len(f(x)) = len(x). For strings: len(f(x)) = len(x).
commutativeFor 2-element array inputs: f([a,b]) = f([b,a])
monotonic_increasingSorted numeric inputs produce non-decreasing outputs
non_negativef(x) >= 0 for all examples
deterministicf(x) = f(x) on repeated calls (always true for synthesized functions)
pureStateless: f(x) produces identical output on repeated invocations

4. Synthesis Rules

The solver (solve.mjs) synthesizes Benoit source code from input/output assertion pairs. It operates as a pattern recognizer, not a general solver.

4.1 Unary Hypotheses

Listed in order of evaluation:

PatternFormulaConfidenceDetection Criterion
Identityx1.0All pairs satisfy output = input
Constantc0.9All outputs identical
Lineara * x + b0.95Two-point interpolation verified against all pairs (tolerance < 0.001)
Quadratica * x * x + b * x + c0.85Three-point system of equations, verified against all pairs
Cubicx * x * x0.9All pairs satisfy x^3 = output
ReLUMath.max(0, x)0.95All pairs satisfy max(0, x) = output
FloorMath.floor(x)0.9All pairs match floor function
SignMath.sign(x)0.9All pairs match sign function
Absolute valueMath.abs(x)0.95All pairs match absolute value
Factorialmatch n -> | 0 => 1 | _ => n * f(n - 1)0.9f(0) = 1 and factorial recurrence holds. Recursive.
Fibonaccimatch n -> | 0 => 0 | 1 => 1 | _ => f(n-1) + f(n-2)0.95f(0) = 0 and Fibonacci recurrence holds. Recursive.
ExponentialMath.pow(base, x) for base in {2, 3, e, 10}0.9All pairs match within tolerance 0.1
LogarithmMath.log(x), Math.log2(x), Math.log10(x)0.9Positive-input pairs match within tolerance 0.1
TrigonometricMath.sin(x), Math.cos(x)0.85All pairs match within tolerance 0.01
Square rootMath.sqrt(x)0.9Positive-input pairs match within tolerance 0.01
Round / CeilMath.round(x), Math.ceil(x)0.9Exact match

4.2 Binary Hypotheses

PatternFormulaConfidence
Additiona + b1.0
Multiplicationa * b1.0
Subtractiona - b1.0
MaximumMath.max(a, b)0.9
MinimumMath.min(a, b)0.9
Moduloa % b0.9
PowerMath.pow(a, b)0.85
Integer divisionMath.floor(a / b)0.85
GCD (recursive)match b -> | 0 => a | _ => f(b, a % b)0.9
LCMMath.abs(a * b) / gcd(a, b)0.85
Average(a + b) / 20.9
HypotenuseMath.sqrt(a * a + b * b)0.85

4.3 Ternary Hypotheses

PatternFormulaConfidence
ClampMath.max(min, Math.min(max, x))0.95

4.4 Conditional Synthesis

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 < 0
  • x >= 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

4.5 String Synthesis

For non-numeric assertions, the solver attempts:

  1. Template patterns: Find consistent prefix/suffix around the input string
  2. Case transformations: toUpperCase(), toLowerCase()
  3. Reversal: split("").reverse().join("")
  4. Length: output = string length

4.6 Property-Guided Ranking

When properties are provided alongside assertions, hypotheses are validated against them. Each property contributes to a score:

PropertyMatch bonusMismatch 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_points00

Hypotheses are sorted by property score first, then by confidence.

4.7 Intent Hypothesis Templates

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.


5. Composition Laws (Derivation Rules)

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.

5.1 Rule Table

Rule IDPredictsCondition (on properties of f, g for f . g)
even_odd_eveneven_compositionf is even_function AND g is odd_function
even_even_eveneven_compositionf is even_function AND g is even_function
any_even_eveneven_compositiong is even_function
nonneg_any_nonnegnon_negative_compositionf is non_negative
even_invol_absorbabsorptionf is even_function AND g is involution
any_id_absorbabsorptiong is identity
id_any_transparentf_transparentf is identity
invol_self_identitycomposition_identityf and g are the same function AND f is involution
nonneg_absorb_nonnegnon_negative_compositiong is non_negative
even_idempotent_absorbabsorptionf is even_function AND g is idempotent AND g is even_function
nonneg_transparentf_transparentf is non_negative AND g is non_negative AND f is idempotent

5.2 Composition Property Definitions

PropertyFormal DefinitionVerification
even_composition(f . g)(-x) = (f . g)(x) for all xTested over positive samples {1, 3, 5}, verifying f(g(x)) = f(g(-x))
non_negative_composition(f . g)(x) >= 0 for all xTested over samples {-3, -1, 0, 1, 3, 5}
absorptionf(g(x)) = f(x) for all xVerified: for all sample x, f(g(x)) = f(x)
f_transparentf(g(x)) = g(x) for all xVerified: for all sample x, f(g(x)) = g(x)
composition_identityf(g(x)) = x for all xVerified: for all sample x, f(g(x)) = x

5.3 Surprise Mechanism

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.


6. Verification Protocol

The full encode-decode-verify cycle proceeds as follows:

Step 1: Sender Encodes Module

encode(source) -> message
  1. Parse source into AST, extract fingerprint (function names, arities, assertions).
  2. For each function definition, run infer() to discover properties.
  3. Discover equivalence classes via behavioral hashing.
  4. Discover inverse pairs among unary functions.
  5. Analyze all unary composition pairs; record non-derivable surprises.
  6. Emit protocol message (JSON).

Step 2: Message Transmission

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

Step 3: Receiver Synthesizes Functions

decode(message) -> result
  1. Validate protocol version.
  2. Pass { functions } to synthesize().
  3. For each synthesized function, transpile the Benoit code to JavaScript and instantiate a callable function.

Step 4: Receiver Verifies Assertions

For each function and each assertion:

  1. Evaluate the assertion's input expression using all synthesized functions.
  2. Compare the result to the expected output.
  3. Record pass/fail.

Step 5: Receiver Verifies Properties

For each function and each claimed property:

  1. Invoke verifyProperty(fn, propType, arity) using the sample space [-10, -5, -3, -1, 0, 1, 3, 5, 10, 42].
  2. Record pass/fail.

Step 6: Receiver Verifies Compositions

  1. 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.
  2. Surprises: For each surprise in the message, verify the claimed composition property.

Step 7: Report

Return verification totals:

{
  "assertions": { "passed": N, "total": M },
  "properties": { "passed": N, "total": M },
  "compositions": { "reconstructed": N, "verified": M }
}

7. Contract Protocol

Step 1: Publish Need

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.

Step 2: Publish Offer

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.

Step 3: Negotiate

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.

Step 4: Bind

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.

Step 5: Verify Compatibility

When an implementation is updated:

verify(contract, newImplementation)

Returns a compatibility report indicating whether the new implementation satisfies all contracted examples and properties.

Registry

The Registry class provides a centralized marketplace:

  • publishNeed(spec) -- register a need
  • publishOffer(needId, impl) -- offer an implementation
  • search(properties) -- find needs by property overlap
  • searchByName(name) -- find needs by substring match
  • resolve(needId) -- auto-select best offer and bind
  • verify(contractId, newImpl) -- check backward compatibility
  • getNeeds(), getOffers(needId), getContracts() -- introspection

8. Intent Protocol

Step 1: Encode Intent

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"

Step 2: Resolve Intent

resolveIntent(intent)

Synthesis proceeds in order:

  1. Numeric solver (solve.mjs): Wraps examples into the assertion format and delegates to synthesize(). Only applies when all inputs and outputs are numeric.
  2. Hypothesis templates: Tests each built-in template (Section 4.7) against all examples.
  3. Generic linear map: Fits output[i] = a * input[i] + b for array-of-numbers examples.

Candidates are ranked by:

  1. Property compliance score: satisfied.length - violated.length * 2
  2. Confidence (descending)

If the best candidate violates required properties and is the only candidate, its confidence is halved as a penalty.

Step 3: Execute Intent

executeIntent(intent, newInput)

Resolves the intent if not already resolved, then applies the synthesized function to the new input. Throws if synthesis failed.

Step 4: Compose Intents

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.

Step 5: Negotiate (Renegotiate)

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.


9. Safety Guarantees

9.1 Magnitude Cap in Composition Verification

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)

9.2 Bounded Sample Spaces

ContextSample SpaceSize
Property inference (unary){-10, -3, -2, -1, 0, 1, 2, 3, 5, 10, 42, 100}12
Property inference (binary)S x S144
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] integers2001

9.3 Safe Function Evaluation

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.

9.4 Deep Equality via JSON Serialization

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:

  • undefined values in arrays
  • Circular references
  • Functions as values
  • -0 vs +0
  • NaN comparisons

9.5 Confidence Degradation

  • 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

10. Module Composition

The composeModules() function (compose.mjs) merges multiple Benoit source modules into a unified algebra.

10.1 Name Collision Resolution

If two modules define functions with the same name, the later module's function is renamed to <name>_m<moduleIndex>.

10.2 Cross-Module Discovery

Three types of cross-module relationships are discovered (only between functions from different modules, unary only):

  1. Equivalences: Functions with identical behavior hashes (same output for all 11 sample values).
  2. Inverse pairs: Functions f, g where f(g(x)) = x AND g(f(x)) = x for all 11 samples.
  3. 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 samples
    • absorption: f(g(x)) = f(x) for all samples (and not identity)
    • even_composition: f(g(-x)) = f(g(x)) for all samples with x != 0
    • non_negative_composition: f(g(x)) >= 0 for all samples, with at least one x < 0

10.3 Diff

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

11. Solver: Unknown Resolution

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>"
}

12. Query Engine: Questions as Incomplete Examples

The query.mjs module treats questions as behavioral specifications with holes.

12.1 Core Operations

  • ask(known, holes, properties?): Creates a query from known examples + input holes
  • answer(query): Fills holes using synthesis
  • challenge(answeredQuery, corrections): Overrides wrong answers with new evidence
  • curious(known, options?): Detects gaps in knowledge (extrapolation, edge cases)

12.2 Quality Measurement

quality(examples) measures question quality on 5 axes:

AxisWeightMeasure
Quantity0.155+ examples = max score
Resolvability0.25Can the solver synthesize?
Consistency0.25Does synthesis match ALL examples?
Diversity0.15Sign coverage, input spread, uniqueness
Ambiguity0.20How many alternative functions fit?

Verdicts: excellent (>= 0.85), good (>= 0.7), adequate (>= 0.5), weak (>= 0.3), insufficient (< 0.3).

12.3 Reformulation

reformulate(examples, opts?) auto-improves a bad question:

  1. Diagnose with quality()
  2. If low diversity: probe boundaries (0, -1, 1, 10, -10, 100) using current best-fit
  3. If too few examples: bootstrap from best-fit across input range
  4. If high ambiguity: test at extreme values where interpretations diverge
  5. If unsolvable: try structural transforms (e.g., sequential -> recurrence)

Returns { original, reformulated, rounds, improved, history }.

12.4 Negotiation Protocol

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.


13. Core: Universal Primitive

The core.mjs module proves all 20 modules reduce to one operation:

given(known).when(hole) → answer
OperationExpression
Protocolgiven(assertions).when(new_input)
Intentgiven(examples).when(new_input)
Querygiven(context).when(hole)
Correctiongiven(old).but(new_evidence)
Compositiongiven(f).pipe(given(g))
DiffasDiff(behavior_a, behavior_b)
Self-testselfTest() — 8/8 pass

Self-reference: Benoit CAN describe itself. The selfTest() function uses given/when to test given/when, and all 8 tests pass.


Appendix A: Protocol Constants

ConstantValueLocation
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 cap30protocol.mjs
Unknown search range[-1000, 1000]solve.mjs
Numeric tolerance0.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

Appendix B: Exported API Surface

protocol.mjs

  • PROTOCOL_VERSION: string
  • encode(source: string): ProtocolMessage
  • decode(message: string | object): DecodedResult
  • exchange(source: string): ExchangeReport

intent.mjs

  • encodeIntent(examples, properties?, constraints?): Intent
  • resolveIntent(intent): ResolvedIntent
  • executeIntent(intent, input): any
  • composeIntents(intentA, intentB): ComposedIntent
  • negotiateIntent(intent, counterExamples): ResolvedIntent

contract.mjs

  • publishNeed(spec): Need
  • publishOffer(needId, implementation, need?): Offer
  • negotiate(need, offers): RankedOffer[]
  • bind(need, offer): Contract
  • verify(contract, newImplementation): VerificationReport
  • Registry class (methods: publishNeed, publishOffer, search, searchByName, resolve, getOffers, getNeeds, getContracts, verify)

compose.mjs

  • composeModules(...sources: string[]): ComposedModule
  • compose(...messages: ProtocolMessage[]): ComposedModule
  • diff(messageA, messageB): DiffReport

infer.mjs

  • infer(benSrc: string): InferenceResult
  • inferAll(benSrc: string): InferenceResult[]

solve.mjs

  • synthesize(fp: Fingerprint): SynthesisResult[]
  • solve(assertion: string, knownFunctions: object): SolveResult

query.mjs

  • ask(known, holes, properties?): Query
  • answer(query): AnsweredQuery
  • challenge(answeredQuery, corrections): AnsweredQuery
  • curious(known, options?): CuriosityReport
  • quality(examples): QualityReport
  • reformulate(examples, opts?): ReformulationResult
  • Dialogue class (methods: teach, ask, negotiate, fulfill, shouldNegotiate, correct, wonder, understanding, summary)

core.mjs

  • given(known, properties?): Resolver
  • asProtocol(assertions): Resolver
  • asIntent(examples, properties?): Resolver
  • asQuery(context): Resolver
  • asDiff(behaviorA, behaviorB): DiffReport
  • asCompose(behaviorF, behaviorG): Resolver
  • selfTest(): SelfTestResult