feat(explore): U4+U13 synkron - utforskningssloeyfa som mandat-former (ORDRE 20260823T185602Z) [skip-docs]

Magentic legges OVER den normative sloeyfa, aldri inni Steg 3: prompt +
kunnskapsbaser -> Mandate -> run_project(mandate=...) UENDRET. Manageren velger
VEI; det som forlater friheten er et Mandate, aldri et forslag. explore() skriver
ingenting - niva 3 (skriverettigheter) tilhoerer pipelinen alene.

Levert i denne oekten (kjernen; kallstedene staar til oekt 57):
- ExplorationContract: seks paakrevde felt uten default. max_reset_count=0 nektes
  paa en MAALING - reset_count >= max_reset_count mot en teller som starter paa 0
  terminerer kjoeringen FOER foerste runde med null ledger-events, altsaa en
  utforskning som utforsket ingenting, forkledd som en stall som aldri skjedde.
- explore() + fresh_exploration_workflow(): fersk builder per utforskning,
  BudgetMiddleware paa HVER agent inkl. manageren, synkron plan review via
  request_info, og max_plan_revisions som binder den ubundne revise-loekka.
- Tre kanaler: tokens OG runder raiser BudgetExceeded (rundene oversatt av vaart
  lag som kind="exploration_rounds", fordi orkestreringen maalt ikke raiser ved
  sitt eget rundetak); alt semantisk er en VERDI i stop.
- quick_validate (niva 1, raadgivende) + navigator-verktoey over safe_resolve.
- U14s tre utsatte events landet som span-events paa EN exploration-span.

Load-bearing MAALT mot HELE suiten, groenn kontroll 975/5, golden ea8c534
uendret: tolv mutasjoner alle roede. TO av dem falsifiserte testen foerst -
skrivefrihets-testen naadde aldri en verktoeykropp (ScriptedChatClient emitterer
ingen verktoeykall), og stdout-testens capsys er blind for ConsoleSpanExporter,
hvis out-default bindes ved modulimport. Begge er rettet; stdout-armen er naa en
subprosess, som er P4-presedensen.

[skip-docs] fordi flaten ikke er naabar for en bruker enna: --explore, det
whitelistede hosting-feltet og sim-scenarioet bygges i oekt 57, og en
README-oppfoering naa ville vaert en paastand om en inngang som ikke finnes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-08-23 22:39:00 +02:00
commit f0c54cc8dc
4 changed files with 1892 additions and 4 deletions

View file

@ -37,10 +37,14 @@ cause.
(``opentelemetry-exporter-otlp-proto-grpc`` / ``-http``) are not declared dependencies. They are
egress, they drag grpc and protobuf into a published wheel for a mode that is off by default, and
MAF already raises an ``ImportError`` that names the package to install. Stated honesty limit:
``PORTFOLIO_OTEL=otlp`` works only after the operator installs one of them. Equally absent are the
``PLAN_CREATED`` / ``REPLANNED`` / ``PROGRESS_LEDGER_UPDATED`` events the plan names they belong
to the exploration loop (U4), which does not exist yet, and an emitter written before its call site
is a shape guessed rather than measured.
``PORTFOLIO_OTEL=otlp`` works only after the operator installs one of them.
**The ``PLAN_CREATED`` / ``REPLANNED`` / ``PROGRESS_LEDGER_UPDATED`` events now exist** (U4, økt
56). They were held back here in økt 55 on the ground that an emitter written before its call site
is a shape guessed rather than measured; the call site is ``explore._absorb``, and the events are
recorded on the exploration span this module's ``exploration_tracer`` hands out. Nothing about the
contract above changed: with tracing off there is no provider, so those events are discarded like
every other span this process makes.
MAF-touching by construction, so this module never enters the framework-neutral context layer
(``okf.py``); the ``test_okf_is_maf_free`` guard keeps that boundary.
@ -233,3 +237,29 @@ def tracing_notice(setup: TracingSetup) -> str | None:
f" Tracing: {TRACING_ENV}={MODE_OTLP} — OpenTelemetry spans are EXPORTED OVER THE NETWORK "
f"to the endpoints declared below\n{rows}"
)
#: The instrumentation scope every exploration span is created under. One name, so a collector
#: can select this framework's own spans apart from MAF's (``invoke_agent``, ``workflow.run``)
#: without matching on span names that MAF owns and may rename.
EXPLORATION_TRACER_NAME: Final = "portfolio_optimiser.explore"
def exploration_tracer() -> Any:
"""The tracer the exploration loop records its decisions on.
``get_tracer`` is safe to call whether or not a provider was installed: with none, OpenTelemetry
hands back a no-op tracer and every span and event is discarded. That is the SAME shape MAF's
own instrumentation already has (``ENABLE_INSTRUMENTATION`` defaults to True and its spans are
thrown away for want of a provider), and it is what lets the exploration emit unconditionally.
Gating emission on ``PORTFOLIO_OTEL`` would be a second resolution of a rule this module owns,
free to disagree with the providers actually installed.
A FUNCTION rather than a module-level tracer, and the reason is ordering: ``configure_tracing``
runs at process startup, and a tracer bound at import time would have been taken from the
global provider that existed BEFORE it a no-op one, permanently. It is also the seam a test
substitutes a local provider through, without installing anything globally.
"""
from opentelemetry import trace
return trace.get_tracer(EXPLORATION_TRACER_NAME)