Apache-2.0 · Planning framework for a team of AI agents

One honest schedule for a team of AI agents.

Each project is a plan.yaml task graph. A command-line planner validates that graph, schedules it against a roster, renders a Gantt chart, and keeps a ready queue. A task becomes done only after Accuracy, the verifier, writes a passing attestation and the Planner records it. Human gates stay out of the dispatch queue until a person decides.

  • licenseApache-2.0
  • python3.11+
  • depsPyYAML only
  • version1.0.0
examples/02-content-launchgantt
  • One plan, one pen.Everyone reads plan.yaml. Only the Planner writes it.
  • One gate for done.Accuracy must record a pass before a task is done.
  • One question for people.Can it be undone? If not, the task needs a human.

01 Core features

Fourteen core features, straight from the README.

Each card is one item from the repository’s core feature list, with the command or file that provides it.

  • A validated YAML plan

    A YAML plan per project, with a JSON Schema and a validator for roster fit, artifact provenance, dependency cycles, and the Accuracy done-lock.

    validate · schemas/plan.schema.json
  • Critical path and capacity

    Critical-path slack and a resource-constrained schedule that honors assignee capacity and each plan’s max_parallel.

    schedule · schedule.yaml
  • Mermaid Gantt charts

    Mermaid Gantt charts from the resource-constrained times. Tasks with zero critical-path slack are marked crit.

    gantt · gantt.md
  • A ready queue

    A ready queue that separates dispatchable tasks from human gates: dispatch and awaiting_human.

    ready
  • Briefs, manifests, attestation

    Task briefs, sha256 artifact manifests, and an Accuracy attestation that is the only path to done.

    brief · record-artifact · attest-done · complete
  • Replanning from a clock

    Replanning from a chosen clock, freezing in-progress and in-review work, with a rate limit and hysteresis.

    replan --now · replan_policy
  • Calibration

    Calibration from done-task actuals, plus a preview that does not rewrite the raw optimistic, likely, and pessimistic hours.

    calibrate --apply · estimate · apply-calibration
  • Portfolio and shared roster

    A portfolio rollup and a shared-roster schedule across open plans.

    portfolio · portfolio schedule
  • Sub-plan rollup

    Sub-plan rollup of makespan and status into the parent task.

    subplan: examples/06-program/child
  • Monte Carlo and what-ifs

    Monte Carlo finish percentiles (p10, p50, p90) and what-if knobs that do not edit the plan.

    simulate --add-capacity · --omit · --deadline
  • Plan templates

    research-report and announcement templates. research-report is one reversible task; announcement adds a publish task that waits for a person.

    instantiate · plans/_templates/
  • Dashboard and local server

    A static dashboard and a read-only local server. The HTML page has no remote assets.

    dashboard · serve
  • Meeting digest

    A meeting digest that writes a Slack payload and posts only when SLACK_DIGEST_WEBHOOK is set and --post is passed.

    digest · digest --post
  • Dispatch dry run

    A dispatch dry run that reports budget headroom, an 80 percent warning, and tasks a kill switch would stop, and sends nothing.

    dispatch-dry-run

Source: README.md, “Core features”.

02 Quickstart

From a fresh clone to a ready queue.

These steps follow the README quickstart. Every command runs from the repository root with python3 -m planner, and every output shown is the one the repository documents. The user guide covers the full schema and every subcommand.

  1. Install

    Python 3.11 or newer. PyYAML is the only Python package dependency. check validates every plan under plans/ and runs the unit tests. It does not make network calls.

    bash
    git clone https://github.com/r0b0tlab/agent-plans.git
    cd agent-plans
    python3 -m venv .venv
    source .venv/bin/activate
    pip install -r requirements.txt
    python3 -m planner check
  2. Validate an example

    Checks the schema, roster fit, provenance, irreversible work, cycles, and an unattested done. Errors exit 1. Warnings do not fail the command.

    bash
    python3 -m planner validate examples/01-solo-feature

    Output

    ok: examples/01-solo-feature/plan.yaml (3 tasks)
  3. Schedule

    Writes schedule.yaml with critical-path fields and resource-constrained start and finish times, laid out from schedule_origin. --from-now anchors in-progress and in-review work and schedules the rest from --at.

    bash
    python3 -m planner schedule examples/01-solo-feature

    Output

    wrote examples/01-solo-feature/schedule.yaml: makespan 3h, critical path T1 -> T2 -> M1 (origin)
  4. Draw the Gantt

    Writes gantt.md with a table and a Mermaid chart. Bars use the resource-constrained times. Running schedule and gantt again matches the first run.

    bash
    python3 -m planner gantt examples/01-solo-feature
  5. See what’s ready

    The queue is computed from dependencies and gates, so you don’t set ready by hand. The merge gate is not waiting yet, because its dependency is still open.

    bash
    python3 -m planner ready examples/01-solo-feature

    Output

    plan: solo-feature-2026-10 dispatch: T1 | cloud-worker | 2h | Implement the feature awaiting_human: (none)
  6. Start a plan of your own

    Copy a template kind into a new directory. Then edit the goal, the deadline, and schedule_origin, because the copied dates are placeholders, and run validate, schedule, and gantt on that directory. For a custom graph, plans/_template/ is the annotated field guide.

    bash
    python3 -m planner instantiate research-report \
      --into plans/<YYYY-MM-DD>-<slug> --plan-id <id>

    A task in plan.yaml looks like this one, from examples/01-solo-feature:

    plan.yaml (excerpt, comments added)
    constraints:
      deadline: "2026-10-10T17:00:00-05:00"
      max_parallel: 3
      schedule_origin: "2026-10-08T09:00:00-05:00"
    deliverables:
      - merge_approval.md
    tasks:
      - id: T1
        title: Implement the feature
        section: Build
        task_type: coding
        objective: Write the feature and a short change note.
        inputs: []
        outputs:
          - artifact: change_note.md
            type: markdown
        depends_on: []          # a bare id means finish-to-start, lag 0
        estimate_hours:         # PERT expected: (o + 4*likely + p) / 6
          optimistic: 1
          likely: 2
          pessimistic: 3
        required_capabilities:
          - coding
        assignee: cloud-worker
        acceptance_criteria:
          - change_note.md names the behavior a reviewer can check.
        risk: low
        reversible: true
        needs_human: false      # must be true when reversible is false
        budget:
          tokens: 10000
          hours: 4
          usd: 0
        max_retries: 1
        status: planned
        actuals:
          start: null
          end: null
          hours: null
          attempts: 0
  7. Brief, work, attest, complete

    Hand the assignee the brief. The worker hashes its output with record-artifact and stops at in_review. Accuracy attests, and the Planner runs complete only on a pass. After a fail, the Planner sets failed or in_review with set-status.

    bash
    python3 -m planner brief <plan> <task>
    python3 -m planner record-artifact <plan> <task> --file <path>
    python3 -m planner attest-done <plan> <task> --verdict pass --at <ISO-8601>
    python3 -m planner complete <plan> <task> --hours <n> --attempts <n> --at <ISO-8601>
  8. Share a dashboard or digest Optional

    dashboard writes a static page with no remote assets. digest prints a meeting-prep summary and writes a Slack payload. It posts only with --post and SLACK_DIGEST_WEBHOOK set. serve is read-only and its default host is 127.0.0.1.

    bash
    python3 -m planner dashboard --at <ISO-8601>
    python3 -m planner digest --at <ISO-8601>
    python3 -m planner serve --host 127.0.0.1 --port 8787 --refresh 300

The ideas, in plain language

Summarized from the user guide’s Concepts section.

Plan
One folder per project. plan.yaml holds the goal, the definition of done, the deadline, the budget, and the tasks. log.md records decisions. Generated files (schedule.yaml, gantt.md, briefs/) are views: edit plan.yaml, then regenerate them.
Tasks
A unit of work with an objective, inputs, named outputs, acceptance criteria, an estimate range, a budget, and a retry limit. A task with all-zero estimates is a milestone.
Owners
Each task has one assignee from roster.yaml. A roster entry can be a bot, a cloud agent, or a human, with capabilities, a capacity (how many tasks it can run at once), and an optional cost.
Dependencies
“This waits for that.” A bare task id means finish-to-start. Start-to-start, finish-to-finish, and start-to-finish also work, with optional lag hours. Cycles are rejected.
Critical path
The critical path method assumes unlimited assignees and finds the chain of tasks with zero slack. In the Mermaid chart those tasks are marked crit.
Slack
How long a task can slip before it delays the finish: its latest start minus its earliest start. Zero slack means the task is critical.
Ready queue
Tasks whose dependencies are done, that haven’t started, and that don’t need a person. ready computes it fresh every time.
Gates
Points where work must stop for a check. There are two kinds: the Accuracy gate before done, and human gates before anything that can’t be undone.
Accuracy done-gate
A task is done only when Accuracy’s attestation says pass, every criterion is met in the task’s own words, and the output hashes match the manifest. complete is the only command that writes done.
Human gates
needs_human: true parks a task under awaiting_human. Holding is the safe default, and anything the gate doesn’t block keeps moving. Irreversible work must be gated.
Calibration
Everyone estimates a bit wrong in a consistent direction. After a task is done, the factor is actual over likely hours, weighted by n / (n + 3) toward 1.0 and clamped to 0.5–2.0. apply-calibration stores the result next to your raw estimate and never replaces it.
Replan
When reality slips, work already running keeps its start and only unstarted tasks move. Changing the goal, the deliverables, or the deadline stays a human decision.

03 Scenarios

Six runnable plans under examples/.

Each plan passes validate and schedule. The commands, outputs, and charts below are the ones documented in docs/scenarios.md. The plans themselves are in examples/. Names are roles from roster.yaml. Run the commands from the repository root.

01

Solo feature with a review gate

A single implementer ships one change. Accuracy reviews it. A person approves the merge. The merge task is irreversible, so needs_human is true.

Directory
examples/01-solo-feature
Plan id
solo-feature-2026-10

The merge gate is not waiting, because T2 has not finished. After T1 and T2 are done, M1 moves to awaiting_human and drops out of dispatch.

validate prints

ok: examples/01-solo-feature/plan.yaml (3 tasks)

schedule prints

wrote examples/01-solo-feature/schedule.yaml: makespan 3h, critical path T1 -> T2 -> M1 (origin)

ready prints

plan: solo-feature-2026-10 dispatch: T1 | cloud-worker | 2h | Implement the feature awaiting_human: (none)
bash
python3 -m planner validate examples/01-solo-feature
python3 -m planner schedule examples/01-solo-feature
python3 -m planner gantt examples/01-solo-feature
python3 -m planner ready examples/01-solo-feature
02

Multi-agent content launch

Research, then social, then design, then Accuracy, then a human publish approval. Publish is irreversible.

Directory
examples/02-content-launch
Plan id
content-launch-2026-10

status includes critical_path: T1 -> T2 -> T3 -> T4 -> M1, cpm_duration_hours: 6, and makespan_hours: 6.

schedule prints

wrote examples/02-content-launch/schedule.yaml: makespan 6h, critical path T1 -> T2 -> T3 -> T4 -> M1 (origin)

ready prints

plan: content-launch-2026-10 dispatch: T1 | research | 2h | Research the announcement awaiting_human: (none)
bash
python3 -m planner validate examples/02-content-launch
python3 -m planner schedule examples/02-content-launch
python3 -m planner gantt examples/02-content-launch
python3 -m planner ready examples/02-content-launch
python3 -m planner status examples/02-content-launch
03

Study with a CPU phase and a GPU handoff

CPU work stays on research. The training task is assigned to cloud-worker, a roster entry with kind: cloud_agent and the gpu capability. That is the external executor: a person or another agent can hold the same assignment if they are on the roster with gpu. The plan ends at a handoff milestone.

Directory
examples/03-ml-handoff
Plan id
ml-handoff-2026-10

T1 and T2 share research, whose capacity is 1, so the CPU phase is serial (0–2h, then 2–4h). T3 runs 4h on the cloud worker. M1 is a zero-duration milestone at 8h.

schedule prints

wrote examples/03-ml-handoff/schedule.yaml: makespan 8h, critical path T1 -> T2 -> T3 -> M1 (origin)

ready prints

plan: ml-handoff-2026-10 dispatch: T1 | research | 2h | Prepare the dataset awaiting_human: (none)
bash
python3 -m planner validate examples/03-ml-handoff
python3 -m planner schedule examples/03-ml-handoff
python3 -m planner gantt examples/03-ml-handoff
python3 -m planner ready examples/03-ml-handoff
04

Shared machine pool

Six independent two-hour batches are assigned to machine-pool, capacity 4. max_parallel is 6, so the plan cap does not bind first. The roster entry is the pool: the scheduler has no separate machine resource.

Directory
examples/04-machine-pool
Plan id
machine-pool-2026-10

status includes cpm_duration_hours: 2 and makespan_hours: 4. CPM assumes unlimited machines, so every batch has zero slack. The resource-constrained schedule starts four batches at 09:00 and the other two at 11:00. ready still lists all six as dispatchable, because the queue tests dependencies, not capacity.

schedule prints

wrote examples/04-machine-pool/schedule.yaml: makespan 4h, critical path J1 (origin)
bash
python3 -m planner validate examples/04-machine-pool
python3 -m planner schedule examples/04-machine-pool
python3 -m planner gantt examples/04-machine-pool
python3 -m planner status examples/04-machine-pool
05

A slipping plan, reschedule, and calibration

Implementation is in_progress. It started at the origin and has already logged 4 hours against a raw expected of 2 hours (optimistic 1, likely 2, pessimistic 3). The plan sets calibration_override.factor to 1.5, which is the factor estimate previews.

Directory
examples/05-slipping-plan
Plan id
slipping-plan-2026-10

Reschedule from 15:00, six hours after the origin. Remaining work on T1 is max(0, expected - actual) = 0, so T1 is frozen at its start and the review is pushed to that clock: makespan 3h becomes 7h. replan and apply-calibration rewrite the plan, so copy the directory first if you want to keep your checkout unchanged.

status reports the slip

schedule_variance: 4h actual / 2h expected (n=1, pooled ratio 2.0000)

estimate task lines

T1 | cloud-worker | coding | raw 2h | factor 1.5 | n=0 | basis plan_factor | preview 3h | stored (none) T2 | accuracy | accuracy_review | raw 1h | factor 1.5 | n=0 | basis plan_factor | preview 1.5h | stored (none)

replan prints, on the committed plan

replan wrote schedule.yaml and gantt.md at version 2. Frozen: T1. Makespan 3h -> 7h.

apply-calibration prints, on a fresh copy

wrote calibrated hours on 2 tasks in slipping-plan-2026-10 at version 2
bash
python3 -m planner validate examples/05-slipping-plan
python3 -m planner gantt examples/05-slipping-plan
python3 -m planner schedule examples/05-slipping-plan
python3 -m planner status examples/05-slipping-plan
python3 -m planner estimate examples/05-slipping-plan
python3 -m planner calibrate
python3 -m planner replan examples/05-slipping-plan --now --reason "implementation ran long" --at 2026-10-08T15:00:00-05:00
python3 -m planner apply-calibration examples/05-slipping-plan --at 2026-10-08T15:00:00-05:00
06

Sub-plan rollup

examples/06-program/child is a two-task study (draft 2h, check 1h, makespan 3h). The parent task T1 points at it with subplan: examples/06-program/child and its own estimate is 1h. The scheduler replaces that 1h with the child makespan.

Parent
examples/06-program
Child
examples/06-program/child

status on the parent includes makespan_hours: 4 and cpm_duration_hours: 4. ready still prints the task’s own expected hours (T1 | research | 1h | Run the study). The chart is the rolled duration.

child schedule prints

wrote examples/06-program/child/schedule.yaml: makespan 3h, critical path C1 -> C2 (origin)

parent schedule prints

wrote examples/06-program/schedule.yaml: makespan 4h, critical path T1 -> T2 (origin)
bash
python3 -m planner validate examples/06-program
python3 -m planner validate examples/06-program/child
python3 -m planner schedule examples/06-program
python3 -m planner schedule examples/06-program/child
python3 -m planner gantt examples/06-program
python3 -m planner gantt examples/06-program/child
python3 -m planner status examples/06-program

04 How it works

Every task goes around the same loop.

This is the everyday loop from the user guide’s Planner, Orchestrator, and human-reviewer workflows. The worker is never the one who sets done, and irreversible tasks must wait for a person.

The agent-plans loop The Orchestrator shapes the ask. The Planner writes plan.yaml and schedules it. The ready queue dispatches tasks to owners. A human gate holds a task where needed. The owner does the work and records a hash. Accuracy attests pass or fail, and a fail goes back for rework. On a pass the Planner runs complete and the task is done. When actuals slip, the Planner replans. ACTUALS SLIP? replan --now Orchestrator shapes the ask goal · deadline Planner writes the plan validate · schedule Ready queue dispatches owners ready · brief human gate only where needed: awaiting_human Owner does the work bot, cloud agent, or human record-artifact → in_review FAIL: REWORK Accuracy checks every criterion attest-done · pass / fail Planner completes only path to done complete Done ✓ hashes match PASS Only the Planner writes plan.yaml. Schedules, Gantt charts, and briefs are regenerated views. The agent-plans loop Orchestrator shapes the ask, then Planner, then ready queue, then an optional human gate, then the owner does the work, then Accuracy attests. A fail goes back to the owner. A pass goes to the Planner, which completes the task, and the task is done. REPLAN Orchestrator shapes the ask: goal, deadline, budget Planner writes plan.yaml · validate · schedule Ready queue ready · brief to the owner human gate only where needed Owner does the work record-artifact → in_review Accuracy attest-done · pass or fail FAIL Planner completes complete · the only path to done Done ✓ Only the Planner writes plan.yaml.
Diagram of the agent-plans loop. The same steps are listed in text below.
  1. Orchestrator shapes the ask. It writes the goal, the constraints, the deadline, and the budget, and gives them to the Planner.
  2. Planner writes the plan. It turns the brief into tasks in plan.yaml, then runs validate, schedule, and gantt.
  3. The ready queue lists what can start. ready prints dispatch and awaiting_human, and brief writes a self-contained task file for each assignee.
  4. Human gates hold where needed. A needs_human task waits for a person. Nothing else waits on it unless it depends on it.
  5. The assignee works and stops at review. It hashes its outputs with record-artifact and stops at in_review, never done.
  6. Accuracy passes or fails it. attest-done records the verdict against every acceptance criterion. A fail never unlocks done; the Planner sets failed or in_review.
  7. The task is done. On a pass, the Planner runs complete. If reality slipped along the way, replan --now moves only unstarted work.

05 FAQ

Questions people ask first.

Does agent-plans run my agents for me?

No. It plans, schedules, briefs, and checks. ready and brief tell you what to hand to whom, and dispatch-dry-run previews what a dispatcher would send, including budget headroom and the kill switch, but it sends nothing. A live webhook dispatcher is listed as a follow-on in docs/vision.md and is not implemented.

Which agents does it work with?

Assignees are entries in roster.yaml with a kind of bot (the default), cloud_agent, or human, plus capabilities, a capacity, and an optional cost. A cloud_agent uses the same brief, artifact manifest, and attestation path as a bot. The handoff is a self-contained Markdown brief with a JSON report-back.

Why can’t the agent that did the work mark it done?

The worker stops at in_review. Accuracy writes the verdict, and the Planner runs complete, which refuses a fail attestation, a missing manifest, or a hash mismatch. set-status refuses done, and validate accepts status: done only with a passing attestation from accuracy whose output hashes match the manifest.

Are attestations cryptographically signed?

No. The done-gate is enforced by the tool: validate locally, and python3 -m planner check as the planner-check CI job. Git does not prove which bot wrote the attestation. The signature field is stored and is not checked. Attestation signatures are listed as a follow-on in docs/vision.md.

Is the schedule optimal?

No, and the docs say so. Resource-constrained scheduling is NP-hard, so the scheduler is a parallel priority-list heuristic, not an exact optimum. Priority is least CPM slack, then longest scheduling duration, then most downstream tasks, then task id. Critical path and slack come from the critical path method.

Do I need a server, a database, or an account?

No. Plans are files in a git repository. You need Python 3.11+ and PyYAML. The dashboard HTML page has no remote assets. serve is optional and read-only (POST, PUT, and DELETE are refused), and its default host is 127.0.0.1. Live plans are read from plans/ unless AGENT_PLANS_DIR points somewhere else.

Will it post to Slack or anywhere else on its own?

No. digest writes dashboard/digest.md and dashboard/slack-payload.json and does not post unless you pass --post. With --post and an empty SLACK_DIGEST_WEBHOOK, it exits 1 and sends nothing. check does not post.

What happens when an estimate is wrong?

Two things. In the moment, replan --now keeps in-progress and in-review work on its recorded start and reschedules the rest, with a rate limit (60 minutes by default) and hysteresis (0.5h by default). Over time, calibrate --apply learns factors from done tasks by assignee and task type, and apply-calibration stores a calibrated duration next to the raw estimate.

What’s the license?

The Apache License, Version 2.0. See LICENSE.

One plan. One pen. Nothing done until it’s checked.