branchiq 0.3.1 copy "branchiq: ^0.3.1" to clipboard
branchiq: ^0.3.1 copied to clipboard

A bounded, deterministic decision intelligence engine for Dart/Flutter. Evaluates decision trees using multi-attribute scoring, beam-search pruning, and priority-first traversal.

BranchIQ #

Deterministic runtime decision intelligence for Dart & Flutter.

BranchIQ evaluates decision trees synchronously, deterministically, and without hidden runtime magic. Given the same tree and configuration, it always produces the same result — guaranteed.

It is a pure Dart library with zero external dependencies. It runs on the calling thread with strict depth and size limits to prevent runaway execution.


Key Features #

  • Deterministic: Identical inputs always produce identical outputs
  • Bounded: Hard limits on tree depth, node count, and traversal iterations
  • Explainable: Human-readable traces and structured JSON snapshots
  • Synchronous: Runs on the calling thread — no isolates, no async, no event-loop delays
  • Pure Dart: No Flutter dependency, no external packages, no hidden globals
  • Testable & Replayable: Snapshots can be serialized and replayed in unit tests

Installation #

dependencies:
  branchiq: ^0.3.0
dart pub get

Compatible with Dart SDK >=3.0.0 <4.0.0. Works on Flutter (iOS, Android, Web, macOS, Windows, Linux) and standalone Dart.


Quickstart #

import 'package:branchiq/branchiq.dart';

void main() {
  // 1. Define decision nodes
  final root = const DecisionNode.constant(
    id: 'root',
    parentId: null,
    childIds: ['accept', 'decline'],
    depth: 0,
  );

  final accept = const DecisionNode.constant(
    id: 'accept',
    parentId: 'root',
    childIds: [],
    probability: 0.9,   // How likely this outcome is
    impact: 0.8,         // How much value it creates ([-1.0, 1.0])
    cost: 50.0,          // How much it costs (non-negative)
    depth: 1,
  );

  final decline = const DecisionNode.constant(
    id: 'decline',
    parentId: 'root',
    childIds: [],
    probability: 0.1,
    impact: -0.2,
    cost: 10.0,
    depth: 1,
  );

  // 2. Build the tree
  final tree = DecisionTree.fromNodes([root, accept, decline]);

  // 3. Configure evaluation
  final engine = BranchIQEngine.createSync();

  final result = engine.evaluateSync(
    tree: tree,
    scoringConfig: ScoringConfig.balanced(costCeiling: 100.0),
    pruningConfig: PruningConfig.defaultSettings(),
    traversalConfig: const TraversalConfig(),
    enableDebug: true,
  );

  // 4. Read results
  print('State:    ${result.runtimeState}');     // completed
  print('Path:     ${result.bestPath.nodeIds}'); // [root, accept]
  print('Utility:  ${result.totalUtility}');

  // 5. Get a plain-English explanation
  print(engine.explain(result));
}

Output:

State:    completed
Path:     [root, accept]
Utility:  0.6591

Path chosen: root -> accept
Total Utility: 0.659...
State: completed
Traces:
[VALIDATION] Validation started.
[VALIDATION] Validation successful.
[SCORING] Scoring started.
[SCORING] Scoring completed.
[PRUNING] Pruning started.
[PRUNING] Pruning completed.
[TRAVERSAL] Traversal started.
[TRAVERSAL] Traversal completed.
[COMPLETION] Pipeline completed in completed state.

How It Works #

BranchIQ processes every tree through a fixed, sequential pipeline:

Input Tree
    │
    ▼
[ 1. Validation ]  — checks structure, cycles, depth limits
    │
    ▼
[ 2. Scoring ]     — assigns utility scores using probability, impact, and cost
    │
    ▼
[ 3. Pruning ]     — eliminates low-value branches before traversal
    │
    ▼
[ 4. Traversal ]   — selects the highest-utility root-to-leaf path
    │
    ▼
  Result + Traces

Scoring #

Each node's score is computed as:

score = (wp × probability) + (wi × impact) - (wc × normalizedCost)

Weights wp, wi, wc must sum to 1.0. Costs are normalized against costCeiling.

ScoringConfig(wp: 0.2, wi: 0.7, wc: 0.1, costCeiling: 500.0)
// 20% probability, 70% impact, 10% cost penalty

Pruning #

Branches are removed if they fall below any configured threshold:

PruningConfig(
  minProbability: 0.05,  // Remove branches with p < 5%
  minScore: 0.0,         // Remove branches with score < 0
  beamWidth: 3,          // Keep only top 3 siblings per level
  maxDepth: 4,
  maxNodeLimit: 100,
)

Traversal #

Priority-first traversal walks from root to the highest-scoring leaf. Ties are broken by lexicographic node ID order — always reproducible.


Deterministic Guarantees #

BranchIQ is designed to be a pure function of its inputs:

Guarantee How it's enforced
No randomness No calls to dart:math.Random anywhere in the codebase
Stable tie-breaking Equal-scoring nodes sorted by ID ('a' < 'b')
No system clock No DateTime.now() or Stopwatch inside evaluation
Stateless engine BranchIQEngine holds no mutable state between calls
Bounded execution Hard caps on depth (12), nodes (1000), and iterations (1000)

Running the same evaluation 1000 times always produces identical results. This is verified by the regression test suite.


Debugging & Inspection #

Enable debug mode for full runtime diagnostics:

final result = engine.evaluateSync(..., enableDebug: true);

// Per-node scoring details
final snapshot = engine.exportDebugSnapshot(result);

for (final entry in snapshot.nodeSnapshots.entries) {
  final data = entry.value;
  print('${entry.key}: score=${data['score']}  pruned=${data['pruningReason']}');
}

Export the full snapshot to JSON:

import 'dart:convert';

final json = engine.exportDebugSnapshot(result).toJson();
print(const JsonEncoder.withIndent('  ').convert(json));

Benchmark Mode #

Collect execution metrics without wall-clock timing:

final result = engine.evaluateSync(..., enableBenchmark: true);

final bench = result.benchmarkSnapshot!;
print('Nodes evaluated:  ${bench.totalNodes}');
print('Traversal steps:  ${bench.traversalIterations}');
print('Nodes pruned:     ${bench.prunedNodes}');
print('Est. allocations: ${bench.estimatedAllocationCount}');

Benchmark snapshots are deterministic — the same tree always produces the same counts.


Runtime States #

State Meaning
completed A valid path was found
fallback All branches were pruned; root was returned as the safe default
failed A structural or safety limit violation occurred

When failed, inspect the error:

if (result.runtimeState == 'failed') {
  print('Error: ${result.errorMessage}');
}

Safety Limits #

BranchIQ enforces hard limits to protect your application:

Limit Default Meaning
Max tree depth 12 levels Prevents deep recursion
Max node count 1000 nodes Prevents runaway memory use
Max traversal iterations 1000 Prevents infinite loops
Max children per node 10 Bounds local fan-out

Trees violating these limits are rejected at construction time with a clear error.


Replay #

BranchIQ v0.2 includes a snapshot-driven replay layer. Evaluate once, then reconstruct, inspect, and compare past evaluations offline — without re-running the engine:

// Export and load a snapshot into a replay session
final snapshot = engine.exportDebugSnapshot(result);
final session = ReplayLoader.load(snapshot);

// Serialize to byte-identical canonical JSON (safe for storage and diffing)
final canonicalJson = session.toCanonicalJson();

// Reconstruct from stored JSON at any time
final restored = ReplayLoader.loadCanonicalJson(canonicalJson);

// Inspect the session offline
final inspector = ReplayInspector(restored);
final pathNodes = inspector.inspectSelectedPath();

Replay never re-runs scoring, pruning, or traversal. All data comes exclusively from the serialized snapshot.


Explainability #

Generate deterministic, evidence-based explanation reports from any replay session:

final report = BranchIQExplainer.explain(session);
print(report.toMarkdown());

// Compare the selected path against any alternative
final comparison = BranchIQExplainer.comparePaths(
  session: session,
  selectedPath: ['root', 'approve', 'auto'],
  rejectedPath: ['root', 'defer'],
);
print(comparison.toMarkdown());

Explanations are purely evidence-based — derived from verified metrics, not AI-generated reasoning or heuristic storytelling.


Snapshot Diffing #

Compare two historical evaluations offline and deterministically:

final diff = SnapshotDiffer.compareSnapshots(source: snapA, target: snapB);
print(diff.toMarkdown());

The differ tracks added, removed, and modified nodes, utility deltas, pruning status changes, and chronological trace differences — all with byte-identical canonical output.


Plugins & Custom Evaluators #

BranchIQ v0.3 introduces a deterministic, replay-safe plugin infrastructure. You can register custom synchronous node evaluators to dynamically adjust scoring metrics during the evaluation phase:

// 1. Create a custom node evaluator
class NetworkAdjuster implements NodeEvaluator {
  @override
  String get id => 'network-adjuster';

  @override
  DecisionNode evaluate(DecisionNode node, EvaluationContext context) {
    if (node.id == 'fetch_network' && context.get<bool>('isOffline') == true) {
      // Return a copy of the node with modified evaluator-owned metrics
      return node.copyWith(
        probability: 0.0,
        cost: node.cost + 100.0,
      );
    }
    return node;
  }
}

// 2. Register it in the PluginRegistry
final registry = PluginRegistry(evaluators: [NetworkAdjuster()]);

// 3. Pass it to the engine
final result = engine.evaluateSync(
  tree: tree,
  plugins: registry,
  // ... other parameters
);

Plugin Infrastructure Boundaries #

To preserve BranchIQ's core deterministic and forensic guarantees, the plugin system has strict boundaries:

  • NodeEvaluator only: Currently, custom evaluators are supported. Expansion hooks (BranchExpander) and reporting hooks (ReportExporter) are deferred.
  • Engine-owned field protection: Plugins cannot alter structural or engine-controlled fields (id, parentId, childIds, depth, confidence). If a plugin attempts to modify these, the engine automatically restores the original values.
  • Synchronous only: Plugins must run synchronously on the calling thread. No Future returns, async execution, reflection, I/O, or mutable global state is allowed.
  • Evidence-only Replay & Explainability: Plugin provenance is recorded directly into DebugSnapshot.pluginProvenance during evaluation. Replay and explanation tools process this recorded evidence offline without re-executing any plugin code.

Examples #

All examples are runnable:

dart run example/minimal_example.dart          # Basic evaluation
dart run example/scoring_example.dart           # Scoring weights & sensitivity
dart run example/pruning_example.dart           # Pruning rules & fallback
dart run example/traversal_example.dart         # Path selection & tie-breaking
dart run example/debug_snapshot_example.dart    # Debug snapshot inspection
dart run example/benchmark_example.dart         # Benchmark mode & determinism
dart run example/replay_example.dart            # Snapshot-driven replay
dart run example/explainability_example.dart    # Evidence-based explanations
dart run example/snapshot_diff_example.dart     # Offline snapshot comparison

Documentation #

Guide Topic
Quickstart Installation, first evaluation, reading results
Scoring Guide Weights, cost normalization, confidence decay
Pruning Guide Probability, score, beam width, fallback
Traversal Guide Path selection, tie-breaking, accumulated utility
Debugging Guide Traces, debug snapshots, benchmark metrics
Replay Guide Snapshot-driven replay, canonical serialization
Explainability Guide Evidence-based explanations, path comparison
Snapshot Diff Guide Offline snapshot comparison, metric deltas

Core architecture documents are in doc/core/.


Use Cases #

  • Cache vs. network selection: Route requests based on signal quality, data age, and battery constraints
  • Adaptive retry policies: Schedule retries based on backoff intervals and network telemetry
  • Offline-first decision flows: Determine whether to serve cached or fresh content
  • Deterministic UI routing: Direct onboarding or feature-flag flows based on user profile scores
  • Bounded workflow orchestration: Find the next step in multi-stage workflows without hardcoded if-else trees

BranchIQ is a deterministic routing and scoring engine. It is not a machine learning system, an autonomous agent, or an AI framework.


Architecture Principles #

  • Deterministic — same inputs, same outputs, always
  • Bounded — execution is capped by depth and node limits
  • Explainable — every decision produces inspectable traces
  • Synchronous — runs on the calling thread; no async, no isolates
  • Pure Dart — no external dependencies, no Flutter requirement
  • Testable — stateless engine, replayable snapshots, full test suite

Contributing #

See CONTRIBUTING.md for contribution guidelines.

Key rules:

  • All changes must maintain strict determinism
  • No randomness, no system clock access, no isolates
  • All mathematical and scoring code requires 100% unit test coverage

Stability #

BranchIQ v0.3.0 is a developer preview. The core evaluation pipeline, replay infrastructure, explainability layer, snapshot diffing, and plugin infrastructure are stable. Public API signatures may evolve before v1.0.


License #

MIT License — see LICENSE.


BranchIQ exists to make runtime decision systems deterministic, explainable, and bounded — without hidden magic.

2
likes
160
points
284
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A bounded, deterministic decision intelligence engine for Dart/Flutter. Evaluates decision trees using multi-attribute scoring, beam-search pruning, and priority-first traversal.

Repository (GitHub)
View/report issues
Contributing

Topics

#decision-tree #deterministic #utility #scoring #dart

License

MIT (license)

More

Packages that depend on branchiq