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.

End-to-end suites

The e2e member boots the real multi-process deployment and drives it over HTTP; its ui/ workspace drives the Studio in a browser. One setting, TAI_E2E_TARGET, selects what every end-to-end suite drives — this one and the browser suites of the Studio and plugin repositories:
  • Unset — the run builds its own stack and drives it.
  • Set — the run builds nothing and drives a running stack: a bare origin (https://stack.example.com), or the name of a target file in the suite’s targets/ directory.
A target file is three fields: the stack’s url, key_env — the name of the environment variable that holds the login key — and provides, the facts the stack cannot report about itself. Everything it can report is read once from its GET /api/system/kinds. Every test declares what it needs from a stack, in one vocabulary: Against a target, a test whose needs are met runs; every other test is skipped with the unmet need as the reason, and a test that declares nothing is treated as built-stack only. When the run builds its own stack the declarations change nothing. In CI the jobs read the target from the e2e_target input of a manual run, else from the TAI_E2E_TARGET repository variable, and the key from the TAI_E2E_KEY secret. Every run writes a JUnit XML report (junit.xml) in which a skipped test carries its reason; CI uploads it as an artifact and renders it into the job summary. The suite’s README covers the details.

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.