fix stale timeout, empty tool calls and trailing-space commands
Three defects from a review of the agent loop. A session's tool schema is built by create(), and load() then replaced the whole object with the one on disk, so a session saved under SOLO_TIMEOUT=42 went on telling the model the 42 s limit while run() enforced whatever the environment said now. The schema moves into mkschema() and is rebuilt after the load, so the description and the deadline always agree. A tool call whose arguments held no command, which a flaky router or a truncated stream can produce, was passed to the shell as an empty string. The shell ran it as [exit 0], the agent asked again, and a peer that always did this pinned the loop at full speed: a mock measuring 685 requests in five seconds of the same call. Such a call is now answered with an error instead of a shell run, command is declared required in the schema, and three rounds in a row that run nothing end the turn. The same mock now stops after three requests. dispatch() read a line without trimming it, so "/help " was taken as the generic parameter editor and wrote a help key into the session, "/quit " did not quit, and "/temperature 0.5 " stored trailing spaces. A line that starts with / or ! is right-trimmed before it is routed; a task keeps its exact text, leading space included, so the model still sees what was typed and a leading space still keeps the line out of the history. Eleven offline regression tests cover the three, including a mock endpoint that sends nothing but empty tool calls. make test: 42 passed, 0 failed.
This commit is contained in:
@@ -258,6 +258,11 @@ below, but it only writes a `timeout` field into the saved session, which the
|
||||
API ignores and which does not change the command timeout. Restart with the
|
||||
environment set instead.
|
||||
|
||||
The timeout is not persisted with the session: the tool description, which
|
||||
quotes it, is rebuilt from the current environment on every start, so a session
|
||||
saved under `SOLO_TIMEOUT=42` and reopened under `SOLO_TIMEOUT=99` describes and
|
||||
enforces 99 seconds.
|
||||
|
||||
## Slash commands
|
||||
|
||||
| Command | Effect |
|
||||
@@ -347,7 +352,10 @@ The loop, in order:
|
||||
|
||||
1. `readline` reads one line. A trailing `\` continues it on the next line.
|
||||
2. `dispatch` routes the line: a slash command changes settings, `!` runs a shell
|
||||
command locally, anything else becomes a task.
|
||||
command locally, anything else becomes a task. The line is right-trimmed
|
||||
first, so `/quit ` and `/help ` are still commands and a value such as
|
||||
`/temperature 0.5 ` carries no trailing spaces; a task keeps its exact
|
||||
text, leading space included.
|
||||
3. A task is appended to the message list as a `user` message and saved.
|
||||
4. `hc` in the forked child performs one HTTP request with libcurl, wrapping the
|
||||
transport-level result as `{"status", "success", "content"}` and writing it
|
||||
@@ -383,6 +391,12 @@ The request is a plain non-streaming `POST /chat/completions` with
|
||||
|
||||
bash(command: string) -> combined stdout/stderr plus an exit marker
|
||||
|
||||
`command` is required by the schema. A tool call that arrives without a usable
|
||||
one, which happens with a flaky router or a truncated stream, is answered with
|
||||
an error instead of being passed to the shell as an empty string, and three
|
||||
rounds in a row with nothing to run end the turn rather than loop on a peer that
|
||||
never sends a command.
|
||||
|
||||
Reasoning fields are handled defensively, because providers disagree on the
|
||||
name: `reasoning` and `reasoning_content` are both read, and both are stored
|
||||
back into the transcript so that a follow-up turn can reuse them.
|
||||
|
||||
Reference in New Issue
Block a user