Review on #1100 caught that the previous commit parallelised every tool call in
a turn, mutations included. `ctx.db_lock` makes the mutating handlers take turns
but does not decide whose turn comes first, so two writes from one assistant
turn could apply in an order the model did not request — `tool_results` stayed
ordered while the database did not.
Reads may still overlap freely, because they change nothing. So the loop now
splits the turn: anything named in `mutating_tools` runs one at a time in
request order, everything else runs concurrently alongside it.
`mutating_tools=None` is the default and means "assume every tool mutates",
which is the fully sequential behaviour that existed before this branch. A
caller opts in by naming its mutating set, so no existing caller changes
behaviour by being left alone.
`MUTATING_TOOL_NAMES` lives in agent_tools next to the dispatch table and lists
exactly the five names routing to a handler that takes `ctx.db_lock`:
create_observations, create_observations_deductive,
create_observations_inductive, update_peer_card, delete_observations.
`extract_preferences` and `finish_consolidation` are deliberately absent — both
only return text telling the model what to call next.
Dialectic passes that set rather than a "these are all reads" boolean. Its
loadout is reads today, but DIALECTIC_TOOLS already carries a commented-out
create_observations_deductive, so a flag would have gone quietly wrong the day
someone uncommented it. The parameter is threaded through honcho_llm_call
instead of imported inside src/llm, which keeps the llm layer free of tool
semantics and avoids the existing agent_tools <- dreamer.specialists cycle.
Two tests added: mutating calls are not reordered even when a later call
finishes first, and the default schedule is fully sequential.