Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
9075ab5
fix: mount weights in cog serve like cog run does (#3044)
markphelps Jun 2, 2026
a1b710a
Bump version to 0.21.0-rc.2 (#3045)
markphelps Jun 2, 2026
9b9f310
chore: regen lockfile
markphelps Jun 2, 2026
8428257
ci: pin mise version in release workflows (#3046)
markphelps Jun 2, 2026
b5c8563
test: cover kong command registration and root globals
markphelps Jun 3, 2026
127e8b1
refactor: share build command logic between cobra and kong
markphelps Jun 3, 2026
957c924
refactor: share push command logic between cobra and kong
markphelps Jun 3, 2026
95b223e
feat: add kong serve and exec parity via shared runtime runners
markphelps Jun 3, 2026
1477b1f
feat: add kong init, login, doctor, debug parity
markphelps Jun 3, 2026
1f1b4c4
feat: add kong predict, run, and train parity via shared runners
markphelps Jun 3, 2026
89a4b4f
feat: add kong weights command parity and fix version short-flag coll…
markphelps Jun 3, 2026
acff164
feat: add kong base-image command parity and remove stubs
markphelps Jun 3, 2026
92f0237
test: verify kong cli parity and clean up unused cobra helpers
markphelps Jun 3, 2026
63c65df
feat: support JSON-native union inputs (#3048)
markphelps Jun 5, 2026
2ab4cc4
test: fuzz schema generation for union inputs + docs fixup (#3049)
markphelps Jun 5, 2026
d6c2b70
Bump version to 0.21.0-rc.3 (#3050)
markphelps Jun 5, 2026
4c0596d
Merge remote-tracking branch 'origin/main' into kong-cli-parity
markphelps Jun 8, 2026
bf8a249
fix: hide kong predict/train/weights/debug and drop dead os.Exit
markphelps Jun 8, 2026
f83efb3
fix: close kong/cobra CLI parity gaps from review
markphelps Jun 8, 2026
89369f1
fix: address Kong/Cobra CLI parity review feedback
markphelps Jun 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 25 additions & 9 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ jobs:
fetch-depth: 0
- uses: jdx/mise-action@v4
with:
version: 2026.4.27
cache_key_prefix: mise-ci-${{ github.job }}
- name: Build SDK
run: mise run ci:build:sdk
Expand Down Expand Up @@ -190,6 +191,7 @@ jobs:
fetch-depth: 0
- uses: jdx/mise-action@v4
with:
version: 2026.4.27
cache_key_prefix: mise-ci-${{ github.job }}
- name: Get version from VERSION.txt
id: version
Expand Down Expand Up @@ -221,6 +223,7 @@ jobs:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
with:
version: 2026.4.27
cache_key_prefix: mise-ci-${{ github.job }}
- name: Check Go formatting
run: mise run fmt:go
Expand All @@ -233,6 +236,7 @@ jobs:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
with:
version: 2026.4.27
cache_key_prefix: mise-ci-${{ github.job }}
- name: Ensure Rust components
run: rustup component add rustfmt clippy
Expand All @@ -247,6 +251,7 @@ jobs:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
with:
version: 2026.4.27
cache_key_prefix: mise-ci-${{ github.job }}
- name: Check Python formatting
run: mise run fmt:python
Expand All @@ -259,6 +264,7 @@ jobs:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
with:
version: 2026.4.27
cache_key_prefix: mise-ci-${{ github.job }}
- name: Check llms.txt is up to date
run: mise run docs:llm:check
Expand All @@ -283,6 +289,7 @@ jobs:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: jdx/mise-action@v4
with:
version: 2026.4.27
cache_key_prefix: mise-ci-${{ github.job }}
- name: Check stubs are up to date
run: |
Expand All @@ -302,6 +309,7 @@ jobs:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
with:
version: 2026.4.27
cache_key_prefix: mise-ci-${{ github.job }}
- name: Lint Go
run: mise run lint:go
Expand All @@ -318,6 +326,7 @@ jobs:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: jdx/mise-action@v4
with:
version: 2026.4.27
cache_key_prefix: mise-ci-${{ github.job }}
- name: Ensure Rust components
run: rustup component add rustfmt clippy
Expand All @@ -332,6 +341,7 @@ jobs:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
with:
version: 2026.4.27
cache_key_prefix: mise-ci-${{ github.job }}
- name: Check licenses, bans, and sources
run: cargo deny --manifest-path crates/Cargo.toml check bans licenses sources
Expand All @@ -349,6 +359,7 @@ jobs:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
with:
version: 2026.4.27
cache_key_prefix: mise-ci-${{ github.job }}
- name: Check advisories (informational)
run: cargo deny --manifest-path crates/Cargo.toml check advisories
Expand All @@ -373,6 +384,7 @@ jobs:
run: tar xf dist/*.tar.gz --strip-components=1
- uses: jdx/mise-action@v4
with:
version: 2026.4.27
cache_key_prefix: mise-ci-${{ github.job }}
- name: Ensure nox is installed
run: mise install pipx:nox --force
Expand All @@ -387,6 +399,7 @@ jobs:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
with:
version: 2026.4.27
cache_key_prefix: mise-ci-${{ github.job }}
- name: Lint Markdown
run: mise run lint:docs
Expand All @@ -407,6 +420,7 @@ jobs:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
with:
version: 2026.4.27
cache_key_prefix: mise-ci-${{ github.job }}
- name: Test Go
shell: bash
Expand All @@ -426,22 +440,19 @@ jobs:
fuzz-go:
name: Fuzz Go
runs-on: ubuntu-latest
timeout-minutes: 10
# test:fuzz auto-discovers every Fuzz* target and runs each for FUZZTIME
# (30s default), so this scales as targets are added.
timeout-minutes: 15
env:
CGO_ENABLED: "1"
steps:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
with:
version: 2026.4.27
cache_key_prefix: mise-ci-${{ github.job }}
- name: Fuzz schema type resolution
run: go test ./pkg/schema/ -run='^$' -fuzz=FuzzResolveSchemaType -fuzztime=30s
- name: Fuzz JSON schema generation
run: go test ./pkg/schema/ -run='^$' -fuzz=FuzzJSONSchema -fuzztime=30s
- name: Fuzz Python parser
run: go test ./pkg/schema/python/ -run='^$' -fuzz=FuzzParsePredictor -fuzztime=30s
- name: Fuzz type annotation parsing
run: go test ./pkg/schema/python/ -run='^$' -fuzz=FuzzParseTypeAnnotation -fuzztime=30s
- name: Fuzz all targets (auto-discovered)
run: mise run test:fuzz

test-rust:
name: Test Rust
Expand All @@ -455,6 +466,7 @@ jobs:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: jdx/mise-action@v4
with:
version: 2026.4.27
cache_key_prefix: mise-ci-${{ github.job }}
- name: Test Rust
run: mise run test:rust
Expand All @@ -478,6 +490,7 @@ jobs:
run: tar xf dist/*.tar.gz --strip-components=1
- uses: jdx/mise-action@v4
with:
version: 2026.4.27
cache_key_prefix: mise-ci-${{ github.job }}
- name: Remove src to ensure tests run against wheel
run: rm -rf python/cog
Expand Down Expand Up @@ -508,6 +521,7 @@ jobs:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: jdx/mise-action@v4
with:
version: 2026.4.27
cache_key_prefix: mise-ci-${{ github.job }}
- name: Test coglet-python bindings
run: uvx nox -s coglet -p ${{ matrix.python-version }}
Expand Down Expand Up @@ -628,6 +642,7 @@ jobs:
chmod +x ./cog
- uses: jdx/mise-action@v4
with:
version: 2026.4.27
cache_key_prefix: mise-ci-${{ github.job }}
- name: Set wheel environment
run: |
Expand Down Expand Up @@ -787,6 +802,7 @@ jobs:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: jdx/mise-action@v4
with:
version: 2026.4.27
cache_key_prefix: mise-ci-${{ github.job }}
- name: Check coglet crates.io publish
run: cargo publish --dry-run -p coglet --manifest-path crates/Cargo.toml
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ jobs:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
with:
version: 2026.4.27
cache_key_prefix: mise-docs-${{ github.job }}

- name: Generate CLI docs
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/mirror-cog-base-images.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ jobs:

- uses: jdx/mise-action@v4
with:
version: 2026.4.27
cache_key_prefix: mise-mirror-${{ github.job }}

- name: Install crane
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/release-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ jobs:

- uses: jdx/mise-action@v4
with:
version: 2026.4.27
cache_key_prefix: mise-rel-${{ github.job }}

- name: Update coglet version constraint
Expand Down Expand Up @@ -219,6 +220,7 @@ jobs:

- uses: jdx/mise-action@v4
with:
version: 2026.4.27
cache_key_prefix: mise-rel-${{ github.job }}

- name: Check for existing release
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/release-publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ jobs:

- uses: jdx/mise-action@v4
with:
version: 2026.4.27
cache_key_prefix: mise-pub-${{ github.job }}
- uses: rust-lang/crates-io-auth-action@v1
id: auth
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/rust-advisories.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
with:
version: 2026.4.27
cache_key_prefix: mise-ci-${{ github.job }}
- name: Check advisories
id: advisories
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/update-compatibility-matrices.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ jobs:
- name: Install tools
uses: jdx/mise-action@v4
with:
version: 2026.4.27
cache: true
cache_key_prefix: mise-compatibility-matrices

Expand Down
2 changes: 1 addition & 1 deletion VERSION.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.21.0-rc.1
0.21.0-rc.3
52 changes: 31 additions & 21 deletions architecture/02-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,15 @@ class Runner(BaseRunner):

The resolver handles local imports relative to the predictor file and project root:

| Import Style | File Resolved |
| ------------------------------------ | ----------------------------------------------------------- |
| `from output_types import X` | `<project>/output_types.py` |
| `from .output_types import X` | `<predictor-dir>/output_types.py` |
| `from models.output import X` | `<project>/models/output.py` |
| `from .models.output import X` | `<predictor-dir>/models/output.py` |
| `from output_types import X as Y` | `<project>/output_types.py` (alias tracked) |
| `from .output_types import X as Y` | `<predictor-dir>/output_types.py` (alias tracked) |
| `from . import output_types` | `<predictor-dir>/output_types.py` (module alias tracked) |
| Import Style | File Resolved |
| ---------------------------------- | -------------------------------------------------------- |
| `from output_types import X` | `<project>/output_types.py` |
| `from .output_types import X` | `<predictor-dir>/output_types.py` |
| `from models.output import X` | `<project>/models/output.py` |
| `from .models.output import X` | `<predictor-dir>/models/output.py` |
| `from output_types import X as Y` | `<project>/output_types.py` (alias tracked) |
| `from .output_types import X as Y` | `<predictor-dir>/output_types.py` (alias tracked) |
| `from . import output_types` | `<predictor-dir>/output_types.py` (module alias tracked) |

**How it distinguishes local from external**: the resolver converts the module path to a filesystem path and checks if the file exists. If `output_types.py` exists in the project directory, it's local. If not (e.g., `from transformers import ...`), it's external. Known external packages (stdlib, torch, numpy, etc.) are skipped without a filesystem check.

Expand Down Expand Up @@ -175,18 +175,28 @@ Each `SchemaType` produces its JSON Schema fragment via `JSONSchema()`:

### Input Types

| Python | JSON Schema | Notes |
| ------------------------------------- | ---------------------------------------------------------------- | -------------------------- |
| `str` | `{"type": "string"}` | |
| `int` | `{"type": "integer"}` | |
| `float` | `{"type": "number"}` | |
| `bool` | `{"type": "boolean"}` | |
| `cog.Path` | `{"type": "string", "format": "uri"}` | URLs downloaded at runtime |
| `cog.File` | `{"type": "string", "format": "uri"}` | File uploads |
| `cog.Secret` | `{"type": "string", "format": "password", "x-cog-secret": true}` | Masked in logs |
| `list[T]` | `{"type": "array", "items": {...}}` | |
| `Optional[T]` | Type T + not in `required` | Input fields only |
| `Literal["a", "b"]` / `choices=[...]` | `{"enum": ["a", "b"]}` | |
| Python | JSON Schema | Notes |
| ------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------------- |
| `str` | `{"type": "string"}` | |
| `int` | `{"type": "integer"}` | |
| `float` | `{"type": "number"}` | |
| `bool` | `{"type": "boolean"}` | |
| `cog.Path` | `{"type": "string", "format": "uri"}` | URLs downloaded at runtime |
| `cog.File` | `{"type": "string", "format": "uri"}` | File uploads |
| `cog.Secret` | `{"type": "string", "format": "password", "x-cog-secret": true}` | Masked in logs |
| `list[T]` | `{"type": "array", "items": {...}}` | |
| `Optional[T]` / `T \| None` | Type T + `nullable: true`, not in `required` | Input fields only; never required |
| `A \| B` / `Union[A, B]` | `{"anyOf": [A, B]}` | Input-only, JSON-native unions only |
| `A \| B \| None` | `{"anyOf": [A, B]}` + `nullable: true` | Multi-variant union; stays in `required` unless a default is supplied |
| `Literal["a", "b"]` / `choices=[...]` | `{"enum": ["a", "b"]}` | |

Input unions are intentionally narrower than output types. Cog supports JSON-native input unions (`str`, `int`, `float`, `bool`, `dict`/`Any`, `list[T]`, and `None`) so request validation can happen at the HTTP boundary and Python normalisation can choose a deterministic value type. Cog rejects unions involving `Path`, `File`, `Secret`, custom coders, and `BaseModel` because those cases are ambiguous for clients or runtime coercion. Output unions remain unsupported (see below).

A plain single-type optional (`Optional[T]` or `T | None`) is **never** placed in `required`, regardless of whether a default is supplied. A multi-variant nullable union (`A | B | None`) is different: because the field carries a concrete `anyOf` value type, it stays in `required` unless a default makes it omittable. This is why the two rows above differ in their `required` behaviour.

Nullable behaviour matches every other optional field: `nullable: true` (plus omission from `required`) means an **omitted** value falls back to the default. An **explicit** JSON `null` is still validated against the field type and is rejected at the HTTP edge, because the runtime validator does not treat OpenAPI's `nullable` keyword as an additional accepted value. "May be null" therefore means "may be omitted", not "accepts an explicit null payload".

> **Runtime caveat:** Cog marks optionals as not-`required` in the schema, but the predictor still needs a Python-level default so the omitted value resolves to `None`. Use `value: Optional[T] = Input(...)` (the `Input(...)` supplies an implicit `None`) or `Input(default=None)`. A bare `value: Optional[T]` annotation with no `= Input(...)` generates a correct "optional" schema but raises `TypeError: missing 1 required positional argument` when the field is omitted at runtime.

### Output Types

Expand Down
72 changes: 72 additions & 0 deletions cmd/cog-kong/baseimage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package main

import (
"context"

"github.com/replicate/cog/pkg/cli"
"github.com/replicate/cog/pkg/docker/command"
)

// BaseImageCmd implements the experimental "cog base-image" command group.
type BaseImageCmd struct {
Dockerfile BaseImageDockerfileCmd `cmd:"" help:"Display Cog base image Dockerfile."`
Build BaseImageBuildCmd `cmd:"" help:"Build Cog base image."`
GenerateMatrix BaseImageGenerateMatrixCmd `cmd:"" name:"generate-matrix" help:"Generate a matrix of Cog base image versions (JSON)."`
}

// baseImageVersionFlags groups the version-selecting flags shared by the
// base-image subcommands.
type baseImageVersionFlags struct {
CUDA string `name:"cuda" help:"CUDA version."`
Python string `name:"python" help:"Python version."`
Torch string `name:"torch" help:"Torch version."`

// Hidden flags for parity with the Cobra base-image command.
BreakSystemPackages bool `name:"break-system-packages" hidden:"" help:"Allow pip to modify uv-managed Python installs."`
BuildContextDir string `name:"build-context-dir" hidden:"" help:"Directory for generated Docker build context artifacts."`
Timestamp int64 `name:"timestamp" hidden:"" default:"-1" help:"Number of seconds since Epoch to use for the build timestamp."`
}

func (f baseImageVersionFlags) options() cli.BaseImageOptions {
return cli.BaseImageOptions{
CUDAVersion: f.CUDA,
PythonVersion: f.Python,
TorchVersion: f.Torch,
BreakSystemPackages: f.BreakSystemPackages,
BuildContextDir: f.BuildContextDir,
Timestamp: f.Timestamp,
}
}

// BaseImageDockerfileCmd implements "cog base-image dockerfile".
type BaseImageDockerfileCmd struct {
baseImageVersionFlags `embed:""`

NoCache bool `name:"no-cache" help:"Do not use cache when building the image."`
Progress string `name:"progress" default:"${progress_default}" enum:"auto,plain,tty,quiet" help:"Set type of build progress output: ${enum}."`
}

func (cmd *BaseImageDockerfileCmd) Run(ctx context.Context) error {
opts := cmd.options()
opts.NoCache = cmd.NoCache
opts.ProgressOutput = cmd.Progress
return cli.RunBaseImageDockerfile(ctx, opts)
Comment thread
markphelps marked this conversation as resolved.
}

// BaseImageBuildCmd implements "cog base-image build".
type BaseImageBuildCmd struct {
baseImageVersionFlags `embed:""`
}

func (cmd *BaseImageBuildCmd) Run(ctx context.Context, dockerClient command.Command) error {
return cli.RunBaseImageBuild(ctx, dockerClient, cmd.options())
}

// BaseImageGenerateMatrixCmd implements "cog base-image generate-matrix".
type BaseImageGenerateMatrixCmd struct {
baseImageVersionFlags `embed:""`
}

func (cmd *BaseImageGenerateMatrixCmd) Run() error {
return cli.RunBaseImageGenerateMatrix(cmd.options())
}
Loading
Loading