doc+fix: background procs force timeout=0, update docs

- Background procs get timeout=0 (no deadline) set by toolsrv at
  the execution layer — not injected as an arg from olliesrv.
  Foreground default remains 30s. User can still override via args.
- Timeout logic simplified: caller sets default, ExecuteTool honors it.
- System prompt: clarify background has no timeout, uses 'stop' not 'kill'
- architecture.md: document lifecycle (connection-based ownership)
- evolution.md: update interrupt section with streaming, lifecycle, id= format
- README.md: add parallel execution to capabilities table
This commit is contained in:
Ollie Agent 2026-08-11 10:18:30 +02:00
parent 97c208fa39
commit 391465be77
6 changed files with 32 additions and 20 deletions

View File

@ -58,7 +58,8 @@ Everything lives under `$XDG_CONFIG_HOME/ollie/` (default: `~/.config/ollie/`)
| **One-shot LLM** | `o generate "explain monads"` or `echo "prompt" \| o generate` |
| **Run an agent** | Create a session + agent via 9P — then connect with any frontend ([ellie](doc/ellie.md), KDE (GUI/KRunner/Kate), `o tui`) |
| **Remote execution** | Set `remote=user@host` in session config |
| **Background processes** | Pass `"background": true` to any tool — output auto-injected into context |
| **Background processes** | Pass `"background": true` to any tool — runs with no timeout, output auto-injected, lifecycle tied to session |
| **Parallel execution** | Non-conflicting tool calls within a turn run in parallel (scope-based conflict scheduling) |
| **Agents** | Write a JSON config in `data/agents/` with agent prompt in `data/prompts/` |
| **Tools** | Drop an executable + `.meta` in `~/.config/ollie/tools/` — load at runtime via `/tool_load` |
| **Domain skills** | Load markdown skill modules at runtime |

View File

@ -52,9 +52,11 @@ Run long-running commands in the background by passing `"background": true` to s
The result is a `<system-proc-background>` tag containing the process ID. You do NOT need to poll for output — background process updates are automatically injected into your context as `<system-proc-interrupt>` blocks alongside tool results. These include status (running/exited), exit code, and the last 20 lines of output.
Background processes have no timeout — they run until they exit, you stop them, or the session ends.
**Rules**:
- Use background for long operations (builds, test suites, deployments, log tailing). Do NOT background short commands (< 5s).
- React to `<system-proc-interrupt>` naturally. If a build fails, fix it. If output is irrelevant, kill the process.
- React to `<system-proc-interrupt>` naturally. If a build fails, fix it. If output is irrelevant, stop the process.
- Stop background processes when done: `echo term | ollie-9p write session/$OLLIE_SESSION_ID/agent/$OLLIE_UNAME/proc/<id>/ctl` (use `kill` instead of `term` to force-kill)
- Read full output: `ollie-9p read session/$OLLIE_SESSION_ID/agent/$OLLIE_UNAME/proc/<id>/out`

View File

@ -26,8 +26,8 @@ type Config struct {
CWD string
Env map[string]string
Yolo bool
Timeout int
Output io.Writer // if set, stream stdout/stderr here in real-time
Timeout int // 0 = no timeout
Output io.Writer // if set, stream stdout/stderr here in real-time
Started chan *os.Process // if set, receives the os.Process after Start()
}
@ -56,11 +56,7 @@ func ExecuteTool(ctx context.Context, info toolsrv.ToolInfo, args json.RawMessag
// Extract dispatch-level flags from args
bypassed := false
timeout := cfg.Timeout
timeoutExplicit := timeout > 0
if !timeoutExplicit {
timeout = 30
}
timeout := cfg.Timeout // 0 = no timeout; caller sets default
sandboxName := "default"
var argMap map[string]interface{}
@ -75,7 +71,6 @@ func ExecuteTool(ctx context.Context, info toolsrv.ToolInfo, args json.RawMessag
delete(argMap, "bypass")
}
if t, ok := argMap["timeout"]; ok {
timeoutExplicit = true
switch v := t.(type) {
case float64:
timeout = int(v)

View File

@ -247,9 +247,11 @@ func (st *State) NewProc(ctx context.Context, payload string, background bool) (
// For foreground, use nil (local buffer in exec path).
var outputWriter io.Writer
var startedCh chan *os.Process
timeout := 30 // default for foreground
if background {
outputWriter = &procWriter{proc: proc}
startedCh = make(chan *os.Process, 1)
timeout = 0 // background procs never timeout
}
// Execute in goroutine
@ -257,7 +259,7 @@ func (st *State) NewProc(ctx context.Context, payload string, background bool) (
defer close(proc.done)
defer cancel()
out, exitCode := st.executeTool(procCtx, info, args, cwd, envCopy, yolo, outputWriter, startedCh)
out, exitCode := st.executeTool(procCtx, info, args, cwd, envCopy, yolo, timeout, outputWriter, startedCh)
proc.mu.Lock()
if !background {
@ -299,7 +301,8 @@ func (st *State) NewProc(ctx context.Context, payload string, background bool) (
}
// executeTool runs a tool and returns output + exit code.
func (st *State) executeTool(ctx context.Context, info toolsrv.ToolInfo, args map[string]string, cwd string, envExtra map[string]string, yolo bool, output io.Writer, started chan *os.Process) (string, int) {
// timeout: 0 = no timeout, >0 = seconds.
func (st *State) executeTool(ctx context.Context, info toolsrv.ToolInfo, args map[string]string, cwd string, envExtra map[string]string, yolo bool, timeout int, output io.Writer, started chan *os.Process) (string, int) {
// Convert args map to JSON for the execution path
jsonArgs := argsToJSON(args)
@ -307,6 +310,7 @@ func (st *State) executeTool(ctx context.Context, info toolsrv.ToolInfo, args ma
CWD: cwd,
Env: envExtra,
Yolo: yolo,
Timeout: timeout,
Output: output,
Started: started,
}

View File

@ -156,12 +156,14 @@ Tool calls within a single turn are dispatched in parallel using resource-based
This means N file edits on different paths complete in one round-trip instead of N sequential calls. Shell is always a barrier (global scope) since its effects are opaque.
#### Background Processes
Any tool call can include `"background": true` to execute asynchronously. The result is an immediate PID; the tool runs in toolsrv's `proc/new.bg`. Background process output is automatically injected into the model's context as `<system-proc-interrupt>` blocks at safe points (alongside subsequent tool results). The model can react to build failures, log events, etc. without polling.
Any tool call can include `"background": true` to execute asynchronously. The result is an immediate process ID; the tool runs in toolsrv's `proc/new.bg` with no timeout. Output streams in real-time into `proc/{id}/out` and is automatically injected into the model's context as `<system-proc-interrupt>` blocks alongside subsequent tool results. The model can react to build failures, log events, etc. without polling.
Process lifecycle is connection-based: toolsrv owns all procs, and session death (toolsrv exit) kills everything via `KillAll()` + `Pdeathsig`. Exited procs remain in the tree for 10 minutes after last read, then are garbage collected. The model controls procs via `proc/{id}/ctl` (term, kill, dismiss).
#### Dispatch Flags
All tools automatically receive four optional parameters (injected into schemas at runtime):
- `bypass` — run outside sandbox via bypass broker
- `timeout` — execution timeout in seconds
- `timeout` — execution timeout in seconds (0 = no timeout; background forces 0)
- `sandbox` — sandbox profile name
- `background` — run asynchronously, auto-inject output

View File

@ -1188,20 +1188,28 @@ opt in to parallelism.
### Background Process Interrupts
Any tool call can include `"background": true` to execute asynchronously
via `proc/new.bg`. The model receives a PID immediately and continues working.
Background process output is automatically injected into the model's context
as `<system-proc-interrupt>` blocks alongside subsequent tool results:
via `proc/new.bg`. The model receives a process ID immediately and continues working.
Background processes have no timeout — they run until they exit naturally,
are stopped via `proc/{id}/ctl`, or die with the session.
Output is streamed in real-time into `proc/{id}/out` and automatically injected
into the model's context as `<system-proc-interrupt>` blocks alongside
subsequent tool results:
```xml
<system-proc-interrupt pid="42" cmd="go test ./..." status="exited" exit="1">
<system-proc-interrupt id="42" cmd="go test ./..." status="exited" exit="1">
--- FAIL: TestFoo (0.00s)
foo_test.go:12: expected 3, got 2
FAIL
</system-proc-interrupt>
```
The model sees these naturally — no polling, no explicit `process_output` calls.
It can react to failures or kill processes by PID when done.
The model sees these naturally — no polling needed. It can react to failures
or stop processes via the proc ctl file (`term`, `kill`, `dismiss`).
**Process lifecycle**: toolsrv owns all procs. Session death (toolsrv exit)
kills all procs via `KillAll()` + `Pdeathsig`. Exited procs remain in the
tree for 10 minutes after last read, then are garbage collected.
### Universal Dispatch Flags