@femtomc/mu-agent 26.2.98 → 26.2.100

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 CHANGED
@@ -19,6 +19,26 @@ Bundled defaults now live as markdown files under `packages/agent/prompts/`:
19
19
 
20
20
  These are loaded by runtime code and are the single source of truth for default system prompts.
21
21
 
22
+ ## Bundled starter skills
23
+
24
+ Bundled starter skills live under `packages/agent/prompts/skills/` and are bootstrapped
25
+ into `~/.mu/skills/` (or `$MU_HOME/skills/`) when missing:
26
+
27
+ - `mu`
28
+ - `memory`
29
+ - `planning`
30
+ - `hierarchical-work-protocol`
31
+ - `subagents`
32
+ - `heartbeats`
33
+ - `crons`
34
+ - `setup-slack`
35
+ - `setup-discord`
36
+ - `setup-telegram`
37
+ - `setup-neovim`
38
+
39
+ Starter skills are version-synced by the CLI bootstrap path when users run `mu`
40
+ commands after upgrading.
41
+
22
42
  ## Install
23
43
 
24
44
  ```bash
@@ -105,6 +105,7 @@ export type OperatorBackendTurnInput = {
105
105
  };
106
106
  export interface MessagingOperatorBackend {
107
107
  runTurn(input: OperatorBackendTurnInput): Promise<OperatorBackendTurnResult>;
108
+ abortSession?(sessionId: string): Promise<boolean> | boolean;
108
109
  dispose?(): void | Promise<void>;
109
110
  }
110
111
  export type OperatorDecision = {
@@ -119,7 +120,7 @@ export type OperatorDecision = {
119
120
  operatorTurnId: string;
120
121
  } | {
121
122
  kind: "reject";
122
- reason: "operator_disabled" | "operator_action_disallowed" | "operator_invalid_output" | "context_missing" | "context_ambiguous" | "context_unauthorized" | "cli_validation_failed";
123
+ reason: "operator_disabled" | "operator_action_disallowed" | "operator_invalid_output" | "operator_cancelled" | "context_missing" | "context_ambiguous" | "context_unauthorized" | "cli_validation_failed";
123
124
  details?: string;
124
125
  operatorSessionId: string;
125
126
  operatorTurnId: string;
@@ -191,6 +192,7 @@ export { DEFAULT_OPERATOR_SYSTEM_PROMPT };
191
192
  export declare class PiMessagingOperatorBackend implements MessagingOperatorBackend {
192
193
  #private;
193
194
  constructor(opts?: PiMessagingOperatorBackendOpts);
195
+ abortSession(sessionId: string): Promise<boolean>;
194
196
  runTurn(input: OperatorBackendTurnInput): Promise<OperatorBackendTurnResult>;
195
197
  dispose(): void;
196
198
  }
@@ -1 +1 @@
1
- {"version":3,"file":"operator.d.ts","sourceRoot":"","sources":["../src/operator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"operator.d.ts","sourceRoot":"","sources":["../src/operator.ts"],"names":[],"mappings":"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"}
package/dist/operator.js CHANGED
@@ -7,6 +7,8 @@ import { createMuSession } from "./session_factory.js";
7
7
  import { DEFAULT_OPERATOR_SYSTEM_PROMPT } from "./default_prompts.js";
8
8
  const OPERATOR_RESPONSE_MAX_CHARS = 12_000;
9
9
  const SAFE_RESPONSE_RE = new RegExp(`^[\\s\\S]{1,${OPERATOR_RESPONSE_MAX_CHARS}}$`);
10
+ const OPERATOR_TIMEOUT_MIN_MS = 1_000;
11
+ const OPERATOR_TIMEOUT_HARD_CAP_MS = 10 * 60 * 1_000;
10
12
  export const OperatorApprovedCommandSchema = z.discriminatedUnion("kind", [
11
13
  z.object({ kind: z.literal("status") }),
12
14
  z.object({ kind: z.literal("ready") }),
@@ -161,12 +163,48 @@ function defaultTurnId() {
161
163
  return `turn-${crypto.randomUUID()}`;
162
164
  }
163
165
  function buildOperatorFailureFallbackMessage(code) {
166
+ const reasonLine = code === "operator_timeout"
167
+ ? "The operator turn exceeded the messaging timeout before a safe response was produced."
168
+ : code === "operator_busy"
169
+ ? "Another operator turn is already in progress for this conversation."
170
+ : code === "operator_empty_response"
171
+ ? "The operator completed without a usable response payload."
172
+ : code === "operator_cancelled"
173
+ ? "The operator turn was cancelled before completion."
174
+ : "An internal operator runtime/formatting error interrupted the turn.";
164
175
  return [
165
- "I ran into an internal operator formatting/runtime issue and could not complete that turn safely.",
176
+ "I could not complete that turn safely.",
166
177
  `Code: ${code}`,
167
- "You can retry, or use an explicit /mu command (for example: /mu status or /mu issue list).",
178
+ reasonLine,
179
+ "You can retry this request in plain conversational text.",
168
180
  ].join("\n");
169
181
  }
182
+ function classifyBackendFailureCode(err) {
183
+ if (!(err instanceof Error)) {
184
+ return "operator_backend_error";
185
+ }
186
+ const message = err.message.trim().toLowerCase();
187
+ if (message.includes("pi operator timeout") || message.includes("operator timeout")) {
188
+ return "operator_timeout";
189
+ }
190
+ if (message.includes("operator_empty_response")) {
191
+ return "operator_empty_response";
192
+ }
193
+ if (message.includes("agent is already processing")) {
194
+ return "operator_busy";
195
+ }
196
+ if (message.includes("aborted") || message.includes("cancelled")) {
197
+ return "operator_cancelled";
198
+ }
199
+ return "operator_backend_error";
200
+ }
201
+ function parseOperatorControlDirective(commandText) {
202
+ const normalized = commandText.trim().toLowerCase();
203
+ if (normalized === "cancel" || normalized === "abort" || normalized === "/mu cancel" || normalized === "/mu abort") {
204
+ return "cancel";
205
+ }
206
+ return null;
207
+ }
170
208
  function isAgentBusyError(err) {
171
209
  if (!(err instanceof Error)) {
172
210
  return false;
@@ -375,6 +413,7 @@ export class MessagingOperatorRuntime {
375
413
  #turnIdFactory;
376
414
  #conversationSessionStore;
377
415
  #sessionByConversation = new Map();
416
+ #suppressedCancelledTurnsBySession = new Map();
378
417
  constructor(opts) {
379
418
  this.#backend = opts.backend;
380
419
  this.#broker = opts.broker ?? new ApprovedCommandBroker();
@@ -414,6 +453,22 @@ export class MessagingOperatorRuntime {
414
453
  }
415
454
  return created;
416
455
  }
456
+ #markSuppressedCancelledTurn(sessionId) {
457
+ const next = (this.#suppressedCancelledTurnsBySession.get(sessionId) ?? 0) + 1;
458
+ this.#suppressedCancelledTurnsBySession.set(sessionId, next);
459
+ }
460
+ #consumeSuppressedCancelledTurn(sessionId) {
461
+ const current = this.#suppressedCancelledTurnsBySession.get(sessionId) ?? 0;
462
+ if (current <= 0) {
463
+ return false;
464
+ }
465
+ if (current === 1) {
466
+ this.#suppressedCancelledTurnsBySession.delete(sessionId);
467
+ return true;
468
+ }
469
+ this.#suppressedCancelledTurnsBySession.set(sessionId, current - 1);
470
+ return true;
471
+ }
417
472
  async handleInbound(opts) {
418
473
  const sessionId = await this.#resolveSessionId(opts.inbound, opts.binding);
419
474
  const turnId = this.#turnIdFactory();
@@ -433,6 +488,21 @@ export class MessagingOperatorRuntime {
433
488
  operatorTurnId: turnId,
434
489
  };
435
490
  }
491
+ const controlDirective = parseOperatorControlDirective(opts.inbound.command_text);
492
+ if (controlDirective === "cancel") {
493
+ const aborted = (await this.#backend.abortSession?.(sessionId)) ?? false;
494
+ if (aborted) {
495
+ this.#markSuppressedCancelledTurn(sessionId);
496
+ }
497
+ return {
498
+ kind: "response",
499
+ message: aborted
500
+ ? "Cancelled the in-flight operator turn for this conversation."
501
+ : "No in-flight operator turn is active for this conversation.",
502
+ operatorSessionId: sessionId,
503
+ operatorTurnId: turnId,
504
+ };
505
+ }
436
506
  let pendingFlashes = [];
437
507
  try {
438
508
  pendingFlashes = await loadPendingSessionFlashes({
@@ -475,9 +545,18 @@ export class MessagingOperatorRuntime {
475
545
  }
476
546
  }
477
547
  catch (err) {
548
+ const failureCode = classifyBackendFailureCode(err);
549
+ if (failureCode === "operator_cancelled" && this.#consumeSuppressedCancelledTurn(sessionId)) {
550
+ return {
551
+ kind: "reject",
552
+ reason: "operator_cancelled",
553
+ operatorSessionId: sessionId,
554
+ operatorTurnId: turnId,
555
+ };
556
+ }
478
557
  return {
479
558
  kind: "response",
480
- message: buildOperatorFailureFallbackMessage("operator_backend_error"),
559
+ message: buildOperatorFailureFallbackMessage(failureCode),
481
560
  operatorSessionId: sessionId,
482
561
  operatorTurnId: turnId,
483
562
  };
@@ -521,6 +600,7 @@ export class MessagingOperatorRuntime {
521
600
  }
522
601
  async stop() {
523
602
  this.#sessionByConversation.clear();
603
+ this.#suppressedCancelledTurnsBySession.clear();
524
604
  await this.#backend.dispose?.();
525
605
  await this.#conversationSessionStore?.stop?.();
526
606
  }
@@ -657,12 +737,14 @@ export class PiMessagingOperatorBackend {
657
737
  #persistSessions;
658
738
  #sessionDirForRepoRoot;
659
739
  #sessions = new Map();
740
+ #activePromptCountsBySession = new Map();
660
741
  constructor(opts = {}) {
661
742
  this.#provider = opts.provider;
662
743
  this.#model = opts.model;
663
744
  this.#thinking = opts.thinking ?? "minimal";
664
745
  this.#systemPrompt = opts.systemPrompt ?? DEFAULT_OPERATOR_SYSTEM_PROMPT;
665
- this.#timeoutMs = Math.max(1_000, Math.trunc(opts.timeoutMs ?? 90_000));
746
+ const requestedTimeoutMs = Math.max(OPERATOR_TIMEOUT_MIN_MS, Math.trunc(opts.timeoutMs ?? 90_000));
747
+ this.#timeoutMs = Math.min(requestedTimeoutMs, OPERATOR_TIMEOUT_HARD_CAP_MS);
666
748
  this.#extensionPaths = opts.extensionPaths ?? [];
667
749
  this.#sessionFactory = opts.sessionFactory ?? createMuSession;
668
750
  this.#nowMs = opts.nowMs ?? Date.now;
@@ -681,6 +763,7 @@ export class PiMessagingOperatorBackend {
681
763
  return;
682
764
  }
683
765
  this.#sessions.delete(sessionId);
766
+ this.#activePromptCountsBySession.delete(sessionId);
684
767
  try {
685
768
  entry.session.dispose();
686
769
  }
@@ -703,6 +786,18 @@ export class PiMessagingOperatorBackend {
703
786
  this.#disposeSession(sessionId);
704
787
  }
705
788
  }
789
+ #incrementActivePrompt(sessionId) {
790
+ const next = (this.#activePromptCountsBySession.get(sessionId) ?? 0) + 1;
791
+ this.#activePromptCountsBySession.set(sessionId, next);
792
+ }
793
+ #decrementActivePrompt(sessionId) {
794
+ const current = this.#activePromptCountsBySession.get(sessionId) ?? 0;
795
+ if (current <= 1) {
796
+ this.#activePromptCountsBySession.delete(sessionId);
797
+ return;
798
+ }
799
+ this.#activePromptCountsBySession.set(sessionId, current - 1);
800
+ }
706
801
  #sessionPersistence(repoRoot, sessionId) {
707
802
  if (!this.#persistSessions) {
708
803
  return undefined;
@@ -784,6 +879,27 @@ export class PiMessagingOperatorBackend {
784
879
  // best effort audit
785
880
  }
786
881
  }
882
+ async abortSession(sessionId) {
883
+ const activeCount = this.#activePromptCountsBySession.get(sessionId) ?? 0;
884
+ if (activeCount <= 0) {
885
+ return false;
886
+ }
887
+ const record = this.#sessions.get(sessionId);
888
+ if (!record) {
889
+ return false;
890
+ }
891
+ const abortFn = record.session.abort;
892
+ if (!abortFn) {
893
+ return false;
894
+ }
895
+ try {
896
+ await abortFn.call(record.session);
897
+ return true;
898
+ }
899
+ catch {
900
+ return false;
901
+ }
902
+ }
787
903
  async runTurn(input) {
788
904
  const sessionRecord = await this.#resolveSession(input.sessionId, input.inbound.repo_root);
789
905
  const session = sessionRecord.session;
@@ -820,11 +936,29 @@ export class PiMessagingOperatorBackend {
820
936
  });
821
937
  const promptText = buildOperatorPrompt(input);
822
938
  const promptOnce = async () => {
939
+ let timeoutHandle = null;
823
940
  const timeoutPromise = new Promise((_, reject) => {
824
- setTimeout(() => reject(new Error("pi operator timeout")), this.#timeoutMs);
941
+ timeoutHandle = setTimeout(() => {
942
+ const abortFn = session.abort;
943
+ if (abortFn) {
944
+ void abortFn.call(session).catch(() => {
945
+ // Best effort abort on timeout.
946
+ });
947
+ }
948
+ reject(new Error("pi operator timeout"));
949
+ }, this.#timeoutMs);
825
950
  });
826
- await Promise.race([session.prompt(promptText, { expandPromptTemplates: false }), timeoutPromise]);
951
+ try {
952
+ await Promise.race([session.prompt(promptText, { expandPromptTemplates: false }), timeoutPromise]);
953
+ }
954
+ finally {
955
+ if (timeoutHandle) {
956
+ clearTimeout(timeoutHandle);
957
+ timeoutHandle = null;
958
+ }
959
+ }
827
960
  };
961
+ this.#incrementActivePrompt(input.sessionId);
828
962
  try {
829
963
  try {
830
964
  await promptOnce();
@@ -848,6 +982,7 @@ export class PiMessagingOperatorBackend {
848
982
  throw err;
849
983
  }
850
984
  finally {
985
+ this.#decrementActivePrompt(input.sessionId);
851
986
  unsub();
852
987
  sessionRecord.lastUsedAtMs = Math.trunc(this.#nowMs());
853
988
  }
@@ -18,6 +18,7 @@ export type MuSession = {
18
18
  prompt: (text: string, options?: {
19
19
  expandPromptTemplates?: boolean;
20
20
  }) => Promise<void>;
21
+ abort?: () => Promise<void>;
21
22
  dispose: () => void;
22
23
  bindExtensions: (bindings: any) => Promise<void>;
23
24
  agent: {
@@ -1 +1 @@
1
- {"version":3,"file":"session_factory.d.ts","sourceRoot":"","sources":["../src/session_factory.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,wBAAwB,GAAG,WAAW,GAAG,iBAAiB,GAAG,KAAK,GAAG,MAAM,CAAC;AAExF,MAAM,MAAM,wBAAwB,GAAG;IACtC,IAAI,CAAC,EAAE,wBAAwB,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,OAAO,CAAC,EAAE,wBAAwB,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACvB,SAAS,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IAC1D,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,qBAAqB,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvF,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,cAAc,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,KAAK,EAAE;QAAE,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAC;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE;QAChB,SAAS,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;KAChC,CAAC;CACF,CAAC;AAkCF,wBAAsB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,CAAC,CA2CnF"}
1
+ {"version":3,"file":"session_factory.d.ts","sourceRoot":"","sources":["../src/session_factory.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,wBAAwB,GAAG,WAAW,GAAG,iBAAiB,GAAG,KAAK,GAAG,MAAM,CAAC;AAExF,MAAM,MAAM,wBAAwB,GAAG;IACtC,IAAI,CAAC,EAAE,wBAAwB,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,OAAO,CAAC,EAAE,wBAAwB,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACvB,SAAS,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IAC1D,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,qBAAqB,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvF,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,cAAc,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,KAAK,EAAE;QAAE,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAC;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE;QAChB,SAAS,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;KAChC,CAAC;CACF,CAAC;AAkCF,wBAAsB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,SAAS,CAAC,CA2CnF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@femtomc/mu-agent",
3
- "version": "26.2.98",
3
+ "version": "26.2.100",
4
4
  "description": "Shared operator runtime for mu assistant sessions and serve extensions.",
5
5
  "keywords": [
6
6
  "mu",
@@ -24,7 +24,7 @@
24
24
  "themes/**"
25
25
  ],
26
26
  "dependencies": {
27
- "@femtomc/mu-core": "26.2.98",
27
+ "@femtomc/mu-core": "26.2.100",
28
28
  "@mariozechner/pi-agent-core": "^0.53.0",
29
29
  "@mariozechner/pi-ai": "^0.53.0",
30
30
  "@mariozechner/pi-coding-agent": "^0.53.0",
@@ -0,0 +1,204 @@
1
+ ---
2
+ name: crons
3
+ description: "Runs cron-program lifecycle workflows for wall-clock scheduling, recurring automation, and scheduler diagnostics. Use when creating, tuning, or repairing time-based automation."
4
+ ---
5
+
6
+ # crons
7
+
8
+ Use this skill when the user asks to schedule, inspect, tune, or debug `mu cron` automation.
9
+
10
+ ## Contents
11
+
12
+ - [Core contract](#core-contract)
13
+ - [Preflight checks](#preflight-checks)
14
+ - [Schedule kinds](#schedule-kinds)
15
+ - [Cron lifecycle workflow](#cron-lifecycle-workflow)
16
+ - [Prompt design for scheduled runs](#prompt-design-for-scheduled-runs)
17
+ - [Diagnostics and recovery](#diagnostics-and-recovery)
18
+ - [Evaluation scenarios](#evaluation-scenarios)
19
+
20
+ ## Core contract
21
+
22
+ 1. **Use explicit schedule semantics**
23
+ - Choose the right schedule kind (`at`, `every`, or `cron`) instead of overloading one pattern.
24
+ - Keep schedule intent readable in `title` and `reason` fields.
25
+
26
+ 2. **CLI-first lifecycle control**
27
+ - Create/update/trigger/enable/disable/delete via `mu cron ...` commands.
28
+ - Do not hand-edit `cron.jsonl`.
29
+
30
+ 3. **Bounded execution prompts**
31
+ - Scheduled runs should execute one bounded control-loop pass and exit.
32
+ - Avoid prompts that imply unbounded autonomous execution.
33
+
34
+ 4. **Read -> mutate -> verify**
35
+ - Inspect scheduler/program state first.
36
+ - After changes, re-read and trigger a smoke run.
37
+
38
+ ## Preflight checks
39
+
40
+ ```bash
41
+ mu status --pretty
42
+ mu control status --pretty
43
+ mu control identities --all --pretty
44
+ mu cron stats --pretty
45
+ mu cron list --limit 20 --pretty
46
+ ```
47
+
48
+ If user expects channel delivery, verify linked operator identity for that channel
49
+ before diagnosing cron execution as broken.
50
+
51
+ ## Schedule kinds
52
+
53
+ 1. **One-shot (`at`)**
54
+ - Run once at an absolute time.
55
+ - Flags: `--schedule-kind at --at <iso8601>` (or `--at-ms <epoch-ms>`)
56
+
57
+ 2. **Fixed interval (`every`)**
58
+ - Run repeatedly on fixed millisecond cadence.
59
+ - Flags: `--schedule-kind every --every-ms <ms>`
60
+ - Optional alignment: `--anchor-ms <epoch-ms>`
61
+
62
+ 3. **Cron expression (`cron`)**
63
+ - Run by cron expression.
64
+ - Flags: `--schedule-kind cron --expr "<cron-expr>" --tz <timezone>`
65
+
66
+ ## Cron lifecycle workflow
67
+
68
+ ### 1) Inspect scheduler and programs
69
+
70
+ ```bash
71
+ mu cron stats --pretty
72
+ mu cron list --limit 20 --pretty
73
+ mu cron get <program-id> --pretty
74
+ ```
75
+
76
+ Use `--json --pretty` for full records.
77
+
78
+ ### 2) Create a cron program
79
+
80
+ One-shot example:
81
+
82
+ ```bash
83
+ mu cron create \
84
+ --title "One-shot audit" \
85
+ --schedule-kind at \
86
+ --at 2026-02-22T02:00:00Z \
87
+ --reason oneshot_audit
88
+ ```
89
+
90
+ Fixed interval example:
91
+
92
+ ```bash
93
+ mu cron create \
94
+ --title "Every 10m check" \
95
+ --schedule-kind every \
96
+ --every-ms 600000 \
97
+ --reason periodic_check
98
+ ```
99
+
100
+ Cron-expression example:
101
+
102
+ ```bash
103
+ mu cron create \
104
+ --title "Nightly maintenance" \
105
+ --schedule-kind cron \
106
+ --expr "0 2 * * *" \
107
+ --tz UTC \
108
+ --reason nightly_maintenance
109
+ ```
110
+
111
+ ### 3) Update schedule or enablement
112
+
113
+ ```bash
114
+ mu cron update <program-id> --enabled false
115
+ mu cron update <program-id> --schedule-kind every --every-ms 300000
116
+ mu cron update <program-id> --schedule-kind cron --expr "0 3 * * *" --tz UTC
117
+ mu cron enable <program-id>
118
+ mu cron disable <program-id>
119
+ ```
120
+
121
+ ### 4) Trigger smoke run
122
+
123
+ ```bash
124
+ mu cron trigger <program-id> --reason smoke_test
125
+ ```
126
+
127
+ Then verify:
128
+
129
+ ```bash
130
+ mu cron get <program-id> --pretty
131
+ mu store tail events --limit 40 --pretty
132
+ mu store tail cp_operator_turns --limit 40 --pretty
133
+ ```
134
+
135
+ ### 5) Delete obsolete programs
136
+
137
+ ```bash
138
+ mu cron delete <program-id>
139
+ ```
140
+
141
+ ## Prompt design for scheduled runs
142
+
143
+ Use concise prompts with explicit bounded-pass behavior.
144
+
145
+ Example:
146
+
147
+ ```text
148
+ Review open issues for root <root-id>. Perform exactly one bounded step:
149
+ select one ready task (or report blocked), apply one action, verify state,
150
+ post concise summary, then exit.
151
+ ```
152
+
153
+ For DAG execution workloads, combine with:
154
+ - `planning`
155
+ - `hierarchical-work-protocol`
156
+ - `subagents`
157
+ - `heartbeats` (for short-cadence wake loops)
158
+
159
+ ## Diagnostics and recovery
160
+
161
+ When cron automation appears stalled or misfiring:
162
+
163
+ 1. Confirm scheduler + program state:
164
+
165
+ ```bash
166
+ mu cron stats --pretty
167
+ mu cron list --enabled true --limit 50 --pretty
168
+ mu cron get <program-id> --pretty
169
+ ```
170
+
171
+ 2. Trigger manually to separate scheduler timing issues from execution issues:
172
+
173
+ ```bash
174
+ mu cron trigger <program-id> --reason manual_recovery_test
175
+ ```
176
+
177
+ 3. Inspect runtime evidence:
178
+
179
+ ```bash
180
+ mu store tail events --limit 60 --pretty
181
+ mu store tail cp_operator_turns --limit 60 --pretty
182
+ mu store tail cp_outbox --limit 40 --pretty
183
+ ```
184
+
185
+ 4. Apply smallest recovery step:
186
+ - fix schedule-kind/flags mismatch
187
+ - correct timezone (`--tz`)
188
+ - simplify prompt scope
189
+ - toggle enablement (`disable` -> `enable`)
190
+ - relink channel identity if delivery is absent
191
+
192
+ ## Evaluation scenarios
193
+
194
+ 1. **One-shot schedule execution (`at`)**
195
+ - Setup: `mu cron create --schedule-kind at --at <future-iso>`.
196
+ - Expected: program executes once, records deterministic status, then does not re-fire.
197
+
198
+ 2. **Interval schedule retune (`every`)**
199
+ - Setup: active interval schedule at 10m.
200
+ - Expected: `mu cron update ... --every-ms 300000` changes cadence to 5m without recreating program ID.
201
+
202
+ 3. **Cron expression + timezone correctness**
203
+ - Setup: cron expression schedule with explicit timezone.
204
+ - Expected: next-run alignment matches timezone expectations; manual trigger succeeds and logs are auditable.