Skip to content

feat(rutis): cancel handles for language hooks, dsh tools adapter, rutis-agent tools example - #261

Merged
yuanhao merged 7 commits into
mainfrom
feat/rutis-ecosystem-adapters
Oct 8, 2026
Merged

yuanhao merged 7 commits into
mainfrom
feat/rutis-ecosystem-adapters

Conversation

@yuanhao

@yuanhao yuanhao commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator

Three additions to yoagent-rutis (unpublished). No yoagent core change.

1. A cancel handle for TypeScript / Python hooks

Every language hook except on_event now gets a cancel handle in its first argument, as the field signal:

  • JavaScript: a real AbortSignal.
  • Python: rutis's Signal (.cancelled, await signal.wait()).

The bridge builds that argument as a Value::Record: the object's fields as data, plus signal: Value::Signal. rutis-bridge 0.7 allows a signal nested inside a call argument, and both runtimes decode it per call. It is aborted when the host drops the call future, which happens when:

  • the run is cancelled (Agent::abort()),
  • the hook's timeout passes,
  • the plugin unloads mid-call.

It is never aborted after a call completes.

It is a field, not an extra positional argument, so Python methods with a fixed signature still accept the call. That was the reason no signal was passed before.

on_event gets no handle, because events are plain data. Caveat: json.dumps(call) in Python now refuses the argument; drop signal first.

Tests (languages_test):

  • TS: a run cancel aborts a waiting call_tool, and a policy timeout aborts a waiting before_tool.
  • Python: the coroutine sees signal.cancelled == True.
  • Control, both languages: a completed call's handle is never aborted.

Also a unit test for with_signal.

2. dsh tools adapter + example + e2e test

plugins/dsh/dsh-tools-adapter.ts is a rutis TS plugin. It injects tools, systemPrompt and yoagent:

  • tools: schemas() mapped to yoagent tools, with an optional allowlist.
  • call_tool: execute({callId, name, arguments, signal: call.signal}). Cancelling the yoagent run aborts the dsh call. isError becomes an error tool result.
  • before_model: the sections dsh plugins added to systemPrompt, as a turn note. The harness identity and persona slots are left out, and sections with unset variables are skipped. Capped at 2000 characters.

plugins/dsh/package.json and its lock pin exact versions in their own install, so the language tests' install stays small:

  • @arcships/rutis* 0.7.0
  • dsh 0.2.0-rc.2
  • @deepseek-ai/cordis 4.0.4
  • dsh-free-search 0.8.1

examples/dsh_tools.rs (feature node) loads these as loader rows: dsh-web, dsh-system-prompt, dsh-tools, an unchanged dsh-free-search, and the adapter.

  • By default the model is scripted, but platform_search really searches the web, so it needs network.
  • --live uses DeepSeek.
  • It checks its own outcome. When scripted, that includes free-search's prompt section reaching the model.

tests/dsh_test.rs uses a no-network fixture dsh plugin, plugins/dsh/fixture-tools.ts (a Cordis plugin built on defineTool). It checks:

  • the fixture's tools are offered,
  • a call returns its text,
  • an isError result becomes an error tool result,
  • the prompt-section note reaches the request and is never stored,
  • cancelling the run aborts a slow dsh tool through its signal (aborted: AbortError).

It is gated like the language tests. CI's rutis-bridge-languages job now runs npm ci in plugins/dsh too.

3. rutis-agent tools example workspace

examples/rutis-agent-tools/ is its own [workspace] with path deps. It is not auto-discovered, not packaged and not in CI.

A rutis plugin injects rutis-agent's ToolRegistry and the bridge's Registry. It registers a Handler whose per-run with_tools maps each ToolDef to a yoagent tool:

  • Results come back as text.
  • ok: false becomes ToolError::Failed, carrying rutis-agent's error: ... text.
  • yoagent's cancel token is passed to execute as rutis-agent's cancel token.

The showcase:

  • rutis-agent's real replace_text edits a file in a temp dir: create, a failing str_replace, a good one, view.
  • A word_count tool is registered into the registry while the host runs, and the next run offers it (hot add).
  • Scripted only: a slow tool is cancelled, and its runner never finishes.

It also has --live (DeepSeek), and it checks its own outcome.

Pinning caveat: crates.io rutis-agent 0.2.0 is built on rutis 0.2. The one built on rutis 0.6 (rutis repo, tag v0.7.0) is unpublished. So the example takes rutis-agent from git at that tag and adds [patch.crates-io] rutis at the same tag. cargo tree -d shows no duplicated rutis (one rutis v0.6.1 (git…v0.7.0)) and one aimux-core.

Checks run locally

  • fmt --check: bridge and the example workspace.
  • clippy --all-targets with RUSTFLAGS=-Dwarnings: no features, node, python, websocket, node,python,websocket; also the example workspace.
  • doc -Dwarnings: default features and all language features.
  • All tests with YOAGENT_RUTIS_REQUIRE_RUNTIMES=1, after npm ci in plugins/ and plugins/dsh/ and a Python 3.12 venv with rutis==0.7.0:
    • languages_test: 11 tests, 2 of them new.
    • dsh_test: 1 test.
    • Everything else passes, and the default-features tests pass too.
  • Examples, all passing their own checks:
    • language_plugins and policy_plugin
    • dsh_tools, offline and --live (DeepSeek searched GitHub and answered)
    • rutis-agent-tools, scripted and --live (DeepSeek drove replace_text, then the hot-added word_count returned 9 words)

The root crate is untouched apart from CLAUDE.md, CHANGELOG and ci.yml.

🤖 Generated with Claude Code

https://claude.ai/code/session_01T7iq5hpndSiHQcnAsywKuG

yuanhao and others added 7 commits October 8, 2026 02:42
Every language hook but on_event now finds a cancel handle in its first
argument, as the field `signal`: an AbortSignal in JavaScript, rutis's
Signal in Python. rutis-bridge aborts it when the bridge drops the call
(run cancelled, hook timeout, plugin unloaded mid-call), never after the
call completed. A field, not an extra positional argument, so fixed-
signature Python methods keep working.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T7iq5hpndSiHQcnAsywKuG
plugins/dsh/dsh-tools-adapter.ts offers a DeepSeek Harness tool registry
to yoagent agents (execute with the bridge's cancel handle as dsh's
signal, isError as an error result, dsh plugins' prompt sections as a
turn note). examples/dsh_tools.rs runs the unchanged dsh-free-search
plugin; tests/dsh_test.rs covers the adapter with a no-network fixture
plugin. CI installs plugins/dsh in rutis-bridge-languages.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T7iq5hpndSiHQcnAsywKuG
examples/rutis-agent-tools maps rutis-agent's ToolRegistry to yoagent
tools per run (replace_text, a hot-added tool, a cancelled call).
rutis-agent from git at rutis v0.7.0 with a [patch.crates-io] for rutis,
so the graph holds one rutis crate.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T7iq5hpndSiHQcnAsywKuG
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T7iq5hpndSiHQcnAsywKuG
…el came after

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T7iq5hpndSiHQcnAsywKuG
A UTF-16 slice could leave a lone surrogate, which the host's JSON decoding
rejects, closing the whole Node runtime session (#261 review).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T7iq5hpndSiHQcnAsywKuG
…y shows

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T7iq5hpndSiHQcnAsywKuG
@yuanhao
yuanhao merged commit 2c01205 into main Oct 8, 2026
15 checks passed
@yuanhao
yuanhao deleted the feat/rutis-ecosystem-adapters branch October 8, 2026 01:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant