agent__check on a finished agent delegated to agent__collect, which
returned the full (unbounded) result and consumed the handle. That
contradicted the tool's own docs and broke the check-then-collect
pattern: a second collect on the same id failed.
check now reports { status: finished } with a pointer to
agent__collect and leaves the handle registered; collect is the single
retrieval verb. The tool description and prompt table are updated to
stop promising that check returns the result.
The turn-end guardrail only counted still-running agents, so an agent
that finished before the turn ended was invisible: its uncollected
result was silently dropped. Jobs were never counted at all.
The guardrail now enumerates every registered task (running and
finished, agents and jobs) via Supervisor::list_tasks and renders a
kind-aware prompt with two sections: still-running tasks to reclaim,
and completed-but-uncollected tasks with the exact collect command.
At the force-terminate cap, finished-but-uncollected handles are
explicitly discarded with a warning naming the lost ids, so the
guardrail cannot loop forever on handles nobody will collect.
Implements T1 of plans/background-jobs-design.md (§6, R7/R8/R9):
- Supervisor.handles is now HashMap<String, TaskHandle> where
TaskHandle = Agent(AgentHandle) | Job(JobHandle); agent-facing
accessors (active_count, effective_active_count, is_finished, take,
inbox, abort_signal_for, list_agents) match only Agent variants,
preserving all existing external behavior byte-for-byte.
- New JobHandle/JobState/JobStatus/JobResult types with pgid-guarded
process-group kill discipline: Drop and cancel_all/cancel_recursive
kill the group only while state.pgid is still set (pid-reuse guard),
via libc::killpg on unix and JoinHandle::abort elsewhere.
- Per-kind job capacity: Supervisor carries max_concurrent_jobs
(builder-set, default 0); job registration rejects at capacity.
- Cross-kind teaching errors at the four agent-lookup miss sites
(agent__check/collect/cancel/send_message) when the id is a
registered job or job_-prefixed; genuinely-unknown ids keep their
existing messages.
- Supervisor init condition is now can_spawn_agents || jobs_enabled in
use_agent and both child-agent spawn paths, with agent capacity 0 in
jobs-only contexts; use_agent cancels the old supervisor recursively
before replacing it.
- max_concurrent_jobs config plumbing: global Config field, AgentConfig
override + accessor, all four AppConfig touch points including the
COYOTE_MAX_CONCURRENT_JOBS env override; shared
effective_max_concurrent_jobs/jobs_enabled predicates
(agent override -> global -> default 5; 0 disables).
- Stage dependency-free RingBuf (64 KiB default) in src/function/jobs.rs
for the upcoming job output pump.
- New sanctioned dependency: libc 0.2 under cfg(unix).
Route CallToolResult content through the render.rs content policy per
plans/mcp-resources-prompts-design.md §6 (T8): oversized text sliced at
TEXT_MAX_BYTES_CLAMP with a self-explaining truncation note, image/audio/
embedded blob content spilled (or inlined when UTF-8-clean) instead of
shipping base64 into model context, and structuredContent subject to the
same ceiling. Clamp server-controlled uri/mime metadata strings to the new
METADATA_MAX_BYTES bound in both the read and tool-result paths, sanitize
the terminal rendering of MCP dispatch errors while keeping raw text in
the tool_call_error payload, and surface resource audience annotations in
both mcp_search results and mcp_read metadata via the catalog.
Emit an mcp_prompt_<server> declaration for servers advertising the
prompts capability, execute prompts via McpRuntime::prompt on both tool
dispatch chains, and return the flattened prompt text as the tool
result. Sanitize server-controlled prompt names, descriptions, and
argument names before terminal rendering, and attribute the .prompt
argument inquire label to its server and prompt.
Per plans/mcp-resources-prompts-design.md §5.2 (T7).
Implements plans/mcp-resources-prompts-design.md section 4.3 (T4):
mcp_read_<server> declaration and handler wired to render.rs, RFC 6570
Level-1-only URI template expansion, defensive ResourceContents parsing,
per-item text paging with pattern filtering, blob spill metadata, an
overall 204800-byte multi-content ceiling, dispatch wiring on both
eval chains, and a render_text paging-stall guard.
Per-server McpServerFeatures (tools fail-open, resources/prompts
fail-closed) now drive which meta-functions are declared, with
gated_meta_function_prefixes as the single gating seam; read/prompt
declarations land together with their handlers. The server-enablement
sentinel keys on the always-emitted search name so resources-only
servers survive role filtering.
Implements plans/mcp-resources-prompts-design.md §4.4/D7 (T5).
Implements T1 of plans/mcp-resources-prompts-design.md (§4.1, §4.6):
- Replace list_tools(None) with cursor-following list_all_tools() at the
three call sites (start_server catalog build, catalog_items, describe)
so paginating servers no longer silently lose tools past page one.
- Add MCP_READ/MCP_PROMPT prefix constants (declared nowhere yet; wired
in T4/T7) plus centralized helpers MCP_META_FUNCTION_PREFIXES,
is_mcp_meta_function, and mcp_meta_function_names.
- Mechanically replace every hand-rolled 3-prefix starts_with triple
(partition in eval_tool_calls, 3 exclusion triples in
select_enabled_functions, 3 inclusion triples + per-server name
construction in select_enabled_mcp_servers) with the helpers,
preserving the existing lax starts_with matching semantics and the
mcp_invoke_* enablement sentinel (sentinel moves to search in T5).
- Behavior-neutral: dispatch chains keep their 3 arms, emission stays
at exactly 3 meta-functions per server, existing tests unmodified.
- Add unit tests: helper classification, prefix-soundness property,
lax-matching pin, ordered candidate-name construction.
The path-escape guard that uninstall applies to recorded paths now also
covers update's obsolete-file deletion through a shared check, so a
tampered store cannot turn either delete site into an arbitrary file
removal. Updates gain a working non-interactive path: --yes now applies
to --update-bundle (locally modified files, obsolete files, and modified
mcp entries are all kept; everything else refreshes), owned mcp entries
whose recorded hash still matches the local entry take the remote side
without prompting, and the non-TTY conflict bails name the flag that
actually works per surface. An update records its new commit and version
only after files and mcp entries land, so an aborted update cannot claim
content it never wrote. The store gains a version field and rejects
stores from newer builds, the corrupt-store error no longer advises the
removal that would forfeit ownership tracking, and duplicate records
tracking one source abort a rename instead of overwriting a record.
Reinstalling from a source URL reclassifies owned unmodified files as
silent refreshes just like updates. git runs with GIT_TERMINAL_PROMPT=0
and a null stdin so private or mistyped URLs fail instead of hanging.
File comparison fills buffers fully before comparing, deleting an
obsolete file prunes emptied directories, mcp.json backfill uses the
fsynced atomic writer, --list-bundles no longer triggers builtin
backfill, bundle-name completion logs store errors instead of swallowing
them and offers --yes, and REPL .uninstall rejects unknown flags.
The user-origin marker on replaced mcp.json entries is now sticky:
re-records and cross-bundle transfers only upgrade replaced to
transferred when the prior record proves bundle origin, so updating a
bundle can no longer make uninstall delete a key the user had before the
bundle replaced it. Canonical source URLs lowercase only the host, since
self-hosted forges treat repository paths as case-sensitive and
collapsing distinct repos misdirects updates and uninstalls. git clone
invocations pass '--' before the URL so a crafted source cannot be
parsed as a git flag. Lifecycle flags (--install, --install-builtins,
--update-bundle, --uninstall) and their companions now conflict
explicitly instead of first-match dispatch silently dropping actions.
--install-from returns as a hidden tombstone that errors with the
replacement instead of feeding the flag to the LLM as prompt text.
--list-bundles dispatches before config load so a pure read no longer
boots MCP servers. write_file_atomic fsyncs before the rename so a crash
cannot persist a truncated store. REPL: .uninstall accepts --yes,
.install rejects trailing tokens after a category, and .install remote
gets a migration hint. Plus polish: host validation rejects '#' and '?',
renamed_to no longer serializes null, derived names get a debug assert
against the validator, completions share DEFAULT_GIT_HOST, README
mentions skills.
- Hang fix: 120s timeout on compression LLM call + raw_stream
channel-close handling (None => break instead of spinning)
- Supervisor effective active count: stop counting finished
JoinHandles as occupying capacity slots
- Universal tool result size cap: truncate_if_needed() on
ToolResult, applied in eval_tool_calls() after escalation block;
configurable via max_tool_result_chars in AppConfig/AgentConfig
- Windowed compression: compression_keep_last config param keeps
the N most recent messages visible after compression
- Fix pre-existing flaky test: add #[serial] to
handle_list_available_unrestricted_when_no_whitelist so it does
not race with TestConfigDirGuard-based tests that temporarily
populate the agents data dir