You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[tiny-modules] agent: explicit dispatch context for the remaining 9 task-locals (follow-up to #7609) #7801
#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_contextPARENT_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
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.
Thread an explicit parent through the orchestration engines (fork_context).
Add a ledger parameter through the provider factory for egress dedup.
Add a per-call progress hook upstream in the media generator, then bind it host-side.
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.
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.
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.
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).
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.
Activity
tinysweeper commentedon Oct 14, 2026
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.Merge pull request #7818 from senamakel/fu-7801-1
Merge pull request #7823 from senamakel/fu-7801-2
Merge pull request #7825 from senamakel/fu-7801-3
Merge pull request #7828 from senamakel/fu-7801-4
Merge pull request #7829 from senamakel/fu-7801-5
senamakel commentedon Oct 14, 2026
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:
EgressLedgerthrough the provider factory chain.fork_contextPARENT_CONTEXT: parent is a value (spawn_agent(parent, ..),with_root_parent(.., |parent| ..),run_subagentoptions).MediaArtifactToolbuilds the video tool per call around an observer bound to the call's own slot, so no vendor change or pin move.CURRENT_TURN_ACTIVITYandCURRENT_METER(not in the original table) are explicit too.check-agent-runtime-boundarynow rejects atask_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_originalready also derives it ontoCoreContext), andsecurity/approval/gate.rsAPPROVAL_CHAT_CONTEXT / APPROVAL_COPILOT_STREAM_CONTEXT / APPROVAL_FLOW_RUN_CONTEXT.CoreContext::current()sites, ~80turn_originsites, egress/artifacts/web3 readers of the chat context) that has no run context to take a parameter from, andspawn_scopedcarries CoreContext and memory identity across spawns. Threading aDispatchCtxthroughinvoke_methodand 573+fn(Map) -> ControllerFuturehandlers does not remove those readers by itself.AGENT_TURN_ORIGINinto theCoreContextcopy thatwith_originalready 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 (touchesTrustedAutomationSource::Workflow, which is public inopenhuman-embed); (3) only then replace the single remaining ambientCURRENT_CONTEXTwith an explicitDispatchCtxhandler parameter. Care point for (1)/(2): the approval chat context deliberately does not crossspawn_scopedtoday (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 onCoreContextwithout being stripped at that boundary.Leaving this issue open for those.
senamakel commentedon Oct 14, 2026
Progress on #7801: #7831 (turn origin onto
CoreContext,AGENT_TURN_ORIGINgone), #7837 (approval context explicit:APPROVAL_CHAT_CONTEXT/APPROVAL_COPILOT_STREAM_CONTEXT/APPROVAL_FLOW_RUN_CONTEXTgone), plus three regression fixes the first change exposed (#7832 bare context invisible to non-origin readers, #7835 origin survives a context change, #7836 stray teststorage.db). Non-testtask_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 acrossspawn_scopedon purpose. Needs an explicitMemoryIdentityon 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. ~70CoreContext::current()/scoped()readers plus everycurrent_slot, tenant, config-loader (load_config_with_timeoutreads the embedder config) and storage-scope reader hang off it, and those run under ~570fn(Map)controller handlers with no parameter to carry it. It needs theDispatchCtxhandler signature decision first.NetGate::disclose/PaymentHook::paytake 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).senamakel commentedon Oct 14, 2026
Progress on #7801: #7838 (root tests ported off the removed
with_parent_context, Refs #7823) and #7839 (memory/scope.rs CURRENTgone: explicitMemoryIdentityon confine/brain/lifecycle, on the run context for turns, sub-agents and team members; denied by the boundary ratchet). Non-testtask_local!statics: 2 -> 1 (CURRENT_CONTEXT). Cloud embedding disclosures can be chat-routed again throughdefault_embedding_provider_for_chat/create_embedding_provider_for_chat; the Jev tool-search retriever stays unrouted untilRankContextcarries a chat upstream.Merge pull request #7841 from senamakel/fu-7801d-1
Merge pull request #7847 from senamakel/fu-7801d-2
Merge pull request #7848 from senamakel/fu-7801d-3
Merge pull request #7849 from senamakel/fu-7801d-4
60 remaining items
senamakel commentedon Oct 16, 2026
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).
senamakel commentedon Oct 16, 2026
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).
senamakel commentedon Oct 16, 2026
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.
Merge pull request #7915 from senamakel/fu-7801f-15
senamakel commentedon Oct 16, 2026
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.
Merge pull request #7918 from senamakel/fu-7801g-1
senamakel commentedon Oct 16, 2026
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.
Merge pull request #7919 from senamakel/fu-7801g-2
senamakel commentedon Oct 16, 2026
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.
Merge pull request #7922 from senamakel/fu-7801g-3
senamakel commentedon Oct 16, 2026
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).
Merge pull request #7926 from senamakel/fu-7801g-4
senamakel commentedon Oct 16, 2026
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).