@neotx/core 0.1.0-alpha.6 → 0.1.0-alpha.9

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/dist/index.d.ts CHANGED
@@ -835,6 +835,8 @@ interface OrchestratorOptions {
835
835
  journalDir?: string | undefined;
836
836
  builtInWorkflowDir?: string | undefined;
837
837
  customWorkflowDir?: string | undefined;
838
+ /** Skip orphan recovery on start — workers should set this to true to avoid false orphan detection on concurrent launches. */
839
+ skipOrphanRecovery?: boolean | undefined;
838
840
  }
839
841
  declare class Orchestrator extends NeoEventEmitter {
840
842
  private readonly config;
@@ -858,6 +860,7 @@ declare class Orchestrator extends NeoEventEmitter {
858
860
  private _costToday;
859
861
  private _startedAt;
860
862
  private _drainResolve;
863
+ private readonly skipOrphanRecovery;
861
864
  constructor(config: NeoConfig, options?: OrchestratorOptions);
862
865
  registerWorkflow(definition: WorkflowDefinition): void;
863
866
  registerAgent(agent: ResolvedAgent): void;
package/dist/index.js CHANGED
@@ -2371,6 +2371,7 @@ var Orchestrator = class extends NeoEventEmitter {
2371
2371
  _costToday = 0;
2372
2372
  _startedAt = 0;
2373
2373
  _drainResolve = null;
2374
+ skipOrphanRecovery;
2374
2375
  constructor(config, options = {}) {
2375
2376
  super();
2376
2377
  this.config = config;
@@ -2378,6 +2379,7 @@ var Orchestrator = class extends NeoEventEmitter {
2378
2379
  this.journalDir = options.journalDir ?? getJournalsDir();
2379
2380
  this.builtInWorkflowDir = options.builtInWorkflowDir;
2380
2381
  this.customWorkflowDir = options.customWorkflowDir;
2382
+ this.skipOrphanRecovery = options.skipOrphanRecovery ?? false;
2381
2383
  for (const repo of config.repos) {
2382
2384
  const resolvedPath = path11.resolve(repo.path);
2383
2385
  const normalizedRepo = { ...repo, path: resolvedPath };
@@ -2495,7 +2497,9 @@ var Orchestrator = class extends NeoEventEmitter {
2495
2497
  this.registerWorkflow(workflow);
2496
2498
  }
2497
2499
  }
2498
- await this.recoverOrphanedRuns();
2500
+ if (!this.skipOrphanRecovery) {
2501
+ await this.recoverOrphanedRuns();
2502
+ }
2499
2503
  await mkdir6(this.config.sessions.dir, { recursive: true });
2500
2504
  }
2501
2505
  async shutdown() {
@@ -3573,7 +3577,16 @@ async function appendLogBuffer(dir, entry) {
3573
3577
  }
3574
3578
 
3575
3579
  // src/supervisor/prompt-builder.ts
3576
- var ROLE = `You are the neo autonomous supervisor. You orchestrate developer agents across repositories. You make decisions autonomously, act on events, and yield quickly.`;
3580
+ var ROLE = `You are the neo autonomous supervisor \u2014 a stateless dispatch controller.
3581
+
3582
+ You receive state (events, memory, work queue) and produce actions (tool calls).
3583
+
3584
+ <behavioral-contract>
3585
+ - Your ONLY visible output is \`neo log\` commands. The TUI shows these and nothing else.
3586
+ - Your text output is NEVER shown to anyone \u2014 every token of text is wasted cost.
3587
+ - Produce tool calls, not explanations. Do not narrate your reasoning.
3588
+ - You NEVER modify code \u2014 that is the agents' job.
3589
+ </behavioral-contract>`;
3577
3590
  var COMMANDS = `### Dispatching agents
3578
3591
  \`\`\`bash
3579
3592
  neo run <agent> --prompt "..." --repo <path> --branch <name> [--priority critical|high|medium|low] [--meta '<json>']
@@ -3604,6 +3617,8 @@ neo agents # list available agents
3604
3617
  neo memory write --type fact --scope /path "Stable fact about repo"
3605
3618
  neo memory write --type focus --expires 2h "Current working context"
3606
3619
  neo memory write --type procedure --scope /path "How to do X"
3620
+ neo memory write --type task --scope /path --severity high --category "neo runs <id>" "Task description"
3621
+ neo memory update <id> --outcome in_progress|done|blocked|abandoned
3607
3622
  neo memory forget <id>
3608
3623
  neo memory search "keyword"
3609
3624
  neo memory list --type fact
@@ -3613,107 +3628,109 @@ neo memory list --type fact
3613
3628
  \`\`\`bash
3614
3629
  neo log <type> "<message>" # visible in TUI only
3615
3630
  \`\`\``;
3631
+ var COMMANDS_COMPACT = `### Commands (reference)
3632
+ \`neo run <agent> --prompt "..." --repo <path> --branch <name> [--meta '<json>']\`
3633
+ \`neo runs [--short | <runId>]\` \xB7 \`neo cost --short\` \xB7 \`neo agents\`
3634
+ \`neo memory write|update|forget|search|list\` \xB7 \`neo log <type> "<msg>"\`
3635
+ Always read output after architect/refiner: \`neo runs <runId>\`.`;
3616
3636
  var HEARTBEAT_RULES = `### Heartbeat lifecycle
3617
- 1. **Check work queue FIRST** \u2014 if you have pending tasks, work on the next one before looking for new work
3618
- 2. Process incoming events (messages, run completions)
3619
- 3. Follow up on pending work (CI checks, deferred dispatches) with \`neo runs\` or \`gh pr checks\`
3620
- 4. Make decisions and dispatch agents
3621
- 5. Update task status (\`neo memory update <id> --outcome in_progress|done|blocked\`) and log decisions
3622
- 6. Yield \u2014 each heartbeat should take seconds, not minutes
3623
3637
 
3624
- **CRITICAL**: Your work queue IS your plan. Do not re-plan work that is already in the queue. When an planner agent produces tasks, create them with \`neo memory write --type task\`, then dispatch them one by one in subsequent heartbeats. Mark each task \`in_progress\` when dispatching, \`done\` when the run completes, \`blocked\` if stuck.
3638
+ <decision-tree>
3639
+ 1. DEDUP FIRST \u2014 check focus for PROCESSED entries. Skip any runId already processed.
3640
+ 2. PENDING TASKS? \u2014 dispatch the next eligible task from work queue. Do not re-plan.
3641
+ 3. EVENTS? \u2014 process run completions, messages, webhooks. Parse agent JSON output.
3642
+ 4. FOLLOW-UPS? \u2014 check CI (\`gh pr checks\`), deferred dispatches (\`neo runs\`).
3643
+ 5. DISPATCH \u2014 route work to agents. Mark tasks \`in_progress\`, add ACTIVE to focus.
3644
+ 6. YIELD \u2014 log your decisions and yield. Do not poll. Completions arrive at future heartbeats.
3645
+ </decision-tree>
3625
3646
 
3626
- After dispatching with \`neo run\`, mark the task \`in_progress\`, note the runId in your focus, and yield. Do NOT poll in a loop.
3627
- Completion events arrive at future heartbeats \u2014 react then.
3628
- If you deferred work (e.g. "CI pending"), you MUST check it at the next heartbeat.`;
3647
+ <rules>
3648
+ - Work queue IS your plan. Never re-plan existing tasks.
3649
+ - Maximize parallelism: dispatch independent tasks in the same heartbeat.
3650
+ - After dispatch: update focus, yield immediately. Do NOT wait for results.
3651
+ - Deferred work (CI pending): MUST check at next heartbeat.
3652
+ </rules>`;
3629
3653
  var REPORTING_RULES = `### Reporting
3630
- \`neo log\` is your ONLY visible output \u2014 the TUI shows these and nothing else.
3631
- - \`neo log decision "..."\` \u2014 why you chose this route
3632
- - \`neo log action "..."\` \u2014 what you dispatched/did
3633
- - \`neo log discovery "..."\` \u2014 ephemeral observations
3634
- - 1-3 sentences per log. Pack maximum info: ticket, agent, branch, runId, cost, PR#. No markdown.
3635
3654
 
3636
- Your text output is NEVER shown to users.`;
3637
- var MEMORY_RULES = `### Memory \u2014 types and when to use each
3655
+ \`neo log\` is your ONLY visible output. Use telegraphic format.
3638
3656
 
3639
- | Type | What | When | TTL |
3640
- |------|------|------|-----|
3641
- | \`fact\` | Stable truth that affects decisions | After discovering something that changes how you dispatch or review | Permanent (decays if unused) |
3642
- | \`procedure\` | How-to recipe learned from failure | After the same issue occurs 3+ times | Permanent (decays if unused) |
3643
- | \`focus\` | Structured working context | After every dispatch, deferral, or priority change | Expires (always set --expires) |
3644
- | \`feedback\` | Recurring review pattern | After seeing the same reviewer complaint 3+ times | Permanent |
3645
- | \`episode\` | Run outcome | Auto-created on run completion \u2014 do NOT write manually | Permanent |
3646
- | \`task\` | Planned work item | After architect output or decomposition | Until done/abandoned |
3657
+ <log-format>
3658
+ neo log decision "<ticket> \u2192 <action> | <1-line reason>"
3659
+ neo log action "<agent> <repo>:<branch> run:<runId> | <context>"
3660
+ neo log discovery "<what> in <where>"
3661
+ </log-format>
3647
3662
 
3648
- #### What to store
3649
- - Architectural truths that affect future dispatch decisions (CI config, build requirements, tooling)
3650
- - Procedures learned from repeated failures (3+ occurrences of the same issue)
3651
- - Active working context in structured focus format (see below)
3663
+ <examples>
3664
+ <example type="good">
3665
+ neo log decision "YC-42 \u2192 developer | clear spec, complexity 3"
3666
+ neo log action "developer standards:feat/YC-42-auth run:5900a64a | task T1"
3667
+ neo log discovery "CI requires node 20 in api-service"
3668
+ </example>
3669
+ <example type="bad">
3670
+ neo log plan "Good! Now let me check the status and update things accordingly."
3671
+ neo log decision "Heartbeat #309: Idle cycle - no action required. All 4 repositories stable."
3672
+ neo log action "I've dispatched a developer agent to work on the authentication feature."
3673
+ </example>
3674
+ </examples>`;
3675
+ var MEMORY_RULES_CORE = `### Memory
3652
3676
 
3653
- #### What NOT to store
3654
- - File counts, line numbers, or structural details derivable from code (\`ls\` or \`cat package.json\` can answer it)
3655
- - Completed work details \u2014 once a PR is merged, forget the related facts unless they are reusable
3656
- - Agent output details already available via \`neo runs <id>\`
3657
- - Facts about repos where no work is currently planned
3677
+ <memory-types>
3678
+ | Type | Store when | TTL |
3679
+ |------|-----------|-----|
3680
+ | \`fact\` | Stable truth affecting dispatch decisions | Permanent (decays) |
3681
+ | \`procedure\` | Same failure 3+ times | Permanent |
3682
+ | \`focus\` | After every dispatch/deferral | --expires required |
3683
+ | \`task\` | Any planned work (tickets, decompositions, follow-ups) | Until done/abandoned |
3684
+ | \`feedback\` | Same review complaint 3+ times | Permanent |
3685
+ </memory-types>
3658
3686
 
3659
- #### Focus format (MANDATORY)
3660
- Focus entries MUST use this structured format \u2014 no free-form paragraphs:
3661
- \`\`\`
3662
- ACTIVE: <runId> <agent> "<task>" branch:<name>
3663
- PENDING: <taskId> "<description>" depends:<taskId>
3664
- WAITING: <what> since:HB<N>
3665
- PROCESSED: <runId> \u2192 <outcome> PR#<N>
3666
- \`\`\`
3687
+ <memory-rules>
3688
+ - Focus MUST use structured format: ACTIVE/PENDING/WAITING/PROCESSED lines only.
3689
+ - NEVER store: file counts, line numbers, completed work details, data available via \`neo runs <id>\`.
3690
+ - After PR merge: forget related facts unless they are reusable architectural truths.
3691
+ - Pattern escalation: same failure 3+ times \u2192 write a \`procedure\`.
3692
+ - Every memory that references external context MUST include a retrieval command (in \`--category\` for tasks, in content for facts/procedures). You are stateless \u2014 if you can't retrieve it later, don't store it.
3693
+ </memory-rules>
3667
3694
 
3668
- \`\`\`bash
3669
- # Focus: structured working context (always set --expires)
3670
- neo memory write --type focus --expires 2h "ACTIVE: 5900a64a developer 'T1: schema+store' branch:feat/task-queue
3671
- PENDING: T2 'CLI --outcome flag' depends:T1
3672
- PENDING: T3+T4 'prompt injection' depends:T1"
3695
+ <task-workflow>
3696
+ Tasks are your work queue. The work queue section above shows them with markers (\`\u25CB\` pending, \`[ACTIVE]\` in_progress, \`[BLOCKED]\` blocked).
3673
3697
 
3674
- # Facts: truths that change how you work (NOT trivia)
3675
- neo memory write --type fact --scope /path/to/repo "CI requires pnpm build before push \u2014 no auto-rebuild in pipeline"
3676
- neo memory write --type fact --scope /path/to/repo "Biome enforces complexity max 20 \u2014 extract helpers for large functions"
3698
+ Create a task for any planned work: incoming tickets, architect decompositions, refiner sub-tickets, follow-up actions, CI fixes.
3699
+ - \`--severity critical|high|medium|low\` \u2014 dispatch highest severity first
3700
+ - \`--tags "initiative:<name>"\` \u2014 groups related tasks (shown as [initiative] headers in queue)
3701
+ - \`--tags "depends:mem_<id>"\` \u2014 task cannot start until dependency is done
3702
+ - \`--category\` \u2014 **MANDATORY** \u2014 the command to retrieve context for this task (shown as \`\u2192 <command>\` in queue)
3677
3703
 
3678
- # Procedures: recipes learned from failure (write after 3+ occurrences)
3679
- neo memory write --type procedure --scope /path/to/repo "Before re-dispatching after orphan failure, check if PR already merged with gh pr view"
3680
- neo memory write --type procedure --scope /path/to/repo "Integration tests require DATABASE_URL env var \u2014 agent must set it"
3704
+ **Context retrieval rule**: every task and relevant memory MUST include a way for you to access its source context at a future heartbeat. You are stateless \u2014 without this, you lose the context.
3705
+ - Agent output: \`--category "neo runs <runId>"\`
3706
+ - Note/plan: \`--category "cat notes/plan-feature.md"\`
3707
+ - Notion ticket: \`--category "API-retrieve-a-page <notionPageId>"\`
3708
+ - Architect decomposition: \`--category "neo runs <architectRunId>"\` (contains milestones + tasks)
3681
3709
 
3682
- # Feedback: recurring reviewer complaints
3683
- neo memory write --type feedback --scope /path/to/repo --category input_validation "Always validate user input at controller boundaries"
3710
+ Lifecycle: create \u2192 \`neo memory update <id> --outcome in_progress\` (on dispatch) \u2192 \`done\` (on success) / \`blocked\` (on failure, will retry) / \`abandoned\` (terminal, won't retry)
3684
3711
 
3685
- # Tasks: work queue items from architect/refiner output
3686
- neo memory write --type task --scope /path/to/repo --severity high --category "neo runs abc123" "T1: Implement auth middleware"
3687
- neo memory write --type task --scope /path/to/repo --severity medium --tags "initiative:auth-v2,depends:mem_abc" --category "cat notes/plan-auth.md" "T2: Add JWT validation"
3712
+ Dispatch rule: pick the highest-severity task with no unmet dependencies. Dispatch independent tasks in parallel. Before dispatching, run the \`--category\` command to retrieve task context.
3713
+ </task-workflow>
3688
3714
 
3689
- # Update task status as you work
3690
- neo memory update <id> --outcome in_progress
3691
- neo memory update <id> --outcome done
3692
- neo memory update <id> --outcome blocked
3693
- neo memory update <id> --outcome abandoned
3715
+ <focus-format>
3716
+ ACTIVE: <runId> <agent> "<task>" branch:<name>
3717
+ PENDING: <taskId> "<description>" depends:<taskId>
3718
+ WAITING: <what> since:HB<N>
3719
+ PROCESSED: <runId> \u2192 <outcome> PR#<N>
3720
+ </focus-format>
3694
3721
 
3695
- # Forget stale entries
3722
+ **Notes** (\`notes/\`, via Bash): use for detailed multi-page plans that span multiple heartbeats. After creating a plan, write a focus summary with \`--category "cat notes/<file>"\`. Delete notes when done.`;
3723
+ var MEMORY_RULES_EXAMPLES = `<memory-commands>
3724
+ neo memory write --type focus --expires 2h "ACTIVE: 5900a64a developer 'T1' branch:feat/x"
3725
+ neo memory write --type fact --scope /repo "CI requires pnpm build \u2014 discovered in run abc123"
3726
+ neo memory write --type procedure --scope /repo "Check gh pr view before re-dispatch"
3727
+ neo memory write --type task --scope /repo --severity high --category "neo runs abc123" --tags "initiative:auth-v2,depends:mem_xyz" "T1: Auth middleware"
3728
+ neo memory update <id> --outcome in_progress|done|blocked|abandoned
3696
3729
  neo memory forget <id>
3697
-
3698
- # Search across all memories (semantic)
3699
- neo memory search "database setup"
3700
- \`\`\`
3701
-
3702
- #### Work queue workflow (Tasks)
3703
- After architect/refiner output, create tasks with \`neo memory write --type task\`:
3704
- - Include \`--category\` with the command to retrieve context (\`neo runs <id>\` or \`cat notes/<file>\`)
3705
- - Use \`--tags depends:mem_<id>\` for task dependencies
3706
- - Use \`--tags initiative:<name>\` to group tasks across repos
3707
- - Update status with \`neo memory update <id> --outcome in_progress|done|blocked|abandoned\`
3708
- - The queue is shown at every heartbeat \u2014 you will not lose track
3709
-
3710
- #### Pattern escalation
3711
- When you encounter the same failure or issue 3+ times, ALWAYS write a \`procedure\` memory so you handle it automatically next time. Do not re-discover the same problem repeatedly.
3712
-
3713
- #### Event deduplication
3714
- After processing a run completion, record the runId as PROCESSED in your focus. If the same runId appears in future events, skip it \u2014 do not re-analyze.
3715
-
3716
- **Notes** (\`notes/\`, via Bash): use for detailed multi-page plans, analysis, and checklists that span multiple heartbeats. After creating or reading a plan, write a focus summary: "Plan: <name> | Tasks: T1-T5 | Current: T1 | Next: T2 (depends T1) | Ref: cat notes/<file>". Delete notes when done.`;
3730
+ </memory-commands>`;
3731
+ function getCommandsSection(heartbeatCount) {
3732
+ return heartbeatCount <= 3 ? COMMANDS : COMMANDS_COMPACT;
3733
+ }
3717
3734
  function buildContextSections(opts) {
3718
3735
  const parts = [];
3719
3736
  if (opts.repos.length > 0) {
@@ -3874,7 +3891,7 @@ function getBasename(scopePath) {
3874
3891
  const parts = scopePath.split("/");
3875
3892
  return parts[parts.length - 1] || scopePath;
3876
3893
  }
3877
- var SIGNIFICANT_TYPES = /* @__PURE__ */ new Set(["decision", "action", "dispatch", "error", "plan"]);
3894
+ var SIGNIFICANT_TYPES = /* @__PURE__ */ new Set(["decision", "action", "dispatch", "error"]);
3878
3895
  function buildRecentActionsSection(entries) {
3879
3896
  const significant = entries.filter((e) => SIGNIFICANT_TYPES.has(e.type));
3880
3897
  if (significant.length === 0) return "";
@@ -3897,7 +3914,7 @@ function buildEventsSection(grouped) {
3897
3914
  const { messages, webhooks, runCompletions } = grouped;
3898
3915
  const totalEvents = messages.length + webhooks.length + runCompletions.length;
3899
3916
  if (totalEvents === 0) {
3900
- return "No new events. Idle heartbeat \u2014 check on active runs if any, or wait.";
3917
+ return "No new events.";
3901
3918
  }
3902
3919
  const parts = [];
3903
3920
  for (const msg of messages) {
@@ -3928,15 +3945,33 @@ ${JSON.stringify(event.data.payload ?? {}, null, 2)}
3928
3945
  return `Internal event: ${event.eventKind}`;
3929
3946
  }
3930
3947
  }
3948
+ function isIdleHeartbeat(opts) {
3949
+ const { messages, webhooks, runCompletions } = opts.grouped;
3950
+ const totalEvents = messages.length + webhooks.length + runCompletions.length;
3951
+ const hasWork = buildWorkQueueSection(opts.memories) !== "";
3952
+ return totalEvents === 0 && opts.activeRuns.length === 0 && !hasWork;
3953
+ }
3954
+ function buildIdlePrompt(opts) {
3955
+ return `<role>
3956
+ ${ROLE}
3957
+ Heartbeat #${opts.heartbeatCount}
3958
+ </role>
3959
+
3960
+ <context>
3961
+ No events. No active runs. No pending tasks.
3962
+ Budget: $${opts.budgetStatus.todayUsd.toFixed(2)} / $${opts.budgetStatus.capUsd.toFixed(2)} (${opts.budgetStatus.remainingPct.toFixed(0)}% remaining)
3963
+ </context>
3964
+
3965
+ <directive>
3966
+ Nothing to do. Run \`neo log discovery "idle"\` and yield. Do not produce any other output.
3967
+ </directive>`;
3968
+ }
3931
3969
  function buildStandardPrompt(opts) {
3932
3970
  const sections = [];
3933
3971
  sections.push(`<role>
3934
3972
  ${ROLE}
3935
3973
  Heartbeat #${opts.heartbeatCount}
3936
3974
  </role>`);
3937
- sections.push(`<commands>
3938
- ${COMMANDS}
3939
- </commands>`);
3940
3975
  const contextParts = [];
3941
3976
  const workQueue = buildWorkQueueSection(opts.memories);
3942
3977
  if (workQueue) {
@@ -3957,16 +3992,21 @@ ${buildEventsSection(opts.grouped)}`);
3957
3992
  sections.push(`<context>
3958
3993
  ${contextParts.join("\n\n")}
3959
3994
  </context>`);
3995
+ sections.push(`<reference>
3996
+ ${getCommandsSection(opts.heartbeatCount)}
3997
+ </reference>`);
3960
3998
  const instructionParts = [];
3961
3999
  instructionParts.push(HEARTBEAT_RULES);
3962
4000
  instructionParts.push(REPORTING_RULES);
3963
- instructionParts.push(MEMORY_RULES);
4001
+ instructionParts.push(MEMORY_RULES_CORE);
3964
4002
  if (opts.customInstructions) {
3965
4003
  instructionParts.push(`### Custom instructions
3966
4004
  ${opts.customInstructions}`);
3967
4005
  }
4006
+ const { messages, webhooks, runCompletions } = opts.grouped;
4007
+ const hasEvents = messages.length + webhooks.length + runCompletions.length > 0;
3968
4008
  instructionParts.push(
3969
- "This is a standard heartbeat. Focus on processing events and dispatching work. If you have tasks in your work queue, dispatch the next eligible one."
4009
+ hasEvents ? "Process events, dispatch eligible work, yield. Each heartbeat costs ~$0.10 \u2014 be efficient." : "No events. If pending work exists, dispatch it. Otherwise yield immediately."
3970
4010
  );
3971
4011
  sections.push(`<instructions>
3972
4012
  ${instructionParts.join("\n\n")}
@@ -3979,9 +4019,6 @@ function buildConsolidationPrompt(opts) {
3979
4019
  ${ROLE}
3980
4020
  Heartbeat #${opts.heartbeatCount} (CONSOLIDATION)
3981
4021
  </role>`);
3982
- sections.push(`<commands>
3983
- ${COMMANDS}
3984
- </commands>`);
3985
4022
  const contextParts = [];
3986
4023
  const workQueueConsolidation = buildWorkQueueSection(opts.memories);
3987
4024
  if (workQueueConsolidation) {
@@ -4002,10 +4039,14 @@ ${buildEventsSection(opts.grouped)}`);
4002
4039
  sections.push(`<context>
4003
4040
  ${contextParts.join("\n\n")}
4004
4041
  </context>`);
4042
+ sections.push(`<reference>
4043
+ ${getCommandsSection(opts.heartbeatCount)}
4044
+ </reference>`);
4005
4045
  const instructionParts = [];
4006
4046
  instructionParts.push(HEARTBEAT_RULES);
4007
4047
  instructionParts.push(REPORTING_RULES);
4008
- instructionParts.push(MEMORY_RULES);
4048
+ instructionParts.push(MEMORY_RULES_CORE);
4049
+ instructionParts.push(MEMORY_RULES_EXAMPLES);
4009
4050
  if (opts.customInstructions) {
4010
4051
  instructionParts.push(`### Custom instructions
4011
4052
  ${opts.customInstructions}`);
@@ -4022,13 +4063,7 @@ If there IS active work, your job:
4022
4063
  2. **Update focus** \u2014 rewrite focus using the MANDATORY structured format (ACTIVE/PENDING/WAITING/PROCESSED). Remove resolved items. Add new context.
4023
4064
  3. **Pattern escalation** \u2014 if agents hit the same issue 3+ times (check recent actions), write a \`procedure\` to prevent recurrence.
4024
4065
  4. **Prune completed work** \u2014 if a PR is merged or an initiative is done, forget related facts that are no longer actionable. Keep only reusable architectural truths.
4025
-
4026
- \`\`\`bash
4027
- neo memory write --type procedure --scope /repo "Before re-dispatching after orphan, check gh pr view first"
4028
- neo memory write --type focus --expires 4h "ACTIVE: abc123 developer 'T3: prompt injection' branch:feat/task-queue
4029
- PROCESSED: 5900a64a \u2192 PR#70 APPROVED"
4030
- neo memory forget <stale-id>
4031
- \`\`\``
4066
+ 5. **Prune done tasks** \u2014 forget tasks with outcome \`done\` or \`abandoned\` older than 7 days.`
4032
4067
  );
4033
4068
  sections.push(`<instructions>
4034
4069
  ${instructionParts.join("\n\n")}
@@ -4041,9 +4076,6 @@ function buildCompactionPrompt(opts) {
4041
4076
  ${ROLE}
4042
4077
  Heartbeat #${opts.heartbeatCount} (COMPACTION)
4043
4078
  </role>`);
4044
- sections.push(`<commands>
4045
- ${COMMANDS}
4046
- </commands>`);
4047
4079
  const contextParts = [];
4048
4080
  contextParts.push(...buildContextSections(opts));
4049
4081
  contextParts.push(buildMemorySection(opts.memories, opts.supervisorDir));
@@ -4054,10 +4086,14 @@ ${COMMANDS}
4054
4086
  sections.push(`<context>
4055
4087
  ${contextParts.join("\n\n")}
4056
4088
  </context>`);
4089
+ sections.push(`<reference>
4090
+ ${getCommandsSection(opts.heartbeatCount)}
4091
+ </reference>`);
4057
4092
  const instructionParts = [];
4058
4093
  instructionParts.push(HEARTBEAT_RULES);
4059
4094
  instructionParts.push(REPORTING_RULES);
4060
- instructionParts.push(MEMORY_RULES);
4095
+ instructionParts.push(MEMORY_RULES_CORE);
4096
+ instructionParts.push(MEMORY_RULES_EXAMPLES);
4061
4097
  if (opts.customInstructions) {
4062
4098
  instructionParts.push(`### Custom instructions
4063
4099
  ${opts.customInstructions}`);
@@ -4070,8 +4106,9 @@ This is a COMPACTION heartbeat. Deep-clean your ENTIRE memory.
4070
4106
  3. **Remove trivial facts** \u2014 file counts, line numbers, structural details that \`ls\` or \`cat package.json\` can answer. These waste context.
4071
4107
  4. **Merge duplicates** \u2014 combine similar facts within the same scope into one.
4072
4108
  5. **Clean up focus** \u2014 forget resolved items, rewrite remaining in structured format.
4073
- 6. **Delete completed notes** from notes/ directory.
4074
- 7. **Stay under 15 facts per scope** \u2014 prioritize facts that affect dispatch decisions.
4109
+ 6. **Prune done tasks** \u2014 forget tasks with outcome \`done\` or \`abandoned\` older than 7 days.
4110
+ 7. **Delete completed notes** from notes/ directory.
4111
+ 8. **Stay under 15 facts per scope** \u2014 prioritize facts that affect dispatch decisions.
4075
4112
 
4076
4113
  Flag contradictions: if two facts contradict, keep the newer one.
4077
4114
 
@@ -4382,6 +4419,12 @@ var HeartbeatLoop = class {
4382
4419
  modeLabel: "consolidation"
4383
4420
  };
4384
4421
  }
4422
+ if (isIdleHeartbeat(sharedOpts)) {
4423
+ return {
4424
+ prompt: buildIdlePrompt(sharedOpts),
4425
+ modeLabel: "idle"
4426
+ };
4427
+ }
4385
4428
  return {
4386
4429
  prompt: buildStandardPrompt(sharedOpts),
4387
4430
  modeLabel: "standard"
@@ -4409,7 +4452,6 @@ var HeartbeatLoop = class {
4409
4452
  }
4410
4453
  const queryOptions = {
4411
4454
  cwd: homedir2(),
4412
- maxTurns: 15,
4413
4455
  allowedTools,
4414
4456
  permissionMode: "bypassPermissions",
4415
4457
  allowDangerouslySkipPermissions: true,