mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
The existing assertion-only executable smoke proved selected outputs but could not detect drift across the integrated Python SDK, JSON-RPC notification stream, and persisted session shape. Keep this separate from ACP snapshots because it must launch the actual platform-native packaged executable through the Python SDK. The deterministic model drives Cordis dynamic tool mounting, a Code Mode worker dispatch, direct spawn delegation, workflow-worker delegation, plugin disposal, and the parent/child persistence lineage. Commit four portable goldens for the SDK result and three JSONL logs. Normalize timestamps, temporary paths, opaque session and agent identifiers, and bulky request headers while retaining ordering, tool names and arguments, header deltas, results, lineage, and final responses so all native build legs compare the same behavior. Run the comparison in the label-gated executable build workflow and document the current coverage in the paired implemented RFC.
288 lines
12 KiB
YAML
288 lines
12 KiB
YAML
name: Build single-exe
|
|
|
|
# Single-file executable (single-exe) builds of the DeepSeek Harness SDK
|
|
# runtime. The build pipeline and target platforms are specified in
|
|
# docs/rfc/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md:
|
|
# each target is built natively on a runner of its own platform (no
|
|
# cross-compilation) by scripts/build-exe-for-python-sdk.ts, which deploys
|
|
# the dsh-jsonrpc-agent-pkg closure manifest with @yao-pkg/pkg into
|
|
# dist-exe/.
|
|
#
|
|
# The run retains exactly the four wheels that make up one Python release:
|
|
# one platform-independent SDK wheel plus one native runtime wheel for each
|
|
# supported platform. The matrix still exercises the bare executable and
|
|
# source tree, but they are intermediate test inputs rather than artifacts.
|
|
#
|
|
# Two explicit triggers, deliberately no per-commit CI: the exe is a
|
|
# release-style deliverable, and the build (full pnpm build + pnpm deploy +
|
|
# pkg across a 3-platform matrix, ~100MB per artifact) is far too expensive
|
|
# to run on every push. Either dispatch it from the Actions tab, or put the
|
|
# `build-exe` label on a pull request to build that PR's merge result
|
|
# (remove and re-apply the label to rerun); any other label leaves the jobs
|
|
# skipped. There is no `ref` input on purpose: actions/checkout already
|
|
# checks out the ref the run was triggered on — the dispatched branch/tag,
|
|
# or the PR merge ref.
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
targets:
|
|
description: >-
|
|
Comma-separated pkg targets to build. Any subset of:
|
|
node24-linux-x64, node24-linux-arm64, node24-macos-arm64.
|
|
Empty builds all three.
|
|
type: string
|
|
required: false
|
|
default: ''
|
|
pull_request:
|
|
types: [labeled]
|
|
|
|
# Runs on the same ref supersede each other (per branch/tag for dispatch,
|
|
# per PR merge ref for label runs).
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
# Least privilege: the jobs only read the repo; artifact upload needs no
|
|
# extra scope.
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
# Turn the `targets` input into the build matrix. The `matrix` context is
|
|
# not available in a job-level `if:` (jobs.<job_id>.if only sees
|
|
# github/needs/vars/inputs), so target selection happens here instead of
|
|
# skipping matrix legs; an unknown target name fails the whole run loudly
|
|
# instead of being silently ignored. The label gate lives here too: `build`
|
|
# needs this job, so skipping it skips the whole run.
|
|
plan:
|
|
name: plan targets
|
|
if: github.event_name == 'workflow_dispatch' || github.event.label.name == 'build-exe'
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
outputs:
|
|
matrix: ${{ steps.plan.outputs.matrix }}
|
|
version: ${{ steps.version.outputs.version }}
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
|
|
- name: Resolve repository version
|
|
id: version
|
|
run: |
|
|
set -euo pipefail
|
|
version="$(jq -r '.version // empty' package.json)"
|
|
[[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || {
|
|
echo "::error::package.json version must be stable X.Y.Z, got '$version'"
|
|
exit 1
|
|
}
|
|
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Compute matrix from targets input
|
|
id: plan
|
|
env:
|
|
# Empty on label runs and on dispatch with the input left blank —
|
|
# both mean "all three targets".
|
|
TARGETS: ${{ inputs.targets || 'node24-linux-x64,node24-linux-arm64,node24-macos-arm64' }}
|
|
run: |
|
|
set -euo pipefail
|
|
matrix='[]'
|
|
IFS=',' read -r -a targets <<< "$TARGETS"
|
|
for raw in "${targets[@]}"; do
|
|
t="$(echo "$raw" | xargs)" # trim surrounding whitespace
|
|
[ -z "$t" ] && continue
|
|
# Native builds only — each target maps to a runner of its own
|
|
# platform: linux-arm64 uses GitHub's hosted arm64 label
|
|
# ubuntu-24.04-arm (there is no ubuntu-latest-arm), macos-arm64
|
|
# uses macos-latest (Apple Silicon since macos-14).
|
|
case "$t" in
|
|
node24-linux-x64) runner=ubuntu-latest ;;
|
|
node24-linux-arm64) runner=ubuntu-24.04-arm ;;
|
|
node24-macos-arm64) runner=macos-latest ;;
|
|
*)
|
|
echo "::error::Unknown target '$t'. Supported: node24-linux-x64, node24-linux-arm64, node24-macos-arm64."
|
|
exit 1
|
|
;;
|
|
esac
|
|
matrix="$(jq -c --arg target "$t" --arg runner "$runner" '. + [{target: $target, runner: $runner}]' <<< "$matrix")"
|
|
done
|
|
if [ "$matrix" = '[]' ]; then
|
|
echo "::error::The targets input selected nothing to build."
|
|
exit 1
|
|
fi
|
|
echo "Matrix: $matrix"
|
|
echo "matrix=$matrix" >> "$GITHUB_OUTPUT"
|
|
|
|
sdk-wheel:
|
|
needs: plan
|
|
name: deepseek_harness-${{ needs.plan.outputs.version }}-py3-none-any.whl
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
|
|
- uses: actions/setup-python@v6
|
|
with:
|
|
python-version: '3.10'
|
|
|
|
- name: Install Python build tooling
|
|
run: python -m pip install uv==0.11.23
|
|
|
|
- name: Build release-shaped SDK wheel
|
|
run: >-
|
|
python scripts/build-python-release.py
|
|
--package sdk
|
|
--output-dir dist-python
|
|
|
|
- uses: actions/upload-artifact@v6
|
|
with:
|
|
name: deepseek_harness-${{ needs.plan.outputs.version }}-py3-none-any.whl
|
|
path: dist-python/deepseek_harness-${{ needs.plan.outputs.version }}-py3-none-any.whl
|
|
if-no-files-found: error
|
|
|
|
build:
|
|
needs: [plan, sdk-wheel]
|
|
name: ${{ matrix.target }}
|
|
runs-on: ${{ matrix.runner }}
|
|
timeout-minutes: 45
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include: ${{ fromJSON(needs.plan.outputs.matrix) }}
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 24
|
|
|
|
- uses: actions/setup-python@v6
|
|
with:
|
|
python-version: '3.10'
|
|
|
|
- name: Install Python build tooling
|
|
run: python -m pip install uv==0.11.23
|
|
|
|
- name: Enable corepack (pnpm)
|
|
run: corepack enable
|
|
|
|
- name: Resolve pnpm store path
|
|
id: pnpm-store
|
|
run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT"
|
|
|
|
# Unlike ci.yml (x64-only), this matrix spans two Linux architectures
|
|
# that share runner.os, so runner.arch is part of the key.
|
|
- uses: actions/cache@v4
|
|
with:
|
|
path: ${{ steps.pnpm-store.outputs.path }}
|
|
key: ${{ runner.os }}-${{ runner.arch }}-node-24-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-${{ runner.arch }}-node-24-pnpm-
|
|
|
|
# The first run per target has pkg-fetch download yao-pkg's patched
|
|
# Node binary into ~/.pkg-cache; cache it so later runs skip the
|
|
# download. The target string pins Node major + platform + arch;
|
|
# pnpm-lock.yaml rolls the key when @yao-pkg/pkg (and with it the
|
|
# pinned patched-binary version) is bumped, with restore-keys still
|
|
# seeding from the previous cache.
|
|
- uses: actions/cache@v4
|
|
with:
|
|
path: ~/.pkg-cache
|
|
key: pkg-fetch-${{ matrix.target }}-${{ hashFiles('pnpm-lock.yaml') }}
|
|
restore-keys: |
|
|
pkg-fetch-${{ matrix.target }}-
|
|
|
|
- name: Install (immutable)
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
# The script runs the whole pipeline itself (pnpm run build → pnpm
|
|
# deploy --prod → pkg) and writes its output to dist-exe/ by default.
|
|
- name: Build single-exe
|
|
run: pnpm exec tsx scripts/build-exe-for-python-sdk.ts --targets=${{ matrix.target }}
|
|
|
|
- name: Resolve platform outputs
|
|
id: runtime
|
|
env:
|
|
TARGET: ${{ matrix.target }}
|
|
VERSION: ${{ needs.plan.outputs.version }}
|
|
run: |
|
|
set -euo pipefail
|
|
platform="${TARGET#node24-}"
|
|
exe="$PWD/dist-exe/dsh-jsonrpc-agent-pkg-$platform"
|
|
[ -x "$exe" ] || { echo "::error::$exe missing or not executable"; exit 1; }
|
|
case "$platform" in
|
|
linux-x64) wheel=deepseek_harness_runtime_bin-$VERSION-py3-none-manylinux_2_28_x86_64.whl ;;
|
|
linux-arm64) wheel=deepseek_harness_runtime_bin-$VERSION-py3-none-manylinux_2_28_aarch64.whl ;;
|
|
macos-arm64) wheel=deepseek_harness_runtime_bin-$VERSION-py3-none-macosx_11_0_arm64.whl ;;
|
|
*) echo "::error::Unsupported runtime platform $platform"; exit 1 ;;
|
|
esac
|
|
echo "platform=$platform" >> "$GITHUB_OUTPUT"
|
|
echo "exe=$exe" >> "$GITHUB_OUTPUT"
|
|
echo "wheel=$wheel" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Full-turn SDK, executable snapshot, and direct-binary smoke
|
|
run: >-
|
|
uv run --python 3.10 --group test --project python/sdk
|
|
python scripts/smoke-python-runtime.py
|
|
--scenario all
|
|
--exe "${{ steps.runtime.outputs.exe }}"
|
|
|
|
- name: Build release-shaped runtime wheel
|
|
run: >-
|
|
python scripts/build-python-release.py
|
|
--package runtime
|
|
--platform "${{ steps.runtime.outputs.platform }}"
|
|
--runtime-exe "${{ steps.runtime.outputs.exe }}"
|
|
--output-dir dist-python
|
|
|
|
- uses: actions/download-artifact@v8
|
|
with:
|
|
name: deepseek_harness-${{ needs.plan.outputs.version }}-py3-none-any.whl
|
|
path: dist-python
|
|
|
|
- name: Install only the SDK into a clean venv and run zero-config
|
|
env:
|
|
VERSION: ${{ needs.plan.outputs.version }}
|
|
run: |
|
|
set -euo pipefail
|
|
python -m venv "$RUNNER_TEMP/dsh-sdk-smoke"
|
|
"$RUNNER_TEMP/dsh-sdk-smoke/bin/python" -m pip install \
|
|
--find-links dist-python \
|
|
deepseek-harness=="$VERSION"
|
|
"$RUNNER_TEMP/dsh-sdk-smoke/bin/python" scripts/smoke-python-runtime.py \
|
|
--scenario sdk-default
|
|
|
|
- name: Check Linux GLIBC requirements
|
|
if: runner.os == 'Linux'
|
|
run: |
|
|
set -euo pipefail
|
|
readelf --version-info "${{ steps.runtime.outputs.exe }}" | tee glibc-versions.txt
|
|
maximum="$(sed -n 's/.*Name: GLIBC_\([0-9.]*\).*/\1/p' glibc-versions.txt | sort -V | tail -1)"
|
|
[ -n "$maximum" ] || { echo "::error::No GLIBC requirements found"; exit 1; }
|
|
dpkg --compare-versions "$maximum" le 2.28 || {
|
|
echo "::error::Executable requires GLIBC_$maximum but wheel claims manylinux_2_28"
|
|
exit 1
|
|
}
|
|
|
|
- name: Run wheel in a manylinux 2.28 container
|
|
if: runner.os == 'Linux'
|
|
env:
|
|
RUNNER_ARCH: ${{ runner.arch }}
|
|
VERSION: ${{ needs.plan.outputs.version }}
|
|
run: |
|
|
set -euo pipefail
|
|
case "$RUNNER_ARCH" in
|
|
X64) image=quay.io/pypa/manylinux_2_28_x86_64 ;;
|
|
ARM64) image=quay.io/pypa/manylinux_2_28_aarch64 ;;
|
|
*) echo "::error::Unsupported Linux runner architecture $RUNNER_ARCH"; exit 1 ;;
|
|
esac
|
|
docker run --rm -e VERSION -v "$PWD:/work" -w /work "$image" bash -euxo pipefail -c '
|
|
/opt/python/cp310-cp310/bin/python -m venv /tmp/dsh-sdk
|
|
/tmp/dsh-sdk/bin/python -m pip install --find-links /work/dist-python deepseek-harness=="$VERSION"
|
|
/tmp/dsh-sdk/bin/python /work/scripts/smoke-python-runtime.py --scenario sdk-default
|
|
'
|
|
|
|
- uses: actions/upload-artifact@v6
|
|
with:
|
|
name: ${{ steps.runtime.outputs.wheel }}
|
|
path: dist-python/${{ steps.runtime.outputs.wheel }}
|
|
if-no-files-found: error
|