Skip to content

Circular dependency between tau_agent and tau_ai #317

Description

@darkspaten

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.)

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions