DeepSeek Harness Plugin Hub

Publish and manage complete Harness Profiles. Discover Plugins for your next setup.

Explore

PluginsPresetsDocsNews

Community

Publish a pluginContactReport an issue

Resources

Plugin Hub on GitHubDeepSeek HarnessSystem statusPrivacy notice
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

Independent and unofficial. Not affiliated with, authorized by, or endorsed by DeepSeek.

Graph Runtime — DSH Plugin for DeepSeek Harness
← Plugins

dsh-graph-runtime

Graph Runtime

Graph runtime capabilities for the DeepSeek Harness: register LangGraph compiled graphs as DSH tools, plus a graph-driven routing agent.

The plugin will be installed here. Keep web if you are unsure.

npx -y @deepseek-ai/dsh plugin --profile web add dsh-graph-runtime@0.2.0
READMECompatibilityVersions

Compatibility and provenance

Graph Runtime is published as dsh-graph-runtime and currently resolves to version 0.2.0. The Hub verifies its manifest and preserves the exact installation source for reproducible installs.

DSH compatibility
*
Runtime surfaces
any
Release source
npm
Registry updated
9/17/2026

Versions

0.2.0stable
9/17/2026
0.1.0stable
9/17/2026

Related plugins

Loading related plugins…

Latest
0.2.0
DSH
*
HMR
Process restart
Tree shaking
Safe tree shaking not declared
Unpacked size
95.2 kB
Files
21
Surface
any
License
MIT
Source
npm
GitHub
★ 1
Weekly downloads
0
Last push
9/17/2026
View source ↗
README badge

Click the badge to copy Markdown for your README.

Do you maintain this Plugin?Claim benefit · Priority security scan

Verify the GitHub repository declared in package.json to manage this listing. After you claim it, Hub will prioritize a security scan of the current version and publish the result when it passes.

Claim this Plugin →
Report an issue
DeepSeek Harness Plugin Hub
ProfilesPluginsCategoriesNewsDocsSign inManage Profiles
ProfilesPluginsCategoriesNewsDocsSign in

Related plugins

More verified plugins in agents-orchestration.

Headless@deepseek-ai/dsh-headlessThe dsh one-shot bundle: a direct core Agent/Session runner over dsh-base with no Host, HTTP, or browser layerExperimental Agent Team Web Profile@deepseek-ai/dsh-experimental-agent-team-web-profileExperimental Web profile layer for Agent Teams Remote and UI pluginsSubagent Codex@deepseek-ai/dsh-subagent-codexOne-shot Codex subagent provider over the official app-server protocolSubagent Claude Code@deepseek-ai/dsh-subagent-claude-codeOne-shot Claude Code subagent provider over the official Agent SDK

README

dsh-graph-runtime

English | 中文

Graph runtime capabilities for the DeepSeek Harness (DSH), published as a standalone Cordis plugin/bundle.

Current capabilities:

  • Graph definition and auto-mounting: define an "extended StateGraph" with defineGraph — a graph factory plus a registration declaration (whether it becomes a DSH tool, whether it joins the registry) plus a tool-argument validation hook. Loading the module completes registration: at plugin startup the already-defined graphs auto-mount, and later definitions mount immediately; developers make no service calls at all. Tool name/description are auto-discovered (compile({ name, description }) or structural synthesis), compilation is lazy and memoized (the registration path has zero compile side effects), and parameter validation, cancellation forwarding, and result rendering are all owned by this plugin.
  • GraphRoutingAgent: a graph-driven routing agent. It hands the graphTools registry (optionally filtered) to the LLM and requires a tool to be chosen; the returned tool call goes through existence, JSON, schema, and author validate checks, retrying with the rejection reason on failure (default 3, configurable); once it passes, the corresponding graph executes. Every chosen-tool execution is wrapped by the graphTools/pre-execute / graphTools/post-execute waterfall extension points (gating, auditing, result transforms).

Installation and wiring

# After publishing
npm install dsh-graph-runtime
# Or from a local path (e.g. inside the yolo-agent repo)
npm install file:../dsh-graph-runtime

Wiring follows the same steps as any other DSH bundle, either or both:

  • Add one entry to the root composition cordis.yml (this package ships its own cordis.patch.yml; adding the package name to the web profile's dsh.profile.bundles auto-mounts it the same way):

    - id: graph-runtime
      name: dsh-graph-runtime
    

Usage

Graph authors only need defineGraph — defining is registering; no mount or service call is required:

// graphs/echo.ts
import { defineGraph } from 'dsh-graph-runtime'
import { Annotation, END, START, StateGraph } from '@langchain/langgraph'

const State = Annotation.Root({
  topic: Annotation<string>,
  log: Annotation<string[]>({
    reducer: (left, right) => [...left, ...right],
    default: () => [],
  }),
})

export const echoGraph = defineGraph({
  name: 'echo_graph',
  description: 'Echo the given topic.',
  build: () =>
    new StateGraph(State)
      .addNode('echo', async (state) => ({ log: [`seen:${state.topic}`] }))
      .addEdge(START, 'echo')
      .addEdge('echo', END),
  asTool: true, // Declared as a model-visible DSH tool; default false
  // asGraphTool: true, // Also listed in the graphTools registry for discovery; default true, can be omitted
  // validate: (input) => Boolean((input as { topic?: string }).topic), // Argument-validation hook; defaults to always passing
})

The only wiring requirement: a graph module must be imported by some bundle entry (a single barrel line import './graphs' suffices). At plugin apply, every already-defined graph auto-mounts, and later definitions mount immediately; a single graph failing to mount (duplicate name, invalid declaration) warns and is skipped without blocking startup.

Tools declared asTool: true register at the plugin's global layer: visible to every agent (including the default agent). For scope-private or runtime-constructed cases, use the imperative entries below.

GraphRoutingAgent

Trimmed from dsh's ReactLoopAgent to a single routing step: one routing round = the LLM picks a tool → validation → (optional retry) → the graph executes.

import { GraphRoutingAgent } from 'dsh-graph-runtime'

const router = new GraphRoutingAgent(ctx, {
  provider: 'deepseek',
  model: 'deepseek-chat',
  filter: { allow: ['echo_graph', 'search_graph'] }, // allow/deny, same as tools.restrict
  maxRetries: 3, // Maximum LLM re-calls after a failed validation; default 3
  // fallback: myFallbackTool, // Optional: overrides the built-in fallback tool
})

const outcome = await router.route({ input: 'echo hello for me' })
// outcome: { tool: 'echo_graph', args: {...}, result: <the graph's final state> }

Behavior details:

  • A tool must be chosen: dsh-llm's GenerateOptions has no toolChoice field, so the "required" semantics are enforced by the loop — when the model picks no tool (a plain-text reply) there is no retry; the fallback tool runs instead and the outcome is marked fallback: true. The default fallback is the built-in graph_routing_fallback (a no-op: empty input, returns {}); override it through fallback with a ToolDefinition or a GraphDefinition, and the overrider is responsible for keeping empty input {} valid under its schema (e.g. parameters: {}).
  • The validation chain (any failure retries with the reason): the tool exists in the (filtered) registry; the arguments are valid JSON; execution-time schema validation (ToolArgsError); the author's validate hook (GraphValidationError, with context.request available during routing for semantic rejection). A graph's own runtime failure is not a routing failure and propagates as-is.
  • Filtering: filter follows the same allow/deny semantics as tools.restrict (both may be given; they intersect); a list naming unknown tools or an empty result after filtering is a configuration error.
  • Exhausted retries end with the last failure reason.

Routing extension points

Routed executions do not pass through dsh's ctx.tools pipeline (the registry there is a separate surface), so the pipeline-shaped extension points live on the routing loop itself as cordis waterfall events: every chosen-tool attempt traverses graphTools/pre-execute before the graph runs and graphTools/post-execute after it settles. Audit logging, metrics, policy gating, and result transforms all hang off them:

// Gate before execution: a deny feeds the reason back to the model for a
// retry, exactly like a validate rejection.
ctx.on('graphTools/pre-execute', async (execution, next) => {
  if (disabledTools.has(execution.tool)) {
    return { kind: 'deny', reason: `"${execution.tool}" is disabled by policy` }
  }
  return next()
})

// Observe or reshape the settled outcome; fires on successes and failures alike.
ctx.on('graphTools/post-execute', async (execution, outcome, next) => {
  if (execution.tool === 'search_graph' && !('error' in outcome)) {
    return { kind: 'accept', result: redact(outcome.result) } // replace the returned value
  }
  return next()
})

Semantics:

  • execution carries tool / args / request (the original routing text) / signal; the same object reaches both events of one attempt.
  • Waterfall order: listeners run in registration order; next() delegates, and answering without next() vetoes the rest — the first decision wins.
  • deny (pre) and block (post) join the ordinary retry loop: the reason is fed back to the model and retries exhaust into the standard error. A block rejects any settled outcome — it can also convert a propagating graph error into a retried choice instead of an error.
  • accept keeps the settled outcome; with result it replaces the value returned to the caller (listeners still observed the original).
  • The fallback path (no tool chosen) traverses neither event; if you need hooks there, wrap your fallback tool's execute. A throwing listener propagates as-is. Listeners register with plain ctx.on like any cordis event; inside a listener, this is the agent's ctx.

Registration declaration

FieldMeaning
asToolWhether to register as a model-visible DSH tool (ctx.tools). Default false; the auto-mount path registers at the plugin's global layer (visible to all agents), the imperative mount path registers at the layer of the ctx passed in.
asGraphToolWhether to list in the graphTools registry for discovery (get/list, GraphRoutingAgent). Default true.

defineGraph fields

FieldMeaning
nameThe graph name, which becomes the registered tool name; globally unique; the reserved name run_code is rejected.
descriptionOptional. The model-facing tool description; defaults to the description written by compile({ description }) on the build() product (when the author compiled it), or a structural description synthesized from nodes and state keys.
buildGraph factory: returns an uncompiled builder (the plugin plain-compile()s it at first invocation, passing no options), or an author-compiled instance. Checkpointer and all compile options belong entirely to the author — for state memory, return builder.compile({ checkpointer: new MemorySaver() }).
validateOptional argument-validation hook: receives (input, context) before the graph runs; context.tool is the tool name, and when routed through GraphRoutingAgent context.request carries the original request text (absent on direct calls). Returning false or throwing rejects (a thrown message becomes the rejection reason); defaults to always passing. This enables "this request is not mine" semantic rejection, which the routing loop retries with the reason.
parametersOptional DSH ParameterSchemaSpec. When provided, the whole argument object becomes the graph input; when omitted, the tool exposes a single required input JSON parameter (its description is appended with the discovered state key names, but the value itself stays opaque). Model-visible graphs should always declare it — see the next section.
threadIdOptional thread key; forwarded as-is into the graph config's thread_id. Whether it produces state memory is decided by the graph's own checkpointer (the plugin manages none).
configurableOptional extra configurable keys forwarded as-is (thread_id is governed by threadId).
timeoutMsOptional cooperative timeout budget; the graph must respond to the cancellation signal and converge.

Parameter schema quality (required reading for model-visible graphs)

parameters is forwarded as-is as a DSH ParameterSchemaSpec and enforced before execution (out-of-range enums, type mismatches, and missing requireds are all stopped by ToolArgsError). For graphs a model will see, keep every parameter explicitly typed, concretely described, and fully enumerated:

defineGraph({
  name: 'greet_graph',
  build: () => buildGreetGraph(),
  parameters: {
    name: { type: 'string', required: true, description: 'The name to greet.' },
    style: {
      type: 'string',
      required: true,
      enum: ['formal', 'casual'],
      description: 'Greeting style.',
    },
    times: { type: 'integer', description: 'Repeat count.', default: 1 },
    tags: { type: 'array', items: { type: 'string' }, description: 'Extra tags.' },
  },
})

Supported capabilities: type is one of string / number / integer / boolean / null / array / object / json, unions use oneOf; every key may carry description / title / default / examples; enums use an enum matching the type (e.g. a string array), single-value constraints use const; nested objects use object + properties + additionalProperties, arrays use items; mark required keys individually with required: true.

Why nothing is derived automatically: langgraph drops Annotation type information at compile time, leaving only key names and aggregation semantics in the runtime structure — a schema with fabricated types or requireds would mislead the model into assembling arguments the validation layer then rejects, which is worse than an honest input. So the fallback without declared parameters is a single required input (json), whose description carries the discovered state keys (e.g. Expected state keys: topic, log (accumulated).); when asTool: true and no parameters are declared, the mount path logs a warn through ctx.logger to nudge the author.

Imperative entries (dynamic / low-level scenarios)

// A runtime-constructed graph: createGraphDefinition constructs without
// publishing (defineGraph auto-mounts, so an explicit mount while the
// plugin is active would double-register), paired with an explicit mount;
// ctx is both the fiber owner and the ctx.tools registration target.
const dynamicGraph = createGraphDefinition({
  name: 'dynamic_flow',
  build: () => buildDynamicGraph(),
  asTool: true,
})
const mounted = ctx.graphTools.mount(dynamicGraph, ctx)

// Discovery side: ctx.graphTools.get('echo_graph') / ctx.graphTools.list()

// Already holding a compiled graph, or only want a one-off conversion:
const entry = ctx.graphTools.register(compiledGraph, ctx)
const tool = ctx.graphTools.create(compiledGraph)

import { createGraphTool } from 'dsh-graph-runtime' also does the pure conversion directly, with the same effect. Passing a graph definition to register fails with an error pointing at mount — graph definitions carry their own registration declaration. defineGraph targets graph authors (declare-and-publish, auto-mount); createGraphDefinition targets runtime-dynamic scenarios (construction stays construction, mounting stays mounting).

Behavior contract

  • Defining is registering: defineGraph publishes the graph definition to a package-level static queue; at plugin apply the queue is taken over and drained, and later definitions mount immediately. A single graph failing to mount (duplicate name, invalid declaration) is skipped with a ctx.logger warning and never blocks startup; after the plugin disposes, defineGraph queues again and can remount with the next plugin load.
  • Registration is only registration: defineGraph/mount/register never call langgraph's compile(); an uncompiled builder is plain-compile()d exactly once at the tool's first real invocation and memoized (structural discovery only reads the builder's nodes/channels and likewise never compiles).
  • The checkpointer belongs to the graph: the plugin never injects or manages compile options; threadId is only a thread_id pass-through, and whether memory takes effect depends on the instance the author returns from build().
  • Each tool call is one graph.invoke; tool arguments pass DSH parameter-schema validation and the author's validate hook before entering the graph, and the caller's AbortSignal is forwarded as-is.
  • The graph's final state must be lossless JSON, or it is rejected with an explicit error; the tool result returns to the model as a pretty-printed JSON text block.
  • Interaction with ctx.tools happens only when asTool: true is declared: the auto-mount path registers at the plugin's global layer (visible to every agent including the default agent); imperative mount(definition, ctx) registers at the layer of the passed ctx (on an agent-scoped ctx, visible only to that agent, shadowing a same-named global tool). The graphTools registry is separate state: listed names are unique, unregister is idempotent, and entries leave automatically with the disposing fiber of the mounting path.
  • Limits of auto-discovery: Annotation type information and addNode descriptions are dropped when langgraph compiles, so a parameter schema cannot be derived automatically. Discovery degrades safely for a custom GraphInvocable missing runtime fields, but the name must be provided explicitly.