hubkit-cli 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. hubcore/__init__.py +1 -0
  2. hubcore/cli.py +273 -0
  3. hubcore/compat.py +187 -0
  4. hubcore/config.py +107 -0
  5. hubcore/data/hub/LICENSE +202 -0
  6. hubcore/data/hub/SKILL.md +125 -0
  7. hubcore/data/hub/config.example.toml +84 -0
  8. hubcore/data/hub/docs/architecture.md +133 -0
  9. hubcore/data/hub/docs/commands.md +66 -0
  10. hubcore/data/hub/docs/configuration.md +151 -0
  11. hubcore/data/hub/docs/troubleshooting.md +21 -0
  12. hubcore/data/hub/intents.example.toml +74 -0
  13. hubcore/data/hub/references/design-sources.md +14 -0
  14. hubcore/data/hub/references/research.md +49 -0
  15. hubcore/data/hub/scripts/hubcore/__init__.py +1 -0
  16. hubcore/data/hub/scripts/hubcore/cli.py +273 -0
  17. hubcore/data/hub/scripts/hubcore/compat.py +187 -0
  18. hubcore/data/hub/scripts/hubcore/config.py +107 -0
  19. hubcore/data/hub/scripts/hubcore/gate.py +145 -0
  20. hubcore/data/hub/scripts/hubcore/hooks.py +326 -0
  21. hubcore/data/hub/scripts/hubcore/install.py +66 -0
  22. hubcore/data/hub/scripts/hubcore/learnings.py +232 -0
  23. hubcore/data/hub/scripts/hubcore/lessons.py +168 -0
  24. hubcore/data/hub/scripts/hubcore/orders.py +508 -0
  25. hubcore/data/hub/scripts/hubcore/redact.py +89 -0
  26. hubcore/data/hub/scripts/hubcore/routed.py +1169 -0
  27. hubcore/data/hub/scripts/hubcore/slow.py +531 -0
  28. hubcore/data/hub/scripts/hubcore/status.py +535 -0
  29. hubcore/data/hub/scripts/hubcore/store.py +172 -0
  30. hubcore/data/hub/scripts/hubcore/watcher.py +167 -0
  31. hubcore/data/hub/scripts/hubkit +37 -0
  32. hubcore/data/hub/scripts/learn.py +84 -0
  33. hubcore/data/hub-mep/SKILL.md +35 -0
  34. hubcore/data/hubkit-setup/SKILL.md +50 -0
  35. hubcore/gate.py +145 -0
  36. hubcore/hooks.py +326 -0
  37. hubcore/install.py +66 -0
  38. hubcore/learnings.py +232 -0
  39. hubcore/lessons.py +168 -0
  40. hubcore/orders.py +508 -0
  41. hubcore/redact.py +89 -0
  42. hubcore/routed.py +1169 -0
  43. hubcore/slow.py +531 -0
  44. hubcore/status.py +535 -0
  45. hubcore/store.py +172 -0
  46. hubcore/watcher.py +167 -0
  47. hubkit_cli-0.1.0.dist-info/METADATA +195 -0
  48. hubkit_cli-0.1.0.dist-info/RECORD +51 -0
  49. hubkit_cli-0.1.0.dist-info/WHEEL +4 -0
  50. hubkit_cli-0.1.0.dist-info/entry_points.txt +2 -0
  51. hubkit_cli-0.1.0.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,66 @@
1
+ # Command reference
2
+
3
+ `hubkit` below means `python3 ~/.claude/skills/hub/scripts/hubkit`. Exit codes: 0 success, 2 a refused or invalid request (message on stderr), 1 `doctor` found a problem. `hook` always exits 0.
4
+
5
+ ## hubkit
6
+
7
+ | Verb | What it does |
8
+ |---|---|
9
+ | `skills --install DIR` | Copies the hub, hub-mep and hubkit-setup skills into DIR (re-run refreshes; the running copy is left alone). |
10
+ | `hooks [--skills-dir DIR] [--merge-guard]` | Prints the `hooks` JSON for settings.json. `--merge-guard` adds the warn-only PreToolUse merge guard. Never edits settings.json. |
11
+ | `hubkit bind [--pid N]` | Bind this session (found by walking up the process tree, or the process `N`) as the hub. Interactive sessions only; refuses while another hub is live. |
12
+ | `hubkit unbind` | Remove the bind. |
13
+ | `hubkit status [--json]` | Rebuild `status.json` and print the status block (or the JSON). |
14
+ | `hubkit order ls` | One line per live order. |
15
+ | `hubkit order status [ID]` | The status line of one order, or all live orders. |
16
+ | `hubkit order open --kind K --quote "..." [--unconfirmed]` | Open an order by hand. |
17
+ | `hubkit order confirm ID` | Turn an unconfirmed order into an open one. |
18
+ | `hubkit order move ID --note "..." [--estimate N]` | Record real movement. The first move also records the order's estimate (N minutes, else the usual time for its kind; printed when defaulted). |
19
+ | `hubkit asks ls [--since 12h]` | His recent prompts (from `recent-prompts.jsonl`), one line each, newest last. Marks the asks as re-read for the Stop gate. |
20
+ | `hubkit order block ID --on "..." [--fix "..."] [--holder who]` | Record what blocks an order and the fix running now (a second call with the same `--on` adds the fix). No fix, or a fix older than `blocker_fix_min` with no result, alerts and holds the Stop gate. |
21
+ | `hubkit order result ID --note "..."` | Record the result of an action: movement, clears the blocker and any STOP-LOSS. Notes from `order move` do neither. |
22
+ | `hubkit order hold ID --quote "..."` / `resume ID` | Hold (with the user's words) and resume. |
23
+ | `hubkit order cancel ID --quote "..."` | Cancel and archive. |
24
+ | `hubkit order close ID --evidence name=value ...` | Close; every name in the kind's `done_when` is required. `file:<path>` values must exist and be newer than the order. Every PR number the order names must appear in the evidence. |
25
+ | `hubkit order metrics [--day YYYY-MM-DD]` | Orders and chases for a day, as JSON. |
26
+ | `hubkit work add --kind active\|tracked\|priority --title "..." [--owner X] [--note ".."]` | Add a live work item; prints its id. |
27
+ | `hubkit work add --kind routed --owner X --title "..." [--pr URL ...] [--head SHA]` | Record routed work by hand. |
28
+ | `hubkit work add --kind ask --title ".." --note ".."` | Register a question that really blocks work on the user; his card answer closes it. |
29
+ | `hubkit work add ... [--estimate N]` | `--estimate` is the minutes the job should take (routed items default to the usual time for their step). |
30
+ | `hubkit work estimate ID --minutes N --why "..."` | Hub-internal. Record a new estimate for a routed item or live order beside the old one (never over it). `N` must exceed the minutes already spent and `--why` is required. This is one way to address an overrun. |
31
+ | `hubkit work block ID --on OTHER` | Hub-internal. Say that routed item ID waits on job OTHER: while OTHER is live, ID raises no STALLED/OVERDUE or SLOW. `hubkit work block ID` (no `--on`) clears it. |
32
+ | `hubkit work done ID --note ".."` | Archive an item. |
33
+ | `hubkit work ls` | List live items. |
34
+ | `hubkit tick [--if-bound] [--no-notify]` | One watchdog pass; prints `ALERT ...` lines. `--if-bound` is a no-op unless a hub is bound or an order is open (for cron). |
35
+ | `hubkit watch [--interval S] [--max-cycles N]` | Loop `tick` (defaults 300 s, 12 cycles). Exits 0 printing `ALERT ...`, 3 after the cycles, 4 if another watch runs. |
36
+ | `hubkit eod [--day D] [--note ".."]` | Write and print the end-of-day review. |
37
+ | `hubkit doctor [--check-clean]` | Print config, state, bind, hook heartbeats, ledger health and a count of secret-shaped strings in state. `--check-clean` also lists checkout files containing a `private.terms` word. Exit 1 on a problem. |
38
+ | `hubkit hook EVENT` | Run one hook (`session`, `prompt`, `answer`, `routed`, `stop`, `record`); reads the payload from stdin. For Claude Code, not for people. |
39
+
40
+ ## learn.py
41
+
42
+ `python3 ~/.claude/skills/hub/scripts/learn.py [--file PATH] VERB`
43
+
44
+ | Verb | What it does |
45
+ |---|---|
46
+ | `add --class C --quote "..." [--gate G1..G8] [--date D] [--situation ..] [--wrong ..] [--right ..] [--evidence ..]` | Record a class of mistake, or a dated repeat of one. |
47
+ | `list [--last N]` | The N most recently seen entries (default 30). |
48
+ | `promote-candidates [--min N]` | Classes seen at least N times (default 2). |
49
+ | `promote CLASS --gate G --rule "..."` | Write a rule line tagged `[CLASS]` into the overlay under its gate. |
50
+ | `address --class C --how "..."` | Mark a class fixed. Refused unless the overlay or SKILL.md changed since it was last seen and holds a `[C]` rule line. |
51
+ | `review [--day D]` | New, repeated, regressed, holding and unaddressed classes for a day. |
52
+
53
+ ## Example
54
+
55
+ ```console
56
+ $ hubkit work add --kind active --title "triage the flaky test"
57
+ W1
58
+ $ hubkit tick --no-notify
59
+ $ hubkit status
60
+ hubkit: you are the hub session for the user. Re-read the hub SKILL.md and the overlay before acting.
61
+ status built 0 min ago
62
+ active: W1 triage the flaky test
63
+ mep: UNVERIFIED (mep CLI not found; it is optional, set [mep] enabled = false in config.toml)
64
+ hook errors in scan window: 0
65
+ open orders: 0
66
+ ```
@@ -0,0 +1,151 @@
1
+ # Configuration reference
2
+
3
+ All user-specific values live in `~/.config/hubkit/` (or `$XDG_CONFIG_HOME/hubkit/`). Nothing personal belongs in the checkout. Every key is optional; a missing key uses the default in `scripts/hubcore/config.py` (`DEFAULTS`). Templates: [`config.example.toml`](../config.example.toml) and [`intents.example.toml`](../intents.example.toml).
4
+
5
+ ## Files and environment
6
+
7
+ | What | Where | Override |
8
+ |---|---|---|
9
+ | Settings | `<config dir>/config.toml` | `$HUBKIT_CONFIG` (full path) |
10
+ | Intents | `<config dir>/intents.toml`, else the `intents.example.toml` shipped in the checkout | `paths.intents` |
11
+ | Overlay (your rules) | `<config dir>/overlay.md` | `paths.overlay` |
12
+ | Extensions | `<config dir>/ext/*.py` | `paths.extensions_dir` |
13
+ | State | `$XDG_STATE_HOME/hubkit` (default `~/.local/state/hubkit`) | `$HUBKIT_STATE`, `paths.state_dir` |
14
+ | Learnings | `<state dir>/learnings.md` | `paths.learnings` |
15
+
16
+ Other environment variables: `HUBKIT_SESSIONS` (directory of Claude Code session files, overrides `paths.sessions_dir`), `HUBKIT_DISABLE` (any value: every hook is a no-op), `HUBKIT_DIE_AFTER` (test hook for crash recovery; do not set).
17
+
18
+ ## config.toml
19
+
20
+ ### `[user]`
21
+ | Key | Default | Meaning |
22
+ |---|---|---|
23
+ | `name` | `"the user"` | How the status block names you. |
24
+ | `timezone` | `""` (system zone) | IANA name, for example `Europe/Berlin`. Times are stored as epoch and shown in this zone; days roll over in it. |
25
+ | `github_login` | `""` | Your GitHub login. On a PR you wrote, the reviewer peer posts from your account and GitHub allows only a COMMENT there, so a COMMENT review with a body (not an empty-body inline-thread reply) at the PR head counts as the review (verdict check, item close, queue advance). Empty keeps the non-author rule everywhere. |
26
+
27
+ ### `[hub]`
28
+ | Key | Default | Meaning |
29
+ |---|---|---|
30
+ | `name` | `"hub"` | Names the hub in the status block and is the session name matched when the SessionStart hook re-binds a resumed hub. |
31
+
32
+ ### `[paths]`
33
+ | Key | Default | Meaning |
34
+ |---|---|---|
35
+ | `state_dir` | `""` | State directory. Must not be inside a git repository or the skill directory. |
36
+ | `intents` | `""` | Path of `intents.toml`. |
37
+ | `overlay` | `""` | Path of `overlay.md`. |
38
+ | `learnings` | `""` | Path of the learnings file. |
39
+ | `sessions_dir` | `~/.claude/sessions` | Where Claude Code writes one `<pid>.json` per running session; used to find and verify the hub. |
40
+ | `transcripts_glob` | `~/.claude/projects/*/*.jsonl` | Transcripts scanned for failures of hubkit's own hooks and for heard-from signals. |
41
+ | `extensions_dir` | `""` | Directory of extension modules. |
42
+
43
+ ### `[tools]`
44
+ | Key | Default | Meaning |
45
+ |---|---|---|
46
+ | `mep` | `"mep"` | Name or path of the optional `mep` CLI. |
47
+ | `gh` | `"gh"` | Name or path of the GitHub CLI. |
48
+ | `notify_cmd` | `""` | Command run with the alert message as its last argument (for example `notify-send -u critical -a hubkit`). Empty means no desktop notification. |
49
+
50
+ ### `[mep]`
51
+ | Key | Default | Meaning |
52
+ |---|---|---|
53
+ | `enabled` | `true` | Ask `mep status --json` for the items on the user. Set `false` if you do not use mep. When enabled and `mep` is missing, the status block shows `mep: UNVERIFIED (...)`. |
54
+ | `timeout_s` | `30` | Seconds before a slow `mep` call is abandoned (the last good answer is kept, labelled stale). |
55
+ | `data_dir` | unset | Exported as `MISEENPLACE_DIR` for the call. |
56
+
57
+ ### `[watchdog]`
58
+ | Key | Default | Meaning |
59
+ |---|---|---|
60
+ | `transcript_scan_hours` | `24` | How far back to scan transcripts for hubkit hook failures. |
61
+ | `cron_stale_min` | `15` | `doctor` flags `seen/cron` as stale when the crontab runs hubkit and the file is older than this. |
62
+
63
+ ### `[slow]`
64
+
65
+ Estimates and the slowness check (hub-internal, see architecture).
66
+
67
+ | Key | Default | Meaning |
68
+ |---|---|---|
69
+ | `min_samples` | `3` | Archived samples needed before the usual time replaces the stall limit. |
70
+ | `order_default_min` | `30` | Default order estimate while there is no history. |
71
+ | `floor_min` | `2` | A job is never SLOW under this many minutes. |
72
+ | `tool_min` | `2` | A tool running this long is named in the why. |
73
+ | `scan_kb` | `2048` | Tail of a transcript read for the why (tool names and queue operations only). |
74
+
75
+ ### `[watcher]`
76
+
77
+ | Key | Default | Meaning |
78
+ |---|---|---|
79
+ | `stuck_min` | `30` | Hub session `busy` this long with live jobs raises `hub:stuck`. |
80
+ | `unhandled_min` | `10` | An alert this old with no hub heartbeat since raises `hub:unhandled`. |
81
+ | `waiting_min` | `3` | A peer waiting on a prompt this long raises `peer:<name>:waiting`. |
82
+ | `active_s` | `120` | A busy holder whose transcript grew this recently is working, not stalled. |
83
+ | `watch_grace_min` | `5` | Live work with no running `hubkit watch` this long raises `watch:dead`. |
84
+ | `notify_user` | `true` | Use the platform default desktop notification for hub and peer alerts when `tools.notify_cmd` is empty. |
85
+
86
+ ### `[stop_guard]`
87
+ | Key | Default | Meaning |
88
+ |---|---|---|
89
+ | `phrases` | `["waits for your go", "say go", "want me to", "shall i"]` | A final message ending in `?` or containing one of these, while no `ask` item exists, is blocked by the stop gate. |
90
+
91
+ ### `[private]`
92
+ | Key | Default | Meaning |
93
+ |---|---|---|
94
+ | `terms` | `[]` | Words that must never appear in the checkout's own files. `hubkit doctor --check-clean` lists the files that contain one (never the match). Keep this file on your machine. |
95
+
96
+ ### `[routed]` (also readable from `intents.toml`; intents win)
97
+ | Key | Default | Meaning |
98
+ |---|---|---|
99
+ | `stall_min` | `15` | Silence from the owner this long is STALLED; the ladder steps at multiples of it. |
100
+ | `review_words` | `["review"]` | A message naming a PR and one of these is a review item. |
101
+ | `review_stall_min` | `5` | Reviews: chase after this. |
102
+ | `review_overdue_min` | `10` | Reviews: hard OVERDUE this long after routing, whatever the owner said. |
103
+ | `skip_owners` | `["inbox"]` | Owners that are never tracked. |
104
+ | `min_chars` | `200` | A message without a PR is work only from this length. |
105
+ | `gh_timeout_s` | `10` | Timeout for one `gh` read. |
106
+ | `queued_wait_min` | `60` | Work queued "after X" gets no review clock until this long after the ask (or until a review shows). |
107
+ | `reply_window_min` | `60` | A hub reply to a peer who wrote this recently is not an ask. |
108
+ | `repo` | `""` | `owner/name`; a bare `#N` resolves against it so `gh` can read the PR. |
109
+ | `repo_aliases` | `{}` | Table of alias to `owner/name`. `<alias> 123`, `<alias>#123` and `owner/name#123` name the PR in that repo instead of the default `repo`. |
110
+ | `owner_logins` | `{}` | Map of owner name to GitHub login, for peers who author PRs. |
111
+ | `reviewer_logins` | `[]` | Logins whose review counts. Empty means any non-author review. |
112
+ | `skip_briefs` | `["mep watch", "inbox watch"]` | Briefs containing one of these are standing watchers, never routed work. |
113
+ | `activity_globs` | two Claude Code paths | Files whose growth shows a background agent is alive. `{id}` is the agent id. |
114
+
115
+ ## intents.toml
116
+
117
+ The first kind whose words match wins, so put the narrower kind first (`hotfix` before `release`).
118
+
119
+ ### `[intake]`
120
+ | Key | Meaning |
121
+ |---|---|
122
+ | `imperative` | An order opens as `open` only if one of these words is present; otherwise it is `unconfirmed`. |
123
+ | `hold_words` | Explicit holds only. Bare "don't", "wait", "stop" are not holds. |
124
+ | `urgent_words` | A hold is ignored, and a held order restarts, when one is in the prompt. |
125
+ | `stop_loss` | This many chases on one order print a stop-loss line. |
126
+ | `not_order_phrases` | Phrases that contain an order word but are not orders ("release notes"). |
127
+ | `question_start` | Regex; an order word in a question is never an order, urgent words or not (the hook then says so and shows `hubkit order open` for opening one by hand). |
128
+ | `unconfirmed_ttl_min` | `tick` expires unconfirmed orders after this long. |
129
+ | `held_review_min` | A hold this long raises an alert. |
130
+ | `cancel_words`, `resume_words` | Words for the cancel and resume verbs. |
131
+ | `verb_max_chars` | Prompts longer than this never hold, cancel, resume or count as chases. |
132
+ | `chase` | Regex; a short prompt matching it while an order is open is a chase. |
133
+
134
+ ### `[corrections]` (the learn loop)
135
+ | Key | Meaning |
136
+ |---|---|
137
+ | `patterns` | Regexes; a typed prompt matching one injects a `CORRECTION` line asking the hub to record it. |
138
+ | `repeat_similarity` | Word-set similarity (0 to 1) above which a prompt counts as repeating a recent one. Default `0.6`. |
139
+ | `repeat_window_min` | How far back a repeat is looked for. Default `30`. |
140
+
141
+ ### `[kinds.<name>]`
142
+ | Key | Meaning |
143
+ |---|---|
144
+ | `words` | Words that open an order of this kind. |
145
+ | `first_action_min` | No `order move` within this many minutes raises an alert. |
146
+ | `stall_min` | No movement for this long raises an alert. |
147
+ | `dispatch` | Text printed when the order opens. |
148
+ | `done_when` | Evidence names `order close` requires (`--evidence name=value`). |
149
+ | `movement_files` | Globs; a file touched after the order opened counts as movement. |
150
+
151
+ Add your own kinds the same way; the words are matched as whole words, case-insensitively.
@@ -0,0 +1,21 @@
1
+ # Troubleshooting
2
+
3
+ Start with `hubkit doctor`: it prints the config path, state path, bind, hook heartbeats (`seen/<hook>`), ledger health, hook errors and a count of secret-shaped strings in state.
4
+
5
+ | Symptom | Cause and fix |
6
+ |---|---|
7
+ | Hooks print nothing | Expected outside the bound hub. Run `hubkit bind` in the session meant to be the hub; `doctor` must show `bind: live <name>`. Headless runs and non-interactive sessions are never the hub. `HUBKIT_DISABLE` also silences hooks. |
8
+ | `seen/session: never` (or any hook) | The hook is not registered in `~/.claude/settings.json`, or Claude Code was not restarted after editing it. Check the command line against the README. |
9
+ | `seen/cron: never` or old | The cron line is missing or fails. `tick` touches `seen/cron` on every run, even the early no-op, so this tells a dead cron from an idle one. `doctor` prints `cron: STALE` when a crontab line runs hubkit but `seen/cron` is older than `watchdog.cron_stale_min`. The usual cause is cron running an older `python3` than 3.14 (hubkit fails with a `SyntaxError` that goes to `/dev/null`): run `env -i PATH="$(grep ^PATH= /etc/environment | cut -d'"' -f2)" sh -c 'python3 --version'` and put the full interpreter path in the crontab line. Add `DISPLAY` and `DBUS_SESSION_BUS_ADDRESS` to the crontab for desktop notifications. |
10
+ | `error: state dir ... is inside a git repository` | State must not be committed. Move it with `$HUBKIT_STATE` or `paths.state_dir`, or run from a home directory that is not a git repository. |
11
+ | `error: no Claude session found for this process (pass --pid)` | `bind` walks up the process tree to a Claude Code process listed in `paths.sessions_dir`. Run it from the hub session, or pass `--pid`. |
12
+ | `error: live hub already bound` | Another hub is bound. `hubkit unbind` from that session, or wait for it to exit (a dead process frees the bind). |
13
+ | `mep: UNVERIFIED (mep CLI not found ...)` | The optional `mep` CLI is missing. Set `[mep] enabled = false` in `config.toml`. |
14
+ | `PR unknown` on routed items | `gh` is missing, not authenticated, or the PR is in a repository the token cannot read. Check `gh auth status`; set `routed.repo` so bare `#N` resolves. |
15
+ | A prompt did not open an order | The sentence was a question, held a `not_order_phrases` phrase, had no `imperative` word (so it is `unconfirmed`: `hubkit order confirm ID`), or came from peer or harness text, which never opens orders. |
16
+ | `close refused: missing evidence` | Give every name in the kind's `done_when`: `--evidence name=value`. `file:<path>` must exist and be newer than the order. |
17
+ | The turn will not end (Stop gate) | Read the `NEXT:` in the block reason. Usually: start the watch (`hubkit watch --interval 120 --max-cycles 30` in the background), act on a stalled item, drop an unbacked verdict, or register the question with `hubkit work add --kind ask`. |
18
+ | `import-errors.log` in the state dir | hubkit failed to import (wrong Python version, damaged checkout). Hooks exit 0 so Claude Code is unaffected. Run `python3 --version` (3.14 or newer needed) and `hubkit doctor`. |
19
+ | `SyntaxError` on `except A, B:` | Python older than 3.14. |
20
+ | `doctor --check-clean` lists files | The files contain a word from `private.terms`. Edit them; the match is not printed. |
21
+ | `learn.py address` refused | No rule line tagged `[class]` exists in the overlay or SKILL.md, or neither changed since the class was last seen. Run `learn.py promote` first. |
@@ -0,0 +1,74 @@
1
+ # Intents: words in the user's own prompts that start an order, and what "done" means.
2
+ # Copy to ~/.config/hubkit/intents.toml (or set paths.intents in config.toml) and edit.
3
+ # The first kind whose words match wins, so put the narrower kind first (hotfix before release).
4
+
5
+ [intake]
6
+ # An order opens as "open" only if one of these words is present; otherwise "unconfirmed"
7
+ # (no clock alerts until `hubkit order confirm <id>`).
8
+ imperative = ["now", "asap", "need", "must", "do", "make", "start", "run", "merge", "fix", "finish", "go", "urgent"]
9
+ # Explicit holds only. Bare "don't", "wait" or "stop" are NOT holds ("don't wait, merge it now").
10
+ hold_words = ["hold off", "on hold", "put it on hold", "hold the", "hold it", "pause", "not yet", "don't deploy", "dont deploy", "do not deploy", "stand by"]
11
+ # A hold is ignored, and a held order restarts, when one of these is in the prompt.
12
+ urgent_words = ["now", "asap", "urgent", "immediately", "right away", "hurry", "don't wait", "dont wait", "do not wait"]
13
+ # This many chases on one order: the hook prints a stop-loss line (stop other work, report the blocker).
14
+ stop_loss = 3
15
+ # A blocker on an order must have a fix running; a fix with no recorded result after this long alerts and holds the Stop gate.
16
+ blocker_fix_min = 3
17
+ # Phrases that contain an order word but are not orders ("the release notes look fine").
18
+ not_order_phrases = ["release notes", "release note", "release branch", "release candidate", "release date", "release process", "release checklist"]
19
+ # An order word in a question is never an order, urgent words or not; the prompt hook says no order was opened and how to open one by hand.
20
+ question_start = '(?i)^\W*(why|what|how|did|does|do you|is|are|was|were|can|could|would|should|when|where|who|which|has|have)\b'
21
+ # Orders left unconfirmed this long are expired by `tick`; a hold this long raises an alert.
22
+ unconfirmed_ttl_min = 30
23
+ held_review_min = 120
24
+ cancel_words = ["cancel", "abort", "drop it", "never mind"]
25
+
26
+ resume_words = ["resume", "go ahead", "continue"]
27
+ # Prompts longer than this never hold, cancel or count as chases (they are discussion).
28
+ verb_max_chars = 120
29
+ # A short prompt matching this while an order is open counts as a chase on that order.
30
+ chase = '(?i)\b(status|eta|done\?|still (not|waiting|running|open)|hold ?up|hurry|where are we|any progress|update\?|why is (it|this|that)|how long)'
31
+
32
+ [stop_gate]
33
+ # A final message holding one of these words, while an order is open and none has a fix on record, cannot end the turn:
34
+ # a blocker is solved, not reported. Whole words or phrases, case-insensitive.
35
+ blocked_words = ["blocked", "is down", "are down", "failed", "failing", "no host", "cannot reach", "can't reach", "unreachable", "not available", "waiting on", "waiting for", "stuck", "needs a person", "needs his call", "needs her call"]
36
+
37
+ [corrections]
38
+ # A human prompt matching one of these regexes (or repeating the previous prompt) makes the prompt
39
+ # hook inject a reminder to record it: learn.py add with the quote.
40
+ patterns = ["(?i)\\b(useless|stupid|dumb|idiot|wtf|r u (dumb|kidding|sleeping))\\b", "(?i)^\\W*(wrong|no+|nope)[!. ]*$", "(?i)\\bwhy (did|didn't|do|are) (you|u)\\b", "(?i)\\bi (told|asked|said)\\b", "(?i)\\balready (told|said|asked|replied)\\b", "(?i)\\bnot what i (asked|said|meant)\\b"]
41
+ repeat_similarity = 0.6
42
+ repeat_window_min = 30
43
+
44
+ [kinds.hotfix]
45
+ words = ["hotfix", "hotpatch"]
46
+ first_action_min = 5 # no `order move` within this many minutes -> alert
47
+ stall_min = 10 # no movement for this long -> alert
48
+ dispatch = "Dispatch the hotfix path; merge the named PR first."
49
+ done_when = ["pr_merged", "prod_live"] # evidence names `order close` requires
50
+ movement_files = [] # globs; a file touched after the order opened counts as movement
51
+
52
+ [kinds.release]
53
+ words = ["release", "app update"]
54
+ first_action_min = 5
55
+ stall_min = 15
56
+ dispatch = "Dispatch the release path."
57
+ done_when = ["prs_merged", "smoke_passed"]
58
+ movement_files = []
59
+
60
+ [kinds.merge]
61
+ words = ["merge"]
62
+ first_action_min = 5
63
+ stall_min = 30
64
+ dispatch = "Merge exactly the named set."
65
+ done_when = ["merged"]
66
+ movement_files = []
67
+
68
+ [kinds.code_red]
69
+ words = ["code red", "p0"]
70
+ first_action_min = 10
71
+ stall_min = 30
72
+ dispatch = "Name an owner and check for a commit or review signal."
73
+ done_when = ["resolved"]
74
+ movement_files = []
@@ -0,0 +1,14 @@
1
+ # Where the design parts come from
2
+
3
+ | Part | Source |
4
+ |---|---|
5
+ | Hub keeps control, workers are tools; never accept "done" from the author | research.md [A2] evaluator-optimizer, [S1] supervisor, "Verification by a separate judge" |
6
+ | Briefs carry objective, output, boundaries; agents see only the brief | [A1], [A3] |
7
+ | Effort scaling, agent cap, parallel launch | [A1]; the cap itself is a user rule, kept in the overlay |
8
+ | Hard stop and human checkpoints; resume from failure | [A2], [A1]; implemented as the stop-loss line on repeated chases and the order `hold` verb |
9
+ | Evaluate early on about 20 real cases; trace decision patterns | [A1]; the tests replay realistic prompts and transcript entries |
10
+ | Persist plan and state outside the context; fresh-context hand-off | [A1]; status file, orders and archive in the state dir, reloaded by the SessionStart block |
11
+ | Block size cap (5 learnings), 30-day expiry and 14-day probation of learnings | keeps post-compaction context small; probation so a fix is proven before it drops |
12
+ | Orders with clock and evidence, watchdog outside the hooks, hooks fail open and stay silent outside the hub | hook failures are invisible to the model, so the watchdog scans transcripts for them |
13
+ | Learnings promoted into rules, addressed only after a rule change | "Learning loops and memory" in research.md: a correction repeated without a rule change is not fixed |
14
+ | Human-only intake, fail-open hooks, stale-labelled external reads | prompt origin must be human, whole-text peer-marker scan, import failure exits 0, `mep` kept as last good answer when slow |
@@ -0,0 +1,49 @@
1
+ # Research: hub orchestration (2026-11-04)
2
+
3
+ Fetched and read in full: [A1], [A2], [A3]. Search snippets only (not opened): [S1], [S2], [S3].
4
+ Figures below come from the fetched pages. Treat snippet-only claims as unverified.
5
+
6
+ ## Sources
7
+ - [A1] https://www.anthropic.com/engineering/multi-agent-research-system (read)
8
+ - [A2] https://www.anthropic.com/engineering/building-effective-agents (read)
9
+ - [A3] https://code.claude.com/docs/en/sub-agents (first 100k chars read)
10
+ - [S1] https://github.com/langchain-ai/langgraph-supervisor-py (snippet)
11
+ - [S2] https://fast.io/resources/openai-agents-sdk-vs-langgraph/ (snippet, vendor blog)
12
+ - [S3] https://www.developersdigest.tech/blog/multi-agent-orchestration-patterns (snippet)
13
+ - Not fetched: the Claude Code skills doc (https://code.claude.com/docs/en/skills).
14
+
15
+ ## What applies to the hub
16
+
17
+ Delegation criteria
18
+ - [A3] Delegate side tasks that would flood the main context with logs, search results or file bodies. Keep in the main thread only what needs back-and-forth.
19
+ - [A1] Scale effort to the task: simple fact-finding = 1 agent, 3-10 tool calls. Early agents spawned 50 subagents for simple queries. Put an effort rule in the brief.
20
+ - [A1] Multi-agent costs about 15x the tokens of chat. Skip it for shared-context work, many dependencies, few parallel steps (most coding).
21
+ - [A2] Orchestrator-workers fits tasks whose subtasks cannot be predicted. Start simple, add structure only when it measurably helps.
22
+ - [S2] Handoffs (control moves) fit linear triage. Agents-as-tools (manager keeps control) fits the hub: it never hands the user over to a peer.
23
+
24
+ Brief quality
25
+ - [A1] Every brief carries: objective, output format, tools and sources to use, clear task boundaries. Vague briefs caused duplicated work and gaps.
26
+ - [A3] A subagent does not see the conversation. The brief is its only context.
27
+ - [S1] A supervisor that calls workers as tools keeps control of context. This matches the hub model.
28
+
29
+ Parallelism
30
+ - [A1] Launch independent workers in parallel, not in series. This cut research time by up to 90%.
31
+ - A cap on parallel agents and a model-tier ceiling are the user's rules; keep them in the overlay.
32
+
33
+ Verification by a separate judge
34
+ - [A2] Evaluator-optimizer: one model produces, another evaluates against clear criteria. Ground truth comes from the environment (tool results), not from the agent's own claim.
35
+ - [A1] Judge the end state, not the path. Use an LLM judge plus human checks for edge cases.
36
+ - The hub never accepts "done" from the agent that did the work.
37
+
38
+ Context hygiene
39
+ - [A1] Save the plan to external memory before context fills. Spawn fresh agents with clean context and hand off carefully. Workers return lightweight references, not bulk output.
40
+ - [A3] Subagents return a summary only. Resume with SendMessage to keep their context. Explore and Plan agents cannot be resumed.
41
+
42
+ Stop conditions and human checkpoints
43
+ - [A2] Pause for human input at checkpoints or blockers. Add a hard stop (max iterations).
44
+ - [A1] Resume from the point of failure, not from scratch. Tell an agent when a tool is failing so it adapts.
45
+
46
+ Learning loops and memory
47
+ - [A1] Start evaluating at once with ~20 real cases. Prompt changes were the main lever. Trace decision patterns, not every transcript.
48
+ - [A1] Persist the plan. [A2] Test tools with real inputs and fix the mistakes the model makes (poka-yoke).
49
+ - Applied here: every user correction becomes a learnings entry tied to a gate check. A class seen twice is promoted into the gate. See SKILL.md section 9.
@@ -0,0 +1 @@
1
+ """hubkit core: orders, status, watchdog, hooks. Stdlib only, Python 3.11+."""