@try-works/dsh-righthand 0.1.9 → 0.1.11
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.
- package/README.md +16 -3
- package/docs/agent-tool-catalog.md +188 -0
- package/docs/mu-repo-analysis.md +194 -0
- package/docs/scenario-patterns.md +107 -0
- package/package.json +2 -1
- package/skills/dsh-righthand/SKILL.md +8 -0
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ tool guard. Every tool is built on the harness's own services
|
|
|
6
6
|
(`storageDomain`, `credentials`, `settings`, `subprocess`, `jobs`, `tools`),
|
|
7
7
|
not hand-rolled primitives.
|
|
8
8
|
|
|
9
|
-
The plugin also ships **blueprint guidance** —
|
|
9
|
+
The plugin also ships **blueprint guidance** — fifteen named recipes for the agent-built tools this plugin exists to compose, six of them committed kits (see [Blueprints](#blueprints) below) — plus a packaged skill, a settings namespace, and an optional tool guard.
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
@@ -75,7 +75,10 @@ Namespace "righthand" (registered by the secrets family):
|
|
|
75
75
|
| `defaultNotifyTopic` | "" | default ntfy topic for `rh_notify_send` |
|
|
76
76
|
|
|
77
77
|
`rh_settings_get` returns the resolved values; `rh_settings_set` merges a
|
|
78
|
-
partial patch (persisted by the harness settings provider).
|
|
78
|
+
partial patch (persisted by the harness settings provider). Schema wall:
|
|
79
|
+
updates with keys the schema does not define are accepted but never come
|
|
80
|
+
back from `rh_settings_get` — register new keys in the plugin schema, or
|
|
81
|
+
keep ad-hoc knobs in `rh_store`.
|
|
79
82
|
|
|
80
83
|
## Blueprints
|
|
81
84
|
|
|
@@ -89,7 +92,11 @@ canonical example of the current DSH-native form (tool-to-tool via
|
|
|
89
92
|
| Blueprint | What it builds |
|
|
90
93
|
|---|---|
|
|
91
94
|
| `blueprint/daily-digest` | Collect sources, summarize, send on a schedule — extractive locally, LLM synthesis as escalation |
|
|
92
|
-
| `blueprint/data-adapter` | Wrap one keyless public API as a tool family: guarded fetch, normalize, one tool per query. Worked
|
|
95
|
+
| `blueprint/data-adapter` | Wrap one keyless public API as a tool family: guarded fetch, normalize, one tool per query. Worked examples ship in the plugin: `rh_weather_*`, `rh_places_*` |
|
|
96
|
+
| `blueprint/reminder-flow` | The agent as its own scheduler: `rh_events_*` checked each turn, `rh_notify_send` delivery, one store receipt per event — exactly-once via the state flip |
|
|
97
|
+
| `blueprint/geo-context` | A place query becomes agent context: geocode, then elevation + weather + air + nearby at those coordinates, TTL-cached in `rh_store` |
|
|
98
|
+
| `blueprint/governed-exec` | Every high-impact command as a governed, receipted step: guard gates the prefix, `rh_run` collects, the store keeps the before/after undo trail |
|
|
99
|
+
| `blueprint/file-vault` | R2 as the agent's file system with the store as the index: `rh_files_*` blobs + metadata records + time-boxed presigned shares |
|
|
93
100
|
| (phase 5) `rh_files_*` on R2 | The first family on an actual Cloudflare primitive: S3-compatible API, SigV4 signing pinned to the AWS test vector, credentials from the credential provider |
|
|
94
101
|
| `blueprint/research-radar` | Multi-source research over a rolling window (Reddit, X, YouTube, HN, Polymarket, web): score, dedupe, synthesize with citations |
|
|
95
102
|
| `blueprint/web-scraper` | Fetch a page, extract title/headings/links/text, make it searchable — static HTML first, render/vision as escalation |
|
|
@@ -108,6 +115,12 @@ families worth adding, from the tools catalogue + permission commit) and
|
|
|
108
115
|
from the whole repo — single-source specs, golden permissions, layering,
|
|
109
116
|
held-state gates, SSRF guarding).
|
|
110
117
|
|
|
118
|
+
[`docs/scenario-patterns.md`](docs/scenario-patterns.md) is the guidance
|
|
119
|
+
built by actually running the tools: key-prefix conventions, the receipt
|
|
120
|
+
pattern, undo trails, the settings schema wall, credential auth gates, and
|
|
121
|
+
exec collect-vs-background — each pattern live-verified against the real
|
|
122
|
+
harness services.
|
|
123
|
+
|
|
111
124
|
## Packaged skill
|
|
112
125
|
|
|
113
126
|
The plugin ships the `dsh-righthand` skill
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Agent tool catalog — analysis of micro.mu/tools + the permission commit
|
|
2
|
+
|
|
3
|
+
Source material:
|
|
4
|
+
- https://micro.mu/tools — the Mu tool catalogue (~30 services, ~130 methods,
|
|
5
|
+
every method an agent-callable tool).
|
|
6
|
+
- https://github.com/micro/mu/commit/4933862770d706f6c56e0ab2af287070811fc52d —
|
|
7
|
+
"five permission flags become two": the permission-model commit.
|
|
8
|
+
|
|
9
|
+
## What the commit teaches (apply to guard-tools)
|
|
10
|
+
|
|
11
|
+
The commit collapses five declared flags into two derived facts:
|
|
12
|
+
|
|
13
|
+
| Mu model | Meaning |
|
|
14
|
+
|---|---|
|
|
15
|
+
| `Requires: Open` | anybody, including nobody at all |
|
|
16
|
+
| `Requires: Caller` | somebody — a funded wallet counts; enough for anything scoped to "theirs" (own notes, own files) |
|
|
17
|
+
| `Requires: Account` | a real account, not just a wallet — requests made *on somebody's behalf* (fetch any URL you name) or spending a shared reputation (mail.Send); "rationing needs somebody to ration" |
|
|
18
|
+
| `Destructive` | an irreversible effect nobody asked for; the flag exists because tool *descriptions* are attacker-controlled text, so a tool that holds one is a prompt-injection surface |
|
|
19
|
+
|
|
20
|
+
Lessons for righthand's guard:
|
|
21
|
+
1. Mu's identity tiers (Open/Caller/Account) and its `limit` per-day counts
|
|
22
|
+
are **not applicable** here: righthand is a single user — the tools are
|
|
23
|
+
built by that user's own agent and run locally or on the user's own
|
|
24
|
+
Cloudflare account. There are no accounts to tier and nothing to ration.
|
|
25
|
+
What *does* carry over is the **destructive marker**: an irreversible
|
|
26
|
+
effect the agent's own tool descriptions could talk it into (a deploy, a
|
|
27
|
+
delete, a DNS change) is the prompt-injection surface the guard exists
|
|
28
|
+
for, and prefix-based allow/deny/ask already covers it — the marker just
|
|
29
|
+
makes the reasoning explicit.
|
|
30
|
+
2. **Golden-file the derived permissions** (`test/permissions.golden`):
|
|
31
|
+
baseline every tool's permission from the derived policy and let the test
|
|
32
|
+
fail when one changes. The commit's own debugging is the guidance: the
|
|
33
|
+
golden first recorded *nothing* (registering a service is not deriving its
|
|
34
|
+
tools), then recorded only the derived policy (and missed the exact
|
|
35
|
+
distinction being collapsed), then recorded prices (failed on test order).
|
|
36
|
+
It now records permissions alone, sees the change, and is stable.
|
|
37
|
+
Righthand equivalent: a `tests/permissions.golden` over every `rh_*` tool.
|
|
38
|
+
|
|
39
|
+
## Tool families Mu ships, mapped onto righthand primitives
|
|
40
|
+
|
|
41
|
+
Legend: **have** = the harness/righthand already provides this;
|
|
42
|
+
**add** = a new righthand family worth building; **data** = a keyless-API
|
|
43
|
+
adapter (blueprint material); **skip** = instance-local social or
|
|
44
|
+
third-party-keyed, out of scope for the core plugin.
|
|
45
|
+
|
|
46
|
+
### Already provided (no work — document as guidance, don't rebuild)
|
|
47
|
+
|
|
48
|
+
| Mu | Righthand/harness equivalent |
|
|
49
|
+
|---|---|
|
|
50
|
+
| shell_run / shell_write | `rh_run` / `rh_run_bg` + `ctx.fs` write tools |
|
|
51
|
+
| web_fetch / web_search / browser_read / browser_shot | harness `web_search` + browser tools + `blueprint/web-scraper` |
|
|
52
|
+
| notes_add/get/list/delete | `rh_store_*` (the notes pattern, scenario 2) |
|
|
53
|
+
| tasks_create/list/update/next | `rh_store_*` task board (documented use case) — candidate for a typed `rh_task_*` family |
|
|
54
|
+
| recall_list/search/conversation | harness session services (`session-query-sqlite`, `session` projection) |
|
|
55
|
+
| docs_write/read/list | `ctx.fs` read/write/edit tools (store keyed index is the delta) |
|
|
56
|
+
|
|
57
|
+
### New righthand families worth adding (core plugin)
|
|
58
|
+
|
|
59
|
+
| Family | Mu analogue | Backing | Effort |
|
|
60
|
+
|---|---|---|---|
|
|
61
|
+
| `rh_task_*` | tasks_* | `rh_store` domain `tasks` (state machine: open → done, `tasks_next`) | small — the store family already proves the pattern |
|
|
62
|
+
| `rh_text_*` | text_classify / text_extract / text_summarise / text_translate | `ctx.llm` (same route as digest summaries) | small — JSON-constrained prompts, one tool per verb |
|
|
63
|
+
| `rh_files_*` | files_put/get/list/share | Cloudflare R2 (put → object URL; share → public/private toggle) | medium — first real "Cloudflare primitive" family |
|
|
64
|
+
| `rh_notify_*` | notify_send/devices | ntfy.sh topic (keyless — the topic name is the only secret, no token) or web-push (self-generated VAPID); store device/topic registry in `rh_store` | small |
|
|
65
|
+
| `rh_events_*` | events_create/list/free | `rh_store` + Cloudflare cron trigger (or OS scheduler locally) | medium — scheduling is the hard half |
|
|
66
|
+
|
|
67
|
+
### Keyless data adapters (blueprint material — one pattern, many tools)
|
|
68
|
+
|
|
69
|
+
> Constraint (2026-08-30): third-party services are allowed **only when they
|
|
70
|
+
> need no API token or credentials** — keyless public endpoints. Anything
|
|
71
|
+
> that needs a key (Twilio, Resend, paid tiers) is out. All of these are
|
|
72
|
+
> keyless.
|
|
73
|
+
|
|
74
|
+
These are all the same shape as `blueprint/daily-digest` + `research-radar`
|
|
75
|
+
adapters: a keyless HTTP API, a probe + fixture test, normalize, cache in
|
|
76
|
+
`rh_store`, optional LLM summarization. One `blueprint/data-adapter` recipe
|
|
77
|
+
would generate the whole set.
|
|
78
|
+
|
|
79
|
+
| Mu | Keyless source |
|
|
80
|
+
|---|---|
|
|
81
|
+
| weather_air / weather_forecast / weather_history / weather_marine | Open-Meteo (no key) |
|
|
82
|
+
| places_geocode / places_address / places_elevation / places_nearby / places_search | Nominatim (OSM), no key — **shipped 0.1.9** as `rh_places_geocode` / `rh_places_address` / `rh_places_elevation` / `rh_places_nearby` (search is covered by geocode) |
|
|
83
|
+
| flights_airport / flights_overhead / flights_track | OpenSky Network (anonymous tier) |
|
|
84
|
+
| hazards_quakes / hazards_alerts / hazards_floods | USGS / GDACS / UK EA flood API |
|
|
85
|
+
| food_product / food_search | Open Food Facts (no key) |
|
|
86
|
+
| markets_convert / markets_list | CoinGecko / exchangerate APIs (free tier) |
|
|
87
|
+
| routes_directions / routes_eta / routes_nearest | OSRM public server |
|
|
88
|
+
| maps_tile / maps_area | static tile URLs (OSM) |
|
|
89
|
+
| news_list / news_read / news_search | RSS + index (the daily-digest fetcher generalised) |
|
|
90
|
+
|
|
91
|
+
### Out of scope for the core plugin (note in guidance, don't build)
|
|
92
|
+
|
|
93
|
+
| Mu | Why skip |
|
|
94
|
+
|---|---|
|
|
95
|
+
| prayer_* (times, qibla, verse, hadith, reflection, search) | Muslim/Islamic tools are out of scope by explicit instruction |
|
|
96
|
+
| mail_send / mail_inbox | needs a mail provider key (Resend/MailChannels) — violates the keyless rule; account-scoped reputation |
|
|
97
|
+
| sms_send / sms_history | Twilio-style key — violates the keyless rule |
|
|
98
|
+
| images_generate | Workers AI image gen is the natural fit — the user's own Cloudflare account, optional paid plugin |
|
|
99
|
+
| wallet_* (USDC on Base) | custodial payments, regulatory surface |
|
|
100
|
+
| chat/social/stream/blog/users | instance-local social graph — nothing to attach to in a personal harness |
|
|
101
|
+
| apps_create/build/fork | a whole hosting surface; closest righthand form is Cloudflare Workers deployment (`blueprint/vision-worker` shape), a phase-2 plugin |
|
|
102
|
+
| transit_* (TfL) | UK-only, keyed in parts; a data adapter if a user asks |
|
|
103
|
+
|
|
104
|
+
## Suggested build order
|
|
105
|
+
|
|
106
|
+
> Constraint (2026-08-30): TypeScript only, no Go. Single user — no accounts,
|
|
107
|
+
> no rationing, no identity tiers. Every tool is built by the user's own
|
|
108
|
+
> agent and runs locally or on the user's own Cloudflare account. The
|
|
109
|
+
> `requires`/`limit` axes from Mu are dropped; the `destructive` marker and
|
|
110
|
+
> the golden-file discipline stay.
|
|
111
|
+
|
|
112
|
+
1. **Guard upgrade**: `destructive: true` marker on guard rules + a
|
|
113
|
+
`tests/permissions.golden` over all `rh_*` tools recording destructive
|
|
114
|
+
flags and guard modes. Prefix-based allow/deny/ask stays — the marker
|
|
115
|
+
documents *why* a prefix is guarded, and the golden catches drift.
|
|
116
|
+
2. **`rh_task_*` + `rh_text_*`** — both tiny over existing services, and
|
|
117
|
+
together they cover Mu's most-used families (tasks, notes, text).
|
|
118
|
+
3. **One data-adapter blueprint** (weather or places as the demo) + the
|
|
119
|
+
recipe that generates the rest, all running locally or as the user's own
|
|
120
|
+
Workers.
|
|
121
|
+
4. **`rh_files_*` on R2** — the first family that is *actually on a
|
|
122
|
+
Cloudflare primitive* (the user's own R2), the plugin's stated purpose.
|
|
123
|
+
5. `rh_events_*`, `rh_notify_*`, then the out-of-scope list as optional
|
|
124
|
+
plugins.
|
|
125
|
+
|
|
126
|
+
## Incorporation decisions (from mu-repo-analysis.md)
|
|
127
|
+
|
|
128
|
+
> Constraints (2026-08-30): **TypeScript only — no Go.** Single user —
|
|
129
|
+
> **no accounts, no rationing**: nothing to tier, nothing to meter per-day.
|
|
130
|
+
> Every tool is built by the user's own agent and runs locally or on the
|
|
131
|
+
> user's own Cloudflare account. Mu lessons that presuppose a multi-user
|
|
132
|
+
> server (identity tiers, limits, quotas, wallets, address spaces) are out;
|
|
133
|
+
> the engineering discipline carries over.
|
|
134
|
+
|
|
135
|
+
The architecture read produced eleven lessons; these are the ones worth
|
|
136
|
+
incorporating now, each mapped to a concrete righthand change, in order.
|
|
137
|
+
Deliberately NOT incorporated below the fold.
|
|
138
|
+
|
|
139
|
+
### Incorporate now
|
|
140
|
+
|
|
141
|
+
1. **Permissions golden first** (Mu lesson 2). Record the answer before
|
|
142
|
+
changing the model: `tests/permissions.golden`, one line per `rh_*` tool
|
|
143
|
+
recording its **destructive flag** and guard mode. Any guard refactor
|
|
144
|
+
that leaves the file unchanged is behaviour-preserving; any diff is the
|
|
145
|
+
list of doors that moved.
|
|
146
|
+
2. **Guard rules gain a `destructive` marker** (lessons 2). Prefix-based
|
|
147
|
+
allow/deny/ask stays; `destructive: true` documents *why* a prefix is
|
|
148
|
+
guarded — irreversible effects (deploy, delete, DNS change) are the
|
|
149
|
+
prompt-injection surface. The marker is declarative only: the enforcement
|
|
150
|
+
is still the rule's mode. No identity tiers, no limits — there is one
|
|
151
|
+
user and nothing to ration.
|
|
152
|
+
3. **Scope = catalogue** (lesson 10). The visible catalogue and the
|
|
153
|
+
enforceable scope must be one mechanism: the SKILL.md and README tool
|
|
154
|
+
tables must state what a guard rule gates, and the golden must derive
|
|
155
|
+
from the same declaration — no second hand-kept list.
|
|
156
|
+
4. **`rh_task_*` family** (lesson 4). Tasks domain over the store:
|
|
157
|
+
create/list/next/update/delete with a state machine; descriptions carry
|
|
158
|
+
the "always say something" rule — a failed run is delivered like an
|
|
159
|
+
answer.
|
|
160
|
+
5. **`rh_text_*` family** (lesson 7). summarise/extract/classify/translate
|
|
161
|
+
over `ctx.llm`, reusing the digest summarizer's prompt discipline: roles
|
|
162
|
+
stay roles, caps are deliberate, per-call failure contained.
|
|
163
|
+
6. **Settings hygiene** (lesson 11). Every settings key must have a
|
|
164
|
+
consumer or go. The namespace holds the user's own Cloudflare account
|
|
165
|
+
facts (`accountId`, `defaultZone`, `defaultScriptPrefix`) — no pricing,
|
|
166
|
+
no limits.
|
|
167
|
+
7. **Data-adapter blueprint with the SSRF checklist** (lesson 6). The
|
|
168
|
+
fetcher blueprint guidance must carry: block non-public destinations,
|
|
169
|
+
revalidate every redirect hop, cap size and time. Adapters may call any
|
|
170
|
+
**keyless** public endpoint — an API token requirement is the line that
|
|
171
|
+
puts a service out of scope.
|
|
172
|
+
|
|
173
|
+
### Deliberately not incorporated
|
|
174
|
+
|
|
175
|
+
| Mu feature | Reason |
|
|
176
|
+
|---|---|
|
|
177
|
+
| Go services / handlers | righthand is TypeScript on the DSH harness |
|
|
178
|
+
| `requires` identity tiers (Open/Caller/Account) | single user — no accounts to tier |
|
|
179
|
+
| `limit` per-day counts / quota.json pricing | single user — nothing to ration, nothing to meter |
|
|
180
|
+
| Spec.Card renderers | the web GUI owns turn summaries; righthand outputs carry `fetchedAt` instead |
|
|
181
|
+
| held-state judge (agent/gate) | guard `ask` is the local judge; a full inbound queue needs a product surface |
|
|
182
|
+
| agents-as-accounts / inbox address space | a product, not a plugin — the DSH harness already is the agent host |
|
|
183
|
+
| wallet_* (USDC, x402) | payments/regulatory surface, absent by design |
|
|
184
|
+
| memory-as-history fix | already how the DSH agent loop works — only prompt-building tools inherit the rule |
|
|
185
|
+
| internal/ layering test | Cordis + the harness enforce the equivalent; the tool-to-tool rule goes in guidance instead |
|
|
186
|
+
| keyed third parties (Twilio, Resend, paid APIs) | the keyless rule: any service that requires an API token or credentials is out |
|
|
187
|
+
| prayer_* / Muslim or Islam-related tools | out of scope by explicit instruction |
|
|
188
|
+
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Mu repository analysis — architecture lessons for righthand
|
|
2
|
+
|
|
3
|
+
Source: https://github.com/micro/mu (Go, ~1200 go files). This is the
|
|
4
|
+
*architecture* read of the repo, not the tool list — that is in
|
|
5
|
+
[agent-tool-catalog.md](agent-tool-catalog.md). Each section names the Mu
|
|
6
|
+
mechanism, the failure it fixed, and the righthand/DSH equivalent to adopt
|
|
7
|
+
or adapt.
|
|
8
|
+
|
|
9
|
+
## 1. One declaration, every surface derives (internal/service/spec.go)
|
|
10
|
+
|
|
11
|
+
Every service declares itself once — name, description, page, icon,
|
|
12
|
+
endpoints with model-facing docs, cost, permissions, and a Card renderer —
|
|
13
|
+
and every surface *derives* from that Spec: MCP tool list, REST reference,
|
|
14
|
+
nav, guest allowlists, agent labels, the destructive list, the account-
|
|
15
|
+
scoped list, pricing ops, and two documentation tables. Before the Spec
|
|
16
|
+
there were **fourteen hand-written lists**, each correct alone and none
|
|
17
|
+
aware of the others — the same capability was called search, search_web,
|
|
18
|
+
index and web_search at once, and Stream and Chat shared an icon for months
|
|
19
|
+
because nothing could notice the repeat.
|
|
20
|
+
|
|
21
|
+
**Righthand equivalent:** the `rh_*` tool family. Today each tool declares
|
|
22
|
+
its description/params/output inline in `defineTool`. The Mu lesson is to
|
|
23
|
+
declare the *spec* once and derive the catalogue — in DSH the tool registry
|
|
24
|
+
already derives the model-facing list; what righthand should add is the
|
|
25
|
+
derived **permissions golden** (§2) and a derived **docs table** (README,
|
|
26
|
+
skill, catalog) rather than hand-kept lists.
|
|
27
|
+
|
|
28
|
+
## 2. Golden-file the derived permissions (test/permissions.golden)
|
|
29
|
+
|
|
30
|
+
`permissions.golden` records one line per tool — needsAccount, destructive,
|
|
31
|
+
bound/open, accountOnly, optionalAuth — and the test fails when any derived
|
|
32
|
+
permission moves. The file's own debugging is the guidance: the first
|
|
33
|
+
version recorded **nothing** (registering a service is not deriving its
|
|
34
|
+
tools — an empty golden that passed, worse than none because it looks like
|
|
35
|
+
cover); the second recorded only the derived policy and missed the exact
|
|
36
|
+
distinction being collapsed; the third recorded prices and failed on test
|
|
37
|
+
order. It now records permissions alone, sees the change, and is stable.
|
|
38
|
+
|
|
39
|
+
**Righthand equivalent:** `tests/permissions.golden` over every `rh_*` tool
|
|
40
|
+
(and, once they exist, the rt_* test tools): one line per tool with the
|
|
41
|
+
guard-relevant facts. Refactors that are behaviour-preserving leave the file
|
|
42
|
+
untouched; any diff is the list of doors that moved.
|
|
43
|
+
|
|
44
|
+
## 3. Layer discipline, asserted by test (test/layering_test.go)
|
|
45
|
+
|
|
46
|
+
Product packages (home, agent, service, admin, account) may import
|
|
47
|
+
internal/; internal/ may never import the product, with two exceptions —
|
|
48
|
+
the assembly programs. The rule is enforced by a regex test, and the bill
|
|
49
|
+
for every past violation is legible: internal/server/hooks.go, seven hundred
|
|
50
|
+
lines of function variables whose only job was to hand one package a pointer
|
|
51
|
+
to another because they could not import each other. Also: a service may
|
|
52
|
+
not import the wallet — asking what something costs is internal/quota, which
|
|
53
|
+
knows prices and not balances.
|
|
54
|
+
|
|
55
|
+
**Righthand equivalent:** the tool modules are already thin consumers over
|
|
56
|
+
harness services. The rule to write down: a tool module answers a question
|
|
57
|
+
about state; an *agent* (or a digest-type helper) decides which question to
|
|
58
|
+
ask — services announce events, agents subscribe (Mu's agent/gate,
|
|
59
|
+
agent/work, agent/digest all work this way). In DSH terms that is
|
|
60
|
+
`ctx.on(...)` + the session-watcher blueprint, never a tool that imports
|
|
61
|
+
another tool's internals — call it by name through `ctx.tools.execute`.
|
|
62
|
+
|
|
63
|
+
## 4. Agents are accounts; work is assigned, not invoked
|
|
64
|
+
|
|
65
|
+
An agent is a user in the same address space — it holds an address
|
|
66
|
+
(mail/XMPP) and can be written to. Tasks assign work (`service/tasks` holds
|
|
67
|
+
it, `agent/work` runs it); a standing instruction falling due and a task
|
|
68
|
+
assigned are the same fact. Results always say something: a run that could
|
|
69
|
+
not happen is delivered like an answer, because "a standing instruction
|
|
70
|
+
that goes quiet looks like the instruction was forgotten."
|
|
71
|
+
|
|
72
|
+
**Righthand equivalent:** `rh_task_*` (already the top build item in the
|
|
73
|
+
catalog doc) + `blueprint/session-watcher` for standing instructions. The
|
|
74
|
+
"always say something" rule belongs in the tool descriptions: a failed
|
|
75
|
+
scheduled job must produce a visible record, not silence.
|
|
76
|
+
|
|
77
|
+
## 5. The held state — fail visible, release when in doubt (agent/gate, internal/thread)
|
|
78
|
+
|
|
79
|
+
A conversation is in the record or it is not; the binary is what made
|
|
80
|
+
unsolicited inbound impossible to handle (dropping is undetectable, filing
|
|
81
|
+
wakes a paid agent). So: **held** — in the record, visible, searchable, and
|
|
82
|
+
nothing acts on it until a judge (one model call per arrival) releases it.
|
|
83
|
+
The judge is *reluctant on purpose*: the costly mistake is holding a real
|
|
84
|
+
message (the plumber texting he is outside), so the prompt releases when in
|
|
85
|
+
doubt. Fail-safe direction: if the model is down, arrivals sit held and
|
|
86
|
+
visible — never silently dropped.
|
|
87
|
+
|
|
88
|
+
**Righthand equivalent:** guard `ask` mode is the local judge; the posture
|
|
89
|
+
to copy is: deny loudly and visibly, never silently drop, and when in doubt
|
|
90
|
+
let it through. Also relevant to `blueprint/inbound-webhook-pipeline`'s
|
|
91
|
+
idempotency dedupe.
|
|
92
|
+
|
|
93
|
+
## 6. Defensive web: safefetch (SSRF guard) + safety (generation policy)
|
|
94
|
+
|
|
95
|
+
`internal/safefetch`: an SSRF-guarded HTTP client for fetching untrusted
|
|
96
|
+
URLs — refuses loopback, private ranges, link-local (including the
|
|
97
|
+
169.254.169.254 metadata address), and multicast; validates **every
|
|
98
|
+
redirect hop**, not just the first; caps response size (2 MiB) and time
|
|
99
|
+
(10 s). `internal/safety`: what the instance will not *generate* — one
|
|
100
|
+
category refused always and not configurable, one category refused by
|
|
101
|
+
default and an operator's decision; a door, not a classifier ("it stops
|
|
102
|
+
casual misuse and raises the cost of the rest").
|
|
103
|
+
|
|
104
|
+
**Righthand equivalent:** the harness's web tools already own SSRF
|
|
105
|
+
protection; the blueprint guidance for any righthand fetcher should carry
|
|
106
|
+
the checklist (block non-public destinations, revalidate redirects, cap
|
|
107
|
+
size/time) and the generation-policy split belongs in the guard/skill
|
|
108
|
+
guidance, not per-tool ad hoc.
|
|
109
|
+
|
|
110
|
+
## 7. Memory is real message history (agent/memory.go)
|
|
111
|
+
|
|
112
|
+
The conversation went to the model as prose inside one user message, with
|
|
113
|
+
assistant turns truncated to 300 chars and the whole blob sent twice. The
|
|
114
|
+
model never saw its own turns as its own; truncation made "what was the
|
|
115
|
+
third thing you listed?" unanswerable by construction. The fix: turns in
|
|
116
|
+
their real roles, nothing truncated, nothing duplicated.
|
|
117
|
+
|
|
118
|
+
**Righthand equivalent:** already the harness model (the DSH agent loop
|
|
119
|
+
does exactly this). For tools that *build prompts* — the digest summarizer,
|
|
120
|
+
any rh_text_* tool — the rule is the same: assistant content stays assistant
|
|
121
|
+
content, and input caps must be deliberate, not string-worries.
|
|
122
|
+
|
|
123
|
+
## 8. Cards: a view, not a widget — and time is the missing field (Spec.Card)
|
|
124
|
+
|
|
125
|
+
Each service can render a Card; the field that mattered was `At` — a
|
|
126
|
+
headline from four minutes ago and a forecast are different kinds of thing,
|
|
127
|
+
so a card that knows when belongs in a chronology (stream) and one that
|
|
128
|
+
does not is a standing view. The Viewer is a struct ("who is looking" is
|
|
129
|
+
domain data, not context values), and the zero value is the shared render,
|
|
130
|
+
safe to cache; personal renders must never be cached. A card is a view — it
|
|
131
|
+
renders and links, holds no state and takes no input.
|
|
132
|
+
|
|
133
|
+
**Righthand equivalent:** the digest MDX already records `fetchedAt`; the
|
|
134
|
+
same discipline generalises — any rh_* summary output should say *when what
|
|
135
|
+
it shows happened* so a stream can order it honestly.
|
|
136
|
+
|
|
137
|
+
## 9. Pricing: price vs limit, and an operator file (quota.json)
|
|
138
|
+
|
|
139
|
+
An operation is charged when it costs the instance something (a model call
|
|
140
|
+
or third-party request); storage and talking-to-your-agent are 0 because
|
|
141
|
+
they have no marginal cost — charging taxed exactly the behaviour the
|
|
142
|
+
product wants more of. `limit` is a hard per-day count, checked *before*
|
|
143
|
+
price, for the operations that reach a stranger — a price stops somebody
|
|
144
|
+
who has to pay and does nothing about a loop. quota.json is an operator's
|
|
145
|
+
file, not code.
|
|
146
|
+
|
|
147
|
+
**Righthand equivalent:** the settings namespace is the quota.json slot;
|
|
148
|
+
the split belongs in guard rules (a rate/limit axis beside allow/deny/ask).
|
|
149
|
+
|
|
150
|
+
## 10. Tool scoping: the list follows the token (internal/api/listscope)
|
|
151
|
+
|
|
152
|
+
Scoping was enforced at dispatch and ignored by tools/list, so a confined
|
|
153
|
+
agent was handed every tool definition and found out by trial and error
|
|
154
|
+
that almost all were refused — the context cost of the whole catalogue and
|
|
155
|
+
none of its use. Fixed: the listing follows the token's scope; an explicit
|
|
156
|
+
`?tools=` list can only narrow further.
|
|
157
|
+
|
|
158
|
+
**Righthand equivalent:** guard rules are the scope; the lesson is that the
|
|
159
|
+
*visible* catalogue and the *enforceable* scope must be one mechanism, or
|
|
160
|
+
the agent pays context for tools it cannot use.
|
|
161
|
+
|
|
162
|
+
## 11. Delete the dead switch (one model tier; five flags → two)
|
|
163
|
+
|
|
164
|
+
Two recurring refactors: the Fast/Best model tier outlived its only control
|
|
165
|
+
(nothing sent the field) and was deleted — "a premium tier needs a control,
|
|
166
|
+
a price somebody has decided on, and something on the page saying what the
|
|
167
|
+
difference buys... not a switch to leave lying around"; and the permission
|
|
168
|
+
flags collapsed from five to two because three of them were three ways of
|
|
169
|
+
saying the same thing and one was dead code nobody had ever set.
|
|
170
|
+
|
|
171
|
+
**Righthand equivalent:** every config key in the righthand settings
|
|
172
|
+
namespace must have a consumer or go; every guard rule field must have a
|
|
173
|
+
meaning a test can see (the golden file is what makes the difference
|
|
174
|
+
visible).
|
|
175
|
+
|
|
176
|
+
## What this means for the righthand build order
|
|
177
|
+
|
|
178
|
+
> Constraints (2026-08-30): TypeScript only — no Go. Single user — no
|
|
179
|
+
> accounts, no rationing: Mu's identity tiers (`requires`) and per-day
|
|
180
|
+
> `limit` counts are out; the `destructive` marker and the golden-file
|
|
181
|
+
> discipline stay. Tools are built by the user's own agent and run locally
|
|
182
|
+
> or on the user's own Cloudflare account.
|
|
183
|
+
|
|
184
|
+
1. **Spec + golden first**: a permissions.golden over the existing `rh_*`
|
|
185
|
+
tools, then the `destructive` marker it records. This is Mu's own
|
|
186
|
+
ordering — record the answer before changing the model.
|
|
187
|
+
2. **`rh_task_*` + `rh_text_*`** (catalog doc §build order) — tasks
|
|
188
|
+
implements §4 (assigned work, always say something), text implements
|
|
189
|
+
§7's prompt discipline.
|
|
190
|
+
3. **Data adapters with the safefetch checklist** in the blueprint guidance
|
|
191
|
+
— §6.
|
|
192
|
+
4. **Scope = catalogue**: when guard scoping lands, the skill/catalog text
|
|
193
|
+
must reflect the same rule — §10.
|
|
194
|
+
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Scenario patterns - righthand recipes, tested by building
|
|
2
|
+
|
|
3
|
+
> Every recipe here ran live on 2026-08-31 against the real harness
|
|
4
|
+
> services (store, settings, credentials, subprocess, jobs) through the
|
|
5
|
+
> agent-facing `rh_*` tools, then the state was cleaned back up. Evidence
|
|
6
|
+
> of each run is inline; re-run them any time - they are safe.
|
|
7
|
+
|
|
8
|
+
## The key conventions (one store, many collections)
|
|
9
|
+
|
|
10
|
+
`rh_store_*` is one key space. The prefix IS the collection;
|
|
11
|
+
`rh_store_list` + a prefix filter is the query language. Conventions:
|
|
12
|
+
|
|
13
|
+
| Prefix | Collection | Example value shape |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| `task:` | task board | `{ state: 'open', result? }` |
|
|
16
|
+
| `digest:` | daily digests | `{ top: [...], fetchedAt }` |
|
|
17
|
+
| `run:` / `uptime:` / `expense:` | dated logs | one record per date, aggregate by scan |
|
|
18
|
+
| `deploy:` / `dns:` / `tidy:` / `smoke:` | receipts and undo trails | `{ before, after, exitCode, at }` |
|
|
19
|
+
| anything else | ad-hoc | JSON-serializable only |
|
|
20
|
+
|
|
21
|
+
Absence is `{ found: false }`, not an error - that is the exists() check.
|
|
22
|
+
Every `rh_store_put` returns a global `writes` counter; the increment
|
|
23
|
+
sequence is the audit trail.
|
|
24
|
+
|
|
25
|
+
## The receipt pattern (scenario 4, live-verified)
|
|
26
|
+
|
|
27
|
+
Build with `rh_run`, keep the result as one receipt key:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
rh_run { argv: ['node', 'scripts/fetch-digest.mjs', 'tests/fixtures/day2.json', '5'], cwd: 'D:/righthand-test' }
|
|
31
|
+
// -> exit 0, stdout = the fetcher's JSON (parse it in the caller)
|
|
32
|
+
rh_store_put { key: 'digest:scenario-test:2026-08-31',
|
|
33
|
+
value: { source: 'day2.json', count: 2, fresh: ['4'],
|
|
34
|
+
top: [{ id: '4', title: '...' }], fetchedAt: '<iso>' } }
|
|
35
|
+
rh_store_get { key: 'digest:scenario-test:2026-08-31' } // found: true, full value back
|
|
36
|
+
rh_store_delete { key: 'digest:scenario-test:2026-08-31' } // clean up when done
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The diff step that makes a digest a digest: fetch today and yesterday,
|
|
40
|
+
compare id sets in the caller, store only `fresh` in the receipt.
|
|
41
|
+
|
|
42
|
+
## Undo trails (scenario 7 pattern)
|
|
43
|
+
|
|
44
|
+
Any mutating build writes what it is about to change BEFORE doing it,
|
|
45
|
+
then the after-state:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
rh_store_put { key: 'tidy:<ts>', value: { moved: [{ from, to }], at: '<iso>' } }
|
|
49
|
+
// ... do the moves with rh_run ...
|
|
50
|
+
// revert = read tidy:<ts> and move each { to -> from } in reverse
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Settings: the schema wall (live finding)
|
|
54
|
+
|
|
55
|
+
`rh_settings_set` accepts any patch - including keys the schema does not
|
|
56
|
+
define: `{ weeklyBudget: 200 }` returned `applied: true`. But
|
|
57
|
+
`rh_settings_get` only ever returns schema-known keys, so the custom key
|
|
58
|
+
never comes back. A scenario that needs its own config knob has exactly
|
|
59
|
+
two honest options:
|
|
60
|
+
|
|
61
|
+
1. register the key in the righthand settings schema (a plugin change), or
|
|
62
|
+
2. keep the knob in `rh_store` under its own prefix.
|
|
63
|
+
|
|
64
|
+
Never depend on an unregistered settings key - it looks written and
|
|
65
|
+
silently disappears.
|
|
66
|
+
|
|
67
|
+
## Credentials: the auth gate (scenario 1/2, live-verified)
|
|
68
|
+
|
|
69
|
+
`rh_credential_describe` is the first step of any Cloudflare scenario and
|
|
70
|
+
it answers yes/no without leaking the value:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
rh_credential_describe { ref: 'CLOUDFLARE_API_TOKEN' }
|
|
74
|
+
// { configured: false, writable: true } -> stop, tell the user what to set
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`set` stores, `unset` removes, and no output ever contains the secret
|
|
78
|
+
(live-verified with a throwaway ref: set -> describe configured:true ->
|
|
79
|
+
unset -> configured:false; the value never appeared).
|
|
80
|
+
|
|
81
|
+
## Exec: collect vs background (live finding)
|
|
82
|
+
|
|
83
|
+
| | `rh_run` | `rh_run_bg` |
|
|
84
|
+
|---|---|---|
|
|
85
|
+
| Returns | exit code + bounded stdout/stderr | a job id |
|
|
86
|
+
| Use when | you need the output in this turn | the command is long and this turn must go on |
|
|
87
|
+
| Polling | - | the job registry reports status/exit (`job_list` / `job_output`) |
|
|
88
|
+
| Output capture | yes (bounded) | NOT captured by the local registry - status and exit only |
|
|
89
|
+
|
|
90
|
+
Live-verified: `rh_run` curl returned HTTP headers + exit 0, and a failing
|
|
91
|
+
`node -e` returned exit 3 with stderr. `rh_run_bg` settled completed with
|
|
92
|
+
exit 0 and an empty output tail - background is for fire-and-forget and
|
|
93
|
+
status polling, collect mode is for answers.
|
|
94
|
+
|
|
95
|
+
## Cleanup discipline
|
|
96
|
+
|
|
97
|
+
Scenarios write, verify, then delete their keys - the live runs above
|
|
98
|
+
left the store exactly as found (baseline keys only). If a run is a
|
|
99
|
+
deliverable rather than a test, the receipt key stays and IS the record.
|
|
100
|
+
|
|
101
|
+
## Scenario matrix
|
|
102
|
+
|
|
103
|
+
`D:/righthand-test/SCENARIOS.md` holds the full tool-per-step matrix for
|
|
104
|
+
nine scenarios (deploy, DNS, grocery, digest, uptime, workout, downloads
|
|
105
|
+
tidy, budget, smoke test). The ones exercised live here: grocery (3),
|
|
106
|
+
digest (4), uptime (5), workout (6), plus the credential/settings/exec
|
|
107
|
+
cross-cuts shared by the rest.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@try-works/dsh-righthand",
|
|
3
3
|
"description": "DeepSeek Harness plugin: DSH-native righthand tools — durable KV store, credential/settings management, governed exec, and a tool guard",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.11",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/index.d.ts",
|
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
"lib",
|
|
75
75
|
"src",
|
|
76
76
|
"skills",
|
|
77
|
+
"docs",
|
|
77
78
|
"cordis.patch.yml",
|
|
78
79
|
"README.md",
|
|
79
80
|
"LICENSE"
|
|
@@ -29,6 +29,8 @@ The righthand toolkit: DSH-native tools over the harness's own services. Use thi
|
|
|
29
29
|
- One domain `righthand_store`: a `rows` table (string key → JSON value + timestamp) and a global write counter.
|
|
30
30
|
- `rh_store_get` returns `{ found, key, value?, updatedAt? }` — `found: false` means the key is absent, not an error.
|
|
31
31
|
- Writes are durable (backend flush before commit) and serialized on one write chain; values must be JSON-serializable.
|
|
32
|
+
- The key prefix IS the collection (`task:`, `digest:`, `run:`, `uptime:`, `deploy:`); `rh_store_list` + a prefix filter is the query language. See `docs/scenario-patterns.md` for the conventions, the receipt pattern and undo trails.
|
|
33
|
+
- Build, then keep one receipt key per run (`{ ...result, fetchedAt }`) — the store is the agent's durable trail; delete scenario keys when a run is only a test.
|
|
32
34
|
|
|
33
35
|
## Events semantics
|
|
34
36
|
|
|
@@ -92,6 +94,8 @@ The righthand toolkit: DSH-native tools over the harness's own services. Use thi
|
|
|
92
94
|
| `defaultR2Bucket` | `""` | default R2 bucket for `rh_files_*` |
|
|
93
95
|
| `defaultNotifyTopic` | `""` | default ntfy topic for `rh_notify_send` |
|
|
94
96
|
|
|
97
|
+
Schema wall: `rh_settings_set` accepts keys the schema does not define, but `rh_settings_get` never returns them — an unregistered key looks written and silently disappears. Register new keys in the schema (a plugin change) or keep ad-hoc knobs in `rh_store`.
|
|
98
|
+
|
|
95
99
|
## Guard policy
|
|
96
100
|
|
|
97
101
|
Rules come from the plugin config (`rules: [{ toolPrefix, mode, ask?, destructive? }]`). `deny` throws before dispatch; `ask` defers to the policy function (return true to allow); `allow` passes through. Tools matching no rule are unaffected. Prefixes match by prefix — `rh_run` also covers `rh_run_bg`.
|
|
@@ -102,3 +106,7 @@ Rules come from the plugin config (`rules: [{ toolPrefix, mode, ask?, destructiv
|
|
|
102
106
|
|
|
103
107
|
- **web profile**: all thirty-seven tools register (`storageDomain` is provided by the web app rows).
|
|
104
108
|
- **other profiles** (e.g. headless): the store, task and events families stay dormant (no `storageDomain`); the other twenty-three tools (secrets, settings, exec, text, weather, places, files, notify) still register. The plugin never fails the boot.
|
|
109
|
+
|
|
110
|
+
## Composition blueprints
|
|
111
|
+
|
|
112
|
+
Repo guidance kits under `experiments/` (each a `blueprint.json` spec + recipe + learnings) for composing the tools into routines. Committed kits: `blueprint/daily-digest` (collect → summarize → send), `blueprint/data-adapter` (wrap a keyless API; worked examples `rh_weather_*` / `rh_places_*`), `blueprint/reminder-flow` (events + notify + receipts), `blueprint/geo-context` (places + weather + store cache), `blueprint/governed-exec` (guard + receipts + undo trails), `blueprint/file-vault` (R2 + store index + presigned shares). The README catalogues nine more for future builds; `docs/scenario-patterns.md` holds the live-tested recipes.
|