Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,15 @@ jobs:
| sed 's/\x1b\[[0-9;]*m//g' | tee screen.txt
grep -q "? Run \`rm -rf build\`?" screen.txt
grep -q "bash ✗" screen.txt
- name: Handoff example (scripted; asks without waiting, the answer starts the next run)
shell: bash
timeout-minutes: 5
run: |
mkdir -p "$RUNNER_TEMP/handoff-project" && cd "$RUNNER_TEMP/handoff-project"
git init -q && git -c user.email=ci@ci -c user.name=ci commit -q --allow-empty -m start
# The example checks its own outcome and exits non-zero on a regression.
cargo run --manifest-path "$GITHUB_WORKSPACE/$MANIFEST" --example handoff_agent -- --demo | tee out.txt
grep -q "ok: asked without waiting, answered later, wrote notes.md, two reports" out.txt

# yoagent claims to be a tested GASP-conformant runtime — this job is the
# test: emit an agent repo with the gasp bridge (mock provider, no network)
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,16 @@ adheres to [Semantic Versioning](https://semver.org/).
- **One frontend protocol** (`integrations/yoagent-frontend`): a `Session` runs one agent for any number of frontends — a pi-tui terminal UI (a rutis plugin) and browsers over WebSocket — with commands in (prompt, steer, follow-up, abort, reset, quit), and out every event (text deltas merged), a run lifecycle that is always delivered (`runStart` / `runEnd` with an `outcome`: completed, aborted, rejected or error — also when the agent's task or the session's driver fails), plugins' questions (`uiRequest`: confirm, select, input, notify; the first answer that fits wins, a safe default without a frontend or in time; `uiResolved` says why it closed), `notice`s, and `closed`, and **UI plugins**: a rutis plugin offers the browser an ES module (`renderTool` for a tool's results, `mountPanel` for a side panel). `PluginHost` replaces the rutis setup (runtimes, loader, explicit per-row runtime routing; `load` waits until plugins run — though a TypeScript plugin reads as running before its async start-up finishes, so wait for the handlers you need — and drops a batch that fails). The browser's WebSocket needs the token in the URL the server prints. Example `coding_agent` (terminal or `--web`), tried live in Chrome. yoagent's core and yoagent-rutis's API do not depend on it.
- **`minimal_browser` example**: one agent and the browser page in about 40 lines, no plugins or Node — the smallest starting point.
- **Plugins reload while the agent runs**: `PluginHost::unload(id)`, `reload(id)` (the edited file's new code: a version that does not import is refused and the running one stays; one that fails when it starts leaves the plugin stopped, and the error says so) and `is_running(id)`; the example's `--watch` reloads a plugin when its file changes and tells the frontends, and the example now requires a tool policy, so a stopped policy plugin denies calls instead of letting them through. No rebuild of the host.
- **`handoff_agent` example and `scripts/try-handoff.sh`**: hand it a task and walk away. It plans with the pi extension `@juicesharp/rpiv-todo` (unchanged; drawn as a checklist by the new `todo-plan` UI plugin) and asks without waiting (`ask_owner`). The answer comes back as a steer message, mid-run or starting the next run. Every run ends with a handoff report, plus facts from the host (the change since the handoff, tokens, time, open questions). Tried live with DeepSeek on a phone-sized page.
- **A page opened late shows what happened**: the session keeps its latest runs' messages (up to `HISTORY`, 5000) and replays them to a frontend that connects, after its `Hello`, so a page opened mid-run or afterwards shows the runs, their tool lines, reports and notices. Questions no longer lock the page: they are docked above the input, non-modal.
- **`Session::ask_later`**: a question kept open until answered, even with no frontend attached (one that connects later gets it). It returns `Some(answer)`, or `None` when nobody will answer: withdrawn (a reset withdraws these, since they belong to the forgotten conversation) or the session ended.
- **The example's approval gate** (`confirm-dangerous.ts`, used by `coding_agent` and `handoff_agent`) asks before anything that could destroy or overwrite the owner's files: `rm`, `git reset --hard`, `git clean`, `git checkout --` / `git restore`, `git branch -D`, a forced push, `find -delete`, a write, `mv`/`cp` or `>` over an existing file, and more. Before, it caught only `rm -r`, `sudo`, `chmod -R`, `git push --force`, `mkfs` and `dd`. Files the agent created this session, and the scratch folder a host names in `YOAGENT_SCRATCH`, are exempt. An ended session now ends its open questions with their default, where they used to be left open. Steering or a follow-up that arrives as a run ends (too late for that run to read) now starts the next run instead of waiting for whatever run comes next.
- Tool cards: a tool result's `details.view` is drawn as a card in the browser (and titles the tool line: in the browser when no UI plugin draws the tool, always in the terminal UI); questions gain `message` and multiple-choice `select` (options sent once each); the `ui` service gains `frontends()`; the terminal UI asks again on an answer that does not fit.

### yoagent-rutis (unreleased)

- **The run's conversation, on request**: a handler can ask for `messages` (yoagent's LLM messages) on the `Turn`, `ToolCall` (`before_tool`) and `Stop` it sees — `Handler::with_messages()` in Rust, `options.messages: true` for TypeScript / Python plugins. Off by default (it copies the conversation for every call); handlers that did not ask never get it.
- pi adapter: what does not map (commands, shortcuts, events yoagent never fires) is reported at `info`, not `warn`: it is expected, not a fault (refusals stay warnings).
- **pi adapter: `message_end` and `context` handlers are checked, not refused.** Each message goes (a copy) to the `message_end` handlers once — the assistant message before any of its tools runs — and each request's messages to the `context` handlers; a handler that only reads works, one that would change what it got stops the adapter (yoagent cannot apply the change), and a check cut short by a timeout or cancel does not pass. The final answer is checked after it was given: a rewrite there fails the run only for a required bridge, and refuses later runs. In the [plugin compatibility](docs/evals/plugin-compatibility.md) matrix this takes pi's top 20 from 9 to 12 usable packages (pi-lens, pi-powerline-footer, context-mode; refused 8 → 5).

## yoagent-rutis 0.2.0 (2026-10-10)
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,8 @@ over; commands and UI don't. An extension that hooks something the adapter can't
refused, not half-run. How the 20 most-downloaded packages of each ecosystem fare (by monthly
downloads, among npm's first 1000 search results per keyword), measured:
[plugin compatibility](https://yologdev.github.io/yoagent/evals/plugin-compatibility.html).
`scripts/try-coding-agent.sh` serves a coding agent with pi and DSH plugins for your browser.
`scripts/try-coding-agent.sh` serves a coding agent with pi and DSH plugins for your browser;
`scripts/try-handoff.sh "<task>"` hands a task to an agent that asks without waiting and reports back.

## Runs where agents run

Expand Down
4 changes: 3 additions & 1 deletion integrations/yoagent-frontend/CLAUDE.md

Large diffs are not rendered by default.

59 changes: 59 additions & 0 deletions integrations/yoagent-frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,65 @@ results rendered as links by the UI plugin, pi's question answered in the page.
Logs (`RUST_LOG`) go to stderr with `--web` / `--demo`, else to
`$TMPDIR/yoagent-coding-agent.log`, since the terminal UI owns the screen.

### `handoff_agent`: hand it a task and walk away

A personal handoff agent for any task: research, writing, planning or code.
It works in the directory you start it from, and puts what it makes there:

```bash
mkdir research && cd research && git init
path/to/yoagent/scripts/try-handoff.sh --live "Research the best noise-cancelling headphones right now; write headphones.md with sources. Ask me my budget, but don't wait."
path/to/yoagent/scripts/try-handoff.sh --phone --live "…" # serve on the LAN, for a phone
cargo run --example handoff_agent -- --demo # headless, scripted (CI): asks, gets an answer, writes notes.md
```

- **It plans** with a pi extension used unchanged, `@juicesharp/rpiv-todo`.
The `todo-plan` UI plugin draws the plan as a checklist in the page.
- **It researches** with DSH's free web search, and works with yoagent's
file and shell tools.
- **It asks without waiting.** `ask_owner` puts a question to every
frontend and returns at once, so the agent carries on with what does not
depend on it. The question stays open while nobody is connected
(`Session::ask_later`), and a phone that connects later gets it. The
answer comes back as a steer message: picked up mid-run, or starting a new
run if the agent had stopped. A dismissed question comes back as
"skipped", and the agent decides itself and says so. A reset withdraws
open questions: they belong to the conversation it forgot.
- **Guardrails:**
- A pi extension (`confirm-dangerous.ts`) asks before anything that could
destroy or overwrite what you already have: `rm`, `git reset --hard`,
`git clean`, a forced push, a write or `>` over an existing file, and
more. Files the agent created this session, and its own scratch
folder, are exempt. These questions do wait, and with nobody
connected they are refused.
- Every run needs that policy (`require_policy`).
- Runs are capped in turns and minutes (`--max-minutes`), and in dollars
(`--budget-usd`) when the model has a price. DeepSeek has none in the
bundled table.
- **Every run ends with a handoff report** (Done / Waiting on you /
Decisions / Next). The agent is sent back until it writes one. The host
then adds facts the model cannot make up: the change since the handoff
started (against your working tree as it was, so edits you had not
committed are not counted), commits, new files, tokens, tool calls,
time, and the questions still open.

Tried live with DeepSeek and a phone-sized Chrome on a research task:
"the best noise-cancelling headphones, ask my budget". It asked about
budget and comfort vs sound, and kept searching while they waited. It
folded in the answers as they came, and wrote a 164-line `headphones.md`
with 33 sources and a pick matched to the answers. It also named the one
review site it could not read.

And on a small Python project ("move the config from JSON to TOML"). The agent started before any
browser was attached.
- **Run 1:** it planned, migrated, verified and committed. It asked two
questions along the way without stopping, and reported them open.
- **The first answer** ("delete config.json") started run 2, which did that.
- **The second question, dismissed,** started run 3: the agent decided
itself and wrote down why.
- **While nobody was there,** `rm -rf __pycache__` was refused, and the
agent worked around it.

## Tests

```bash
Expand Down
Loading
Loading