The path to ALLOW
Every quickstart run of ARVIS ends in REQUIRES_CONFIRMATION. That
surprises integrators, who reasonably conclude the kernel blocks
everything. It does not: it certifies nothing it has not measured,
and a first turn has measured nothing.
This page is the honest map of what stands between a call and an
ALLOW, what a host can do about each item today, and what it
cannot do yet in 0.1.x. Every statement here was measured on the
current tree; the numbers come from the M10 campaigns
(docs/math/M10_empirical_stability_validation_and_runtime_validation.md).
The short answer
ALLOW requires a measured contraction on a threaded trajectory
inside the projected domain, with the adaptive guard live. Six
conditions, in the order a host meets them:
- the host carries the trajectory across turns,
- the input carries structured signals, so there is an energy to measure at all,
- the projected observation sits inside the domain, away from its dangerous bounds,
- the adaptive stability layer is live with a negative measured margin (dwell has been served; an empty clock vetoes),
- the composite energy actually decreases on that turn,
- it decreases by more than the soft-filter threshold.
Miss any one and the verdict floors at REQUIRES_CONFIRMATION (or
refuses outright: condition 4 vetoes at ABSTAIN, because an empty
dwell clock is a violated switching condition, not a missing
nicety). That is the monotone-hardening doctrine (F-001) doing its
job: a floor is never silently lifted.
1. Carry the trajectory (the host contract)
A single ask() measures one point. The stability monitor needs a
sequence, so it starts in the warmup regime and stays there until
it has enough samples (10 by default, MonitorConfig.regime_min_samples).
The contract is one opaque blob, in and out:
from arvis import ArvisEngine
engine = ArvisEngine()
state = None
for question in questions:
extra = {"scientific_state": state} if state is not None else None
view = engine.ask(question, user_id="u1", extra=extra)
state = view.next_scientific_state # carry it forward
next_scientific_state is opaque on purpose: ARVIS never asks a host
to understand it, only to hand it back. It is deliberately absent
from to_dict(), which carries the decision contract, not host
plumbing.
Runnable version: examples/07_session_threading.py. Without
threading the regime stays warmup forever, however many calls the
host makes; with it, the regime moves at turn 9.
2. Give the kernel something to measure
A bare informational input, a plain text prompt with no structured
signals, does not produce a full cognitive projection. ARVIS attaches
the minimal certificate (see arvis/kernel/projection/certificate.py)
so the turn is still governed rather than surprisingly rejected, but
there is no composite energy on that path: delta_w stays None,
and a contraction that was never measured is never certified.
Measured on the public contract: 40 threaded turns of plain text
prompts return REQUIRES_CONFIRMATION throughout, delta_w None
on every one of them. Threading is necessary and it is not
sufficient.
If your integration only ever sends bare prompts, treat
REQUIRES_CONFIRMATION as the correct and permanent answer. ALLOW
is not withheld from you; it is undefined for that input.
3. Stay inside the domain, away from the dangerous bounds
The projected observation must be inside the certified domain, and far enough from the bounds that matter. Two distinct things:
- domain validity: the five projected axes (
state.system_tension,state.coherence_score,risk.conflict_pressure,control.control_signal,trace.adaptive_kappa_eff) must be present and in range. A missing axis is not certified. - boundary margin: the distance to the nearest dangerous bound
must exceed
projection_boundary_threshold(0.1 by default).
Until campaign FIX, the margin measured the distance to the nearest
bound whatever it meant, so an axis at its healthy extreme counted
as boundary proximity. risk.conflict_pressure is fed by the
collapse risk, whose healthy value is exactly 0.0, its lower bound:
a system at zero collapse risk was read as sitting on the domain
edge and floored. The healthier the run, the closer to the "edge".
Bounds now declare which end means danger; the fix removed 909
spurious boundary flags on the D-2.0 corpus (1376 turns to 467)
without moving a single verdict.
Practical consequence for a host: supply the five axes, and keep them off their dangerous ends (high tension, low coherence, high conflict, saturated or absent control).
4. The energy has to actually decrease
The gate certifies a measured contraction, not a calm-looking
snapshot. With a perfectly static input the composite energy is
constant, delta_w = 0.0, and a zero delta is not a contraction:
the verdict floors at REQUIRES_CONFIRMATION. This is by design and
will not change: ALLOW means "the trajectory demonstrably
contracted", not "nothing bad was observed".
Until campaign ALLOW, this section described a condition the kernel
could not actually evaluate. The projection certificate assessed its
Lyapunov axis against ctx._dv, a private attribute holding the
drift score, which is clamped to [0, 1] and therefore never
negative. Any drift at all was published as a measured Lyapunov
incompatibility, which floored the verdict twice over
(projection_unsafe and validity_projection_unavailable). The
axis is now assessed against the composite energy delta or reported
unassessed, and the two reason codes went from 1270 occurrences on
the D-2.0 corpus to zero. M10 section 12 has the full account.
5. It has to decrease by enough, relative to its energy
A contraction below the registered rate is floored with reason
weak_stability, in the local soft filter of the decision stack.
Since campaign SEUIL the boundary is a rate, not a step:
weak iff |delta_w| < max(0.05 * W_current, 0.005)
Five per cent of the turn's current energy (one third of the
measured median contraction rate), with an absolute floor of 0.005
so a session hovering near zero energy cannot certify noise. The
constants were calibrated from the M10 corpora and registered before
the campaign run (DM-S1, M10 section 14); they live in
arvis/math/stability/weak_stability_policy.py and changing them is
a new registration. A host may still set delta_w_soft_threshold on
the context as an explicit absolute override.
The practical meaning for an integrator: shedding 1.2 per cent of a small energy is a certified contraction, while the same absolute step on a large energy is not. A geometrically converging session, whose steps shrink as it succeeds, is no longer refused for succeeding.
What 0.1.x lets you reach
ALLOW is reachable. Measured on the current tree, across both M10
campaigns (on the corrected dwell clock of campaign PROJ and the
registered rate floor of campaign SEUIL):
| D-1.0 (1440 turns) | D-2.0 (1632 turns) | |
|---|---|---|
| ALLOW | 5 (0.35%) | 4 (0.25%) |
| REQUIRES_CONFIRMATION | 166 | 224 |
| ABSTAIN | 1269 | 1404 |
Every one of those turns has a strictly negative composite delta, a
live adaptive layer with a negative measured margin, and a served
dwell; they occur only in the nominal and long_horizon families.
Campaign GATE-SEM (M10 section 15) withdrew the majority of the
previously published ALLOW, including all 8 nominal_feedback ones:
they sat on the first threaded turn, where the adaptive guard was
silently dead, and a guard that is not standing certifies nothing.
Adversarial ALLOW remains 0.0 on both corpora, and the smallest
admitted contraction is |dW| 0.0105.
So a 0.1.x host should still design for the two-level ladder
(REQUIRES_CONFIRMATION and ABSTAIN) as the common case, and
treat ALLOW as an outcome that a well-instrumented, threaded,
genuinely contracting session can now reach rather than a capability
under construction.
The dwell history now crosses the contract
The switching condition is ln(J) / tau_d < kappa_eff, where
tau_d is how long the system has stayed in one regime. A fresh
runtime has tau_d = 0, which makes the left-hand side enormous and
adds switching_unsafe_monitoring to the reasons.
Since campaign PROJ the opaque blob of step 1 carries the dwell
clock: thread the blob and tau_d accumulates, one tick per
completed turn, and the switching reason disappears once enough
dwell is served. Blobs produced before the change simply start a
fresh clock. Without threading, tau_d restarts at zero on every
call, which is one more reason step 1 is where every integration
starts.
Since campaign GATE-SEM an empty or freshly reset dwell clock is a
hard veto, not a silent gap: the adaptive margin is computed on the
floored dwell and comes out massively positive, so the turn refuses
(ABSTAIN) until real dwell is served. Before that campaign the
margin computation died on tau_d = 0 and the absent layer
constrained nothing, which is exactly where most published ALLOW
turned out to live (M10 section 15). The envelope also now exports
switching_safe_measured beside the posture-effective value, so a
soft profile can never hide the raw measurement.
Debugging your own runs
The reasons are exported on every turn. With a host-supplied extra
dict you can read the whole journal back:
bag = {}
view = engine.ask("...", user_id="u1", extra=bag)
bag["fusion_reasons"] # why the verdict was floored
bag["projection_margin"] # distance to the dangerous bounds
bag["projection_trace"]["view"] # the five normalized axes
bag["switching_metrics"] # tau_d and the switching decision
bag["verdict_transition_trace"] # every tightening, with its stage
A verdict you did not expect is always explained by one of those
five. delta_w at None in the same bag is the signature of step 2:
nothing was measured, so nothing could be certified.
Since campaign PROJ the bag also carries the post-hoc attestation
(projection_post_certification_level,
projection_post_lyapunov_compatible): the decision's own view
re-validated after the gate, when the composite delta finally
exists. The decision certificate itself is never rewritten, so what
you read above is always what actually decided.