Decision-engine scope: governed adapters for selected problems
Current capability:
company_decision_solvesupports two decision problems: inventory/service-level policy and rolling-horizon re-optimization. It records caller inputs as assumptions, evaluates and exposes candidate policies, and returns reviewable proofs. The rolling loop returns forecast calibration measures. Proposed actions are not executed; they carry the approval type required for a governed action. The engine does not read company data itself.Ambition: Connect governed enterprise context and measured outcomes to a broader set of decision workflows, so state, uncertainty, alternatives, constraints, actions and results can inform later decisions. A complete enterprise semantic layer, broad cross-domain scorecards, and proven general improvement to later decisions are not established capabilities.
Company OS is the substrate a decision engine runs inside, not the engine. This document records what the company_decision_solve seam provides, which decision problems have a real engine behind them, and how the remaining catalog problems attach without a second governance model.
Catalog entry (use this wording)
Decision-intelligence engines — hostable as governed adapters through
company_decision_solve: caller inputs recorded as assumptions, deterministic uncertainty sampling, every candidate evaluated, proposed actions tagged with the approval action type they need (spend), replayableinput_digest. Shipped engines: inventory / service-level policy (#1) and rolling-horizon re-optimization with forecast calibration (#18). The other sixteen catalog problems have no engine yet; they attach by adding aProblemKind.
What the seam guarantees
| Property | Mechanism |
|---|---|
| Assumptions are visible | Every caller field is echoed in proof.assumptions with source: caller_assumption; the rolling loop marks fields it updated as caller_assumption_updated_by_observations. The engine never reads company data itself. |
| Replayable | proof.input_digest is SHA-256 over canonical JSON of the problem; identical inputs produce an identical proof (sampling is SplitMix64 seeded from the digest, or stratified inverse-CDF). |
| Reviewable | proof.candidates lists every policy evaluated, not only the winner; proof.sensitivity re-scores the chosen policy at ±10 % demand; proof.decision.flags names capacity_bound / target_service_level_unmet. |
| Proposes, never executes | proof.proposed_actions[*].executed is always false; each carries approval_action_type (spend for purchase orders) so the run raises the matching approval request instead of acting. |
| Read-only tool | company_decision_solve is classified read_only in src/tools/mod.rs; it needs no mutation scope and writes nothing. The run's tool-call receipt is the record. |
Engines
inventory_policy — demand, inventory and service-level (#1)
Input: per-period demand model (normal, log_normal, or empirical samples), lead time, review period, unit cost, holding and shortage cost per unit, optional target_service_level, on-hand / on-order, optional capacity_max_units.
Method: order-up-to (base-stock) level over the protection interval (lead time + review period). Demand over the interval is sampled deterministically; candidates on a service-level grid plus the critical fractile p/(p+h) are scored by sample average for expected holding units, expected shortage units, cycle service level, fill rate, and cost. The lowest-cost candidate wins, or the cheapest meeting the target when one is set.
Output decision: order_up_to, order_quantity_now, service level, fill rate, expected cost, working_capital_at_policy, and one proposed purchase_order when the inventory position is below the policy.
Verified: crates/company-os-decision-engine tests pin the normal quantile, check the chosen level against the analytic newsvendor solution, and cover target override, capacity cap, determinism, log-normal and empirical models.
rolling_horizon — autonomous re-optimization (#18)
start makes the first decision and places the order in a pipeline. Each observe(realized_demand) scores the forecast the last decision rested on (mean, p10, p90), advances arrivals and consumption, records shortage, updates the demand model (exponential smoothing for parametric models; window append for empirical), re-solves, and returns the next proof with calibration: observation count, mean absolute percentage error, bias, and 80 % interval coverage. The state is a JSON value (company_os.decision_rolling_state.v1) the caller carries between calls; it round-trips and replays identically.
Calibration is the evidence a reviewer uses to decide whether the loop should keep being trusted; a persistently positive bias or low coverage is the signal to stop auto-proposing and escalate.
Inventory decision pilot (capability under evaluation)
The pilot proves the Decide → Act → Measure → Learn → Re-solve loop on one item against one baseline before any other engine is added. It does not show that the loop improves decisions; it produces the evidence a reviewer needs to judge that.
| Step | Mechanism |
|---|---|
| Decide | pilot::start / pilot::observe solve and open a DecisionRecord (company_os.inventory_pilot_decision_record.v1) |
| Act | pilot::record_action stores what was executed. Any order needs approval_ref and action_ref; a quantity that differs from the recommendation needs override_reason and counts as an override. The rolling pipeline carries the executed quantity, not the recommended one. |
| Measure | pilot::observe(realized_demand) closes the record with both arms: the engine as operated, and the predeclared baseline replayed on the same demand |
| Learn | the rolling demand model updates from the observation, and calibration is reported |
| Re-solve | the next record opens from the updated model and position |
Each record carries:
- the source snapshot: source ref, content digest, as-of, and the demand and period definitions;
- the definitions version (
company_os.inventory_pilot_definitions.v1); - the full proof: assumptions, uncertainty summary, every candidate, and constraint flags;
- the baseline order;
- a
decision_digestover everything known at decision time; - the action evidence;
- the realized outcome.
The baseline (fixed_order_up_to), the holding-cost rate, the source snapshot, and the minimum period count are fixed at start and bound by setup_digest. A state whose setup was edited afterwards is refused.
pilot::scorecard compares the arms on the predeclared measures:
- fill rate;
- cycle service level;
- shortage cost;
- holding cost;
- total cost;
- average working capital (on-hand stock at unit cost);
- the override rate, which is reported for the engine arm only.
Before min_periods_for_comparison it compares nothing. The scorecard always reports earned_claim_level: none and status: capability_under_evaluation: it covers one item and one run, has no uncertainty interval, and no producing code may raise its own claim level (claim contract §6).
company_os_core::inventory_pilot_episode maps each record onto a Decision Episode without writing anything:
- the candidates, the baseline, and an operator-set option become the sealed alternatives;
decision_digestbecomescontext_digest;- every pilot measure, plus its
baseline_counterfactual, is a predeclared expected outcome; - a followed recommendation resolves as
automated, with the approval as its authority; - an override becomes an
overrideintervention plus ahumanresolution; - the period's outcome is observed against
inventory_pilot_baseline:<setup_digest>. Engine-arm measures arederivedand baseline measuresestimated.
Callers submit the OpenDecision and commands through the authenticated episode HTTP adapter.
The Python pilot preflight (scripts/company_os_decision_learning_pilot.py) was not changed here. Its fixed metric set does not yet include the inventory measures.
Agents drive the pilot through company_decision_solve with problem: inventory_pilot and a pilot_action:
starttakesinventoryandpilot_setup.record_actiontakesstateandaction.observetakesstateandrealized_demand.scorecardtakesstate.
Each response (company_os.inventory_pilot_response.v1) returns the next state, the open_record, and the scorecard. observe also returns the closed_record it measured. The tool stays read_only: record_action stores what the caller reports was executed elsewhere under its approval.
Not wired yet:
- Nothing posts pilot episodes automatically.
- No pilot has run on real company data.
Verified: crates/company-os-decision-engine/tests/pilot.rs (including the JSON entry point pilot::handle) and crates/company-os-core/tests/inventory_pilot_episode.rs. The tool file src/tools/decision_engine_tools.rs and its tests were compiled and run against the real engine in a standalone harness, with Tool/ToolOutput copied from src/tools/mod.rs. A full root-crate build has not been run.
Not shipped
Production planning and scheduling, procurement and supplier mix, multi-echelon network flows, maintenance policy, workforce allocation, portfolio and capex prioritization, experiment selection, order allocation, working-capital coordination, pricing and promotion, clinical and energy dispatch, and investment sizing have no engine. Each needs its own model and usually a solver; they attach by adding a module and ProblemKind to crates/company-os-decision-engine so the proof shape, digest, and proposed-action contract stay the same.
Adding a problem
- Add a module with a
Problemtype (deny_unknown_fields, avalidate), a deterministic sampler seeded frominput_digest, and asolvethat fillsDecisionProof— every candidate, flags, sensitivity, proposed actions with an approval action type. - Add the
ProblemKindvariant and a branch insrc/tools/decision_engine_tools.rs. - Prove at least one analytic case and a determinism case in the crate.