feat(plugins): add DeepSeek Harness agent adapter - #1461
Open
cosmicBboy wants to merge 5 commits into
Open
Conversation
Add `flyteplugins-agents-deepseek`, running DeepSeek Harness (`deepseek-harness-sdk`) agents on Flyte. It follows the shared agent-adapter contract — `tool` + `run_agent`/`run_agent_sync` with the standard keyword surface — and passes `assert_adapter_conforms`. Tools need a different mechanism here than in the client-side SDKs. The harness has no tool-registration message: its wire protocol is `initialize` / `session/prompt`, and its tool surface is whatever its Cordis composition provides inside the runtime subprocess (its in-process `harness.registerTool` is a TypeScript plugin API the Python SDK can't reach). What every composition does provide is local bash in a workspace we choose, so each Flyte-task tool is published there as an executable shim that calls back over a Unix socket into `task.aio(...)` — a durable child action with its own container/resources, retries and caching. The tool manual rides on the prompt, since there is no tool-declaration channel either. `DeepSeekHarness.run` is blocking, so it is driven via `asyncio.to_thread`; that is what keeps the event loop free to serve tool calls while the agent works. Durability is session resume rather than per-turn replay (the model loop is in a subprocess Flyte doesn't intercept, as with the Claude adapter): the harness's JSONL session store is mirrored onto a `flyte.Checkpoint`, keyed by a session id derived from the task's action, so a retry continues the conversation. With `memory_key` the same archive is kept in a keyed `MemoryStore` instead, giving cross-run memory and subsuming crash-resume. Also renders the run timeline into the task report, mapping the runtime's session events (assistant turns with token usage, `tool/call` / `tool/result`, `turn/end`) plus the bridge's per-tool outcomes. Includes 61 tests — the bridge ones run the published shim as a real subprocess, covering shim -> socket -> `task.aio` end to end, including concurrent calls and tool failures — plus six examples and the adapter README. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…t example "tread on each other" tripped codespell (tread ==> thread, treat). Reworded to "interfere with each other" rather than widening the repo-wide ignore list. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
| @env.task(retries=2) | ||
| async def run_tests(directory: str) -> str: | ||
| """Run the test suite in a directory and return the pytest output.""" | ||
| done = await asyncio.to_thread( |
Contributor
There was a problem hiding this comment.
can we use asyncio.create_subprocess_exec or asyncio.create_subprocess_shell instead? we could also use flyte.sandbox but i don't think it makes much of a difference in this case.
Contributor
|
just wondering if it’s worth supporting this given that it still seems to be in preview and the sdk isn’t as mature. looks like there are a lot of workarounds needed to get it working properly, right? i’m fine with supporting it but just wondering. |
Collaborator
Author
|
yeah, I think we can just sit on this one until the DeepSeek harness is out of preview |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
flyteplugins-agents-deepseek— running DeepSeek Harness (deepseek-harness-sdk) agents on Flyte, following the patterns established by the other adapters inplugins/agents/.It passes the shared
assert_adapter_conformscheck, sotool+run_agent/run_agent_syncpresent the same call shape as every other adapter:Why the tool layer is different
Worth a look in review, since it departs from the client-side adapters.
DeepSeek Harness has no tool-registration message. Its wire protocol is
initialize/session/prompt, and its tool surface is whatever its Cordis plugin composition provides inside the runtime subprocess — there is nowhere to hand a Python function. (The runtime does have a first-classharness.registerTool(ctx, tool), but it's a TypeScript plugin API the Python SDK can't reach, so it's not available to an adapter working with the stock composition.)What every composition does provide is local bash, scoped to a working directory the adapter chooses. So the bridge meets it there:
<workspace>/.flyte_tools/<name>as a small executable shim (stdlib-only Python, run under this process's own interpreter, so the harness runtime needs nothing installed);run_agentlistens on a Unix domain socket in a private temp dir;.flyte_tools/get_weather '{"city": "Paris"}', the shim forwards the JSON args over the socket, and the adapter awaitstask.aio(...)— a durable child action with its own container/resources, retries and caching.Since there's no tool-declaration channel either, the tool manual (names, parameter types, an example invocation each) is prepended to the prompt. A failing tool comes back as a non-zero exit with the reason on stderr, so the agent can react instead of the run dying. The shims are the only thing written into the workspace and are removed when the run ends.
DeepSeekHarness.runis blocking, so it's driven viaasyncio.to_thread— that's what keeps the event loop free to serve tool calls while the agent works.Durability, memory, observability
flyte.traceleaf. The harness's JSONL session store is mirrored onto aflyte.Checkpoint, keyed by a session id derived from the task's action, so a retry continues the conversation instead of restarting. Tool durability/caching applies regardless.memory_key, the same session archive lives in a keyedMemoryStoreinstead, giving cross-run memory — which also covers crash-resume, so it supersedes the per-run checkpoint.tool/call/tool/result,turn/end) plus the bridge's per-tool outcomes are rendered into the task report. Notifications arrive on the worker thread and are marshalled back onto the loop before touching the report, so timeline ordering stays correct.workspace=points the harness's own bash/editor at a real directory (e.g. a downloadedflyte.io.Dir), which is what the harness is actually built for.Testing
61 tests, all offline — no network, no API key, no controller.
The bridge tests are the substantive ones: they run the published shim as a real subprocess, exactly as the harness's bash tool would, covering shim → socket →
task.aioend to end, including concurrent calls, tool failures, and bad arguments. Onerun_agenttest has a fake harness invoke a shim from inside its blockingrun, which is the actual contract — tool calls must be served whilerunis in flight.Separately, I smoke-tested that the config the adapter builds launches the real bundled runtime and completes the JSON-RPC
initializehandshake (no API key needed for that part), so the subprocess/config wiring is verified against the real SDK rather than only mocks.Not verified: no live model call was made (no DeepSeek API key available), so whether a real model reliably picks up and invokes the shims from the prompt manual is untested. That's the one part of this design that depends on model behavior rather than mechanism, and it's worth a live run before release.
Also
plugins/agents/README.mdand the publish workflow matrix. Test CI auto-discovers it (it has atests/dir), so no change needed there.deepseek-harness-sdkis currently pre-release only (0.1.0rc7), henceprerelease = "allow"in the plugin's[tool.uv]. Its pinned runtime wheel covers Linux x86-64/aarch64 and macOS 14+ arm64, so CI (ubuntu x64) resolves fine.DeepSeekHarnessConfig.🤖 Generated with Claude Code