Resolve packaged profile proxies with Node ESM import conditions from each package installation, and fail loud when an explicit runtime export or legacy main entry is missing. Serialize the shared profile fallback under the existing cross-process writer lock so concurrent dsh processes cannot observe partial proxies; either carrier now replaces the other carrier’s managed entry without manual cleanup. Give Python initialize its own 10-second default bound and name the selected profile in timeout diagnostics, while leaving ordinary agent turns unbounded by default. Package the dynamically resolved web frontend and skill-badge assets so the runtime wheel’s normal dsh profiles do not depend on pkg static-discovery accidents. Rewrite the root launch rule and every active stale SDK-runtime note to the shipped dsh profile architecture in both languages. Focused tests prove import-only and transitive package exports, lock contention, cross-carrier transitions, missing-entry failures, asset inventory, and bounded initialization.
6.2 KiB
Agent Note: Python SDK runtime through the dsh profile launcher
Status: implemented
English | 中文
Problem
The Python SDK distributed a private Node application that booted a complete external cordis.yml, while every other supported application entered through dsh profiles. That exception duplicated environment loading, configuration ownership, plugin resolution, shutdown, artifact names, and test paths. It also made SDK customization an all-or-nothing application tree: a caller replacing one plugin had to own the JSON-RPC server and every unrelated deployment row.
A normal profile cannot be adopted only at the Python wrapper. The runtime executable must contain the dsh CLI, shipped profile and bundle files, native libraries, and a module-resolution path that works when profile files and external plugins live outside pkg's virtual filesystem.
Decision
One application launcher
The runtime executable packages @deepseek-ai/dsh and runs its ordinary command grammar. The Python client selects --profile sdk by default, forwards ordered absolute --patch paths, and may select another dsh executable or profile. The private @deepseek-ai/dsh-sdk-python-runtime application package and checked-in runtime cordis.yml do not exist. JSON-RPC serving remains the @deepseek-ai/dsh-sdk-app bundle and @deepseek-ai/dsh-sdk-jsonrpc-server plugin, not a Python-owned boot path.
The public Python configuration is dsh_bin, profile, ordered patches, dsh_home, process cwd/environment, provider/model/token selection, a bounded initialization timeout, and optional turn/shutdown timeouts. It does not expose a complete Cordis tree or arbitrary launch argv. RunResult reports the protocol-owned run values and does not duplicate the profile's persistence path.
Every Python launch requires either explicit dsh_home or a non-empty DSH_HOME in the child environment. The SDK never discovers ~/.dsh. The selected home consistently owns profiles, external plugins, credentials, settings, and sessions.
Plugin customization
Persistent SDK customization uses the same profile interfaces as direct CLI use. dsh plugin --profile sdk ... manages external dependencies and bundle order, $DSH_HOME/profiles/sdk/cordis.patch.yml owns persistent row changes, the home patch applies machine-local changes across profiles, and Python patches supplies invocation-specific overlays. A different profile is valid only when it retains an SDK server row. Missing profiles, bundles, server rows, and invalid patches fail without a complete-config fallback; a profile that remains alive without serving JSON-RPC fails the independently bounded initialization handshake with a diagnostic naming that profile.
The runtime wheel installs a dsh console command. Ordinary profile and SDK execution remains Node-free; external package management requires a caller-installed pnpm.
Executable packaging
The zero-code deployment manifest is dsh-python-runtime-closure. It packages node_modules/@deepseek-ai/dsh/lib/bin.js and profile, bundle, preset, native-addon, and shared-library assets into deepseek-harness-sdk-runtime-<platform>-<arch>. The wheel distribution names, Python import modules, JSON-RPC messages, and wire-stable serverInfo.name = deepseek-harness-sdk-runtime remain unchanged.
Plain Node profiles use symlinks in $DSH_HOME/profiles/node_modules to share installation packages with external plugins. An operating-system symlink cannot traverse pkg's /snapshot filesystem, so the packaged CLI writes small real ESM proxy packages instead. Each proxy resolves the source package's explicit runtime exports under ESM import conditions and re-exports its virtual module URLs. One cross-process writer lock serializes fallback healing, preventing partial proxy visibility and allowing either carrier to replace the other carrier's managed entry. Loader rows and external plugin peers therefore resolve through the normal profile parent walk while retaining one Cordis and one instance of each bundled module.
The published target set is Linux x64, Linux arm64, and macOS arm64. Installed-wheel black-box CI owns artifact provenance, default and patched profiles, external bundle installation, native tools, MCP, direct JSON-RPC, snapshots, and trusted real-provider turns on every target.
Existing decisions and supersession
This decision implements and supersedes the Python exception and deferred-migration sections of the single dsh application launcher. It supersedes the private application, external complete-config, artifact-name, and customization facts in the single-file Python SDK runtime distribution, which remains authoritative for pkg/SEA, wheel construction, native target validation, and publication. No active note is fully superseded, so none is archived.
Alternatives considered
Keep complete cordis.yml as an advanced escape hatch. Rejected because it preserves a second application assembly and lets a caller bypass profile environment, plugin, and shutdown ownership.
Silently use ~/.dsh for compatibility. Rejected because an SDK process must not inherit a person's plugins, credentials, settings, or sessions without an explicit choice.
Copy the virtual dependency tree into every home. Rejected because it duplicates hundreds of megabytes and loads a second Cordis instance. Export-preserving proxies are small and retain module identity.
Bundle pnpm and Node package management into every SDK launch. Rejected because installed plugins are deployment state, not per-turn runtime work. Only dsh plugin needs the external package manager.
Consequences
Python callers configure the same profile vocabulary as TypeScript and direct CLI users, and arbitrary external bundles can extend an SDK profile without replacing the application tree. Homes must now be selected explicitly, complete-config and session_root parameters are unavailable, and the executable includes shared-library assets plus profile-module proxies. The stronger installed-wheel CI makes those package, profile, native, and provider paths release requirements rather than source-only assumptions.