Skip to content

Memory-access prompt section silently drops once AgentDefinitionRegistry::init_global has run — intermittently red main, blocking PRs #6199

Description

@M3gA-Mind

Summary

main is intermittently red on openhuman::agent::harness::session::builder::builder_tests::part_01_tests::memory_access_instruction_is_present_with_learning_disabled, and it is blocking unrelated PRs. The cause is not flakiness in the usual sense: the outcome flips deterministically on whether AgentDefinitionRegistry::init_global has already run anywhere in the test process.

Evidence that this is on main, not on any one PR

Same test, same assertion, same line (builder_tests_part_01_tests.rs:543, "the memory-access section must not be gated on learning.enabled") on main twice today:

When Run Shape
2026-09-10 06:34 34445793082 (merge of #6173) full suite — 11765 passed; 1 failed
2026-09-10 12:23 34476483764 (merge of #6182) scoped -- openhuman::agent2397 passed; 1 failed

It also failed on PR #6189, whose diff touches only agent/harness/subagent_runner/ops/graph* and cannot reach the prompt builder. The PR CI Gate failure there is downstream — Coverage never produced diff-coverage.md.

Root cause — reproduced deterministically

build_session_agent_inner(config, "orchestrator", /* definition */ None, …) takes the legacy orchestrator fallback at src/openhuman/agent/harness/session/builder/factory.rs:844:

(None, Some(reg)) => {
    let synthed = match reg.get("orchestrator") {
        Some(orch_def) => collect_orchestrator_tools(orch_def, reg,),
        None => Vec::new(),
    };
    (synthed, None)   // filter_from_scope = None
}

and then, at factory.rs:911:

let mut visible = match filter_from_scope {
    Some(set) => set,
    None => delegation_tools.iter().map(|t| t.name().to_string()).collect(),
};

So visible is populated from the delegation tools alone. any_tool_offered (agent/learning/prompt_sections.rs:222) reads it as a whitelist:

let allowed_by_filter = visible.is_empty() || visible.contains(*name);
  • Registry not initialiseddelegation_tools empty → visible empty → is_empty() means "no filter" → MemoryAccessSection registered → test passes.
  • Registry initialisedvisible = delegation tool names, which do not include memory_recall / memory_search → section skipped (helpers.rs:131) → test fails.

AgentDefinitionRegistry::init_global is a process-global, called lazily from src/openhuman/memory/goals/enrich.rs:117 and at startup from src/openhuman/platform/socket/medulla/mod.rs:403. Whether some earlier test in the same process has tripped it is decided by parallel test scheduling — hence red under --test-threads default and green when re-run serially.

Probe

Dropped into builder_tests_part_01_tests.rs, building the same agent twice in one process with only init_global in between:

let before = /* build_system_prompt */.contains(MEMORY_ACCESS_INSTRUCTION.trim());
let init = AgentDefinitionRegistry::init_global(&config.workspace_dir);
let after  = /* same build again */.contains(MEMORY_ACCESS_INSTRUCTION.trim());
PROBE registry_global_before=true init_ok=true section_before=true section_after=false

true → false from that one call. Not a timing race — a state flip.

Why this may be more than a test bug

visible is not only prompt gating. Per the comment at factory.rs:918, it is "the single source of truth that the policy session, advertised specs, and the run-time visible-name gate all consume". On this path a session whose definition scope is absent or Wildcard ends up with a non-empty visible containing only delegation tools — which reads as "these tools and nothing else" rather than the intended "no filter".

If any production path reaches build_session_agent_inner with definition: None while the registry is live — and the registry is initialised at startup — that session would lose the #6040 memory-access instruction, and possibly more. I have not verified which production callers pass definition: None, so I am not claiming a shipped regression; that is the question this issue needs answered first.

Suggested direction

The None => delegation_tools… arm conflates two meanings of an empty visible. A wildcard/absent scope that merely synthesised delegation wrappers is not an allowlist, and collapsing it into one loses that. Options, in preference order:

  1. Keep "no filter" as no filter: when filter_from_scope is None, leave visible empty and track delegation tool names separately for whatever needs to advertise them.
  2. If visible must carry the delegation names, union in the base tool set on the wildcard path, the way the disallowed_tools + ToolScope::Wildcard branch at factory.rs:932 already does.

Either way the test stops depending on global registry state, which is the property worth pinning with a regression test.

Reproduce

cargo test -p openhuman --lib --features "$(bash scripts/ci/product-features.sh)"

Fails intermittently. The cluster it appears in varies run to run — I saw budget_gate::{dropping_the_crate_permit_releases_the_scheduler_permit, explicit_release_returns_capacity_before_end_of_scope}, mcp::registry::tools::list_tools_errors_for_unconnected_server, integrations::composio::tools::list_toolkits_in_direct_mode_returns_empty_without_hitting_backend, and modules::ops::a_bounded_wait_with_nothing_cached_and_downloads_off_fails_rather_than_loading alongside it. Those others are not covered by this analysis and may well have separate causes; only the memory-access one is root-caused here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions