feat(jobs): long-running job substrate (Tier A milestone 1) - #83
Merged
Conversation
Typed long-running tools start detached work via an internal JobManager over
pluggable execution backends behind one JobBackend interface. The LLM only ever
sees the tool; JobManager is internal infrastructure (never an LLM tool) and
there is no generic job_submit.
- tools/jobs/backends.py: JobBackend + JobState; SubprocessBackend (detached,
start_new_session, restart-safe on-disk exit_code sentinel via subshell, zombie
reaping with returncode fallback) and InProcessBackend (thread pool).
- tools/jobs/manager.py: JobManager + JobRecord — persistence under
~/.{app_name}/jobs/, startup reconciliation, concurrency cap+queue.
- tools/jobs/tools.py: reference run_shell_job (long_running=True,
longrunning.run_shell_job -> default user-verify) + observe-only
job_status/job_result/job_logs/job_cancel/job_list (jobs.manage).
- registry: @register_tool(long_running=) flag on ToolDefinition.
- wiring: JOB_MANAGER service key, base_manager job_manager property +
_TOOL_SERVICE_MAP + lazy init branch; max_concurrent_jobs setting.
- /jobs command (running+queued; all/<id>/cancel/clean).
- tests/tools/test_jobs.py (15 tests): backends, cap+queue, cancel, persistence,
vanished-process reconcile, clean, tools via service registry.
Deferred: harness Jobs UI monitor (M2), REST/cloud backends + domain tools (M3),
push/resume auto-ingest (phase 2). Offline suite: 1552 passed.
…B_TOOLS=[job_status] Addresses tool-sprawl: most agents now add just two job tools — their typed long-running tool + job_status. job_status is enriched to also return the result once the job is terminal, making job_result/job_logs redundant for the LLM. JOB_TOOLS slimmed to [job_status]; the full set moves to opt-in JOB_MANAGEMENT_TOOLS (which also powers /jobs). All five tools stay registered and importable; listing/cancelling is normally a human action via /jobs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Long-running job substrate — Tier A milestone 1
Implements §3.2 of the domain-tool-packages plan. A long-running job is a typed tool; the LLM only sees the tool, which calls an internal
JobManagerover pluggable execution backends. There is no genericjob_submitandJobManageris never an LLM-facing tool.What's here (all backend-agnostic; layers 1 + 3 of the design)
JobManager+JobBackend(tools/jobs/): one interface, two backends shipped — subprocess (detached,start_new_session; restart-safe completion via an on-diskexit_codesentinel written from a subshell; zombie reaping with a returncode fallback) and in-process (thread pool, for lighter work).~/.{app_name}/jobs/; on startup, non-terminal jobs are re-polled (a vanished process →unknown).max_concurrent_jobs, default 4).run_shell_job(long_running=True, capabilitylongrunning.run_shell_job→ default user-verify via the existing permission ASK flow) + observe-onlyjob_status/job_result/job_logs/job_cancel/job_list(jobs.manage) that read/manage but never start jobs.@register_tool(long_running=…),JOB_MANAGERservice key,base_managerjob_managerproperty +_TOOL_SERVICE_MAP+ lazy init branch,max_concurrent_jobssetting./jobscommand:/jobs(running+queued),/jobs all,/jobs <id>,/jobs cancel <id>,/jobs clean.tests/tools/test_jobs.py(15) — backends, cap+queue, cancel, persistence reload, vanished-process reconcile→unknown, clean, and the tools via the service registry.Deferred (by design)
Offline suite: 1552 passed, 5 deselected (llm), 0 failed.