feat: add background job runner, job__* handlers, and start gates

Detached tokio::process runner with a frozen JobEnvSnapshot (env-derived
bin dirs, vault-interpolated agent envs, COYOTE_TOOL_TIMEOUT resolved at
start), process_group(0) with pgid-guarded SIGTERM/SIGKILL escalation,
capture-only ring-buffer telemetry, and LLM_OUTPUT read after wait().
MCP jobs snapshot a single-entry McpRuntime holding only the validated
server and render through the same free fn as the foreground path.

job__start enforces its gates synchronously before any spawn:
jobs_enabled, the backgroundable whitelist with directionality teaching
errors, the per-request declared-names stash captured in
before_chat_completion, then capacity (lazy supervisor get-or-init in
plain sessions). job__check/list read the shared JobState cell without
consuming; job__collect blocks with the escalation early-out and applies
a tail-biased char-boundary cap plus optional tail_lines; job__cancel
kills the group with a 5s grace.

Job declarations are injected iff jobs are enabled at agent init, the
plain-session function-init sites, and the exit_agent rebuild; job__ is
carved out of enabled_tools filtering and excluded from
concrete_tool_names so REPL toggles cannot grant or revoke it.
This commit is contained in:
2026-08-25 17:36:17 -06:00
parent fcc3756634
commit cb025b7fff
8 changed files with 1965 additions and 23 deletions
+11 -1
View File
@@ -3,7 +3,7 @@ use super::*;
use crate::{
client::Model,
config::memory,
function::{Functions, run_llm_function},
function::{Functions, jobs::DEFAULT_MAX_CONCURRENT_JOBS, run_llm_function},
graph, rag,
};
@@ -225,6 +225,16 @@ impl Agent {
functions.append_supervisor_functions();
}
if app.function_calling_support
&& agent_config
.max_concurrent_jobs
.or(app.max_concurrent_jobs)
.unwrap_or(DEFAULT_MAX_CONCURRENT_JOBS)
> 0
{
functions.append_job_functions();
}
functions.append_teammate_functions();
functions.append_user_interaction_functions();