Skip to content

[tiny-modules] agent: explicit dispatch context for the remaining 9 task-locals (follow-up to #7609) #7801

Open

Description

@senamakel

Follow-up from #7609 (tinyagents epic #7540, master #7523).

Context

#7790 and #7799 moved 13 of OpenHuman's 22 non-test task_local! statics onto the explicit run context (HostTurnEnv / AgentChatEnv → OpenHumanRunContext, inherited by child()). The openhuman-task-local ratchet went from 106 to 85.

The 9 that remain are not turn-scoped. They are read at dispatch level, where no run context exists today. Moving them needs a design change, not a mechanical migration.

Remaining task-locals

Task-local Why it is hard
core/runtime/context (CoreContext) Read at ~130 sites, including RPC handlers with no run context
memory/scope Memory ops resolve scope ambiently (~40 sites)
turn_origin Policy reads it outside turns
approval/gate (×3) Approval gate state read by policy and RPC paths
fork_context PARENT_CONTEXT Orchestration engines and ~55 test sites read it ambiently
egress dedup ledger Emitted deep in the provider factory chain
media generation progress Video wait observer is built once per tool inside the vendored generator; needs an upstream per-call hook

Proposed approach

  1. Introduce an explicit dispatch context (DispatchCtx) passed through core::invoke::invoke_method and controller handlers, carrying CoreContext, memory scope, turn origin and the approval gate. Turns derive it from OpenHumanRunContext.
  2. Thread an explicit parent through the orchestration engines (fork_context).
  3. Add a ledger parameter through the provider factory for egress dedup.
  4. Add a per-call progress hook upstream in the media generator, then bind it host-side.
  5. Lower the openhuman-task-local ratchet to 0 and delete the remaining baseline entries.

Acceptance

  • No non-test task_local! left in crates/openhuman-core.
  • check-agent-runtime-boundary.mjs has no openhuman-task-local baseline entries.
  • Tests prove each value reaches RPC handlers, sub-agent forks, spawned tasks and await points.

Activity

  1. added
    priority: p3Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect.
    on Oct 14, 2026
  2. tinysweeper commented on Oct 14, 2026

    @tinysweeper

    Follow-up refactor tracking the remaining 9 non-turn-scoped task_locals in crates/openhuman-core that need an explicit dispatch context (DispatchCtx) threaded through invoke_method, orchestration engines, the provider factory ledger, and the media generator progress hook, then ratcheting the openhuman-task-local baseline to zero. This is planned evolution of the tinyagents epic rather than a broken behavior, so it is a scoped engineering task at p3.

    Labelled priority: p3.

  3. added theissue type on Oct 14, 2026
  4. added 5 commits that reference this issue on Oct 14, 2026
  5. senamakel commented on Oct 14, 2026

    @senamakel
    MemberAuthor

    Progress on #7801 (merged, each gated locally): #7818 egress ledger (4cdc3ff), #7823 parent execution context (7e025ee), #7825 media job state (c4b0eb9), #7828 web_chat turn activity (ef55987), #7829 shell output meter plus a ratchet rule (0f0e65b). Non-test task_local! statics: 11 -> 6; boundary baseline 114 -> 99 entries (it also dropped 2 stale re-export entries).

    Done:

    • egress dedup: explicit EgressLedger through the provider factory chain.
    • fork_context PARENT_CONTEXT: parent is a value (spawn_agent(parent, ..), with_root_parent(.., |parent| ..), run_subagent options).
    • media progress: no upstream hook was needed. MediaArtifactTool builds the video tool per call around an observer bound to the call's own slot, so no vendor change or pin move.
    • CURRENT_TURN_ACTIVITY and CURRENT_METER (not in the original table) are explicit too.
    • check-agent-runtime-boundary now rejects a task_local! declaration in any non-test file outside the four below.

    Remaining (6 statics in 4 files), not changed here on purpose:

    • CURRENT_CONTEXT (core/runtime/context.rs), memory/scope.rs CURRENT, agent/turn_origin.rs AGENT_TURN_ORIGIN (a legacy mirror: with_origin already also derives it onto CoreContext), and security/approval/gate.rs APPROVAL_CHAT_CONTEXT / APPROVAL_COPILOT_STREAM_CONTEXT / APPROVAL_FLOW_RUN_CONTEXT.
    • Why: these are read from deep, non-tool code (config loading and 33 CoreContext::current() sites, ~80 turn_origin sites, egress/artifacts/web3 readers of the chat context) that has no run context to take a parameter from, and spawn_scoped carries CoreContext and memory identity across spawns. Threading a DispatchCtx through invoke_method and 573+ fn(Map) -> ControllerFuture handlers does not remove those readers by itself.
    • Suggested next steps: (1) fold AGENT_TURN_ORIGIN into the CoreContext copy that with_origin already derives (needs a bare-context constructor so tests without a default context keep working); (2) carry the copilot flag and flow run id on the origin or run context (touches TrustedAutomationSource::Workflow, which is public in openhuman-embed); (3) only then replace the single remaining ambient CURRENT_CONTEXT with an explicit DispatchCtx handler parameter. Care point for (1)/(2): the approval chat context deliberately does not cross spawn_scoped today (detached sub-agents rely on its absence, Bug: cron_add blocked by security policy — agent turn has no origin label #5499), so it must not ride on CoreContext without being stripped at that boundary.
      Leaving this issue open for those.
  6. senamakel commented on Oct 14, 2026

    @senamakel
    MemberAuthor

    Progress on #7801: #7831 (turn origin onto CoreContext, AGENT_TURN_ORIGIN gone), #7837 (approval context explicit: APPROVAL_CHAT_CONTEXT / APPROVAL_COPILOT_STREAM_CONTEXT / APPROVAL_FLOW_RUN_CONTEXT gone), plus three regression fixes the first change exposed (#7832 bare context invisible to non-origin readers, #7835 origin survives a context change, #7836 stray test storage.db). Non-test task_local! statics: 6 -> 2.

    Left, and why it was not done mechanically:

    • memory/scope.rs CURRENT: ~10 production readers (confine, brain, lifecycle, tool_writes, memory_ingest, CallFacts, cost scope, tinymemes) behind RPC handlers that run unscoped (= root identity) and agent tools; the identity is also carried across spawn_scoped on purpose. Needs an explicit MemoryIdentity on the confine/ops/lifecycle entry points plus the spawn sites; embed's memory facade must be checked for the identity it expects. Doable as its own PR.
    • core/runtime/context.rs CURRENT_CONTEXT: not a threading job. ~70 CoreContext::current() / scoped() readers plus every current_slot, tenant, config-loader (load_config_with_timeout reads the embedder config) and storage-scope reader hang off it, and those run under ~570 fn(Map) controller handlers with no parameter to carry it. It needs the DispatchCtx handler signature decision first.
    • Two seams still need an upstream tinytools change to go fully explicit: NetGate::disclose / PaymentHook::pay take no run context (worked around by per-call tool construction, tools::per_call), and the cloud embedder's egress guard (its disclosures are no longer chat-routed).
  7. senamakel commented on Oct 14, 2026

    @senamakel
    MemberAuthor

    Progress on #7801: #7838 (root tests ported off the removed with_parent_context, Refs #7823) and #7839 (memory/scope.rs CURRENT gone: explicit MemoryIdentity on confine/brain/lifecycle, on the run context for turns, sub-agents and team members; denied by the boundary ratchet). Non-test task_local! statics: 2 -> 1 (CURRENT_CONTEXT). Cloud embedding disclosures can be chat-routed again through default_embedding_provider_for_chat / create_embedding_provider_for_chat; the Jev tool-search retriever stays unrouted until RankContext carries a chat upstream.

  8. added 4 commits that reference this issue on Oct 14, 2026
  9. 60 remaining items

  10. senamakel commented on Oct 16, 2026

    @senamakel
    MemberAuthor

    Round 12 (#7905, merged): flows builder gates take the caller's DispatchCtx. ambient+load_config 64 -> 60 (ambient 55, load_config_with_timeout 5; scope 33, current 14, scoped 4, current_tenant 2).

  11. senamakel commented on Oct 16, 2026

    @senamakel
    MemberAuthor

    Round 13 (#7908, merged): flows LLM cap and inference-readiness probe take an explicit DispatchCtx; ambient wrappers create_chat_model/_with_model_id removed. Counts on main: ambient 53, load_config_with_timeout 5, spawn_scoped 0, spawn_blocking_scoped 0, CoreContext::scope( 33, current( 14, scoped( 4, current_tenant( 2. Remaining ambient sites are mostly boundary fallbacks (trait predicates, bus handlers without event tenant, module-bus legacy imports).

  12. senamakel commented on Oct 16, 2026

    @senamakel
    MemberAuthor

    Round 14 (#7909): voice services + trigger archive close take explicit DispatchCtx. ambient 53->46, load_config_with_timeout 5, spawn_scoped 0, spawn_blocking_scoped 0, scope 33, current 14, scoped 4, current_tenant 2.

  13. added a commit that references this issue on Oct 16, 2026
  14. senamakel commented on Oct 16, 2026

    @senamakel
    MemberAuthor

    Round 15 (#7915 merged): module legacy storage importers (import_once, tinyconnectors/juice/mcp/wallet/tinybox) take an explicit DispatchCtx. ambient + load_config_with_timeout 51 -> 48 (ambient 43, load_config_with_timeout 5). Remaining: scope 33, current 14, scoped 4, current_tenant 2.

  15. added a commit that references this issue on Oct 16, 2026
  16. senamakel commented on Oct 16, 2026

    @senamakel
    MemberAuthor

    Round g-1 (#7918, merged): DispatchCtx::ambient 43->40, load_config_with_timeout( 5 (total 48->45); CoreContext::scope( 33, current( 14, scoped( 4, current_tenant( 2 unchanged. Removed load_current_or_init_ambient; MCP callback task and storage relay now spawn under explicit contexts.

  17. added a commit that references this issue on Oct 16, 2026
  18. senamakel commented on Oct 16, 2026

    @senamakel
    MemberAuthor

    Round 2 (PR #7919, merged): flow runs now act under a context carrying their Workflow origin (DispatchCtx::with_turn_origin); gate_call_for_tier, the nested agent runner and flow memory adapter read it from there. Remaining production: ambient 36, load_config_with_timeout 5, spawn_scoped 0, spawn_blocking_scoped 0, scope 33, current 14, scoped 4, current_tenant 2.

  19. added a commit that references this issue on Oct 16, 2026
  20. senamakel commented on Oct 16, 2026

    @senamakel
    MemberAuthor

    Round 3 (#7922, merged): four turn-origin reads (SecurityGate::turn_origin, effective_agent_chat_origin, Agent::run_single_with_approval, begin_user_effects) now read the context their caller holds. ambient 36 -> 32, load_config_with_timeout 5 -> 5 (total 41 -> 37). Remaining sites mostly have no context in reach: trait predicates, module legacy-import callers (connectors, tinybox, mcp, tokenjuice), session_store workspace chain (~14 turn_states_for callers), web_chat publish callers, prompt context (needs a PromptContext field).

  21. added a commit that references this issue on Oct 16, 2026
  22. senamakel commented on Oct 16, 2026

    @senamakel
    MemberAuthor

    Round 4 (#7926, merged): ToolHook::before_tool_decision_in and TurnContext::with_dispatch pass the turn's context to the profile belt and permission hook. ambient 32 -> 30, load_config_with_timeout 5 -> 5 (total 37 -> 35). spawn_scoped 0, spawn_blocking_scoped 0. Remaining blockers unchanged (trait predicates, module legacy-import callers, session_store workspace chain, web_chat publish callers, prompt context, FlowsHost, storage/composio caches, ranker).

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

    priority: p3Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect.

    Type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions