tai42
monorepo. The core layers define and implement the contract, the plugins build
on it, and the e2e suite proves them together. Contribute in the member
directory the change touches, under the repository’s CONTRIBUTING guide.
The repository
tai42
The monorepo — every first-party package as a workspace member.
Plugins
Every shipped plugin and its member path, in the Plugins section.
The shared discipline
Every member holds to the same rules, enforced in the monorepo’s CI:- The leaf rule. Among
tai42-*packages, the skeleton importstai42-contract,tai42-kit, andtai42-clionly; a plugin importstai42-contract(andtai42-kitwhere it needs shared helpers) only. No plugin imports the skeleton — providers register through thetai42_apphandle when the manifest loads them. Each member’sruffbanned-apiwall enforces this: the monorepo makes the imports physically reachable, so the wall is the guard. - Providers stay out of the core. Connectors, storage, config, backends, and monitoring ship as plugins that register at manifest load — never imported by the skeleton.
- Optional drivers stay optional. A heavy client driver reaches the engine through an extra; a module whose extra is missing fails loudly at import, never a silent skip.
- Errors surface loudly. A missing module, a malformed config entry, or a failed sub-step raises and propagates.
- Typed packages. Every package ships
py.typedand keeps the type-checker clean.
Develop
One clone, one sync. The workspace resolves everytai42-* member in-tree, so no other checkout has to be present:
core/contract, core/kit, core/cli, core/skeleton) carry their dev tooling as a dev extra rather than the dev dependency group — append --extra dev to their sync. --no-sync runs each gate against the synced environment instead of re-resolving. See the member’s README for the exact extras it installs, and the repository’s CONTRIBUTING guide for the rest — any secret scan, and the licensing statement.
How dependencies resolve
The workspace sources are the resolution.[tool.uv.workspace] lists core/*, plugins/*, and e2e; each member’s [tool.uv.sources] points its tai42-* siblings at { workspace = true }, so uv sync resolves them from the in-tree members, never from the index. One uv.lock at the repository root pins the whole set.
- A cross-member change is atomic. Editing a member and widening a dependent’s version cap land in one commit, one lock, one PR — no publish step, no propagation PR, and no per-repo lock that goes stale when a sibling bumps.
- CI resolves the same workspace. Each member’s gate runs
uv sync --locked --package <name>against the committed root lock, so a repo’s gate proves it against exactly the members a contributor develops with. There is noUV_NO_SOURCES, no sibling-clone step, and nouv lock --no-sources. - The
e2esuite proves the fleet together. Cross-member drift surfaces in the integration run, not in each member’s own gate.
Release
The monorepo releases through one manifest-moderelease-please train. Scope each conventional-commit by the member directory it touches (fix(agents): …, feat(toolbox): …); a fix:/feat: commit on a member opens a release PR for that package alone. Merging it tags tai42-<pkg>-v<ver> — the component-scoped tag that lets 32 packages share one repository — and publishes that package to PyPI.
Before it publishes, the release diffs each package’s public API against its previous release tag with griffe and fails when the surface change outruns the version bump it rides. A breaking surface change must ride a conventional commit marked breaking — a major bump for a package at >=1.0, though a 0.x package may take a breaking change in a minor.
The gate’s mode is configuration. label-honesty allows a 0.x package the minor-breaking slot; strict removes it, so a breaking change then requires the 1.0 graduation.
See also
- The layering — how contract, kit, cli, and skeleton relate.
- Plugins — every listed plugin and what it provides.

