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:
1 parent
f815060584
commit
269590a93d
5 files changed
+120
No files matched your search
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in new issue
Block a user