Repository navigation
feat(rutis): cancel handles for language hooks, dsh tools adapter, rutis-agent tools example - #261
Merged
Merged
Conversation
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
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.
Three additions to
yoagent-rutis(unpublished). No yoagent core change.1. A cancel handle for TypeScript / Python hooks
Every language hook except
on_eventnow gets a cancel handle in its first argument, as the fieldsignal:AbortSignal.Signal(.cancelled,await signal.wait()).The bridge builds that argument as a
Value::Record: the object's fields as data, plussignal: 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:Agent::abort()),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_eventgets no handle, because events are plain data. Caveat:json.dumps(call)in Python now refuses the argument; dropsignalfirst.Tests (
languages_test):call_tool, and a policy timeout aborts a waitingbefore_tool.signal.cancelled == True.Also a unit test for
with_signal.2. dsh tools adapter + example + e2e test
plugins/dsh/dsh-tools-adapter.tsis a rutis TS plugin. It injectstools,systemPromptandyoagent: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.isErrorbecomes an error tool result.before_model: the sections dsh plugins added tosystemPrompt, 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.jsonand its lock pin exact versions in their own install, so the language tests' install stays small:@arcships/rutis*0.7.0@deepseek-ai/cordis4.0.4dsh-free-search0.8.1examples/dsh_tools.rs(featurenode) loads these as loader rows:dsh-web,dsh-system-prompt,dsh-tools, an unchangeddsh-free-search, and the adapter.platform_searchreally searches the web, so it needs network.--liveuses DeepSeek.tests/dsh_test.rsuses a no-network fixture dsh plugin,plugins/dsh/fixture-tools.ts(a Cordis plugin built ondefineTool). It checks:isErrorresult becomes an error tool result,aborted: AbortError).It is gated like the language tests. CI's
rutis-bridge-languagesjob now runsnpm ciinplugins/dshtoo.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
ToolRegistryand the bridge'sRegistry. It registers aHandlerwhose per-runwith_toolsmaps eachToolDefto a yoagent tool:ok: falsebecomesToolError::Failed, carrying rutis-agent'serror: ...text.executeas rutis-agent's cancel token.The showcase:
replace_textedits a file in a temp dir: create, a failingstr_replace, a good one, view.word_counttool is registered into the registry while the host runs, and the next run offers it (hot add).It also has
--live(DeepSeek), and it checks its own outcome.Pinning caveat: crates.io
rutis-agent0.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] rutisat the same tag.cargo tree -dshows no duplicatedrutis(onerutis v0.6.1 (git…v0.7.0)) and oneaimux-core.Checks run locally
fmt --check: bridge and the example workspace.clippy --all-targetswithRUSTFLAGS=-Dwarnings: no features,node,python,websocket,node,python,websocket; also the example workspace.doc -Dwarnings: default features and all language features.YOAGENT_RUTIS_REQUIRE_RUNTIMES=1, afternpm ciinplugins/andplugins/dsh/and a Python 3.12 venv withrutis==0.7.0:languages_test: 11 tests, 2 of them new.dsh_test: 1 test.language_pluginsandpolicy_plugindsh_tools, offline and--live(DeepSeek searched GitHub and answered)rutis-agent-tools, scripted and--live(DeepSeek drovereplace_text, then the hot-addedword_countreturned9 words)The root crate is untouched apart from CLAUDE.md, CHANGELOG and ci.yml.
🤖 Generated with Claude Code
https://claude.ai/code/session_01T7iq5hpndSiHQcnAsywKuG