Modules reach into each other's internals to coordinate behavior, bypassing public interfaces.
Module A reaches into module B's private fields or undocumented behavior; the agent reasoning about A must also load B's internals to make any change.
Cooperation happens through narrow, explicit interfaces; secrets stay secret.
Cooperation flows through a narrow named interface the agent can read once; A's reasoning context excludes B's implementation details.
Smellier version
class A { _data; }class B {read(a) {return a._data.value;}}
Fresher version
class A { value() { return this._data.value; } }class B { read(a) { return a.value(); } }
Coupling at the implementation level — refactoring one breaks the other in non-obvious ways.
Refactoring one module silently breaks the other in ways the type system doesn't catch; the agent must trace cross-module assumptions on every edit.
Establishing a real public interface between modules forces both sides to commit to a contract; until the interface stabilizes, the boundary is more painful than the original direct access.
Defining a real public interface adds a contract the agent must respect at both ends; until the interface stabilizes, every change forces synchronized edits across both modules.
Module boundaries become real; tests exercise the public surface; refactoring is local.
Each module's public surface is the only contract callers depend on; the agent reads one module to predict behavior instead of loading both modules together to verify the unwritten coupling still holds.
Building elaborate public APIs between modules that genuinely belong together — the encapsulation overhead exceeds the coupling cost the boundary was meant to fix.
Erecting elaborate public APIs between modules that genuinely belong together creates a fake boundary the agent must navigate at every interaction with no isolation gain.