The documented dependency direction for Tau is tau_coding -> tau_agent -> tau_ai (one-way, downward.)
This one-way boundary an important architectural idea captured in the dev notes and website docs (e.g. the "portable brain" principle.) This also makes Tau a clear teaching codebase as each layer answers one question and can be studied on its own.
However, a bidirectional import cycle may have emerged between tau_agent and tau_ai:
tau_agent -> tau_ai (3 imports, all in the core portable layer):
src/tau_agent/loop.py:26 from tau_ai.events import ProviderResponseStartEvent, ...
src/tau_agent/loop.py:34 from tau_ai.provider import CancellationToken, ModelProvider
src/tau_agent/harness.py:16 from tau_ai.provider import ModelProvider
tau_ai -> tau_agent (every file in the provider layer, as intended):
src/tau_ai/provider.py from tau_agent.messages import AgentMessage
src/tau_ai/provider.py from tau_agent.tools import AgentTool
src/tau_ai/events.py from tau_agent.messages import AssistantMessage
src/tau_ai/events.py from tau_agent.tools import ToolCall
src/tau_ai/fake.py from tau_agent.messages / tau_agent.tools
src/tau_ai/env.py from tau_agent.types import JSONValue
src/tau_ai/retry.py from tau_agent.types import JSONValue
src/tau_ai/anthropic.py from tau_agent.{messages,tools,types}
src/tau_ai/google.py from tau_agent.{messages,tools,types}
src/tau_ai/mistral.py from tau_agent.{messages,tools,types}
src/tau_ai/openai_compatible.py from tau_agent.{messages,tools,types}
src/tau_ai/openai_codex.py from tau_agent.{messages,tools,types}
The tau_ai -> tau_agent direction is as intended. Provider adapters legitimately depend on core agent types (AgentMessage, ToolCall, etc.) The concern is the 3 upward imports: tau_agent reaching back into tau_ai for the ModelProvider protocol, CancellationToken, and ProviderEvent types.
If a central pedagogical point is layer independence, fixing this cycle would make the code match the docs. A reader following the architecture guide and then opening loop.py would see the one-way dependency hold up in practice.
Proposed fix
The ModelProvider protocol and ProviderEvent types are the only things tau_agent imports from tau_ai. This appears to be a case for the dependency inversion principle.
Applied here:
tau_agent defines the ModelProvider protocol (the contract the loop depends on)
tau_ai implements that protocol (the adapters satisfy it)
tau_agent defines the ProviderEvent types (the events the loop dispatches on)
tau_ai emits those types (the adapters yield them)
Concretely then, could we move ModelProvider, CancellationToken, and all ProviderEvent types from tau_ai into two new files in tau_agent? Replace the old tau_ai files with pure re-export shims so all existing from tau_ai import ModelProvider call sites continue to work without modification?
Concern
The fix needs to be mindful of Python isinstance() w.r.t. if we define the same event class in both the old file and the new file being treated as two different classes. When the agent loop tries to check incoming events using isinstance(), it could silently fail and ignore the events. I believe we must delete the class definitions from the old file and replace them with imports pointing to the new file (re-exports.)
The documented dependency direction for Tau is
tau_coding -> tau_agent -> tau_ai(one-way, downward.)This one-way boundary an important architectural idea captured in the dev notes and website docs (e.g. the "portable brain" principle.) This also makes Tau a clear teaching codebase as each layer answers one question and can be studied on its own.
However, a bidirectional import cycle may have emerged between
tau_agentandtau_ai:tau_agent->tau_ai(3 imports, all in the core portable layer):tau_ai->tau_agent(every file in the provider layer, as intended):The
tau_ai->tau_agentdirection is as intended. Provider adapters legitimately depend on core agent types (AgentMessage,ToolCall, etc.) The concern is the 3 upward imports:tau_agentreaching back intotau_aifor theModelProviderprotocol,CancellationToken, andProviderEventtypes.If a central pedagogical point is layer independence, fixing this cycle would make the code match the docs. A reader following the architecture guide and then opening
loop.pywould see the one-way dependency hold up in practice.Proposed fix
The
ModelProviderprotocol andProviderEventtypes are the only thingstau_agentimports fromtau_ai. This appears to be a case for the dependency inversion principle.Applied here:
tau_agentdefines theModelProviderprotocol (the contract the loop depends on)tau_aiimplements that protocol (the adapters satisfy it)tau_agentdefines theProviderEventtypes (the events the loop dispatches on)tau_aiemits those types (the adapters yield them)Concretely then, could we move
ModelProvider,CancellationToken, and allProviderEventtypes fromtau_aiinto two new files intau_agent? Replace the oldtau_aifiles with pure re-export shims so all existingfrom tau_ai import ModelProvidercall sites continue to work without modification?Concern
The fix needs to be mindful of Python
isinstance()w.r.t. if we define the same event class in both the old file and the new file being treated as two different classes. When the agent loop tries to check incoming events usingisinstance(), it could silently fail and ignore the events. I believe we must delete the class definitions from the old file and replace them with imports pointing to the new file (re-exports.)