Skip to main content
The platform’s first-party packages live in one repository — the 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.
Members resolve at these paths:

The shared discipline

Every member holds to the same rules, enforced in the monorepo’s CI:
  • The leaf rule. Among tai42-* packages, the skeleton imports tai42-contract, tai42-kit, and tai42-cli only; a plugin imports tai42-contract (and tai42-kit where it needs shared helpers) only. No plugin imports the skeleton — providers register through the tai42_app handle when the manifest loads them. Each member’s ruff banned-api wall 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.typed and keeps the type-checker clean.

Develop

One clone, one sync. The workspace resolves every tai42-* member in-tree, so no other checkout has to be present:
Work on a single member by syncing just it, then run its gates from its directory:
The four core layers (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 no UV_NO_SOURCES, no sibling-clone step, and no uv lock --no-sources.
  • The e2e suite 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-mode release-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.
The tag is pushed before the publish runs, so a change the gate rejects lands as a ghost tag — the tag and its release exist, but the package never reached the index. Do not patch past it: the gate diffs against the previous tag — ghost included — so the unpublished break now diffs clean, and a following fix: would publish it to the index as a patch with no refusal at all. Recover with an honest major — the same breaking surface carried by a feat!/BREAKING CHANGE commit, which release-please attributes by the files it touches and publishes at the bump the change always needed.

See also

  • The layering — how contract, kit, cli, and skeleton relate.
  • Plugins — every listed plugin and what it provides.