0002. The current branch travels in the process dictionary and in Ash's shared context
Copy Markdown View SourceStatus: accepted, 2026-10-22. Issue: #3 (implemented in #3 and #4). Resolves the [confirm] in design section 3.
Context
Hooks must know which branch, if any, the running action belongs to. Design section 3 asked whether Ash's shared
context is a better carrier than the process dictionary. Read in Ash 3.37.0:
- Ash has no ambient context. There is no
Ash.set_context;Ash.Context.to_opts/2only copies options that the caller passed explicitly. A plainAsh.update!(herd, ...)insideRamal.run/2, or anAsh.readinside a validation, passes none, sosharedalone cannot reach it. - Ash does leave the calling process. Relationship loads run in tasks through
Ash.Actions.Read.AsyncLimiterandAsh.ProcessHelpers.async/2when the data layer supports:async_engine(AshPostgres does) outside a transaction, and reads with atimeoutrun inAsh.ProcessHelpers.task_with_timeout/5.Ash.ProcessHelpers.get_context_for_transfer/1transfers only tracer state, so the process dictionary does not follow into those tasks. - Ash copies
context[:shared]into the queries it builds for those loads (lib/ash/actions/read/relationships.ex,Ash.Query.set_context(Map.take(query.context, [:shared]))).
Decision
Use both. Ramal.run/2 stores the branch id in the process dictionary (Ramal.Current), which reaches every hook
and every nested Ash call in the calling process. The first hook that runs (the read preparation or the write
change) copies the id into the query's or changeset's shared context, which Ash carries into the tasks it spawns.
A hook resolves the branch from shared first and from the process dictionary second. Only the id is carried, never
the struct, because the struct changes with every write and the branch server owns the truth.
Consequences
- Relationship loads that Ash runs in tasks still see the branch. Issue #4 tests this with a relationship load.
- Tasks the host starts itself do not inherit the branch; the host calls
Ramal.run/2inside the task. Unchanged. - The
sharedbridge only exists once a hook has run, so code that spawns a task before the first Ash call insiderun/2needsrun/2inside that task.