Doramagic Project Pack · Human Manual
loopgain
**An open-source cost controller for AI agent loops.**
Core LoopGain Engine and Trajectory Classifier
Related topics: Funnel Telemetry: Lifecycle, Initialization, and Known Issues, Usage Examples, Error Signals, and Common Workflows
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Funnel Telemetry: Lifecycle, Initialization, and Known Issues, Usage Examples, Error Signals, and Common Workflows
Core LoopGain Engine and Trajectory Classifier
The Core LoopGain engine is the central runtime that records, classifies, and routes agent trajectories during a session, while the Trajectory Classifier is the component that decides which trajectories are worth keeping, replaying, or discarding. Together they implement the closed-loop "observe → classify → adapt" pattern that gives the project its name.
Purpose and Scope
LoopGain provides a feedback loop for AI agent systems: each agent step is observed, scored by the trajectory classifier, and the resulting signal is fed back into adapter configuration so that subsequent steps converge faster on successful paths. The package exposes this through a small public surface defined in loopgain/__init__.py, where the engine and classifier are re-exported alongside funnel helpers. Source: loopgain/__init__.py:1-40.
The engine is intentionally engine-agnostic. It does not call LLMs directly; instead it accepts structured events (on_init, on_first_observe, note_outcome, note_adapter) and produces classification verdicts and adapter suggestions. This separation lets it sit in front of any agent framework without owning the agent's decision logic.
Core Engine Architecture
The engine in loopgain/core.py maintains three pieces of state: a mode flag (_mode), a lazy-loaded classifier instance, and an in-memory trajectory buffer. The mode flag is what gates whether the engine is actively recording. Source: loopgain/core.py:1-80.
Initialization follows a lazy-load pattern. The first call to on_init() or on_first_observe() invokes _ensure_loaded(), which is responsible for hydrating the classifier, defaulting _mode to _ENABLED, and applying any pending configuration. Until that method runs, _mode remains None. Source: loopgain/core.py:42-78.
note_outcome() and note_adapter() are the two recording entry points. Each guards on self._mode != _ENABLED before doing any work. This guard is the subject of a known community bug: if these methods are invoked before _ensure_loaded() runs, _mode is None rather than _DISABLED, and the comparison None != _ENABLED is still True in Python — but downstream code that assumes _mode is either _ENABLED or _DISABLED can misclassify the pre-init window as enabled and silently no-op. Source: loopgain/funnel.py:120-180. The funnel module wraps these engine methods to provide the public note_outcome / note_adapter API and inherits the same lifecycle hazard.
| State | _mode value | Behavior of note_outcome |
|---|---|---|
| Pre-init | None | Guard evaluates, but state is ambiguous (bug) |
| Loaded, enabled | _ENABLED | Records outcome, forwards to classifier |
| Loaded, disabled | _DISABLED | Returns early, no record |
Trajectory Classifier
The classifier lives in loopgain/classifier.py and is the only component that decides what to do with a recorded trajectory. It exposes a classify(trajectory) method that returns a verdict (KEEP, REPLAY, DROP) and, for KEEP verdicts, a confidence score and suggested adapter parameters. Source: loopgain/classifier.py:1-60.
The classifier ships in two protocol versions. Version 1 was the original heuristic-only scorer; version 2, documented in PROTOCOL_v2_classifier.md, adds structured feature extraction (step count, error rate, token efficiency, adapter delta) and a learned scoring head. The engine selects the version based on configuration at _ensure_loaded() time. Source: PROTOCOL_v2_classifier.md:1-120.
Reported evaluation results for v2 are summarized in RESULTS_v2_classifier.md, including agreement with human-labeled trajectories and rejection rates on adversarial inputs. Source: RESULTS_v2_classifier.md:1-80. These results are what the engine uses to calibrate confidence thresholds when emitting adapter suggestions back to the caller.
Lifecycle and Known Pitfalls
The engine's lifecycle is: construct → first observe → load → record → classify → adapt. Each stage is observable, and the funnel layer (loopgain/funnel.py) is the recommended entry point because it enforces ordering on the caller's behalf. Source: loopgain/funnel.py:1-100.
The community-reported initialization bug is worth highlighting for any integrator: calling note_outcome() or note_adapter() before on_init() is currently a silent no-op rather than a raised error. A defensive workaround is to call on_first_observe() with a synthetic event before any recording call, which forces _ensure_loaded() to run and pins _mode to a known value. Source: loopgain/funnel.py:140-180.
For trajectory inspection, the engine writes recorded events to an in-memory ring buffer keyed by session id; the classifier consumes them lazily during classify(). This means recording is O(1) per event and classification cost scales with the configured trajectory window, not total session length. Source: loopgain/core.py:90-140.
Putting It Together
flowchart LR
Agent[Agent Loop] -->|on_first_observe| Core[loopgain/core.py]
Core -->|ensure_loaded| Cls[loopgain/classifier.py]
Agent -->|note_outcome| Funnel[loopgain/funnel.py]
Agent -->|note_adapter| Funnel
Funnel --> Core
Core -->|trajectory| Cls
Cls -->|verdict + adapter| Core
Core -->|adapter suggestion| AgentThe data flow is unidirectional per cycle: the agent emits events into the funnel, the funnel forwards them to the core, the core buffers them, the classifier consumes the buffer and returns a verdict, and the verdict drives the next adapter suggestion. The loop closes when the agent applies that suggestion on a subsequent step.
Source: https://github.com/loopgain-ai/loopgain / Human Manual
Funnel Telemetry: Lifecycle, Initialization, and Known Issues
Related topics: Core LoopGain Engine and Trajectory Classifier
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Core LoopGain Engine and Trajectory Classifier
Funnel Telemetry: Lifecycle, Initialization, and Known Issues
Funnel telemetry is the subsystem inside loopgain/funnel.py that records user-visible outcomes and adapter signals emitted during an instrumented run, then forwards them through loopgain/telemetry.py according to an opt-in mode flag. This page documents how the funnel reaches an active state, which methods participate in that lifecycle, and the most prominent initialization bug filed by the community.
Purpose and Scope
The funnel module owns the runtime record-keeping surface for the loopgain CLI. Its methods are called by the rest of the codebase whenever a meaningful event occurs (an outcome completed, an adapter reported a signal, etc.). The funnel does not decide transport policy — that responsibility is delegated to loopgain/telemetry.py, which the funnel consults via the _mode flag and the _ENABLED constant. Entry points such as loopgain/__main__.py and loopgain/cli.py are responsible for triggering the funnel's lifecycle hooks during process startup. Source: loopgain/funnel.py:1-40
Lifecycle and Initialization
The funnel transitions through three observable states, driven by two public hooks:
- Uninitialized —
self._modeisNonebecause_ensure_loaded()has not yet run. - Loaded —
_ensure_loaded()has resolved the effective mode against the user's opt-in settings and_modeis set. - Enabled / Disabled — once loaded,
_modeequals either_ENABLEDor a sentinel that causes note methods to short-circuit.
The two public hooks that drive this transition are:
on_init()— typically invoked early in the CLI bootstrap path. It calls_ensure_loaded()so that subsequent note calls have a meaningful mode value.on_first_observe()— a safety-net hook invoked the first time the funnel is asked to record a signal. Ifon_init()was skipped, this hook still triggers_ensure_loaded().
Until either hook runs, _mode remains None, which is the root cause of the known issue described below. Source: loopgain/funnel.py:40-120
CLI startup ──► on_init() ──► _ensure_loaded() ──► _mode = _ENABLED | disabled
▲
First note call without on_init() ──► on_first_observe() ┘
The mode-resolution logic is shared with loopgain/telemetry.py, which centralizes the constants (_ENABLED) and the policy for resolving environment variables and CLI flags. Source: loopgain/telemetry.py:1-60
Telemetry API Surface
The funnel exposes two record-keeping methods, both gated on the mode flag:
note_outcome(...)— records a terminal outcome for the current run.note_adapter(...)— records a signal from an adapter integration.
Both methods begin with the same guard:
if self._mode != _ENABLED:
return
This guard is intentional once _mode has been resolved: it suppresses work when telemetry is disabled or when the process is running in a non-telemetry context. The methods themselves perform no I/O when disabled — they simply return. Source: loopgain/funnel.py:120-200
The CLI wires these methods into user-facing flows: loopgain/cli.py calls into the funnel at command boundaries, and loopgain/__main__.py ensures on_init() runs before any CLI handler executes so that the funnel is in the loaded state when user code first triggers a note call. Source: loopgain/cli.py:1-80, loopgain/__main__.py:1-40
Known Issue: Silent Pre-Initialization Failure
The community's top-engagement issue describes a quiet failure mode in the guard above. When note_outcome() or note_adapter() is invoked before either on_init() or on_first_observe() has been called, self._mode is still None. The comparison None != _ENABLED evaluates to True, so the method returns early without recording the event and without raising an error.
| Scenario | _mode value | Guard result | Behavior |
|---|---|---|---|
After on_init() | _ENABLED or disabled sentinel | matches policy | Correct |
After on_first_observe() only | _ENABLED or disabled sentinel | matches policy | Correct |
| Before either hook | None | True (returns early) | Silent data loss |
The practical impact is that callers who invoke the funnel before the CLI bootstrap completes — for example, from a library consumer, a test harness, or an embedded usage — see their telemetry events vanish without any diagnostic. Source: loopgain/funnel.py:120-200
The project documentation in TELEMETRY.md describes the intended public contract but, per the issue report, does not currently warn that the funnel must be initialized through one of the lifecycle hooks before any note method is safe to call. Source: TELEMETRY.md:1-80
Mitigations and Recommended Usage
Until the guard is corrected, callers should observe the following contract:
- Invoke
on_init()explicitly when constructing a process that uses the funnel outside the CLI entry point. - Rely on
on_first_observe()as a backstop only ifon_init()is impractical; it will lazily load the mode on first use. - Never assume
note_outcome()ornote_adapter()has recorded anything until the funnel has been initialized, and consider adding a startup-time assertion in test code that constructs the funnel directly.
These practices align with the lifecycle state machine documented above and ensure that the mode comparison in the guard reflects a resolved policy rather than the uninitialized None sentinel. Source: loopgain/funnel.py:40-200, loopgain/telemetry.py:1-60, TELEMETRY.md:1-80
Source: https://github.com/loopgain-ai/loopgain / Human Manual
Framework Adapters: Wiring LoopGain into Agent Stacks
Related topics: Core LoopGain Engine and Trajectory Classifier, Usage Examples, Error Signals, and Common Workflows
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Core LoopGain Engine and Trajectory Classifier, Usage Examples, Error Signals, and Common Workflows
Framework Adapters: Wiring LoopGain into Agent Stacks
Purpose and Scope
The loopgain/integrations/ package is the bridge between LoopGain's learning loop and the surrounding agent ecosystem. While the core funnel tracks observations, adapter references, and outcomes, the integrations package supplies thin, framework-specific shims that translate third-party orchestration events into funnel calls. Each module targets a single orchestration library and exposes a stable surface that downstream applications can import without depending on LoopGain internals directly. Source: loopgain/integrations/__init__.py:1-40
The adapters cover the most common agent runtimes in the Python ecosystem: stateful graph runtimes (langgraph.py), role-based multi-agent crews (crewai.py), conversational multi-agent dialogues (autogen.py), the broader langchain.py Runnable/Chain surface, and the OpenAI Agents SDK via openai_agents.py. Keeping these in a dedicated subpackage prevents framework-specific imports from leaking into the core and lets users opt in to only the runtime they actually use. Source: loopgain/integrations/langgraph.py:1-30, loopgain/integrations/crewai.py:1-30
Adapter Architecture
Each adapter follows the same conceptual shape: it listens for lifecycle events emitted by its target framework and forwards them to a shared Funnel instance. The funnel exposes the canonical hooks — on_init, on_first_observe, note_adapter, and note_outcome — and the adapters are responsible for mapping framework callbacks onto them. This separation lets the core stay framework-agnostic while integrations remain narrow and easy to audit. Source: loopgain/integrations/langgraph.py:30-80, loopgain/integrations/autogen.py:30-80
A typical adapter wraps the framework's execution primitive (a StateGraph, a Crew, an AgentRuntime, a Runnable, or an Agent runner) and installs hooks at well-known boundaries: start of run, completion of a node/tool call, and end of run. The hook bodies delegate immediately to the funnel so that adapters never own business state — they only translate events.
| Framework | Module | Primary Hook Translated |
|---|---|---|
| LangGraph | langgraph.py | Node entry/exit on the state graph |
| CrewAI | crewai.py | Agent and task lifecycle callbacks |
| AutoGen | autogen.py | Group chat / reply turn events |
| LangChain | langchain.py | Runnable start/end and Chain callbacks |
| OpenAI Agents | openai_agents.py | Agent loop and tool invocation events |
The package's __init__.py re-exports the public adapter entry points so that consumers can write from loopgain.integrations import langgraph_adapter (or similar) without reaching into submodules. This keeps the import surface stable even when internal refactors move symbols around. Source: loopgain/integrations/__init__.py:1-40, loopgain/integrations/langchain.py:1-30
Wiring LoopGain into an Agent Stack
The standard wiring pattern is the same across frameworks:
- Construct or obtain a
Funnelconfigured for the run (mode, persistence, identifiers). - Import the relevant adapter and pass the funnel into it together with the framework object (graph, crew, runtime, runnable, or agent).
- Run the framework normally; the adapter pipes events into the funnel transparently.
Concretely, an adapter is expected to call on_init when a run begins, on_first_observe on the first observable signal inside that run, note_adapter whenever a sub-component (tool, agent, node) is invoked, and note_outcome when the run terminates with success or failure. Because all four hooks live on the funnel, adapters stay declarative: they don't decide *what* to record, only *when*. Source: loopgain/integrations/langgraph.py:40-100, loopgain/integrations/openai_agents.py:40-100
The funnel in turn guards each hook against premature calls by checking its internal _mode flag against the _ENABLED constant. This guard exists so that adapters firing before initialization do not crash, but it relies on _ensure_loaded() having run at least once. Source: loopgain/integrations/__init__.py:1-40
Known Issue: Silent Skip When `_mode` Is Uninitialized
A reported bug illustrates a sharp edge at the adapter/funnel boundary: note_outcome() and note_adapter() on the funnel short-circuit when self._mode != _ENABLED, but _mode is None until _ensure_loaded() is invoked. Adapters that fire these hooks before the funnel has been initialized — for example, an OpenAI Agents tool callback that runs ahead of on_init, or a LangGraph node that emits during graph construction — will silently no-op instead of buffering or raising. Source: loopgain/integrations/__init__.py:1-40, loopgain/integrations/openai_agents.py:60-120
The practical mitigation for adapter authors is to guarantee that Funnel.on_init() (or any method that triggers _ensure_loaded()) is called before the framework can emit any event. Wiring adapters around a funnel that is constructed and initialized synchronously up front avoids the race. Downstream users encountering missing observations should verify that on_init precedes the first framework callback. Source: loopgain/integrations/crewai.py:50-110, loopgain/integrations/langchain.py:50-110
This issue is also a useful diagnostic signal: if an integration "works" but LoopGain reports no outcomes, the most likely cause is hook ordering rather than a framework incompatibility.
Source: https://github.com/loopgain-ai/loopgain / Human Manual
Usage Examples, Error Signals, and Common Workflows
Related topics: Core LoopGain Engine and Trajectory Classifier, Framework Adapters: Wiring LoopGain into Agent Stacks
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Core LoopGain Engine and Trajectory Classifier, Framework Adapters: Wiring LoopGain into Agent Stacks
Usage Examples, Error Signals, and Common Workflows
Loopgain ships with a progression of runnable examples that demonstrate how to wire the funnel into real tasks, how to interpret the signals it emits when something goes wrong, and how the canonical happy-path workflow looks from initialization to termination. This page walks through the example catalog, the error and stall signals users should learn to recognize, and the common workflows that tie them together.
Example Catalog and What Each One Demonstrates
The examples/ directory is organized as a tutorial in escalating difficulty. Each script is self-contained and exercises a single failure mode or use case.
01_code_pytest.py— The introductory example. It pairs the funnel with a Python code-generation task whose quality signal is the pytest exit status. It shows the minimum viable loop: build a funnel, attach a generator, run the observe-and-judge cycle, and let the adapter adjust. Source: examples/01_code_pytest.py02_json_extract.py— Demonstrates a structured-output task where the reward is schema-conformance. It illustrates how the funnel handles objective, machine-checkable scores that do not require an LLM judge. Source: examples/02_json_extract.py03_essay_critique.py— Introduces a subjective task scored by a critic model. The funnel delegates the reward signal to a separate evaluator rather than a test suite. Source: examples/03_essay_critique.py04_sql_synth.py— A domain-specific synthesis task (SQL generation) with execution-based validation. It demonstrates how to plug a custom scorer into the funnel without subclassing core components. Source: examples/04_sql_synth.py05_unsolvable_stalls.py— A diagnostic example. It deliberately sets up a problem the funnel cannot make progress on so users can see the stall signal in action. Source: examples/05_unsolvable_stalls.py06_diverges.py— Demonstrates divergence: a scenario where the adapter's updates push the generator further from a solution instead of closer, and how the funnel surfaces that behavior. Source: examples/06_diverges.py
Error Signals and Silent-Failure Modes
Loopgain communicates state primarily through return values and log lines rather than raised exceptions. Learning the vocabulary of these signals is essential because several APIs are intentionally non-raising by design.
`_mode` Is `None` Until Initialization
The community-reported issue (top-engagement bug) describes a silent failure: note_outcome() and note_adapter() check self._mode != _ENABLED before recording events, but self._mode is None until _ensure_loaded() runs inside on_init() or on_first_observe(). Any user-supplied scorer or callback that calls those methods before initialization will appear to succeed while contributing no data. Source: loopgain/funnel.py
The practical symptom is that the funnel reports a normal-looking loop with no recorded outcomes and no adapter updates. Diagnostic steps:
- Confirm
on_init()oron_first_observe()has executed before any external caller invokesnote_outcome()/note_adapter(). - Check the funnel's logs for the
_ensure_loaded()marker line; if absent, the recorded event count will be zero regardless of how many times the API was called.
Stall Signals
05_unsolvable_stalls.py is the canonical demonstration. When the funnel detects that the reward distribution has not improved over a configured window, it emits a stall signal. Code that depends on continuous progress should handle this by either relaxing the task, raising the iteration budget, or terminating the loop gracefully.
Divergence Signals
06_diverges.py shows the opposite pathology: rewards trend downward. The funnel logs a divergence marker and, depending on configuration, may auto-pause adapter updates. Callers should treat divergence as a signal to inspect the adapter's reward shaping rather than the generator itself.
Common Workflows
The happy-path workflow across all six examples follows a consistent shape. The diagram below captures the canonical state transitions and where the error signals above attach.
stateDiagram-v2
[*] --> Uninitialized
Uninitialized --> Loaded: on_init()
Loaded --> Observing: on_first_observe()
Observing --> Observing: note_outcome()
Observing --> Observing: note_adapter()
Observing --> Converged: reward plateau met
Observing --> Stalled: no progress
Observing --> Diverging: reward decreasing
Stalled --> [*]
Diverging --> [*]
Converged --> [*]In practice the steps are:
- Construct a funnel instance and configure the generator, scorer, and adapter.
- Initialize by calling
on_init(). From this point onwardself._mode == _ENABLEDand downstream event calls are recorded. Source: loopgain/funnel.py - Observe candidate outputs through
on_first_observe()followed by repeatedon_observe()cycles. Each observation should be paired with a call tonote_outcome()from the scorer. - Adapt by calling
note_adapter()whenever the adapter performs an update step. Skipping this call leaves the funnel's view of the adapter stale. - Terminate when the funnel reaches a converged, stalled, or diverging terminal state, as exemplified by
05_unsolvable_stalls.pyand06_diverges.py. Source: examples/05_unsolvable_stalls.py, Source: examples/06_diverges.py
Task-Type Patterns
Three recurring patterns cover most user scenarios:
- Test-based scoring —
01_code_pytest.pyand04_sql_synth.pyboth rely on an external command producing a pass/fail. The funnel treats the boolean reward as ground truth and converges quickly on objective tasks. - Schema-based scoring —
02_json_extract.pyuses deterministic structural validation. This pattern is robust and produces the cleanest convergence traces. - LLM-judge scoring —
03_essay_critique.pyintroduces a noisy, subjective scorer. Expect more variance, and pair this pattern with explicit stall tolerances because the reward signal itself is non-deterministic.
Best Practices
- Always invoke
on_init()before any custom scorer wires in, to avoid the silent-failure mode documented in the community issue. - Treat any terminal state (
Converged,Stalled,Diverging) as a first-class outcome worth logging, not as an error to suppress. - When porting a workflow from one example to another, preserve the event-recording order:
on_first_observe→note_outcome→note_adapter. Reordering breaks the funnel's internal accounting.
Source: https://github.com/loopgain-ai/loopgain / Human Manual
Doramagic Pitfall Log
Source-linked risks stay visible on the manual page so the preview does not read like a recommendation.
May increase setup, validation, or first-run risk for the user.
May increase setup, validation, or first-run risk for the user.
May increase setup, validation, or first-run risk for the user.
May increase setup, validation, or first-run risk for the user.
Doramagic Pitfall Log
Found 8 structured pitfall item(s), including 0 high/blocking item(s). Top priority: Configuration risk - Configuration risk requires verification.
1. Configuration risk: Configuration risk requires verification
- Severity: medium
- Finding: Project evidence flags a configuration risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Recommended check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: capability.host_targets | https://news.ycombinator.com/item?id=48919562
2. Capability evidence risk: Capability evidence risk requires verification
- Severity: medium
- Finding: README/documentation is current enough for a first validation pass.
- User impact: May increase setup, validation, or first-run risk for the user.
- Recommended check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: capability.assumptions | https://news.ycombinator.com/item?id=48919562
3. Runtime risk: Runtime risk requires verification
- Severity: medium
- Finding: Project evidence flags a runtime risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Recommended check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: community_evidence:github | https://github.com/loopgain-ai/loopgain/issues/1
4. Maintenance risk: Maintenance risk requires verification
- Severity: medium
- Finding: Project evidence flags a maintenance risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Recommended check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: evidence.maintainer_signals | https://news.ycombinator.com/item?id=48919562
5. Security or permission risk: Security or permission risk requires verification
- Severity: medium
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Recommended check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: downstream_validation.risk_items | https://news.ycombinator.com/item?id=48919562
6. Security or permission risk: Security or permission risk requires verification
- Severity: medium
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Recommended check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: risks.scoring_risks | https://news.ycombinator.com/item?id=48919562
7. Maintenance risk: Maintenance risk requires verification
- Severity: low
- Finding: issue_or_pr_quality=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Recommended check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: evidence.maintainer_signals | https://news.ycombinator.com/item?id=48919562
8. Maintenance risk: Maintenance risk requires verification
- Severity: low
- Finding: release_recency=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Recommended check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: evidence.maintainer_signals | https://news.ycombinator.com/item?id=48919562
Source: Doramagic discovery, validation, and Project Pack records
Community Discussion Evidence
These external discussion links are review inputs, not standalone proof that the project is production-ready.
Count of project-level external discussion links exposed on this manual page.
Open the linked issues or discussions before treating the pack as ready for your environment.
Community Discussion Evidence
Doramagic exposes project-level community discussion separately from official documentation. Review these links before using loopgain with real data or production workflows.
- note_outcome() and note_adapter() silently fail when called before on_in - github / github_issue
- Configuration risk requires verification - GitHub / issue
Source: Project Pack community evidence and pitfall evidence