Task Agent is a small, forkable workspace for task-first autonomous-agent workflows.
It is intentionally generic: no private task history, no local data, and no bundled personal integrations. Project-level operating rules live in AGENTS.md.
Every commit before this date was rewritten to remove deployment-specific host
paths and private project, task, and trip names that the earlier commits still
carried in examples. Only those strings changed: the tree at the tip is
byte-identical to what it was before the rewrite. Commit hashes did change, so a
clone made before 2026-08-06 has no commit in common with origin/main.
If you have such a clone, discard its local history:
git fetch --all && git reset --hard origin/mainCommit anything you want to keep to a separate branch first; the reset discards
uncommitted and unpushed work. One merged leftover branch was renamed in the same
change and is now port-generic-agent-workspace-work; prune stale
remote-tracking refs with git fetch --prune.
tasks/skeleton for durable task artifactstasks/USER_PREFERENCES.example.mdas a starting point for durable user defaultsdata/projects/skeleton for multi-task project recordsdata/local-projects.example.mdas a starting point for local repository/path indexesAGENTS.md,.cursor/rules/, andCLAUDE.mdas one shared rule set for Codex, Cursor, and Claude Codeskills/task-creator/for creating task directories and updating the indexskills/task-runner/for parent-child CLI agent execution, detached-run supervision, and the dev-pipeline workflowskills/context-discovery/for the durable-context lookup a task does before broad search or live checksskills/task-artifacts/for keeping task artifacts current during workskills/project-organizer/for durable project recordsskills/repo-health/for restore, publication, deliverables, and pre-push checksskills/skill-maintainer/for creating or changing skillsdocs/for architecture, task execution, Claude Code setup, and self-development workflows
Prerequisites are Python 3.11+, Git, network access, and an installed and
authenticated Codex, Claude, or Cursor CLI for the dev-pipeline owner. Clone the
repository, then from its directory create the environment and install the
pinned public dependency together with the test tools:
git clone https://github.com/rdudov/task-agent.git
cd task-agent
python3 -m venv .venv
.venv/bin/pip install -r requirements.lock
.venv/bin/pip install -e .The repository is also a Python distribution. An application can bind the engine to an immutable Git revision without copying its lifecycle code:
task-agent-engine @ git+https://github.com/rdudov/task-agent.git@<40-character-commit>
That install exposes task-agent, task-agent-engine, and
task-agent-tasks-index. All installed entry points and installed package files
executed by absolute path share the runner's workspace resolver:
TASK_AGENT_ROOT selects the workspace when set, otherwise the current
directory does. The task index no longer derives a workspace from the installed
package's file depth. Direct execution of a source-checkout script still derives
the checkout root from that source path when TASK_AGENT_ROOT is unset, and
TASKS_INDEX_ROOT is the task index's isolated-test override. Installed
completion finds
the metadata entrypoint beside its active Python interpreter even when an
application adapter loads the engine as top-level modules. A post-preparation
metadata-owner failure is projected as a durable refusal instead of aborting
projection, allowing an installation to correct any terminal statement already
sent. Absolute task paths need no workspace convention.
Consumer runtimes and services should use this exact reviewed revision and a
lock file. Editable installs are reserved for isolated contributor virtual
environments where source edits are intentionally live; they must not back a
shared runtime or service.
Create a task:
skills/task-creator/scripts/create_task.sh "Example task" "Try the task-agent workflow"Run health checks:
.venv/bin/python skills/repo-health/scripts/check_repo_health.py --allow-empty-tasks
PYTHONPATH=skills/task-runner/scripts .venv/bin/python -m pytest skills/task-runner/tests skills/task-creator/tests skills/repo-health/testsBefore pushing a source change from this workspace, run:
.venv/bin/python skills/repo-health/scripts/check_pre_push.py --remote originTo block deployment-specific project/task/trip names without publishing them,
put one marker per line in ignored .state/private-history-markers, or point
TASK_AGENT_PRIVATE_HISTORY_MARKERS at another local file. Ordinary markers are
literal and case-insensitive, so a differently capitalized name cannot bypass
the list. Prefix a marker with re: only when a bounded, case-sensitive regular
expression is needed, for example to distinguish a display name from an ordinary
lowercase word or to cover an identifier format. An invalid expression refuses
the check. The guard also refuses foreign remote and unknown ref namespaces
while allowing ordinary local branches, tags, notes, and stash.
An empty marker list is not a pass. A fresh clone has no .state/, so the name
check has nothing to compare against; both check_pre_push.py and
check_repo_health.py now say so on stderr instead of reporting a clean run.
Pass --require-private-history-markers to check_pre_push.py to turn that
notice into a failure.
The same rules reach Codex, Cursor, and Claude Code without being copied. AGENTS.md holds the project rules, .cursor/rules/*.mdc hold the always-on rules, and CLAUDE.md imports both rather than restating them; .claude/ contains only symlinks into the canonical files. Adding a Cursor rule means adding its .claude/imports/ symlink and its CLAUDE.md import line — see docs/claude-code-setup.md.
.venv/bin/python skills/task-runner/scripts/task_runner.py author \
tasks/001-example --repo /path/to/target-repoThe child runner follows the parent CLI agent, so a Codex session delegates to Codex and a Claude session to Claude. Pass --runner codex|claude|agent to decide explicitly, or set TASK_AGENT_CHILD_RUNNER. All three drive both workflows: under dev-pipeline the agent runner becomes the core's cursor owner runtime. Every run records which rule decided.
The normal standard commands own their access profiles: author grants verified
workspace-write to the exact repeated Git-worktree --repo set and derives
each worktree's Git directory/common directory so the author can commit, while
review TASK keeps that candidate set read-only, the task notebook writable,
and Bash/network available for live checks. The exact candidate set comes from
the bound author admission; a repository-less author produces an empty candidate
set and remains reviewable through the writable task notebook. The caller does
not choose a sandbox or repeat reviewer targets. Missing or invalid targets fail
before child spawn. A read-only author may carry exact targets into review; an
older admission without a role-owned access profile must be relaunched through
the current runner because review will not guess targets from historical runner
metadata. The generic start command retains explicit
--sandbox-mode (read-only, workspace-write, danger-full-access) for
advanced and dev-pipeline paths. TASK_AGENT_WORKSPACE_ROOT sets how far full
access reaches; it defaults to the parent of this checkout.
For the standard author role, repeatable --repo /path/to/target-repo makes each named repository
an additional workspace/access root for Codex, Claude, or Cursor Agent while
the task-agent checkout remains the primary workspace.
Write modes verify both worktree and Git-metadata writability before launch and
record the result.
Material work binds an independent reviewer before its author starts. When the author finishes, run that reviewer as the next phase of the same task; the launcher selects the already-bound family and gives it read-only access:
.venv/bin/python skills/task-runner/scripts/task_runner.py review tasks/001-exampleThree records remain because they answer different acceptance questions: admissions preserve who promised to review, rounds preserve what that reviewer decided, and phase history detects author work after approval. Installations add transport and resource policy; they do not keep another pairing record.
Only one task may hold a repository in write mode at a time. A write-mode launch
uses one Git-repository-locked check-and-claim operation, and is refused while
another task is writing there or has changed it without closing its own gates.
The claim binds staged and non-ignored untracked content as well as tracked
worktree state. Unknown liveness refuses a foreign writer rather than granting
permission. A dead abandoned scope is durably settled when the unchanged
fingerprint proves a no-op. Before a real start replaces the previous
runner.json,
it transfers that run's matching terminal write-scope evidence into the
append-only admission ledger. A --dry-run replaces no record and preserves
none: it reports the launch it prepared and leaves the task's files as the last
real run left them. That exact evidence can recover the scope across
PID namespaces after runner metadata replacement; another run's terminal record
cannot. A launch that ends before a child exists releases launch_pending, so
the terminal failure does not itself prevent a retry. Divergent work remains a
recomputed obligation for other tasks until it is
reverted or the owner's gates pass, while the owner may enter same-number rework
without freezing the ambiguous attribution. Cancelling the owning task the
ordinary way releases what it still held — a task that will never be asked for
its gates again must not hold a repository forever — and the ledger records that
release, its reason and the run IDs it covered instead of dropping them quietly.
start returns once the run is confirmed; the watcher and the child keep running in their own sessions, so closing the terminal does not end the work. On a host systemd machine the watcher gets its own transient scope; elsewhere the recorded boundary says it inherits the caller cgroup. Both processes are recorded by kernel start-time identity and PID namespace rather than by pid alone. An observer in another namespace reports liveness as unknown and cannot replace, stop, or reattach the host run. reattach restores a lost watcher and refuses when the pid was recycled or a watcher is already live.
stop records a public-pipeline handoff request-stop marker before signalling
a live dev-pipeline process. This lets an ordinary resume reopen the exact
review or rework phase and retain the author session. If the marker cannot be
recorded, stop refuses before sending the signal; unexplained process loss
therefore keeps the core's fail-closed orphan handling.
.venv/bin/python skills/task-runner/scripts/task_engine.py state tasks/001-exampleOne JSON document: task identity and status, the phase the task is in and the
sequence of phases it went through, contract gate status, whether completion may
be accepted and why not, observed freshness, and what is running. phases,
actuality and admission --repo R are the narrower views.
This is the public surface. A product layer, a transport adapter or another installation asks here instead of importing internals, so nothing downstream breaks when a helper is renamed.
One goal, one number. A user goal keeps a single task directory for its whole
life. Review is a phase of that task, and so is the rework a review asks for —
implementation → review → rework → review → completed is the history of one
task, not five, and phases.json records it with the cause of each transition.
Both execution profiles produce the same vocabulary.
Actuality is observed. Freshness comes from the modification times of the task's artifacts, never from a timestamp a child wrote about itself: a child that stalls can leave a fresh timestamp behind, and one that dies cannot correct the last it wrote.
--workflow dev-pipeline runs a task through the standalone dev-pipeline CLI, which drives an evidence-gated Codex, Claude, or Cursor owner session:
.venv/bin/python skills/task-runner/scripts/task_runner.py start tasks/001-example \
--workflow dev-pipeline --repo /path/to/target-repoThe workflow dependency is the separate public repository
rdudov/dev-pipeline. It is pinned in
requirements.txt and requirements.lock, so the Quick Start installs the
tested revision. To develop both repositories locally instead, replace it only
in an isolated contributor virtual environment with an editable checkout:
.venv/bin/pip install -e /path/to/dev-pipelineBoth standard-child and dev-pipeline-owner instructions apply the same no-code-first order: do nothing, remove/disable, configure/reuse, simplify, then add the smallest necessary code and justify why it is needed. Independent review applies the corresponding avoidable-complexity criterion.
The pinned revision includes the core's provider-neutral assurance contract and
review events, and imposes no limit on review rounds: rework and review repeat
under one task number until the work is accepted, so a pin must never be moved
back to a revision that stops at a count and asks whether to continue. A configured review uses review_started and
review_rework_required for visible review/rework phase transitions;
review_approved keeps the task in review until the following lifecycle event
advances it. Older installed cores still degrade compatibly by omitting phases
for events they do not emit.
The engine is transport-neutral, but its extension point is now explicit and
versioned. --application package.module:object loads application API v1;
--destination passes an opaque installation-owned value that is hashed in
runner metadata and never stored in clear text. Notable neutral lifecycle
events—including independent-review start, required rework, review refusal,
and an exact quota wait—are offered to deliver_event. The default application is inert, so a
plain template still sends nothing. On restart, recover_transport gives the
application the durable validated event log so its own receipt policy can
reconcile delivery without the engine guessing whether a resend is safe.
API v1 also has an additive, optional pre-finalization capability: an
application may declare the exact live-evidence ids its prepare_completion
method can establish. The request carries the exact intersection of that
capability list with the effective contract, so the application performs only
the terminal work this task enforces. The engine invokes it only when every other completion
condition except authoritative task status already passes. After successful
preparation, the engine closes task metadata through the installed
task-agent-tasks-index set-status owner and evaluates the full predicate
again. A failed preparation never changes task status. Existing v1 applications
without the declaration keep the original ordering and still close metadata in
their owner workflow.
When that deferred predicate or the application preparation refuses, the
adapter preserves that exact reason in status.json and marks the refusal as an
automatic-finalization branch. It does not replace the preparation blocker with
the earlier full-predicate status check. Installation transports can therefore
explain the actual blocker without directing a user to perform metadata closure
that the registered finalizer owns.
The same adapter can return a launch memory policy for --memory-limit, attach
native-session arguments to a standard start|resume|retry, classify the
supervised exit as an exact quota wait for its scheduler, and add
installation-specific completion problems such as an unresolved document
receipt. The public runner still owns the process,
session-state persistence, event ordering, artifact projection, and completion
refusal. A child-written terminal state is rechecked through the same durable
engine gate before acceptance. An application owns only its resource values,
transport receipts, and scheduler. API v1 is importable as
task_agent.application_adapter; session state refuses secret-bearing keys.
For standard runs the parent forwards the registration, operation, and opaque
destination to the detached watcher, which reuses the exact prepared session
record. A missing or changed value is a visible launch refusal, never an inert
application fallback or a fresh native session. Because the raw destination is
not persisted, restart-time transport recovery must resolve its recipient from
installation-owned state.
By default the runner resolves the CLI installed at .venv/bin/dev-pipeline,
then falls back to PATH. TASK_AGENT_DEV_PIPELINE_BIN or
--dev-pipeline-bin can select another executable explicitly; an unresolved
CLI fails before an owner process is started.
The same resolver is used by normal runs, direct adapter invocation, and
review-candidate.
Task frontmatter is written only through tasks_index.py. An author completes
every plan step and records passing live evidence; if review is still missing,
or a run fails, blocks, or pauses, the finalizer keeps the frontmatter blocked.
A clean approved standard review records its round after installation-specific
pause handling and performs the completed transition itself after every other
gate passes. Verdict cleanup is committed only when the reviewer process starts.
A completion reported without those durable gates is blocked. For
a contract with mandatory prose policy families,
run the bounded reviewer over the final committed candidate:
.venv/bin/python skills/task-runner/scripts/task_runner.py review-candidate \
tasks/001-example --repo /path/to/target-repoTask Agent is released under the MIT License.
tasks/ and data/ are durable local artifacts. This template tracks only skeleton and example files; real task history and reusable data should be backed up by your own local backup flow. The task index under .state/ is rebuilt from that history and needs no backup of its own.