Skip to content

Package Publishing Guide


Table of Contents


Overview

Cogniverse consists of 12 independent packages organized in a layered architecture. The publishing scripts build and publish 4 main packages (core, agents, vespa, runtime) together with the internal packages they require:

Packages Supported by Publishing Scripts

Package Description Dependencies
cogniverse-core Business logic and system configuration cogniverse-foundation, cogniverse-sdk, cogniverse-evaluation
cogniverse-agents Agent implementations cogniverse-sdk, cogniverse-core, cogniverse-synthetic
cogniverse-vespa Vespa backend integration cogniverse-sdk, cogniverse-core
cogniverse-runtime FastAPI server runtime cogniverse-sdk, cogniverse-core, cogniverse-synthetic (agents/vespa optional)

Other Packages

Package Description Layer
cogniverse-sdk Base types and protocols Foundation
cogniverse-foundation Telemetry, config, caching, DSPy helpers, registry Foundation
cogniverse-evaluation Evaluation framework and Phoenix analytics Core
cogniverse-telemetry-phoenix Phoenix telemetry implementation Implementation
cogniverse-synthetic Synthetic data generation Implementation
cogniverse-finetuning Model fine-tuning and optimization Implementation
cogniverse-cli cogniverse CLI for deploying and managing the platform (Helm charts, cluster, sandbox, secrets) Application
cogniverse-messaging Messaging gateway for Telegram/Slack integration with the runtime Application

cogniverse-cli ships the deployment assets its path helpers resolve when no checkout is present: the git-tracked files under charts/cogniverse, workflows and configs, as cogniverse_cli/data/. libs/cli/hatch_build.py adds them to the sdist and the wheel, so a wheel built from the unpacked sdist carries the same files, and fails the build when an asset listed in required-assets in libs/cli/pyproject.toml is missing. uv build --package cogniverse-cli builds the sdist and then the wheel from it.

Publishing Workflow

flowchart LR
    A["<span style='color:#000'><b>Tag</b><br/>make release VERSION=x.y.z</span>"]
    B["<span style='color:#000'><b>Build</b><br/>build release set<br/>in dependency order</span>"]
    C["<span style='color:#000'><b>Test</b><br/>pytest by layer<br/>test dependencies</span>"]
    D["<span style='color:#000'><b>Publish</b><br/>publish the manifest's artifacts<br/>in dependency order</span>"]
    E["<span style='color:#000'><b>Release</b><br/>GitHub Release</span>"]

    A --> B --> C --> D --> E

    style A fill:#ffcc80,stroke:#ef6c00,color:#000
    style B fill:#90caf9,stroke:#1565c0,color:#000
    style C fill:#a5d6a7,stroke:#388e3c,color:#000
    style D fill:#ce93d8,stroke:#7b1fa2,color:#000
    style E fill:#b0bec5,stroke:#546e7a,color:#000

Publishing Scripts Handle the Release Set

Pushing a v* tag drives CI, which builds + publishes these packages (build_packages.sh / publish_packages.sh); hatch-vcs stamps each from the tag. build_packages.sh builds them together with every internal package they require — the release set, in dependency order: sdk, foundation, core, evaluation, synthetic, vespa, agents, telemetry-phoenix, runtime.

Note: publish_packages.sh uploads exactly the artifacts listed in dist/BUILD_MANIFEST.json — the whole release set, sdk through runtime. finetuning, cli and messaging are not built or published by the scripts.


Package Structure

Each package follows UV workspace structure with layer organization:

libs/
├── FOUNDATION LAYER
├── sdk/                     # cogniverse-sdk
│   ├── cogniverse_sdk/
│   │   ├── __init__.py
│   │   ├── document.py
│   │   └── interfaces/
│   ├── pyproject.toml
│   └── README.md
├── foundation/              # cogniverse-foundation
│   ├── cogniverse_foundation/
│   │   ├── __init__.py
│   │   ├── confidence.py
│   │   ├── telemetry/
│   │   ├── config/
│   │   ├── caching/
│   │   ├── dspy/
│   │   └── registry/
│   ├── pyproject.toml
│   └── README.md
├── CORE LAYER
├── evaluation/              # cogniverse-evaluation
├── core/                    # cogniverse-core
├── IMPLEMENTATION LAYER
├── telemetry-phoenix/       # cogniverse-telemetry-phoenix
├── agents/                  # cogniverse-agents
├── vespa/                   # cogniverse-vespa
├── synthetic/               # cogniverse-synthetic
├── finetuning/              # cogniverse-finetuning
├── APPLICATION LAYER
├── runtime/                 # cogniverse-runtime
├── cli/                     # cogniverse-cli (standalone, no cogniverse deps)
└── messaging/               # cogniverse-messaging (standalone, no cogniverse deps)

Key File: pyproject.toml (Foundation Layer Example)

# libs/foundation/pyproject.toml
[project]
name = "cogniverse-foundation"
dynamic = ["version"]              # derived from git tags by hatch-vcs
description = "Cogniverse Foundation - Cross-cutting concerns and shared infrastructure"
requires-python = ">=3.12"
dependencies = [
    "cogniverse-sdk",
    "dspy-ai==3.4.0",
    "opentelemetry-api==1.41.0",
    "opentelemetry-sdk==1.41.0",
    "pydantic==2.13.5",
    "sqlalchemy==2.0.49",
    "pandas==2.3.3",
]

[tool.uv.sources]
cogniverse-sdk = { workspace = true }

[build-system]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"

[tool.hatch.version]
source = "vcs"

[tool.hatch.version.raw-options]
root = "../.."                     # package is at libs/<pkg>; git root is two up

Key File: pyproject.toml (Implementation Layer Example)

# libs/agents/pyproject.toml
[project]
name = "cogniverse-agents"
dynamic = ["version"]              # hatch-vcs, as above
description = "Agent implementations, routing logic, and search enhancement for Cogniverse"
requires-python = ">=3.12"
dependencies = [
    "cogniverse-sdk",
    "cogniverse-core",
    "cogniverse-synthetic",
    # Optimization and ML (xgboost gate routing — small, no torch dep)
    "xgboost==3.2.0",
    "scikit-learn==1.8.0",
    "scipy==1.17.1",
    # NLP and language (spacy + langextract pure Python, no torch)
    "spacy==3.8.14",
    "langextract==1.2.1",
    # MLflow for experiment tracking
    "mlflow==3.11.1",
]

[project.optional-dependencies]
# Heavy ML stack for the in-process inference fallback paths.
# Production routes through vLLM / pylate sidecars and never imports
# these — install only for offline dev / local-path tests.
torch-local = [
    "torch==2.8.0",
    "transformers==4.56.2",
    "colpali-engine==0.3.13",
    "sentence-transformers==5.1.1",
    "gliner==0.2.26",
]

[tool.uv.sources]
cogniverse-sdk = { workspace = true }
cogniverse-core = { workspace = true }
cogniverse-synthetic = { workspace = true }

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

Prerequisites

Required Tools

# UV package manager
pip install uv

# Python dependency for versioning (reads use the stdlib tomllib).
# Already provided by the dev dependency group: `uv sync --group dev`.
pip install tomli-w

# Twine: publish_packages.sh installs twine==7.0.0 and its dependencies from the
# hash-pinned scripts/publish-requirements.txt into a throwaway Python 3.12 venv;
# nothing to install.

PyPI Account Setup

1. Create PyPI Accounts

  • PyPI (Production): https://pypi.org/account/register/
  • TestPyPI (Testing): https://test.pypi.org/account/register/

2. Generate API Tokens

PyPI:

  1. Go to https://pypi.org/manage/account/token/

  2. Create token with scope: "Entire account"

  3. Save token securely (starts with pypi-)

TestPyPI:

  1. Go to https://test.pypi.org/manage/account/token/

  2. Create token with scope: "Entire account"

  3. Save token securely (starts with pypi-)

3. Configure Credentials

Option A: Environment Variables (Recommended for CI/CD)

export PYPI_TOKEN="pypi-your-production-token"
export TEST_PYPI_TOKEN="pypi-your-test-token"

Option B: .pypirc File (Local Development)

# ~/.pypirc
[distutils]
index-servers =
    pypi
    testpypi

[pypi]
username = __token__
password = pypi-your-production-token

[testpypi]
repository = https://test.pypi.org/legacy/
username = __token__
password = pypi-your-test-token

Security Note: Never commit .pypirc or tokens to version control!


Version Management

Semantic Versioning

Cogniverse follows Semantic Versioning 2.0.0:

MAJOR.MINOR.PATCH[-PRERELEASE]
  ↓     ↓     ↓         ↓
  0  .  1  .  0  -  alpha.0

MAJOR: Breaking changes
MINOR: New features (backward compatible)
PATCH: Bug fixes
PRERELEASE: alpha, beta, rc

The git tag is the single source of truth

There is no hardcoded version in any pyproject.toml. Every package declares dynamic = ["version"] and derives its version from git tags via hatch-vcs (the hatchling front-end to setuptools-scm):

  • At a tag v0.2.0 → the wheel is exactly 0.2.0.
  • Between tags → a PEP 440 dev version such as 0.2.1.dev5+g<sha> ("5 commits past v0.2.0"). That honestly marks an unreleased build; it is not a defect.

What a build resolves to right now (the wheel filename carries the version):

uv build --package cogniverse-core --out-dir /tmp/v && ls /tmp/v

The chart carries a static appVersion in charts/cogniverse/Chart.yaml (Helm needs a concrete version at install time). It is the release line the Docker image tags derive from, and make release keeps it in step with the tag.

Cutting a release

First release only: the publish steps need registry/PyPI credentials that are not configured yet — set them once (see Required GitHub Secrets) before pushing a tag.

One command bumps the chart and creates the tag:

make release VERSION=0.2.0

It sets Chart.yaml version/appVersion + the chart image tags to 0.2.0, commits, and creates the annotated v0.2.0 tag. Pushing that tag triggers CI, which builds and publishes everything at 0.2.0:

Artifact Versioned by Published by
Python wheels + sdists (release set) hatch-vcs, from the tag publish-packages.yml → PyPI
Docker images (runtime ×3, web, gliner, 3 sidecars) the tag (github.ref_name) release-images.yml → docker.io/cogniverse
Helm chart Chart.yaml appVersion/version release-images.yml (publish-chart job) → OCI oci://registry-1.docker.io/cogniverse

Each release image also gets an SBOM + build provenance attached and is signed (keyless cosign / Sigstore) in the same job, so consumers can scan and verify it. For air-gapped clusters, mirror-third-party.yml (workflow_dispatch) copies the third-party images (vespa/phoenix/ollama/redis/ minio/vllm/envoy/…) — enumerated from a chart render — into a registry you name.

The old scripts/version_bump.py (rewriting a hardcoded version in every file) is superseded — the version lives in git, not in the files.

Image tags: release vs. dev

  • Release (release-images.yml on a v* tag) → docker.io/cogniverse/<name>:<version> plus an immutable :<git-sha>, pullPolicy: IfNotPresent (a clean cluster pulls).
  • Local dev (cogniverse up → build_images) → cogniverse/<name>:<git-version>, where each image's <git-version> comes from its own Dockerfile and local COPY/ADD inputs after applying .dockerignore; the ignore file itself is also an input. Tags replace + with - (e.g. 0.1.dev2137-g9ba33d3f). A source change therefore retags only the images that consume it. Existing host tags are not rebuilt, and existing k3d-node tags are not re-imported. PyLate appends -cpu, -cuda, or -rocm because its build argument changes image bytes; runtime encodes that backend in its repositories. pullPolicy: Never selects those local images. values.k3s.yaml carries a static <line>-dev placeholder that cogniverse up replaces via per-image --set values from dev_image_set_values.

.dockerignore excludes .git, so the runtime images (which install the workspace and thus trigger hatch-vcs) receive the version through the SETUPTOOLS_SCM_PRETEND_VERSION build-arg that build_images / release-images.yml set from the host, where git is available. Each package therefore carries the version derived for its containing image.


Building Packages

Build Script

Script: scripts/build_packages.sh

Basic Build

# Build all packages
./scripts/build_packages.sh

# Clean build (remove previous artifacts)
./scripts/build_packages.sh --clean

# Verbose output
./scripts/build_packages.sh --verbose

Advanced Options

# Build with tests
./scripts/build_packages.sh --test

# Strict mode (fail on test failures)
./scripts/build_packages.sh --test --strict

# Continue on errors
CONTINUE_ON_ERROR=true ./scripts/build_packages.sh

Build Output

dist/
├── cogniverse_sdk-0.2.0-py3-none-any.whl
├── cogniverse_sdk-0.2.0.tar.gz
├── ...                                  # one wheel + sdist per release package
├── cogniverse_runtime-0.2.0-py3-none-any.whl
├── cogniverse_runtime-0.2.0.tar.gz
└── BUILD_MANIFEST.json

BUILD_MANIFEST.json lists exactly the artifacts this invocation built, in build order:

{
  "version": "0.2.0",
  "packages": [
    {
      "name": "cogniverse-sdk",
      "version": "0.2.0",
      "requires": [],
      "wheel": {"filename": "cogniverse_sdk-0.2.0-py3-none-any.whl", "sha256": "..."},
      "sdist": {"filename": "cogniverse_sdk-0.2.0.tar.gz", "sha256": "..."}
    }
  ]
}

requires holds the package's internal requirements (base and extras).

Build Process

  1. Version: uv build --no-sources --sdist builds each package's sdist from the workspace, so hatch-vcs computes the version from git. All work happens in a per-invocation staging directory (printed as Staging directory:), removed on exit, success or failure; the backend's temporary files go inside it.
  2. Pinned source: scripts/release_manifest.py stage unpacks that sdist into the staging directory and pins every internal requirement (base and extras) in its pyproject.toml to ==<version>. The workspace pyproject.toml files and uv.lock are not modified.
  3. Build: uv build --no-sources builds the release sdist from the pinned source, then its wheel from that sdist, with SETUPTOOLS_SCM_PRETEND_VERSION=<version>. The published sdist carries the pinned pyproject.toml, so a wheel rebuilt from it has the same Requires-Dist.
  4. Validation: scripts/release_manifest.py build (run with uv run --no-sync, so the project environment must be synced) reads METADATA/PKG-INFO from each wheel and sdist. Name and version must agree across the pyproject, both filenames and both metadata files; versions are PEP 440 (packaging.version.Version), so tag builds (0.2.0) and dev builds (0.2.1.dev1+g<sha>) are both valid. All packages must share one version, wheel and sdist Requires-Dist must match, every internal requirement must be pinned to ==<version>, and every internal requirement must be built earlier in the release set. An internal requirement that already has a version specifier in the workspace fails the build.
  5. Collection: Only after every package validates are the artifacts copied into dist/ and the manifest written. Existing files in dist/ are left untouched; an existing file with the same name but different bytes fails the build. --clean removes dist/ first.

Any failure exits nonzero and leaves no BUILD_MANIFEST.json.


Testing Packages

Local Testing

Install from Built Distributions

# Create test environment
python -m venv test-env
source test-env/bin/activate

# Install built packages in dependency order

# Core package (built first)
pip install dist/cogniverse_core-*.whl

# Agent and backend packages (depend on core)
pip install dist/cogniverse_agents-*.whl
pip install dist/cogniverse_vespa-*.whl

# Application packages (depend on core, agents, vespa)
pip install dist/cogniverse_runtime-*.whl

# Verify imports
python -c "from cogniverse_foundation.config.unified_config import SystemConfig"
python -c "from cogniverse_agents.gateway_agent import GatewayAgent"
python -c "from cogniverse_vespa import VespaBackend"
python -c "from cogniverse_runtime.main import app"
python -c "print('All packages imported successfully')"

Run Test Suite

# Run all tests
JAX_PLATFORM_NAME=cpu uv run pytest

# Run package-specific tests
JAX_PLATFORM_NAME=cpu uv run pytest tests/common/
JAX_PLATFORM_NAME=cpu uv run pytest tests/agents/

Publishing to TestPyPI

Why TestPyPI?

  • Safe testing environment for PyPI without affecting production
  • Validate package metadata before production release
  • Test installation from PyPI-like repository

Manual Publishing

# 1. Build packages
./scripts/build_packages.sh --clean

# 2. Publish to TestPyPI (with dry run first)
TEST_PYPI_TOKEN="your-test-token" ./scripts/publish_packages.sh --test --dry-run

# 3. Actual publish
TEST_PYPI_TOKEN="your-test-token" ./scripts/publish_packages.sh --test

# Output:
# [INFO] Publishing package: cogniverse-sdk 0.1.0
# [SUCCESS] Uploaded or already present: cogniverse-sdk 0.1.0
# ...
# [INFO]   Uploaded or already present: 10 package(s)
# Verified 20 file(s) at https://test.pypi.org/simple/ against the manifest digests

What the script checks

  • Manifest only: it uploads the wheel and sdist of each package in dist/BUILD_MANIFEST.json, in manifest order, and nothing else in dist/. A missing manifest, a missing listed file, or a file whose sha256 differs from the manifest fails before any upload.
  • Local versions refused: a build of an untagged commit (0.2.1.dev1+g<sha>) carries a local version, which PyPI and TestPyPI reject; the script exits 1 before any upload (also with --dry-run).
  • twine check runs on exactly the manifest's files.
  • Child results: each package is one twine upload --skip-existing of its wheel and sdist, and twine's exit status is the result. A nonzero exit is a failure, never counted as "already exists". Without --continue the remaining packages are not attempted; with --continue they are, and the script still exits 1.
  • Duplicates: twine 7.0.0 supports --skip-existing only for PyPI and TestPyPI; it skips a file when the index answers 409, or 400 with "already exist", and exits 0.
  • Index verification: after every upload succeeds, the script reads the index's simple page for each package and requires every manifest file to be served with the manifest's sha256 (waiting up to VERIFY_TIMEOUT seconds, default 300; a 5xx answer or failed request is retried within that time, any other non-404 error fails at once). A same-named file with other bytes — a skipped duplicate that is not this build — fails the publication.
  • Target only: the upload goes to PyPI, or TestPyPI with --test. If TWINE_REPOSITORY_URL or TWINE_REPOSITORY is set, the script exits 1 before doing anything.
  • Confirmation: without --yes the script asks; any answer but yes, or no input, exits 1 with nothing uploaded.
  • --dry-run runs the manifest checks and twine check, lists the uploads, and neither uploads nor queries the index for the release packages (uv may still download the pinned publish tool from PyPI when it is not cached). Its exit 0 is not evidence of publication.

Test Installation

# Install from TestPyPI
pip install --index-url https://test.pypi.org/simple/ \
            --extra-index-url https://pypi.org/simple/ \
            cogniverse-core

# Note: --extra-index-url allows dependencies from production PyPI

Verification

# Test package functionality
python -c "
from cogniverse_foundation.config.unified_config import SystemConfig
config = SystemConfig()
print(f'Successfully imported: {config.search_backend}')
"

Publishing to PyPI

Pre-publish Checklist

  • All tests passing
  • Version bumped correctly
  • Git tag created (v0.1.0)
  • CHANGELOG updated (optional - file not currently maintained)
  • README accurate
  • License included
  • Tested on TestPyPI

Manual Publishing

# 1. Build packages
./scripts/build_packages.sh --clean --test

# 2. Publish to PyPI (with dry run first)
PYPI_TOKEN="your-production-token" ./scripts/publish_packages.sh --dry-run

# 3. Actual publish
PYPI_TOKEN="your-production-token" ./scripts/publish_packages.sh

# Output ends with:
# Verified 20 file(s) at https://pypi.org/simple/ against the manifest digests

Note: The scripts publish the ten release-set packages listed in the manifest. finetuning, cli and messaging require manual publishing.

The CLI cannot join the staged release set as is: scripts/release_manifest.py deletes PKG-INFO from each staged source, and libs/cli/hatch_build.py takes PKG-INFO as its only sign of an sdist build. A CLI published by hand (uv build --package cogniverse-cli) requires cogniverse-foundation with no version pin.

Verify Publication

# Check PyPI page
open https://pypi.org/project/cogniverse-core/

# Test installation
pip install cogniverse-core==0.1.0

# Test functionality
python -c "from cogniverse_foundation.config.unified_config import SystemConfig; print('Success!')"

Automated Publishing (CI/CD)

GitHub Actions Workflow

File: .github/workflows/publish-packages.yml

Trigger: Version Tags

# Create and push version tag
git tag -a v0.1.0 -m "Release 0.1.0"
git push origin v0.1.0

# GitHub Actions automatically:
# 1. Builds packages
# 2. Runs tests
# 3. Publishes to PyPI
# 4. Creates GitHub Release

Trigger: Manual Workflow Dispatch

# Via GitHub UI:
# 1. Go to Actions → "Publish SDK Packages"
# 2. Click "Run workflow"
# 3. Select target: testpypi or pypi
# 4. Optional: Enable dry run

Run it from a v* tag ref ("Use workflow from" → Tags). A branch build carries a local version, and "Verify release artifacts" refuses it, dry run included.

Workflow Stages

flowchart TD
    A["<span style='color:#000'><b>Build</b><br/>Build release set<br/>(5 published packages + internal dependencies)</span>"]
    B["<span style='color:#000'><b>Test</b><br/>Install manifest wheels in a fresh venv,<br/>run test suite with Vespa</span>"]
    C["<span style='color:#000'><b>TestPyPI</b><br/>Dry run, publish, verify index digests</span>"]
    D["<span style='color:#000'><b>PyPI</b><br/>Dry run, publish, verify index digests</span>"]
    E["<span style='color:#000'><b>GitHub Release</b><br/>Create release with notes</span>"]

    A --> B
    B --> C
    B --> D
    D --> E

    style A fill:#90caf9,stroke:#1565c0,color:#000
    style B fill:#a5d6a7,stroke:#388e3c,color:#000
    style C fill:#ffcc80,stroke:#ef6c00,color:#000
    style D fill:#ce93d8,stroke:#7b1fa2,color:#000
    style E fill:#b0bec5,stroke:#546e7a,color:#000

Note: GitHub Release only depends on the PyPI publish job (release tags skip TestPyPI); prerelease tags publish to TestPyPI without cutting a GitHub Release.

Tag-Based Publishing Rules

Tag Format Target Example
v*.*.* PyPI v0.1.0
v*.*.*-alpha.* TestPyPI v0.1.0-alpha.0
v*.*.*-beta.* TestPyPI v0.1.0-beta.1
v*.*.*-rc.* TestPyPI v0.1.0-rc.1

Required GitHub Secrets

Set these under Settings → Secrets and variables → Actions before cutting a release. The repo currently has none configured, so a v* tag would fail at the credential steps (Docker login, chart push, PyPI upload) until they exist.

Secret Used by For
DOCKERHUB_USERNAME release-images.yml, mirror-third-party.yml Docker Hub account name
DOCKERHUB_TOKEN same Docker Hub access token (not the password) — image + OCI chart push
PYPI_TOKEN publish-packages.yml production PyPI wheel upload
TEST_PYPI_TOKEN publish-packages.yml TestPyPI upload (-rc / -beta / -alpha tags)

GITHUB_TOKEN is injected automatically, and keyless cosign signing uses the workflow's OIDC identity — so no signing-key secret is needed.

gh secret set DOCKERHUB_USERNAME --body "<dockerhub-user>"
gh secret set DOCKERHUB_TOKEN    --body "<dockerhub-access-token>"
gh secret set PYPI_TOKEN         --body "<pypi-token>"
gh secret set TEST_PYPI_TOKEN    --body "<testpypi-token>"

Troubleshooting

Common Issues

Version Already Exists on PyPI

Error:

error: https://pypi.org/simple/: cogniverse_core-0.1.0-py3-none-any.whl has sha256 <on index>, manifest has <this build>

A same-named file that is byte-identical to this build is skipped and passes; one with other bytes fails. With --no-skip-existing, twine reports HTTPError: 400 Bad Request ... File already exists for either.

Solution:

# PyPI doesn't allow overwriting a version — cut the next one.
make release VERSION=0.1.1
git push origin main --follow-tags   # CI rebuilds + republishes at 0.1.1

Missing Dependencies in Build

Error:

ModuleNotFoundError: No module named 'cogniverse_core'

Solution:

# Sync workspace dependencies
uv sync

# Rebuild
./scripts/build_packages.sh --clean

Twine Upload Fails

Error:

twine.exceptions.TwineException: Invalid or non-existent authentication information

Solution:

# Check credentials
cat ~/.pypirc

# Or use environment variables
export PYPI_TOKEN="pypi-your-token"
./scripts/publish_packages.sh

Package Import Fails After Install

Error:

ImportError: cannot import name 'SystemConfig' from 'cogniverse_foundation.config.unified_config'

Solution:

# Check package contents
unzip -l dist/cogniverse_foundation-*.whl | grep config

# Verify __init__.py exports
cat libs/foundation/cogniverse_foundation/__init__.py

# Rebuild with correct exports
./scripts/build_packages.sh --clean


Best Practices

Version Management

  1. Pick the version per semantic versioning — the number goes in the tag, not in any file:

    make release VERSION=1.0.0    # breaking changes
    make release VERSION=0.2.0    # new features
    make release VERSION=0.1.1    # bug fixes
    

  2. Test prereleases first — a -rc/-beta/-alpha tag publishes to TestPyPI (not PyPI), per publish-packages.yml:

    make release VERSION=0.2.0-rc.1
    git push origin main --follow-tags
    # exercise it from TestPyPI, then cut the final:
    make release VERSION=0.2.0
    

  3. The tag is the release — pushing it drives both the PyPI and Docker publishes; there is nothing else to run:

    git push origin main --follow-tags
    

Building

  1. Always use clean builds for releases

    ./scripts/build_packages.sh --clean
    

  2. Run tests before publishing

    ./scripts/build_packages.sh --clean --test --strict
    

  3. Verify build artifacts

    # Check wheel contents
    unzip -l dist/*.whl
    
    # Verify the manifest's files and run twine check on exactly those
    ./scripts/publish_packages.sh --test --dry-run
    

Publishing

  1. Always test on TestPyPI first

    # Test publish
    ./scripts/publish_packages.sh --test
    
    # Verify installation
    pip install --index-url https://test.pypi.org/simple/ cogniverse-core
    
    # Then publish to production
    ./scripts/publish_packages.sh
    

  2. Use dry runs for validation

    ./scripts/publish_packages.sh --dry-run
    

  3. Document changes in CHANGELOG (optional)

    ## [0.2.0] - 2025-10-15
    ### Added
    - New routing strategy: GLiNER-based
    - Multi-tenant Phoenix projects
    
    ### Changed
    - Improved memory performance
    
    ### Fixed
    - Vespa schema deployment bug
    

Note: CHANGELOG.md is not currently maintained in this project. This is a recommended best practice if you choose to implement it.

Security

  1. Never commit secrets

    # Add to .gitignore
    echo "*.pypirc" >> .gitignore
    echo ".env" >> .gitignore
    

  2. Use environment variables in CI/CD

    # GitHub Actions
    env:
      PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
    

  3. Rotate API tokens regularly

  4. PyPI tokens should be rotated every 6 months
  5. Use scoped tokens when possible

Quality Assurance

  1. Maintain package READMEs
  2. Each package should have a clear README
  3. Include installation and usage examples

  4. Keep pyproject.toml updated

  5. Accurate dependencies
  6. Correct Python version requirements
  7. Proper classifiers

  8. Test cross-package dependencies

    # Install only runtime (should pull dependencies)
    pip install cogniverse-runtime
    
    # Verify all dependencies installed
    pip list | grep cogniverse
    


Complete Publishing Workflow

Standard Release Process

# 1. Land your changes on main with CI green
git switch main && git pull

# 2. Cut the release — bumps Chart.yaml + chart image tags, commits, tags v0.2.0
make release VERSION=0.2.0
git show --stat v0.2.0            # review the release commit + tag before pushing

# 3. Push main + the tag. The tag drives CI, which:
#    - stamps every Python wheel 0.2.0 (hatch-vcs) and publishes to PyPI
#      (publish-packages.yml — a -rc/-beta/-alpha tag goes to TestPyPI instead)
#    - builds + pushes docker.io/cogniverse/*:0.2.0 (+ :<git-sha>)
#      (release-images.yml)
git push origin main --follow-tags

# 4. Verify
open https://pypi.org/project/cogniverse-core/
docker pull cogniverse/runtime-cpu:0.2.0

No manual build_packages.sh / publish_packages.sh run is needed for a normal release — pushing the tag does it. Those scripts remain for local/offline builds.



Support

  • PyPI Issues: Check PyPI Help
  • Build Issues: Review the build_packages.sh output; dist/BUILD_MANIFEST.json exists only after a successful build
  • CI/CD Issues: Check GitHub Actions logs