AXLR / Catalog / personal

eisenhower_matrix

Sort tasks into the Eisenhower matrix (do, schedule, delegate, eliminate) from urgent/important flags, 1-5 scores with thresholds, or due dates counted from a given today, and return an action plan ordered by quadrant, due date and effort, with the reason for each placement, as JSON and a Markdown table.

Install

/tools install eisenhower_matrix
/tools install eisenhower_matrix@1.0.0

In a console session, or let the model find it in normal mode: axlr_catalog describes it and axlr_install_tool asks for the same install card. The card reads catalog axlr · eisenhower_matrix 1.0.0 · verified once the index signature, the manifest digest and every file digest check out. Then the model runs it with axlr_run_tool eisenhower_matrix; each run is approved as a local_exec.

Arguments

One JSON object on stdin, validated against the schema before the program starts.

nametyperequireddefaultmeaning
tasksarray of ['object', 'string']yesitems ≥ 1; items ≤ 1000
todaystringnoRequired with due dates. pattern ^\d{4}-\d{2}-\d{2}$
thresholdsobjectno
include_eliminatebooleannofalse
Input schema (JSON)
{
  "type": "object",
  "required": [
    "tasks"
  ],
  "additionalProperties": false,
  "properties": {
    "tasks": {
      "type": "array",
      "minItems": 1,
      "maxItems": 1000,
      "items": {
        "type": [
          "object",
          "string"
        ],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string"
          },
          "urgent": {
            "type": "boolean"
          },
          "important": {
            "type": "boolean"
          },
          "urgency": {
            "type": "number",
            "minimum": 1,
            "maximum": 5
          },
          "importance": {
            "type": "number",
            "minimum": 1,
            "maximum": 5
          },
          "due": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          },
          "effort": {
            "type": "number",
            "minimum": 0,
            "description": "Hours or any unit; used to order"
          },
          "notes": {
            "type": "string"
          }
        }
      }
    },
    "today": {
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
      "description": "Required with due dates"
    },
    "thresholds": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "urgency": {
          "type": "number",
          "minimum": 1,
          "maximum": 5,
          "default": 4
        },
        "importance": {
          "type": "number",
          "minimum": 1,
          "maximum": 5,
          "default": 3
        },
        "due_within_days": {
          "type": "integer",
          "minimum": 0,
          "default": 2
        }
      }
    },
    "include_eliminate": {
      "type": "boolean",
      "default": false
    }
  }
}

Example

Input

{
  "today": "2026-10-18",
  "tasks": [
    {
      "name": "Tax return",
      "importance": 5,
      "due": "2026-10-19",
      "effort": 3
    },
    {
      "name": "Learn Rust",
      "importance": 4,
      "effort": 20
    },
    {
      "name": "Call back vendor",
      "urgency": 5,
      "importance": 2
    }
  ]
}

Output (stdout)

{
  "plan": [
    {
      "step": 1,
      "task": "Tax return",
      "action": "Do first",
      "due": "2026-10-19",
      "effort": 3
    },
    {
      "step": 2,
      "task": "Learn Rust",
      "action": "Schedule",
      "effort": 20
    },
    {
      "step": 3,
      "task": "Call back vendor",
      "action": "Delegate"
    }
  ]
}

Files

The command the install card registers is python3 eisenhower_matrix.py, run in the workspace with PATH and HOME as its only environment. Each file's SHA-256 is in the signed index; the console checks them before showing the card and again before writing.

eisenhower_matrix.py · sha256 4f9d013dc2784c671859ca04421fecd59199d9a55f9bbefb7d543494706c8ff8
#!/usr/bin/env python3
"""Eisenhower matrix: sort tasks into the four quadrants of urgency and importance and return an
action plan ordered by due date and effort, as JSON and as Markdown.

Input (JSON on stdin):
  tasks       [{"name", "urgent"?, "important"?, "urgency"?, "importance"?, "due"?, "effort"?, "notes"?}]
              urgent / important  booleans (win over everything else)
              urgency / importance  scores 1-5, compared with the thresholds
              due     "YYYY-MM-DD": urgent when due within `due_within_days` of `today` (or overdue)
              effort  hours (or any unit, used only to order)
  today       "YYYY-MM-DD"  required when a task has a due date (never the system clock)
  thresholds  {"urgency": 4, "importance": 3, "due_within_days": 2}
  include_eliminate  true: list quadrant 4 in the plan too (default false)
"""
import datetime as dt
import json
import sys
from decimal import Decimal, InvalidOperation

QUADRANTS = {
    1: ("do", "Do first", "urgent and important"),
    2: ("schedule", "Schedule", "important, not urgent"),
    3: ("delegate", "Delegate", "urgent, not important"),
    4: ("eliminate", "Eliminate", "neither urgent nor important"),
}


def fail(message):
    print(json.dumps({"error": message}))
    sys.exit(2)


def date(value, field):
    try:
        return dt.date.fromisoformat(value)
    except (TypeError, ValueError):
        fail(f"{field} must be a date YYYY-MM-DD")


def score(value, field):
    if isinstance(value, bool) or not isinstance(value, (int, float)) or not 1 <= value <= 5:
        fail(f"{field} must be a number from 1 to 5")
    return value


def main():
    try:
        request = json.load(sys.stdin)
    except ValueError as error:
        fail(f"stdin is not JSON: {error}")
    if not isinstance(request, dict):
        fail("input must be a JSON object")
    extra = set(request) - {"tasks", "today", "thresholds", "include_eliminate"}
    if extra:
        fail(f"unknown fields: {sorted(extra)}")
    tasks = request.get("tasks")
    if not isinstance(tasks, list) or not tasks or len(tasks) > 1000:
        fail("tasks must be a list of 1 to 1000 tasks")
    th = request.get("thresholds", {})
    if not isinstance(th, dict) or set(th) - {"urgency", "importance", "due_within_days"}:
        fail("thresholds may have urgency, importance and due_within_days")
    u_th = score(th.get("urgency", 4), "thresholds.urgency")
    i_th = score(th.get("importance", 3), "thresholds.importance")
    window = th.get("due_within_days", 2)
    if isinstance(window, bool) or not isinstance(window, int) or window < 0:
        fail("thresholds.due_within_days must be a non-negative integer")
    include4 = request.get("include_eliminate", False)
    if not isinstance(include4, bool):
        fail("include_eliminate must be a boolean")
    today = date(request["today"], "today") if "today" in request else None

    rows = []
    for i, task in enumerate(tasks):
        f = f"tasks[{i}]"
        if isinstance(task, str):
            task = {"name": task}
        if not isinstance(task, dict):
            fail(f"{f} must be an object")
        extra = set(task) - {"name", "urgent", "important", "urgency", "importance", "due", "effort", "notes"}
        if extra:
            fail(f"{f}: unknown keys {sorted(extra)}")
        name = task.get("name")
        if not isinstance(name, str) or not name.strip():
            fail(f"{f}.name must be a non-empty string")
        reasons = []
        due = date(task["due"], f + ".due") if task.get("due") is not None else None
        days = None
        if due is not None:
            if today is None:
                fail("today is required when tasks have due dates")
            days = (due - today).days
        if "urgent" in task:
            if not isinstance(task["urgent"], bool):
                fail(f"{f}.urgent must be a boolean")
            urgent = task["urgent"]
            reasons.append("urgent: given")
        elif "urgency" in task:
            urgent = score(task["urgency"], f + ".urgency") >= u_th
            reasons.append(f"urgency {task['urgency']} {'>=' if urgent else '<'} {u_th}")
            if not urgent and days is not None and days <= window:
                urgent = True
                reasons[-1] += f", but due in {days} day(s)" if days >= 0 else f", but overdue by {-days} day(s)"
        elif days is not None:
            urgent = days <= window
            reasons.append(f"due in {days} day(s) {'<=' if urgent else '>'} {window}" if days >= 0 else f"overdue by {-days} day(s)")
        else:
            urgent = False
            reasons.append("no urgency or due date: not urgent")
        if "important" in task:
            if not isinstance(task["important"], bool):
                fail(f"{f}.important must be a boolean")
            important = task["important"]
            reasons.append("important: given")
        elif "importance" in task:
            important = score(task["importance"], f + ".importance") >= i_th
            reasons.append(f"importance {task['importance']} {'>=' if important else '<'} {i_th}")
        else:
            important = False
            reasons.append("no importance given: not important")
        effort = None
        if task.get("effort") is not None:
            try:
                effort = Decimal(str(task["effort"]))
            except InvalidOperation:
                fail(f"{f}.effort must be a number")
            if isinstance(task["effort"], bool) or not effort.is_finite() or effort < 0:
                fail(f"{f}.effort must be a non-negative number")
        quadrant = 1 if urgent and important else 2 if important else 3 if urgent else 4
        row = {"name": name.strip(), "quadrant": quadrant, "action": QUADRANTS[quadrant][0], "urgent": urgent,
               "important": important, "why": "; ".join(reasons)}
        if due is not None:
            row["due"] = due.isoformat()
            row["days_left"] = days
            if days < 0:
                row["overdue"] = True
        if effort is not None:
            row["effort"] = float(effort) if effort != effort.to_integral_value() else int(effort)
        if task.get("notes") is not None:
            if not isinstance(task["notes"], str):
                fail(f"{f}.notes must be a string")
            row["notes"] = task["notes"]
        row["_order"] = (quadrant, due or dt.date.max, effort if effort is not None else Decimal("Infinity"),
                         -(task.get("importance") or 0), i)
        rows.append(row)

    ordered = sorted(rows, key=lambda r: r["_order"])
    plan = []
    for r in ordered:
        if r["quadrant"] == 4 and not include4:
            continue
        step = {"step": len(plan) + 1, "task": r["name"], "action": QUADRANTS[r["quadrant"]][1]}
        if "due" in r:
            step["due"] = r["due"]
        if "effort" in r:
            step["effort"] = r["effort"]
        plan.append(step)
    quadrants = {}
    for q, (key, title, meaning) in QUADRANTS.items():
        quadrants[key] = {"quadrant": q, "title": title, "meaning": meaning,
                          "tasks": [r["name"] for r in ordered if r["quadrant"] == q]}
    for r in rows:
        del r["_order"]

    md = ["# Eisenhower matrix", ""]
    if today:
        md += [f"As of {today.isoformat()}.", ""]
    md += ["| | Urgent | Not urgent |", "|:--|:--|:--|"]
    cell = lambda key: "<br>".join(quadrants[key]["tasks"]) or "—"
    md.append(f"| **Important** | **Do:** {cell('do')} | **Schedule:** {cell('schedule')} |")
    md.append(f"| **Not important** | **Delegate:** {cell('delegate')} | **Eliminate:** {cell('eliminate')} |")
    md += ["", "## Plan", ""]
    for s in plan:
        extra = []
        if "due" in s:
            extra.append(f"due {s['due']}")
        if "effort" in s:
            extra.append(f"effort {s['effort']}")
        md.append(f"{s['step']}. {s['action']}: {s['task']}" + (f" ({', '.join(extra)})" if extra else ""))
    if not plan:
        md.append("Nothing to do.")
    eliminated = quadrants["eliminate"]["tasks"]
    if eliminated and not include4:
        md += ["", "Dropped (neither urgent nor important): " + ", ".join(eliminated) + "."]
    result = {"today": today.isoformat() if today else None,
              "thresholds": {"urgency": u_th, "importance": i_th, "due_within_days": window},
              "quadrants": quadrants, "plan": plan, "tasks": rows,
              "order": "quadrant (do, schedule, delegate), then due date (earliest first, none last), then effort (smallest first), then importance",
              "markdown": "\n".join(md) + "\n"}
    print(json.dumps(result, ensure_ascii=False))


if __name__ == "__main__":
    main()

Where it is served

The registry serves only AXLR clients, so a browser gets 403; fetch with a User-Agent that starts with axlr:

curl -A axlr-docs/1.0 -O https://registry.underpassai.com/tools/eisenhower_matrix/1.0.0/manifest.json
curl -A axlr-docs/1.0 -O https://registry.underpassai.com/tools/eisenhower_matrix/1.0.0/eisenhower_matrix.py