branchiq 0.1.0
branchiq: ^0.1.0 copied to clipboard
A bounded, deterministic runtime decision intelligence engine for Dart and Flutter. Evaluates decision trees using multi-attribute scoring, beam-search pruning, and priority-first traversal. Synchrono [...]
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.1.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.
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
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 |
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.1.0 is a developer preview. The core evaluation pipeline is 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.