7.5 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 runnable minimal example selects the shipped sdk-minimal 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 <name> ... manages external dependencies and bundle order, $DSH_HOME/profiles/<name>/cordis.patch.yml owns persistent row changes, the home patch applies machine-local changes across profiles, and Python patches supplies invocation-specific overlays. A selected 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 standalone sdk-minimal profile lists one repository-owned bundle that inserts its complete explicit tree without dsh-base. Its persistent Bash and string-replace editor are present by composition; the shared JSON-RPC server exposes no root-agent tool filter. Dynamic runtime context, workspace instructions, settings, managed credentials, telemetry, compaction, and every other base row are absent. The same runtime still packages the full sdk and web profiles as separate choices.
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 ESM export map directly under Node import conditions, exposes targets that exist in the installation, and re-exports their virtual module URLs. Export rows without an ESM runtime target and executable-only or declaration-only packages produce no unusable proxy entry; malformed export maps fail startup. A complete matching generation returns without acquiring the cross-process writer lock. A missing or stale entry acquires the lock, rechecks the generation, and repairs it without exposing partial proxies; either carrier can 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, macOS arm64, and Windows x64. 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. The Windows x64 runtime decision owns the fourth artifact and its platform-specific shell surface.
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. The standalone sdk-minimal profile supersedes only this note's minimal-overlay realization. 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 introducing another launcher. Homes are selected explicitly, complete-config and session_root parameters are unavailable, and the executable includes shared-library assets plus profile-module proxies. The full sdk, standalone sdk-minimal, and web applications remain separate profiles inside the same packaged CLI. The installed-wheel CI makes those package, profile, native, and provider paths release requirements rather than source-only assumptions.