mur-common 2.91.1

Shared types and traits for the MUR ecosystem
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
<div align="center">

<svg xmlns="http://www.w3.org/2000/svg" viewBox="300 120 780 560" width="200" height="143">
    <path fill="#001E32" d="M585.89,613.5l1.11,47c0.06,2.49-1.91,4.55-4.39,4.61c-2.49,0.06-4.55-1.91-4.61-4.39c0-0.07,0-0.15,0-0.21l1.11-47c0.04-1.87,1.6-3.35,3.47-3.31C584.4,610.23,585.84,611.7,585.89,613.5z"></path>
    <path fill="#001E32" d="M601.7,658.02l-21.48-18.15c-1.55-1.31-1.74-3.62-0.43-5.17c1.26-1.49,3.46-1.72,5-0.57l22.52,16.85c1.99,1.49,2.4,4.31,0.91,6.3c-1.49,1.99-4.31,2.4-6.3,0.91C601.84,658.14,601.76,658.08,601.7,658.02z"></path>
    <path fill="#001E32" d="M557.7,650.98l22.52-16.85c1.62-1.21,3.92-0.88,5.13,0.74c1.17,1.56,0.9,3.75-0.57,5l-21.48,18.15c-1.9,1.6-4.74,1.37-6.34-0.53c-1.6-1.9-1.37-4.74,0.53-6.34C557.55,651.09,557.63,651.03,557.7,650.98z"></path>
    <path fill="#001E32" d="M786.89,613.5l1.11,47c0.06,2.49-1.91,4.55-4.39,4.61c-2.49,0.06-4.55-1.91-4.61-4.39c0-0.07,0-0.15,0-0.21l1.11-47c0.04-1.87,1.6-3.35,3.47-3.31C785.4,610.23,786.84,611.7,786.89,613.5z"></path>
    <path fill="#001E32" d="M758.7,650.98l22.52-16.85c1.62-1.21,3.92-0.88,5.13,0.74c1.17,1.56,0.9,3.75-0.57,5l-21.48,18.15c-1.9,1.6-4.74,1.37-6.34-0.53c-1.6-1.9-1.37-4.74,0.53-6.34C758.55,651.09,758.63,651.03,758.7,650.98z"></path>
    <path fill="#001E32" d="M802.7,658.02l-21.48-18.15c-1.55-1.31-1.74-3.62-0.43-5.17c1.26-1.49,3.46-1.72,5-0.57l22.52,16.85c1.99,1.49,2.4,4.31,0.91,6.3c-1.49,1.99-4.31,2.4-6.3,0.91C802.84,658.14,802.76,658.08,802.7,658.02z"></path>
    <path fill="#58A6FF" d="M377.64,341.15l-54.89-12.2c-4.73-1.05-9.41,2.04-10.25,6.82c-3.05,17.39-2.64,54.92,58.14,53.38"></path>
    <path fill="#58A6FF" d="M988.36,341.15l54.89-12.2c4.73-1.05,9.41,2.04,10.25,6.82c3.05,17.39,2.64,54.92-58.14,53.38"></path>
    <path fill="#58A6FF" d="M979.65,273.5c-50.41-112.17-137.28-107.11-137.28-107.11L687,165l-155.37,1.39c0,0-86.87-5.06-137.28,107.11c0,0-144.86,286.86,166.46,344.07c0,0,53.32,11.6,126.19,11.75c72.87-0.15,126.19-11.75,126.19-11.75C1124.51,560.36,979.65,273.5,979.65,273.5z"></path>
    <path fill="#001E32" d="M927.07,253.4l-0.01-0.01C919.63,244.43,851,157,817,138c-13-8-25.7-9.74-34.43-10.35c-8.72-0.71-17.51,0.05-25.76,1.84c-12.8,2.06-57.54,29.38-58.46,29.97c-10.56,6.28-21.12,6.29-31.68,0c-18.36-11.86-44.98-27.8-58.47-29.98c-8.25-1.8-17.04-2.56-25.76-1.84C573.7,128.26,561,130,548,138c-34,19-102.63,106.43-110.06,115.39l-0.01,0.01c-7.14,8.61-7.18,21.37,0.42,30.05c8.43,9.63,23.08,10.6,32.7,2.16c5.88-5.85,86.94-86.61,107.68-95.22c2.92-1.02,5.79-1.58,8.66-1.89c2.88-0.22,5.82-0.26,8.98,0.24c1.75,0.07,31.51,13.43,55.68,24.35c19.67,8.88,42.21,8.82,61.84-0.15c23.82-10.89,53.05-24.13,54.72-24.2c3.16-0.5,6.1-0.46,8.98-0.24c2.87,0.31,5.73,0.87,8.66,1.89C807,199,888.07,279.76,893.94,285.61c9.63,8.43,24.27,7.47,32.7-2.16C934.25,274.77,934.21,262.01,927.07,253.4z"></path>
    <circle fill="#FFFAF5" cx="582.54" cy="364.71" r="111"></circle>
    <circle fill="#001E32" cx="582.54" cy="364.71" r="39"></circle>
    <circle fill="#FFFAF5" cx="783.46" cy="364.76" r="111"></circle>
    <circle fill="#001E32" cx="783.46" cy="364.76" r="39"></circle>
    <path fill="#187FC4" d="M718.59,415.27c-5.93-14.61-14.14-22.36-35.55-23.26c-0.03,0-0.06,0-0.08,0c-21.41,0.9-29.62,8.65-35.55,23.26c-2.85,7.04,2.27,14.73,9.87,14.73l25.72,0l25.72,0C716.32,430,721.44,422.3,718.59,415.27z"></path>
    <path fill="#187FC4" d="M718.59,443.73c-5.93,14.61-14.14,22.36-35.55,23.26c-0.03,0-0.06,0-0.08,0c-21.41-0.9-29.62-8.65-35.55-23.26c-2.85-7.04,2.27-14.73,9.87-14.73l25.72,0l25.72,0C716.32,429,721.44,436.7,718.59,443.73z"></path>
</svg>

# MUR

**The local-first AI agent platform, in native Rust.**

Run a fleet of specialized AI agents on the machine you already own — agents that
learn from every session, speak with an on-device voice, plug into the AI tools
you already use, and can be handed to a friend as a single file.

[![CI](https://github.com/mur-run/mur/actions/workflows/ci.yml/badge.svg)](https://github.com/mur-run/mur/actions/workflows/ci.yml)
[![Release](https://img.shields.io/github/v/release/mur-run/mur)](https://github.com/mur-run/mur/releases/latest)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
![Rust](https://img.shields.io/badge/Rust-2024_edition-orange?logo=rust)
![Platform](https://img.shields.io/badge/CLI-macOS_·_Linux_·_Windows-555)

[Quick start](#-quick-start) · [Features](#-what-can-a-mur-agent-do) · [Architecture](#-architecture) · [CLI](#-cli-at-a-glance) · [Docs](https://app.mur.run/docs/core) · [Website](https://mur.run)

</div>

---

<p align="center">
  <img src="assets/mur-hub.png" alt="MUR Hub — fleet dashboard with conversation rail, companion status, and desktop-pet style presets" width="92%" />
  <br/>
  <sub><b>MUR Hub</b> — your fleet's home: one master–detail shell for agents, fleets, chats, and the library, ⌘K palette, desktop-pet style presets. UI in English · 繁體中文 · 简体中文.</sub>
</p>

## What is MUR?

Every AI tool you use today is stateless and cloud-tethered: each session starts
from zero, and the agent lives in someone else's datacenter. MUR inverts both
assumptions.

MUR runs **specialized agents as long-lived local processes** — each with its own
model binding, system prompt, MCP servers, skills, schedule, voice, and
permissions — supervised by one small Rust runtime speaking
[A2A v0.3](https://github.com/a2aproject/A2A). On top of the runtime sits a
**memory pipeline with a maturity lifecycle**: what an agent learns in one
session is captured, scored, stored as plain YAML, retrieved by hybrid semantic
search, and injected into the next session — and it decays when it stops being
useful, so no junk accumulates.

You talk to your fleet through the **MUR Hub** desktop app (chat, approvals,
desktop pets), by **voice**, from an **iPhone**, from the **terminal**, or
through **Slack / Telegram / Jira**. And when an agent becomes genuinely useful,
you can **export it as a signed `.muragent` file** and give it to someone who has
never heard of MUR.

> **In one line:** a native-Rust, local-first fleet of specialized AI agents that
> learn and evolve — light enough to be always-on on the Mac you already own, and
> each exportable as a companion you can hand to anyone.

### Why local-first?

- **It fits on your machine.** ~200K lines of native Rust — no Electron, no
  Python sidecar. An always-on fleet plus a local LLM fits in consumer RAM.
- **Marginal cost ≈ 0.** Inference runs on your hardware. Everything local is
  free, with no per-token meter.
- **Privacy is structural, not a setting.** Memory, recordings, telemetry, and
  voice stay under `~/.mur/`. One redaction chokepoint sits in front of disk —
  the same code for the runtime's telemetry and for the CLI's hook capture log,
  so a credential that appears on a command line is `[REDACTED:…]` in both.
  Agents cannot read the credential store or the capture logs at all: those are
  refused at the grant gate and denied in the kernel sandbox, not merely absent
  from a grant. And a compile-time test forbids the companion module from
  importing network clients.

### How MUR compares

| Capability | MUR | Agent harnesses<br>(Archon, …) | Coding agents<br>(Claude Code, Cursor) | Memory layers<br>(Mem0, Zep, …) |
|---|:-:|:-:|:-:|:-:|
| Local-first multi-agent runtime (native Rust) | ✅ | ✗ | ✗ | ✗ |
| Memory that evolves (decay + Draft→Canonical lifecycle) | ✅ | ✗ | ✗ | partial |
| Kernel sandbox (Landlock / seccomp / SBPL / Job Object) | ✅ | ✗ | ✗ | ✗ |
| Export an agent as a giveable artifact | ✅ | ✗ | ✗ | ✗ |
| On-device voice, DND-aware | ✅ | ✗ | ✗ | ✗ |
| Feeds learning into 16+ existing AI tools | ✅ | ✗ | partial | ✗ |

---

## 🚀 Quick start

### The 5-minute path — MUR Hub (macOS, Apple Silicon)

1. Download **[MUR-Hub-aarch64-apple-darwin.dmg](https://github.com/mur-run/mur/releases/latest)** from the latest release.
2. Drag **MUR Hub** into Applications and open it.
3. Say hi — the built-in concierge agent **MUR** is alive immediately: **offline,
   no API key, no signup**, running on a bundled local multimodal model.
4. **+ New Agent** asks where the next one comes from — a **role template** MUR
   fills in for you (skills, system prompt, least-privilege permissions), the
   **official catalog**, or a `.muragent` a friend shared. Giving it a pet look
   is the last step of every route, so any agent can live on your desktop.

Received a `.muragent` file from a friend? **Double-click it.** Hub verifies the
signature, walks you through model setup, and the agent comes alive.

> Power users: Hub menu → *Install Command-Line Tools…* puts `mur` on your `PATH`.

### The CLI path

```bash
# macOS / Linux
curl -fsSL https://mur.run/install.sh | sh

# Windows (PowerShell)
irm https://mur.run/install.ps1 | iex

# Homebrew (macOS arm64)
brew install mur-run/tap/mur

# From source
cargo install mur-core        # installs the `mur` binary

# Later: upgrade in place — agents AND the daemon restart onto the new
# binary, each verified and reported in a per-agent summary table
mur update --restart-agents
```

```bash
mur init                                      # interactive setup wizard
mur agent create coach --model llama3.2:3b    # create an agent (default provider: ollama)
mur agent install-service coach               # run it as a launchd/systemd user service
mur agent cli coach                           # streaming TUI chat with tool approvals
mur agent cli dev qa ops                      # three agents, tiled panes (tmux/zellij/WezTerm/kitty)
                                              #   --resume continues the last conversation
murmur coach                                  # quick form (murmur symlink), identical to mur agent cli coach
murmur coach --skin mur                       # skins: ansi (default — follows your terminal's own
                                              #   colours) | light | mur (brand) | clay (warm terracotta
                                              #   on dark); /skin switches and remembers
#   in the chat: !cargo test                  # runs locally; the output goes to the agent as your message

mur agent stop coach                          # stops it for real: unloads the service first, so
                                              #   the supervisor cannot respawn it a second later
mur agent remove coach                        # unregisters it — add --purge to delete its data too
```

<p align="center"><img src="assets/demo.gif" alt="mur agent cli — streaming TUI chat with a local agent" width="92%" /></p>

In the chat, `/model` lists your registered models and switches the agent to another one mid-conversation — no restart. `/effort` shows the reasoning levels *this agent's model* actually accepts and sets one for the conversation (`--save` to make it stick); a level the model has no step for is reported, not silently swallowed. `/login` shows OAuth health for every provider and re-authenticates one without leaving the TUI: it re-reads the credential, asks the owner CLI to refresh, and only falls back to a real browser login if neither worked. `/secret <KEY>` hands the agent a credential — a gitea token, an API key — through a hidden prompt instead of the chat box, so the value never enters the conversation the model reads or the signed channel it is stored in; the agent gets it as `$KEY` in its shell, and every tool result comes back with the value masked. Type `/` to open a completion menu of every slash command and the agent's skills — `↑↓` to move, `Tab`/`Enter` to accept, `Esc` to dismiss. It completes arguments too, read from the agent rather than a fixed list: `/effort` offers the levels this model actually has, `/model` your registry aliases, `/secret` the keys it already holds, `/forget` its own notes. A model with no reasoning parameter offers nothing and says so, instead of a level it would have to silently drop. A settings menu marks the value in force, so `/model`, `/effort`, `/skin`, `/auto` and `/verbose` show where you are before you move. `/channels` lists the agent's channels with a stable number each — `/channels 2` switches by number, `/channels 01a0d420` (or `#01a0d420`) by id, and `--follow` tails one; the number is assigned once at creation and never reused, so deleting a channel never renumbers the rest. `/search <query>` queries the project index from the chat and shows a three-line preview per hit, with `--lines N` when three is not enough and `--expand <id>` to pull one hit's full chunk into the transcript — a hit whose interesting part sits below the cut no longer sends you out to a shell grep; `--all` widens the search past the current project and `--send` hands the results to the agent as your message. `!` runs a command on your machine — `!cargo test`, `!git log --oneline -5` — and its output goes to the agent as your message the moment it finishes, so you can ask about it without pasting anything; `Tab` completes commands on your `$PATH` and then paths under the chat's working directory. And when the agent offers you choices, they appear as `Tab`-to-fill suggestions right in the input: a single one as greyed ghost text, several as a picker.

### Models & providers

Agents draw from a local provider/model registry at `~/.mur/models.yaml`:

```bash
mur model connect anthropic                   # one key, many models: prompts for the API key (stored
                                              #   in the Keychain), lists the vendor's models, adds
                                              #   the ones you pick — with pricing already filled in
mur model connect deepseek --base-url https://api.deepseek.com
mur model connect                             # no vendor: probe local runtimes (Ollama / MLX / LM Studio)

mur model add gpt5 --provider openai --model gpt-5.2 --secret env:OPENAI_API_KEY
                                              # add one model by hand; pricing + context window are
                                              #   auto-filled from the models.dev catalog (--no-fetch
                                              #   to skip, --input-cost/--output-cost to set by hand)
mur model list                                # list registered models
mur model show gpt5                           # provider, model, effective in/out cost, context window
mur model prices refresh                      # refresh the cached models.dev price catalog
mur model doctor                              # offline check: dangling model_refs, ids the catalog
                                              #   never carried, profiles disagreeing with their ref,
                                              #   and secrets sitting in plaintext on disk
mur model import ~/from-laptop/models.yaml    # merge another machine's registry (never deletes;
                                              #   reports which secret refs need a key here)
```

Setting up a second machine is copying `models.yaml` and running `mur model import`: the file holds
secret *references*, never key material, so it is safe to move around — and the import tells you
exactly which refs still need a key on the new machine.

Providers rename and retire model ids constantly. The registry key is the stable name your agents point at, so a rename is **one edit to `models.yaml`** and every agent using that key follows — no per-agent migration. `mur model doctor` reports where that indirection has come apart; it is read-only and never rewrites a model id for you, because which model an agent runs is a cost and behaviour decision that shouldn't change silently.

API keys are stored as `SecretRef`s (`env:`, `keychain:`, `file:`, `cmd:`) — never written to config in plaintext. The **MUR Hub** desktop app has a **Model Library** that connects cloud providers (key saved to the macOS Keychain), auto-detects local runtimes (Ollama / MLX / LM Studio), discovers their models via `/v1/models`, and adds them to the registry — no YAML editing required.

**Dial reasoning up or down, per agent.** Every provider spells this differently — OpenAI takes a level name, Anthropic its own scale, DeepSeek V4 low/high/max with no middle step, Qwen and GLM only an on/off switch, and Mistral's Magistral models reject the parameter outright. MUR keeps one scale and one table that knows which levels each model really takes, so `mur agent effort <name> high` means the same thing everywhere and a level a model cannot use is degraded instead of erroring. Set it per agent from the CLI, for one conversation with `/effort`, or in the MUR Hub's **Behavior** tab.

**Reuse the subscriptions you already pay for.** The companion [mur-model-gateway](https://github.com/mur-run/mur-model-gateway) runs a local endpoint (`127.0.0.1:8088`) that routes Anthropic / OpenAI / Gemini calls through one outlet and attaches credentials from your OS keychain — point a registry entry's `base_url` at it and your agents ride your existing Claude Code login instead of a separate metered API key.

**ChatGPT Subscription, no API key.** MUR Hub's Model Library has a dedicated **ChatGPT Subscription** provider, separate from the usage-billed OpenAI entry: sign in through Codex CLI (the browser flow Codex already owns), let the Hub install the gateway with a Codex credential source, and pick the models your plan offers. Registry entries are `provider: codex` with no `secret` — the runtime sends authless requests only to the loopback `http://127.0.0.1:8088/codex/v1` and refuses anything else, so a typo cannot land on OpenAI Platform billing. Model pickers and fallback chains carry a billing label, and MUR never adds a usage-billed fallback behind a subscription model on its own. Disconnecting MUR leaves the shared Codex login untouched; signing out is a separate, confirmed step because it affects Codex CLI and IDE too. Details: [`docs/model-gateway.md`](docs/model-gateway.md).

**A slow model is not a hung one.** Nothing in MUR cuts a model off by a clock. There is no total request timeout: a reply that is still arriving keeps arriving, however long it takes, which matters most for a local model that spends a minute loading weights and evaluating a long prompt before its first token. What *is* bounded is silence — a stream that stops sending for `MUR_LLM_IDLE_TIMEOUT_SECS` (default 120) ends with the partial reply kept and visibly marked `[output truncated: the model stopped sending]`, never as a failure, and the settlement card names the bound that bit. The wait for the *first* chunk has its own, longer bound, `MUR_LLM_FIRST_DELTA_TIMEOUT_SECS` (default 300), because cold start and a dead connection look identical from outside and one number cannot serve both. Raise either for a slow local box; a value of `0` is refused rather than read as "no bound".

**A 429 waits exactly as long as the server asks.** When a provider rate-limits a call and sends `retry-after`, MUR sleeps that long (seconds or an HTTP date, clamped to 120s) and retries — the agentic loop up to three times, the companion outbox on its usual four-attempt schedule. Without the header nothing changes: the same exponential backoff as before. The log line and the network-audit entry say which one was used (`source=retry-after` vs `source=backoff`), so a stall is never a mystery.

**Claude Subscription, the same way.** The Model Library's **Claude Subscription** provider signs in through Claude Code (`claude auth login --claudeai`), lists models from the catalog, and writes `provider: claude` entries that can only reach the loopback gateway's `/v1` route — no `secret`, and a `base_url` edit to `api.anthropic.com` is refused at startup instead of quietly switching the bill. Entries you already point at the gateway as `provider: anthropic` keep working; `mur model doctor` shows which ones could carry the explicit label.

**Or skip the gateway: run the turn inside the CLI itself.** A registry entry
with `provider: cli:claude` puts an agent's turns inside a spawned `claude`,
which owns the loop while MUR owns the tools — they are mounted into it over
MCP, so every call still lands in MUR's handler with its entitlements, secret
masking and approval gate. This needs no gateway build and no second login: the
spawn uses your existing Claude Code credential. What it costs is a session
transcript in your own `~/.claude/projects/<cwd>/`, because a spawned CLI
records its turns the way any other does; your credentials and settings are not
touched. Tool isolation comes from the flags, not from where the login lives —
`--tools "" --strict-mcp-config` leaves the model with MUR's tools and nothing
else, verified from the CLI's own startup report rather than from what it says
about itself.

`codex` is registered and **disabled**. Its shell is built in and no flag
removes it — `-s read-only` restricts the filesystem, which is not the same as
establishing what an action may do — so a spawned `codex` could run commands
that never pass MUR's gate. It stays off until a verified process sandbox
exists, and it is listed rather than hidden so the reason is visible to anyone
who has it installed. `agy` has no row at all: it offers no way to relocate its
configuration and no way to disable its 57 built-in tools.

#### Cloud LLM backend (opt-in)

Conversation stages inherit the top-level `llm:` block by default. Each stage
accepts an optional `BackendConfig` override in `~/.mur/config.yaml`, so you
can pin individual stages to a different provider while everything else inherits
the top-level setting:

```yaml
conversations:
  compact:
    # extractive stage → cloud (fast + cheap). abstractive_backend is left
    # unset here, so it inherits the top-level `llm:` block (local or
    # cloud, whatever that's set to) — give it its own override to pin it
    # independently.
    extractive_backend:
      provider: anthropic          # ollama | anthropic | openai | openrouter | gemini
      model: claude-haiku-4-5
      api_key_env: ANTHROPIC_API_KEY
      # endpoint: https://api.anthropic.com   # optional override
      # timeout_secs: 120                     # optional, default 120
  ask:
    # answer stage → cloud. rewriter_backend is left unset here, so the
    # rewriter follows this same answer-stage backend (cloud too) — set
    # rewriter_backend explicitly if you want the rewriter pinned somewhere
    # else (e.g. kept local while the answer stage runs in the cloud).
    backend:
      provider: anthropic
      model: claude-sonnet-5
      api_key_env: ANTHROPIC_API_KEY
    # rewriter_backend:                       # same shape, per-stage
  rollup:
    # weekly/monthly rollups take the same two overrides as compact
    # extractive_backend: …
    # abstractive_backend: …
```

Fields: `provider`, `model`, optional `endpoint`, `api_key_env` (name of the
env var holding the key — the key itself never lives in config), `api_key_ref`
(a secret-ref string such as `env:VARNAME`, checked before `api_key_env`), and
`timeout_secs`. Leaving an override unset makes that stage inherit the
top-level `llm:` block — there is no separate local-only fallback anymore.

`mur chat doctor` prints every stage with the provider, model and endpoint it
will actually dial, marked `[pinned]` or `[follows smart]`, then probes each
distinct endpoint once — so you can verify routing before any conversation
data exists.

**Upgrading:** configs written before per-stage backends stored a bare model
name plus an `ollama_endpoint`. MUR converts them the first time it loads your
config and writes the result back once. A stage still on its shipped defaults
becomes an inherit; a stage you had customized is pinned to an explicit Ollama
backend, preserving exactly what it did before.

Typical cost with Haiku-extractive + Sonnet-ask is on the order of a few
dollars per month of daily use. Verify your setup with the ignored live
test: `cargo test -p mur-core live_anthropic_haiku_responds -- --ignored`
(requires `ANTHROPIC_API_KEY`; costs ~$0.0001 per run).

### Teach the AI tools you already use

MUR's memory layer works even if you never create an agent — it rides along with
Claude Code, Codex, Cursor, Gemini-family CLIs, and a dozen more:

```bash
mur init --hooks                              # install hooks for detected AI tools
mur sync                                      # write learned patterns into each tool's native config
mur notes search "how we handle auth errors"  # query your accumulated memory
```

### Dev-discipline skills (built-in)

MUR ships a curated engineering-discipline pack — internalized from the
MIT-licensed [obra/superpowers](https://github.com/obra/superpowers) and
[mattpocock/skills](https://github.com/mattpocock/skills) (see
`docs/ATTRIBUTIONS.md`), merged and adapted to MUR's runtime (no sub-agents
required; delegation-aware). One hub routes; sixteen on-demand leaves carry
the method:

`mur-dev` (hub) · `mur-grilling` · `mur-brainstorm` · `mur-domain-modeling` ·
`mur-writing-plans` · `mur-tickets` · `mur-executing-plans` ·
`mur-delegate-dev` · `mur-worktree` · `mur-tdd` · `mur-debugging` ·
`mur-code-review` · `mur-receiving-review` · `mur-verification` ·
`mur-finishing-branch` · `mur-merge-conflicts` · `mur-skill-authoring`

- Zero token cost until used: only the hub appears in the session-start
  learning index; leaves load on demand (`mur skill show mur-tdd`).
- Never-shadow: with the superpowers plugin installed, the hub hides itself
  on the CLI surface (`skills.dev_discipline_index: auto|always|never` in
  `~/.mur/config.yaml`); a user-authored skill with the same name is never
  overwritten.

---

## ✨ What can a MUR agent do?

### 🤖 Run as a real local process

One BusyBox-style runtime binary, one symlink per agent (`mur_agent_coach`).
Each agent owns its model binding (Ollama, MLX, Anthropic, OpenAI, … via the
`~/.mur/models.yaml` registry), system prompt, MCP servers, skills,
keychain-backed secrets, cron schedules, webhook receiver, and a rotating Ed25519
identity. `mur agent` exposes 40+ subcommands for the full lifecycle — create,
chat, export, schedule, permissions, telemetry, trash, rollback. When you need
to reach past them, `mur agent dial <name> <method> [json]` calls any A2A method
on a running agent and prints the raw result — `memory/reload`, `tasks/list`,
`turn/steer`, `model/set`.

Its tools can hand back images, not just text. Point `read_file` at a
screenshot, a photo, a rendered chart, and a vision-capable agent *looks* at it;
an MCP server that returns image content reaches the model the same way. Before
this the bytes were decoded as text and the model — with no way to say it never
saw a picture — described one anyway.

A slow `bash` command is never killed by its own timeout. `timeout_secs`
(default 30s, cap 600s) now bounds only how long one call waits — past
that, the command keeps running and the reply carries a `job_id`;
`bash_wait` keeps waiting on it, `bash_kill` stops it (its whole process
group, so `cargo`/test binaries/pipeline stages die with the shell).
Before this, a build or test suite that ran past its timeout was killed
mid-way and reported as a failure with no way to recover the work.

An agent reads the project's own instruction files. In every directory from
the repo root down to the working directory, the first of `AGENTS.md`,
`AGENT.md` or `CLAUDE.md` is loaded, root first, so a deeper file reads as the
more specific rule. They reach the model as project context in the first user
message, not as operator rules. They cannot widen permissions: a file the
agent could not `read_file` itself is left out. The total is capped at 32 KiB
or half the conversation-history budget, whichever is smaller, and anything
cut is named rather than silently dropped.

### 🧠 Learn — and forget — like a teammate

```mermaid
flowchart LR
    C["capture<br/>significance · feedback"] --> S["store<br/>YAML truth + vector index"]
    S --> R["retrieve<br/>vector 0.7 + BM25 0.3"]
    R --> I["inject<br/>hooks · MCP · prompts"]
    I -.->|next session| C
    E["evolve<br/>decay · maturity · recombination"] <-.-> S
```

Knowledge moves through a maturity lifecycle driven by real usage, with decay
half-lives by tier (session 14d / project 90d / core 365d):

```mermaid
stateDiagram-v2
    direction LR
    [*] --> Draft
    Draft --> Emerging: validated by usage
    Emerging --> Stable: repeated wins
    Stable --> Canonical: proven over months
    Canonical --> Stable: unused — decay
    Stable --> Emerging: unused — decay
```

Recurring tool sequences across sessions are mined into **suggested workflows**
(`mur workflow suggest`) — no drag-and-drop DAG editor, no marketplace; your own
recorded behavior is the authoring tool.

Capture is **ambient**: once hooks are installed, every session is recorded
locally (scrubbed at write, retention-GC'd, one line of config to turn off).
`mur in` just marks the current session as important; `mur out` reviews what
MUR queued for you — workflow proposals harvested from recent sessions, and
memory notes your agents want to share. Accept a workflow proposal and it
becomes a draft you can run with `mur run`.

Agents **remember proactively**: state a durable preference mid-chat ("from
now on, reply in zh-TW") and the agent saves it as a memory note — and tells
you so, in one line, with `/forget` as the undo (`/memories` lists everything
it knows). Notes come in two kinds with matched decay: `rule` (behavioral
guidance, fast half-life) and `fact` (environment truth, slow half-life). A
reserved injection slot keeps fresh notes from being permanently outbid by
mature skills. Say it again and the note is updated in place rather than
colliding — and one you had forgotten comes back. Ask an agent what it knows and
it answers from its own `recall`, reading the very set its prompt was built from;
`mur notes list --agent <name>` shows you the same thing. Off switch / confirm-first:
`memory.capture` in `~/.mur/config.yaml`.

**None of it waits for a restart.** A note saved mid-chat is in the very next
turn's prompt, and so is anything you change from another terminal — a skill
installed, a note removed, a `skill.yaml` edited in vim. The running agent
compares the tree against what it loaded and re-reads only when they differ, so
nothing has to notify it and an agent started later needs no catching up.

What an agent remembers **stays its own until you say otherwise**: each
remember also files a proposal into your `mur out` review lane — accept and
the note goes global (every agent's loader sees it), dismiss and the agent
keeps its private copy. Nothing an agent inferred reaches other agents
without usage-earned maturity or that explicit human gate.

Knowledge **federates on maturity, signed both ways**: each agent's sleep
cycle drops an Ed25519-signed snapshot request; the daemon verifies it
outside the sandbox and assembles the curated skills (lifecycle ≥ `stable`
by default) into that agent's local cache. Outbound is signed too — evidence
signals and memory proposals are signed with the agent's identity key as they
leave its home, and ingest verifies who said it (and that it may) before
anything is applied; the review lane labels each proposal `✓ signed`.
`MUR_SIGNAL_REQUIRE_SIG=1` turns tolerance for legacy unsigned drops off.

### 💬 Be everywhere you are

- **Live fleet progress in the terminal** — when an agent kicks off a fleet
  from chat, `murmur` automatically arms a per-member status rail and streams
  milestone lines (delegations with their sub-goal, member-written completion
  summaries with elapsed time, run outcomes, approval gates) into the
  transcript as they land in the fleet's signed channel.
- **MUR Hub** — one master–detail shell for every page (agents, fleets,
  chats, skills / workflows / MCP / plugins, settings): source list with filter
  and facets, full-width detail, ⌘K palette, **⌘↩ to open any agent or fleet in
  its own window**, a side-peek from Home, ⌘-click multi-select with bulk
  Start / Stop, streaming replies, human-in-the-loop tool approvals, a
  **Permissions** section that shows every entitlement the sandbox will enforce
  and edits it in place (folders through the native picker, hosts, spawn, tool
  rules — each change is one CLI call and a re-read, so the Hub and
  `mur agent perm show` never disagree), and drag-out **desktop pets** with
  expressions and speech bubbles.
  [Docs](https://app.mur.run/docs/core/mur-hub).
- **Voice** — fully on-device TTS (Kokoro 82M) + STT (whisper.cpp); respects
  Do Not Disturb, Focus, and a busy microphone.
- **iPhone** — the in-repo iOS companion (`mur-mobile-app`) pairs over LAN with
  `mur agent pair` (QR); off-LAN traffic falls back to a relay that forwards only
  end-to-end-signed envelopes. All AI stays on your Mac.
- **Watch together** — agents open videos in VLC, explain the current scene,
  analyze whole videos with timestamps, and (opt-in) comment on scene changes —
  on a local multimodal model.
- **Bridges** — Slack, Telegram, Jira (`@mur implement PROJ-123`), and webhooks.

### 🎁 Be given away

```mermaid
flowchart LR
    A["Your agent<br/>in Hub or CLI"] -- "Share /<br/>mur agent export" --> B["coach.muragent<br/>signed · sanitized · data-only"]
    B -- "any channel" --> C["Friend<br/>double-clicks"]
    C --> D["Hub verifies signature,<br/>guides model setup"]
    D --> E["Agent alive<br/>on their machine"]
```

The `.muragent` package is DSSE-signed and **contains no executable code and no
secrets** — private keys and API keys are stripped at export. The recipient
installs MUR Hub once (signed + notarized); after that, agents travel as plain
files.

MUR publishes agents the same way. `mur official list` browses the curated
catalog and `mur official install agents/<name>` installs one — the bundle is
signed by MUR and carries a license bound to your account, so an installed
official agent verifies on your machine and nowhere else. The Hub's **+ New
Agent** wizard offers the same catalog as a source.

### 🔐 Stay governed

- **Kernel sandbox** per OS — Landlock + seccomp (Linux), SBPL (macOS), Job
  Object (Windows) — plus a DNS-resolver guard that filters network egress.
- **Human-in-the-loop** — tool calls pause for your approval in Hub. In
  `mur agent cli` a session starts with auto-approve ON (the status bar's
  `AUTO` badge says so); `--ask` or `/auto off` makes it ask first. While a gate is open the
  decision keys only count when you aren't mid-message, and a session-wide
  grant takes two presses — typing an ordinary sentence can't hand a tool
  blanket approval. An open gate always renders somewhere, `/auto off` revokes
  the per-tool grants it claims to revoke, and a gate that times out stops
  asking. Tool calls from one model response arrive as a single card rather
  than one prompt each — every call is still decided on its own, and there is
  no approve-all. A settled decision is remembered by action hash, so the same
  call stops being asked twice, and an explicit no outranks any standing grant.
  *Always* writes the narrowest exact tool rule; where a call reaches outside
  its entitlements the card offers that grant as a separate control, never
  folded into the rule. Reads never have to stop the run: `--auto-reads` covers
  `read_file` and provably read-only shell commands, in the TUI and in
  `--plain` alike.
- **Nobody watching is not permission** — a durable monitor acting on what it
  found asks the same way. Reading its evidence runs unattended; anything that
  would change something outside MUR parks a request pinned to that exact
  action, and the monitor waits in `awaiting-approval` for as long as it takes
  — approvals defer, they never time out into acting alone. `mur monitor show`
  prints the command that releases it. A monitor's read credential is not its
  write credential either: restarting a CI run needs a second grant the spec
  states outright, so watching something never quietly becomes touching it.

- **Credentials the model never sees** — `/secret <KEY>` in `mur agent cli`
  reads a token through a hidden prompt, stores it in the OS keychain, and
  hands it to the running agent without a restart. The model is told the name
  and nothing else: the value is injected into its shell's environment, and
  every tool result is scrubbed of it on the way back. Pasting a token into the
  chat put it in the model's context, in the agent's signed append-only
  channel, and at your provider — pattern-matching redaction never caught the
  ones without a recognisable prefix.
- **Build lane** — a toolchain that compiles its own executables can't be
  expressed as a list of binaries: `cargo` runs build scripts and test
  executables at paths that don't exist until the build creates them. Grant the
  build-output directory instead — `mur agent perm allow-spawn-dir <agent>
  <dir>` — and an agent can finally verify its own work. Narrow by
  construction: `/`, `/usr`, `/opt`, your home directory and a bare mount point
  are refused, and filesystem and network entitlements still bound whatever
  runs.
- **A grant that never reached the kernel** — a filesystem entitlement only
  takes effect if its path exists when the agent starts, so granting a
  directory you hadn't created yet used to succeed at the CLI, survive a
  restart, and still fail with a bare `Operation not permitted`. `mur agent
  perm allow-read` / `allow-write` now refuse a path that doesn't exist and
  print the `mkdir -p` to run. `mur agent doctor <name>` reports the two ways a
  grant goes missing, which have different fixes: a path that doesn't exist and
  will be dropped when the sandbox seals, and a grant whose *scope* swallows a
  protected path. What happens then depends on the kernel: Landlock has no deny
  rule, so Linux drops the whole grant; macOS installs it and re-closes the
  protected paths inside it, which leaves the grant covering less than it reads
  as. `doctor` says which of the two you are looking at rather than asserting
  one everywhere.
  `mur agent perm list-paths <name>` shows the other half of that picture —
  every filesystem grant against what the sandbox actually installed, so a
  grant the kernel discarded is visible as `✗ dropped` rather than as an
  entry in a config file that quietly does nothing. Some paths can never be granted at all — another agent's signing key,
  the runtime binary, an autostart directory, and your credential store
  (`~/.mur/secrets`, `auth.json`, a `.env`) — because they decide what starts
  next or are the keys themselves; `allow-read` / `allow-write` refuse them,
  and the runtime binary itself is signed by MUR's Developer ID in release
  builds — a swapped binary is refused at spawn, never run. A grant written with
  `~` now holds at every layer: the kernel policy always expanded it, the
  in-process tool gate did not, so the sandbox allowed a read that the tool then
  refused as `path not entitled` — and a `~/.ssh` *deny* entry, the form MUR
  itself suggests, was inert at that gate.
  A freshly seeded MUR owns
  `~/.mur/{skills,workflows,fleets,artifacts}`, so it can build the skill,
  workflow or fleet it just designed instead of handing you a list of commands.
- **Settings that were accepted and then did nothing** — `mur agent perm
  allow-host` took `10.0.0.5:3306`, listed it back, and matched nothing: host
  allowlists compare portless hosts, and the OS sandbox restricts by port with
  the host left as `*`. It now refuses that form and names what would actually
  take effect. Same family, different surface: an agent whose secret or model
  reference failed to resolve used to start anyway and answer as an echo stub —
  a "working" agent that parrots you back. The agent now says so itself: a model
  reference that does not resolve, or a provider client that will not build,
  answers every message with the reason and the commands that fix it, and only a
  deliberate `provider: echo` still echoes. `mur agent doctor <name>` runs the
  same resolution the runtime does and reports it, and `mur agent start` waits
  for the runtime to claim it is up instead of reporting success the instant the
  process forks.
- **Loop settings that can't quietly mean something else** — a fleet loop ends
  when its job queue drains, when a member emits an agreed marker on a line of
  its own, or when the router judges it done. `mur fleet set-loop` refuses a
  value that would be silently reinterpreted: a calendar date is not a deadline,
  a cron expression that can never fire is not a schedule. `mur fleet stop`
  still ends everything.
- **Bounded, not budgeted** — three knobs, one schema at every scope:
  `deadline`, `stuck`, `cost_usd` in `config.yaml` → `fleet.yaml` → `profile.yaml`,
  inner scope replacing the outer. Nothing else stops a run: iteration caps and
  token budgets are gone, and a stale `hitl.max_iterations` is loaded, ignored,
  and named once at start. A turn you are watching in `murmur` has no hard stop
  — the stuck clock warns, you press Esc. Unattended work (`mur agent send`,
  schedules, fleet loops, daemon auto-run) stops on its deadline — built-in 30m
  for a task, 1h for a fleet run — or after 10m with no real progress (a file
  write, a channel event, a tool call that differs from the last one), and a
  delegated member inherits the fleet's *remaining* clock, not a fresh one. A
  cost cap applies only to a metered model; a local fleet is bounded by its
  deadline. `mur limits <fleet|agent>` prints every bound in force and which
  scope set it; every stop reaches the settlement card with its reason and the
  one-line remedy.
- **Long work returns a handle; liveness is a heartbeat** — `fleet_run` and
  `parallel_jobs` answer `{run_id, status: dispatched}` within a second and
  you poll `mur_job_status <run_id>` (or `mur fleet status`); the result lands
  in the fleet's channel, and the MCP per-call timeout stays 120 s on purpose.
  A running turn beats `turn/heartbeat` every 30 s, so the dial gives a
  beating peer 90 s of silence before calling it `stopped responding` — and a
  router that thinks for ten minutes is alive the whole time. `needs:` in
  `fleet.yaml` names the tools the work requires; a member missing one fails
  at dispatch (`cannot start: <agent> has no write_file — mur agent perm
  tool-allow …`) instead of after its budget, and any `not authorized:`
  refusal withdraws that tool for the rest of the turn instead of being
  retried.
- **Runs that report their own failures** — an agent handing a job to a fleet
  used to hit a kernel refusal on a binary its profile plainly allowed: the
  sandbox resolved the name by scanning the exec directories when the agent
  started, while the spawn resolved it through `PATH` minutes later, and a
  package upgrade in between was enough to make those two different files. The
  grant and the spawn now read one derivation, so they cannot disagree. What
  came back from a failed run was just as thin — six steps marked `failed` and
  not one reason, leaving agent logs as the only route to a cause. Every
  terminal step now records why, `mur job status` and `mur fleet status` print
  it, and a step that fails with nothing to say says that. Delegation fan-out is
  bounded on the ordinary path too, not only under the experimental worktree
  flag: members share one local gateway and one upstream quota, so an uncapped
  fan-out is a self-DoS (`MUR_FLEET_FANOUT` raises the bound; it clamps rather
  than unbounds).
- **Schedules that fire when they say they will** — `mur workflow schedule set`
  takes a flat workflow *or* a workflow skill, and resolves the name against
  both when you create the schedule, so a name it accepts is a name that will
  run. The listed zone is the one launchd and cron actually use — local, not a
  hardcoded `UTC` label that had people shift a working schedule by their own
  offset. And the job inherits the `PATH` of the shell that created it, so a
  step calling `mysql` or `gh` reaches the binaries you just tested it against
  rather than the four directories a scheduler hands out.
- **Approvals that wait for you, not the other way round** — a run nobody is
  watching used to spend the full five-minute approval window discovering
  exactly that, then fail the step and kill the request, so approving it the
  next morning released nothing. Now the request is parked: the step is
  **blocked** rather than failed, independent branches still finish, and the
  loop stops instead of re-asking the same question every iteration. Approve
  whenever you get to it — Hub's *Needs You* card or `mur channel approve` —
  and the next run continues from there, because approvals are matched on the
  action's content hash, not a per-call id. Change the action and the old
  approval no longer covers it. A fleet can state its own policy in
  `fleet.yaml`: `hitl.mode: defer | wait | deny`, and
  `hitl.auto_approve_tiers: [write]` to take standing responsibility for a
  tier — capped at `write`, because spend, destructive and privileged actions
  cost more than noticing afterwards can undo. Your explicit *no* to an action
  outranks a standing grant for its tier, and every auto-approval is still
  written to the channel, so "what did it do without asking me?" always has an
  answer.
- **A run status that can't outlive the run** — every fleet or workflow run
  writes one record, and `mur job status` / `mur fleet status` both read that
  one record through one derivation. A run whose orchestrator died reports
  **dead** the moment you ask instead of claiming to be running until a timeout
  expires; a long run is not falsely failed while it is still working; and a
  record rebuilt from the channel admits its heartbeat is unknown rather than
  printing a stale pid as fact. An unreadable record is reported as unreadable,
  never as a run that never existed.
- **Settlement** — a turn that changed anything ends with a card the runtime
  draws from its own tool records, not from the model's summary: what was
  **verified** (a command ran and passed), what was **changed** (files edited,
  nothing run), what was **blocked**, and whether the turn stopped early. The
  verified row is printed even when it's empty — `✔ verified (nothing ran — no
  evidence this works)` — so "all fixed" over an empty column reads as the
  contradiction it is. The same ledger rides along as JSON, so `mur agent send`,
  fleet steps and Hub get the accounting without scraping prose.
- **Capability routing** — an agent blocked by the sandbox doesn't hand the job
  back to you: the denial names the fleets that actually hold that binary, and
  the agent delegates. `mur agent who --can cargo` shows the same picture,
  derived from what the kernel enforces rather than from a list anyone
  maintains — including the capable fleets you haven't authorized yet, and the
  command that authorizes them.
- **Deletion safety** — destructive file actions go through a trash with a
  cancel window and explicit restore (`mur agent trash`); nothing is
  hard-deleted on a timer.
- **Auditability** — every action lands in an append-only JSONL ledger;
  **MUR Commander** (companion crate) adds an Ed25519-signed constitution and a
  hash-chained audit log for cross-network fleets.
- **Governed distribution** — agents, fleets, and **capabilities** (bundled MCP
  servers + skills + program requirements, `mur capability install`) carry pinned
  provenance under a strict *never-shadow* rule: an imported plugin or bundled
  skill can never silently override a builtin. `mur skill doctor` flags drift and
  de-pins stale vendored copies; imported add-ons re-verify on `mur agent addon reimport`.
- **Enforced MCP pins** — an agent **refuses to start** when an MCP server's
  binary no longer matches the hash pinned at install, or isn't signed.
  `mur agent mcp inspect <agent>` shows pinned vs current; `mur agent mcp pin
  <agent> <server>` re-approves; `mur doctor` reports drift across every agent
  before you meet it as a failed startup. MUR's own bundled MCP server re-pins
  itself when MUR upgrades, and interpreter-launched servers (`npx …`, `python
  -m …`) are reported as unprotected rather than enforced — hashing the
  interpreter breaks on unrelated runtime upgrades without covering what it runs.
- **A reminder that actually fires** — ask an agent to remind you at ten
  tomorrow and it used to write a note in a list with no clock, which expired
  quietly three weeks later. An agent cannot create its own schedule — its
  entries live in a `profile.yaml` the sandbox denies it, deliberately, so a
  running agent cannot widen its own permissions and restart into them. So it
  asks: `mur agent schedule proposals <agent>` shows what it asked for, in its
  own words alongside the cron, **when it would fire in your timezone, and which
  of the two it is** — `fires once, on …` or `first fires …, and repeats` —
  because `0 10 1 9 *` tells a reviewer nothing about whether the agent
  understood "tomorrow". That distinction is load-bearing: cron has no year
  field, so a request for one morning can only be written as an annual
  recurrence, and without a bound it would arrive again every September. `accept`
  turns it into a real entry on the real scheduler, bound included.
- **An outstanding-work list that ages and checks itself** — agents record what
  they left undone, and that list used to only grow: it once carried items about
  a release six versions old next to a breakfast reminder three weeks past. A
  reported item now goes stale after two weeks — dropped from the default view,
  counted in the summary line, still there under `mur open --all`. Stale
  *demotes*; nothing deletes what a person recorded because a timer said so.
  `mur open --check` goes further and runs the item's own `next` command, for
  the subset that only looks (`ls`, `test -f`, `git log`); anything that would
  act, or that needs a shell, is refused whole rather than trimmed to its safe
  prefix. The result ranks the list and says how much of it could be answered —
  `checked 1 of 4 reported items` — because a check that reports nothing about
  its own reach is indistinguishable from one that found nothing wrong.
- **A router that can't hand your work to a model that can't do it** — Smart
  background routing runs low-stakes turns on a cheaper model. It also, until
  now, handed image recognition to a text-tier model: the picker ranked
  candidates by price and never asked whether they could see. Nothing catches
  that afterwards — the cascade escalates on a *malformed* reply, and a
  confidently wrong recognition is perfectly well-formed — and the decision
  caption only renders in Hub chat, which background turns never reach. So a
  router may now only substitute a model that can serve the request: price
  orders the eligible set, it doesn't decide who's in it. Silence about vision
  counts as absence of it, because that failure is silent; silence about tools
  doesn't, because a tool-incapable model is refused loudly and the chain simply
  advances. Explicit choices are never filtered — your `model_ref`, your pinned
  re-run — they're yours to get wrong. Smart is now **off by default**
  (`mur model smart on`), and per agent it's genuinely three-state:
  `mur agent smart <name> follow|on|off`, where `follow` means follow. The
  toggle used to lie in the other direction too — an agent with no fallback
  chain never ran Smart at all, whatever the setting said. And the decisions
  are no longer write-only: `mur agent routing <name> --downgrades-only` reads
  them back out of the telemetry that was always being written, marking with
  `↓` the turns MUR chose the model for you. The gate stops the failure MUR can
  recognise; no automated check can tell you the cheap model was simply *worse*
  at something without paying to run the turn twice. That's what looking is for.

### 🔌 Power the tools you already pay for

Three integration layers, by interaction shape:

| Layer | Shape | What it does |
|---|---|---|
| **Hooks** | fire-and-forget | `mur sync` writes memory into each tool's native config; session hooks inject context automatically |
| **MCP server** | interactive | `mur-mcp-server` (stdio) exposes 18 tools — search, recall, project code search, agent status, token compression, media control |
| **Skills** | teaching | curated manifests that tell agents *when and why* to reach for MUR |

Synced tools include Claude Code, Gemini CLI, Auggie, Cursor, Copilot CLI,
OpenClaw, OpenCode, Amp, Codex, Aider, Windsurf, Zed, Junie, Trae, Cline, and
Amazon Q. The compression tools (`mur_compress` / `mur_retrieve`) shrink large
payloads 40–80%, reversibly — originals stay retrievable by hash.

---

## 🦀 Architecture

<p align="center">
  <img src="docs/diagrams/mur-architecture.svg" alt="MUR system architecture" width="100%" />
</p>

| Crate | Role |
|---|---|
| [`mur-core`](mur-core) | The `mur` CLI — memory pipeline, sync, sources, dashboard server, agent management |
| [`mur-common`](mur-common) | Shared types — `Pattern`, `Workflow`, A2A envelopes, `.muragent` format |
| [`mur-agent-runtime`](mur-agent-runtime) | Per-agent A2A v0.3 supervisor — sandbox, voice, export, telemetry |
| [`mur-daemon`](mur-daemon) | Always-on background daemon — queues, schedules, dashboard API |
| [`mur-mcp-server`](mur-mcp-server) | stdio MCP server exposing MUR to AI clients mid-conversation |
| [`mur-compress`](mur-compress) | Offline, reversible token compression |
| [`mur-gui-core`](mur-gui-core) | Shared GUI library — sidecar supervisor, companion bridge, A2A client |
| [`mur-agent-launcher`](mur-agent-launcher) | <100 KB per-agent stub (Dock identity, file association) |
| [`mur-mobile-sdk`](mur-mobile-sdk) | Rust mobile core (UniFFI → Swift/Kotlin) — transport, signed envelopes, audio framing |
| [`mur-hub-gui`](mur-hub-gui) | **MUR Hub** desktop app (Tauri 2 + React) |
| [`mur-mobile-app`](mur-mobile-app) | iOS voice companion (Swift) |

**MUR Commander** — the cross-network orchestration, governance, and
evaluation plane — ships as a separate crate.

On disk, everything lives under `~/.mur/`: agents, skills, notes, and workflows
as **human-readable, git-friendly YAML** (the source of truth), plus a
LanceDB vector index that is always rebuildable (`mur internals reindex`). No
opaque database lock-in.

---

## 🧰 CLI at a glance

```bash
mur daemon serve     # web dashboard at http://localhost:3847
mur dashboard        # terminal TUI dashboard
```

<details>
<summary><b>Full command tree</b> (34 top-level commands)</summary>

```
mur
├── init / doctor / update / stats / verify
├── agent        create · start · stop · restart · remove · cli · send · card · dial · who · limits ·
│                export · install · install-service · addon · companion · voice · pair ·
│                schedule (add · proposals · accept) · perm (incl. list-paths · remove-path · set-mode proxy_only) · secret ·
│                fallback · smart · routing · effort · trash · rollback … (40+)
├── capability   install · list · show · remove   (MCP + skills + programs bundled → an agent)
├── fleet        create · list · show · status · run [--run-id] · set-loop · limits · send · jobs [--since]   (squads of agents over a shared channel)
├── limits       <fleet|agent> [--json] · --global · --deadline · --stuck · --cost-usd · --unset   (every execution bound in force, with its source)
├── monitor      add · list · show · cancel · retry   (durable monitors for work that outlives the turn: CI runs, MUR runs, subprocesses)
├── official     list · install   (official agents/fleets from the app.mur.run catalog)
├── deep-research  setup · secret · status · ask   (web research with wizard UX)
├── skill        install · search · show · doctor · generate · suggest · evolve · recombine ·
│                publish · audit · trust · exchange · drafts · eval …
├── notes        create · search · list · show
├── workflow     run · suggest · list · schedule · show · search · new · publish · install
├── session      start · stop · record · status · list · review · show · export · push
├── open         add · done · --check   (what is still outstanding, by whether MUR saw it)
├── sync         (16+ AI tools) · status · fleet pull/push/both
├── hook         unified hook entry for AI tools (prompt / tool / stop / session-start)
├── chat         conversations archive + ask
├── model        connect · import · add · list · show · remove · doctor · prices · role · route ·
│                default · fallback · smart · migrate   (connect = one key, many models)
├── source       external knowledge — Obsidian · Notion · Joplin
├── project      index · search   (semantic code search)
├── daemon       start · stop · restart · status · serve · sleep
├── dashboard    terminal dashboard
├── browser      record · replay · auth · broker · list · show · export · status   (browser work through Playwright MCP)
├── commander    pin · status · directive   (governance: pin the operator key, issue and inspect directives)
├── auth         login · logout
├── team         shared skills (private registries)
├── push / fetch signal outbox / inbox ↔ server
├── deploy       Docker Compose deployment
└── internals    low-level store access · reindex
```

</details>

### Deep research, simplified

```
mur deep-research setup        # one-time wizard: model, workers, budget, egress consent
mur deep-research              # status panel
mur deep-research "question"   # preflight (start workers, re-pin gateway) + guarded run
```

`provision` / `run` remain as the flag-based advanced path. Egress is only ever granted in `setup`/`provision --grant-egress` (explicit consent); the smart run never touches grants.

#### Search provider keys

Research search works with no key at all (it scrapes DuckDuckGo's HTML
endpoint). A provider key is a reliability upgrade — DDG rate-limits a busy
fleet from one IP and answers with an anti-bot challenge instead of results.

```
mur deep-research secret --brave       # Brave Search (default if no flag given)
mur deep-research secret --tavily      # Tavily
mur deep-research secret --serpapi     # SerpApi
mur deep-research secret --firecrawl   # Firecrawl
mur deep-research secret --list        # which providers have a key (never prints one)
mur deep-research secret --tavily --clear
```

The key is read from the terminal **without echo**, or from stdin when piped
(`echo "$KEY" | mur deep-research secret --tavily`). It is never accepted as a
command-line argument — argv is visible to every process via `ps` and lands in
your shell history.

What gets stored where: the key goes into the **OS keychain**, and only a
reference to it (`keychain:mur/tavily`) is written to
`~/.mur/config.yaml` under `research_gateway.tavily_api_key_ref`. The secret
itself never enters the file, so the config stays safe to sync, diff and paste
into a bug report.

Configure more than one and search tries them in order — Brave first, then
Tavily, SerpApi, Firecrawl — falling through to the next on any failure, and
finally to keyless DuckDuckGo. A bad key degrades search; it never blacks it
out. Each provider also honours an env override
(`MUR_RESEARCH_BRAVE_KEY`, `MUR_RESEARCH_TAVILY_KEY`, …) which wins over
config.yaml.

Restart any running research workers for a new key to take effect.

Inside a murmur chat the same three verbs are a slash command, and they render on
your screen without costing the agent a turn — the transcript it sees stays clean:

```
/deep-research                 # status panel
/deep-research ask <question>  # start a run, progress streams while you keep typing
/deep-research <question>      # `ask` is optional — any other text is the question
/deep-research stop            # end it (outcome = stopped)
```

`/research` is the same command, and `/deep-research setup` is the one verb the
slash form does not run: it points you at `mur deep-research setup` in a
terminal, because the wizard asks for egress consent.

Agents reach it through the built-in `fleet_run` tool rather than the CLI. It
never holds the call open for the length of a run — it dispatches and answers
with a handle, always:

```
fleet_run {fleet: "deep-research", goal: "<question>"}
→ {"run_id": "fleet-deep-research-019bd4c1-…", "status": "dispatched", …}
mur_job_status fleet-deep-research-019bd4c1-…
→ run … — state: running, liveness: alive
  progress: iteration 2 · 3✓ 0✗ 2 pending · spend $0.31/$2.00
```

`mur_job_status` answers from the run record, and attaches that `progress:` line
from the fleet's progress file only when the file's own `run_id` matches the id
you asked about — so an earlier run's progress is never reported as this one's.
A preflight that failed before any run record existed is not a `mur_job_status`
answer at all (it says `no run recorded`); it shows up in the bare
`mur deep-research` panel, which reads the progress file directly.

Runs report progress: each step prints `✓ s2 research dr_worker_2 $0.08 42s` as it
completes, every iteration ends with a summary (`iteration 2 done: 3✓ 0✗ 2 pending ·
spend $0.31/$2.00 · model claude_haiku`), and the bare `mur deep-research` panel shows
the in-flight run (per-phase counts, running steps, spend vs budget) or the last run's
outcome. Progress lives in `~/.mur/fleet-state/deep-research/.run_progress.json` (best-effort;
never affects the run).

### Durable monitors

Work that outlives the turn that started it — a CI run, a MUR fleet run, a Codex
or Claude Code subprocess — gets a monitor that keeps checking until the source
gives a real answer, across daemon restarts:

```
mur monitor add --file wait-for-ci.yaml   # validates, probes once, registers
mur monitor list                          # what still needs attention
mur monitor show <id> --history           # evidence + append-only history
mur monitor cancel <id>                   # stop watching (never cancels the work)
mur monitor retry <id>                    # bring an exhausted monitor back
```

When something notable happens — a monitor stalls, crosses its soft deadline,
settles, goes unhealthy, or gives up — MUR says so once, in the daemon log and
(opt-in, `notifications.desktop: true`) as a desktop notification. Routine
polling says nothing. Each message names the monitor, its source, what is
known, when the next check is, and the single next step.

`unknown` — the API rate-limited us, the run is not found yet, the process is
gone without an exit record — is reported as exactly that, never as `failed`.
Deadlines (stalled 20m · soft 3h · hard 8h) count from when the work really
started. Design: `docs/superpowers/specs/2026-09-11-durable-monitor-design.md`.

Inside a murmur session the footer shows `monitor(n)` only when something
needs attention — exhausted, parked awaiting an action, stalled, or
unhealthy, never for ordinary healthy polling. `Ctrl+T` or `Alt+M` prints
the same list `mur monitor list` prints into the transcript.

A monitor can act on what it finds, not just report it. Recording evidence,
rescheduling the next check, and sending the notification happen on their
own. Anything that would change something outside MUR stops and asks first:
`mur monitor show <id>` prints the exact `mur channel approve monitor-<id>
<hitl-id>` line that releases it, the request is pinned to that one action,
and approving a different action never releases it. The wait has no clock —
a parked approval does not expire and does not count against anything.
Remediation itself does have a limit: MUR stops after
`policy.max_remediation_attempts` (default 3) remedies that did not fix
anything and marks the monitor `exhausted` rather than retrying forever. A
remedy that worked is not counted against that cap — succeeding is not
giving up — though a monitor still owing another gated action can reach the
cap on that one.
Kicking off downstream work and applying a known remedy are recognized
action types but do not execute yet — approving one is recorded, and MUR
says plainly that this build cannot carry it out, rather than pretending it
did.

Rerunning a failed CI job does execute — the one write action this build
carries out — but only for a GitHub Actions monitor, and only if its spec
grants a second credential, `source.write_credential_ref`, kept separate
from the read-only `credential_ref` used to observe the run: `mur monitor
add` refuses a spec that asks for `rerun` without one, at creation rather
than after someone approves it, because approving an action isn't the same
as consenting to a standing capability — and if the grant is there but
doesn't resolve on this machine, `add` says so then, instead of letting you
discover it after approving a remedy. It still stops and asks first like
any write-tier action, reruns only the jobs that failed rather than the
whole run, and the new run it starts is **not** itself monitored — register
a second monitor if you want that one watched too. A rerun MUR could not
even dispatch is a failed remediation attempt, not a verdict on the
original work. Monitors created before this release are unaffected: the
grant is checked when a monitor is added, so nothing already in the database
is refused or upgraded after the fact — an older monitor that asks for
`rerun` without a grant keeps running and its rerun is refused if anyone
approves one. MUR has no command for editing a monitor in place; cancel it
and add it again with the grant.

When the rules run out, MUR can ask a model what to do — **off by default**
(`monitor_resolver.enabled` in `config.yaml`), because a background daemon
that starts sending your monitor's context to a model on its own schedule,
with nobody watching, is not something an upgrade should decide for you.
While it is off nothing in that path runs and no request leaves the machine.

Switched on, it is consulted in exactly two situations, both of them "the
structured rules could not settle this": every remedy the spec listed for
that failure failed, or the spec listed none at all. It gets one consultation
per observation cycle — not per tick — and what it is shown is redacted
through the same chokepoint everything else MUR writes passes through.

What comes back is not trusted with much. It may name one of three verbs —
`notify`, `collect_logs`, `rerun` — and a reply naming anything else is
discarded whole rather than downgraded to something safe. It cannot state a
risk level: the tier still comes from the fixed table keyed on the action
type, so a model proposing `rerun` gets the same pinned approval request you
would get from a spec that asked for one, and you approve it the same way.
A consultation that fails or is refused is recorded and changes nothing —
the monitor settles without advice, because failing to get advice is not the
same as the work failing.

A `mur fleet run` registers its own monitor for you automatically. When
registration can't complete right away, the run still proceeds — it names
the run id and says whether tracking is queued to retry or has given up,
never leaving you thinking it is watched when it isn't. A queued
registration that keeps failing eventually gives up too; because there is no
monitor yet to show that on, look for it in the daemon log.

---

## 🔨 Build from source

```bash
git clone https://github.com/mur-run/mur.git && cd mur

cargo build --workspace          # debug build (GUI apps are workspace-excluded)
cargo nextest run --workspace    # tests (CI uses nextest)
cargo clippy --workspace -- -D warnings

./build.sh                       # release build with the embedded web dashboard
./install.sh                     # build + install to ~/.local/bin (no sudo; MUR_INSTALL_DIR overrides)
```

The two Tauri apps (`mur-hub-gui`, legacy `mur-agent-gui`) build from their own
manifests so the workspace build never pulls WebKitGTK / Cocoa / WebView2. The
iOS app builds with `mur-mobile-app/build-ios.sh`.

---

## 🧭 Roadmap

- **Cost-Router orchestrator** — route the easy ~80% of sub-tasks to local
  models and spawn a frontier coding agent (`claude` / `codex` / `agy`) only for
  the hard parts, as governed, sandboxed subprocesses. Spec merged; router in
  progress.
- **Fleet Sync (Pro)** — replicate your *evolved* fleet (profiles, skills,
  workflows, and their maturity/lifecycle state) across devices. Everything
  local stays free.
- **Hub on Windows / Linux**, and an **Android companion** from the same Rust
  mobile core.

Want to teach an agent something new? See
[Authoring Skills](docs/authoring-skills.md).

Design history lives in [`docs/superpowers/specs/`](docs/superpowers/specs) and
[`docs/architecture/runtime-overview.md`](docs/architecture/runtime-overview.md).

---

## 🤝 Contributing

Issues and PRs are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).

```bash
cargo nextest run --workspace && cargo clippy --workspace -- -D warnings
```

## 📄 License

[MIT](LICENSE)

---

<div align="center">
<sub><b>Local first. Native Rust. Yours.</b></sub><br/>
<sub><a href="https://mur.run">mur.run</a> · <a href="https://app.mur.run/docs/core">Docs</a> · <a href="https://github.com/mur-run/mur/releases">Releases</a> · <a href="https://github.com/mur-run/mur/issues">Issues</a></sub>
</div>