feat(kvb-org-priority): let the operator reorder the agents' priority

rasci.py gains agents, which groups the open tasks by worker in rank order (operator pins first, then WSJF) and prints rank, agent, count and tasks, and order "A,B,...", which checks the names against that list and, under the register lock, pins the listed agents' tasks 1..n in the chosen order, keeping each agent's own task order, and drops the pins of every agent left out so they rank by WSJF after them. The new kvb-org-priority skill shows the current order, offers keep, up to two reasoned suggestions or the operator's own order through Other, applies it and tells the chair and every reordered agent. kvb-org-rasci documents both commands and names order as the one exception to writing only your own rows. test_rasci covers the order, the pins in show, the dropped pins and the usage errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kevin Veen-BirkenbachandClaude Opus 5.5 committed 2026-11-04 17:07:22 +01:00
1 parent f815060584
commit 269590a93d
5 files changed
+120

No files matched your search

+4
View File
@@ -71,6 +71,7 @@ flowchart LR
org --> org_wsjf[kvb-org-wsjf]
org --> org_robot[kvb-org-robot]
org --> org_operator[kvb-org-operator]
org --> org_priority[kvb-org-priority]
skill --> skill_tune[kvb-skill-tune]
skill --> skill_auto[kvb-skill-auto]
skill --> skill_essence[kvb-skill-essence]
@@ -157,6 +158,7 @@ flowchart LR
wsjf[kvb-org-wsjf]
robot[kvb-org-robot]
operator[kvb-org-operator]
priority[kvb-org-priority]
end
subgraph cat_skill [skill]
autotune[kvb-skill-tune]
@@ -224,6 +226,8 @@ flowchart LR
artefacts --> rasci
democracy --> wsjf
wsjf --> rasci
priority --> rasci
priority -. pins outrank .-> wsjf
operator --> rasci
dream --> rasci
+53
View File
@@ -0,0 +1,53 @@
---
name: kvb-org-priority
description: >
Let the operator reorder the agents' priority from a selection; pins their
tasks in that order. Trigger on /kvb-org-priority, "prioritaet", "reihenfolge
der agents".
---
The operator's order of the agents outranks every WSJF score. It is written
as `--prio` pins into the `kvb-org-rasci` register, so the chair's grants of
the `kvb-org-wsjf` skill follow it. The script is
`${CLAUDE_SKILL_DIR}/../kvb-org-rasci/scripts/rasci.py`.
```bash
rasci.py agents
rasci.py order "<agent>,<agent>,..."
```
## Step 1: current order
`rasci.py agents` prints one line per agent with open tasks, in the
register's rank: number, agent, task count, tasks. Show it as a table with
each agent's status and OKR from `rasci.py show`.
## Step 2: selection
One `AskUserQuestion`, `multiSelect: false`, the order of each option in its
`preview`:
- **Keep the current order**: nothing changes.
- **Suggested order**, at most two, each with its reason in the
description: hotfix lines of the default branch first, then by WSJF; or
blocked agents' unblockers first.
- The operator's own order arrives through `Other`, as agent names or the
numbers of step 1 (map them to names), comma-separated.
An agent left out of the chosen order loses its pins and ranks by WSJF after
the listed ones; say so in the question.
## Step 3: apply
`rasci.py order "<A>,<B>,..."` pins the listed agents' tasks 1..n in that
order, keeping each agent's own task order. It writes other sessions' rows:
the operator decides priority, so this is the one exception to "write only
your own rows". Show `rasci.py agents` again, then tell the resource chair
of the `kvb-org-organize` skill and every reordered agent their new rank, one
message each.
## Rules
- Never reorder without the operator's pick; a suggestion is not a decision.
- A name the register does not know stops the step with the list of known
agents; never guess the closest one.
+4
View File
@@ -23,6 +23,8 @@ rasci.py artifacts [--ended] [--task <task>]
rasci.py estimate <task> "<party>" --value <fib> --complexity <fib>
rasci.py wsjf [--all]
rasci.py tools
rasci.py agents
rasci.py order "<agent>,<agent>,..."
```
## Objective and key results
@@ -58,6 +60,8 @@ published). `done` is refused while one is open; a task ended short of them is
(`--prio`) first, then WSJF from the estimates the chair collects; `show`
prints the rank as Prio and the score as WSJF.
- Write only your own rows; another session's task belongs to that session.
The one exception is `order`, the operator's pick through the
`kvb-org-priority` skill.
- On /kvb-org-rasci print `show` unchanged; its `gaps:` line names tasks
without exactly one A, without R, or open without a key result. `get`
prints one task in full, key results numbered.
+36
View File
@@ -213,6 +213,38 @@ def ranked(rows: dict[str, dict]) -> list[str]:
return sorted(rows, key=key)
def by_agent(rows: dict[str, dict]) -> dict[str, list[str]]:
agents: dict[str, list[str]] = {}
for task in ranked(rows):
agents.setdefault(rows[task].get("worker") or "-", []).append(task)
return agents
def cmd_agents(_: argparse.Namespace) -> int:
for rank, (agent, tasks) in enumerate(by_agent(merged(include_done=False)).items(), 1):
print(f"{rank}\t{agent}\t{len(tasks)}\t{', '.join(tasks)}")
return 0
def cmd_order(args: argparse.Namespace) -> int:
names = [name.strip() for name in args.agents.split(",") if name.strip()]
agents = by_agent(merged(include_done=False))
unknown = [name for name in names if name not in agents]
if not names or unknown or len(set(names)) != len(names):
fail(f"order needs distinct agents from `agents`; unknown: {', '.join(unknown) or '-'}")
with database() as tasks:
prio = 0
for name in names:
for task in agents[name]:
prio += 1
tasks.setdefault(task, {})["prio"] = prio
for name in agents.keys() - set(names):
for task in agents[name]:
tasks.get(task, {}).pop("prio", None)
print(f"pinned {prio} tasks of {len(names)} agents; the others rank by WSJF after them")
return 0
def okr(row: dict) -> str:
results = row.get("key_results", [])
text = cell(row.get("objective") or "-", 40)
@@ -489,6 +521,10 @@ def main() -> int:
p.add_argument("--task")
p.set_defaults(func=cmd_artifacts)
sub.add_parser("tools").set_defaults(func=cmd_tools)
sub.add_parser("agents").set_defaults(func=cmd_agents)
p = sub.add_parser("order")
p.add_argument("agents")
p.set_defaults(func=cmd_order)
args = parser.parse_args()
return args.func(args)
+23
View File
@@ -237,6 +237,29 @@ class TestRasci(unittest.TestCase):
self._run("estimate", "work/b", "One", "--drop")
self.assertFalse(any(line.startswith("estimate ") for line in self._out("get", "work/b")))
def test_order_pins_the_agents_in_the_operators_order(self):
self._run("set", "work/a1", "--worker", "Alpha", "--prio", "1")
self._run("set", "work/a2", "--worker", "Alpha", "--prio", "2")
self._run("set", "work/b1", "--worker", "Beta", "--prio", "3")
self._run("set", "work/c1", "--worker", "Gamma")
self._run("estimate", "work/c1", "One", "--value", "8", "--complexity", "2")
self.assertEqual(
self._out("agents"),
["1\tAlpha\t2\twork/a1, work/a2", "2\tBeta\t1\twork/b1", "3\tGamma\t1\twork/c1"],
)
self.assertEqual(
self._out("order", "Gamma, Beta"), ["pinned 2 tasks of 2 agents; the others rank by WSJF after them"]
)
self.assertEqual(
self._out("agents"),
["1\tGamma\t1\twork/c1", "2\tBeta\t1\twork/b1", "3\tAlpha\t2\twork/a1, work/a2"],
)
self.assertTrue(self._out("show")[2].startswith("| 1 | 4 | work/c1 |"))
self.assertNotIn("prio: 1", self._out("get", "work/a1"))
for args in (("order", "Gamma,Nobody"), ("order", "Beta,Beta"), ("order", " , ")):
with self.subTest(args=args):
self.assertEqual(self._run(*args).returncode, 2)
class TestOrgSkillsUseTheRegister(unittest.TestCase):
def test_every_org_skill_names_the_register(self):