@neotx/core 0.1.0-alpha.5 → 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 +3 -0
- package/dist/index.js +160 -112
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
@@ -1207,7 +1207,10 @@ function isProcessAlive(pid) {
|
|
|
1207
1207
|
try {
|
|
1208
1208
|
process.kill(pid, 0);
|
|
1209
1209
|
return true;
|
|
1210
|
-
} catch {
|
|
1210
|
+
} catch (error) {
|
|
1211
|
+
if (error instanceof Error && "code" in error && error.code === "EPERM") {
|
|
1212
|
+
return true;
|
|
1213
|
+
}
|
|
1211
1214
|
return false;
|
|
1212
1215
|
}
|
|
1213
1216
|
}
|
|
@@ -1282,6 +1285,7 @@ var RunStore = class {
|
|
|
1282
1285
|
const content = await readFile4(filePath, "utf-8");
|
|
1283
1286
|
const run = JSON.parse(content);
|
|
1284
1287
|
if (run.status !== "running") return null;
|
|
1288
|
+
if (run.pid && run.pid === process.pid) return null;
|
|
1285
1289
|
if (run.pid && isProcessAlive(run.pid)) return null;
|
|
1286
1290
|
const ageMs = Date.now() - new Date(run.createdAt).getTime();
|
|
1287
1291
|
if (ageMs < ORPHAN_GRACE_PERIOD_MS) return null;
|
|
@@ -2367,6 +2371,7 @@ var Orchestrator = class extends NeoEventEmitter {
|
|
|
2367
2371
|
_costToday = 0;
|
|
2368
2372
|
_startedAt = 0;
|
|
2369
2373
|
_drainResolve = null;
|
|
2374
|
+
skipOrphanRecovery;
|
|
2370
2375
|
constructor(config, options = {}) {
|
|
2371
2376
|
super();
|
|
2372
2377
|
this.config = config;
|
|
@@ -2374,6 +2379,7 @@ var Orchestrator = class extends NeoEventEmitter {
|
|
|
2374
2379
|
this.journalDir = options.journalDir ?? getJournalsDir();
|
|
2375
2380
|
this.builtInWorkflowDir = options.builtInWorkflowDir;
|
|
2376
2381
|
this.customWorkflowDir = options.customWorkflowDir;
|
|
2382
|
+
this.skipOrphanRecovery = options.skipOrphanRecovery ?? false;
|
|
2377
2383
|
for (const repo of config.repos) {
|
|
2378
2384
|
const resolvedPath = path11.resolve(repo.path);
|
|
2379
2385
|
const normalizedRepo = { ...repo, path: resolvedPath };
|
|
@@ -2491,7 +2497,9 @@ var Orchestrator = class extends NeoEventEmitter {
|
|
|
2491
2497
|
this.registerWorkflow(workflow);
|
|
2492
2498
|
}
|
|
2493
2499
|
}
|
|
2494
|
-
|
|
2500
|
+
if (!this.skipOrphanRecovery) {
|
|
2501
|
+
await this.recoverOrphanedRuns();
|
|
2502
|
+
}
|
|
2495
2503
|
await mkdir6(this.config.sessions.dir, { recursive: true });
|
|
2496
2504
|
}
|
|
2497
2505
|
async shutdown() {
|
|
@@ -2602,6 +2610,7 @@ var Orchestrator = class extends NeoEventEmitter {
|
|
|
2602
2610
|
workflow: input.workflow,
|
|
2603
2611
|
repo: input.repo,
|
|
2604
2612
|
prompt: input.prompt,
|
|
2613
|
+
pid: process.pid,
|
|
2605
2614
|
status: "running",
|
|
2606
2615
|
steps: {},
|
|
2607
2616
|
createdAt: activeSession.startedAt,
|
|
@@ -2789,6 +2798,7 @@ var Orchestrator = class extends NeoEventEmitter {
|
|
|
2789
2798
|
workflow: input.workflow,
|
|
2790
2799
|
repo: input.repo,
|
|
2791
2800
|
prompt: input.prompt,
|
|
2801
|
+
pid: process.pid,
|
|
2792
2802
|
branch: taskResult.branch,
|
|
2793
2803
|
status: taskResult.status === "success" ? "completed" : "failed",
|
|
2794
2804
|
steps: taskResult.steps,
|
|
@@ -3567,7 +3577,16 @@ async function appendLogBuffer(dir, entry) {
|
|
|
3567
3577
|
}
|
|
3568
3578
|
|
|
3569
3579
|
// src/supervisor/prompt-builder.ts
|
|
3570
|
-
var ROLE = `You are the neo autonomous supervisor
|
|
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>`;
|
|
3571
3590
|
var COMMANDS = `### Dispatching agents
|
|
3572
3591
|
\`\`\`bash
|
|
3573
3592
|
neo run <agent> --prompt "..." --repo <path> --branch <name> [--priority critical|high|medium|low] [--meta '<json>']
|
|
@@ -3598,6 +3617,8 @@ neo agents # list available agents
|
|
|
3598
3617
|
neo memory write --type fact --scope /path "Stable fact about repo"
|
|
3599
3618
|
neo memory write --type focus --expires 2h "Current working context"
|
|
3600
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
|
|
3601
3622
|
neo memory forget <id>
|
|
3602
3623
|
neo memory search "keyword"
|
|
3603
3624
|
neo memory list --type fact
|
|
@@ -3607,107 +3628,109 @@ neo memory list --type fact
|
|
|
3607
3628
|
\`\`\`bash
|
|
3608
3629
|
neo log <type> "<message>" # visible in TUI only
|
|
3609
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>\`.`;
|
|
3610
3636
|
var HEARTBEAT_RULES = `### Heartbeat lifecycle
|
|
3611
|
-
1. **Check work queue FIRST** \u2014 if you have pending tasks, work on the next one before looking for new work
|
|
3612
|
-
2. Process incoming events (messages, run completions)
|
|
3613
|
-
3. Follow up on pending work (CI checks, deferred dispatches) with \`neo runs\` or \`gh pr checks\`
|
|
3614
|
-
4. Make decisions and dispatch agents
|
|
3615
|
-
5. Update task status (\`neo memory update <id> --outcome in_progress|done|blocked\`) and log decisions
|
|
3616
|
-
6. Yield \u2014 each heartbeat should take seconds, not minutes
|
|
3617
3637
|
|
|
3618
|
-
|
|
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>
|
|
3619
3646
|
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
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>`;
|
|
3623
3653
|
var REPORTING_RULES = `### Reporting
|
|
3624
|
-
\`neo log\` is your ONLY visible output \u2014 the TUI shows these and nothing else.
|
|
3625
|
-
- \`neo log decision "..."\` \u2014 why you chose this route
|
|
3626
|
-
- \`neo log action "..."\` \u2014 what you dispatched/did
|
|
3627
|
-
- \`neo log discovery "..."\` \u2014 ephemeral observations
|
|
3628
|
-
- 1-3 sentences per log. Pack maximum info: ticket, agent, branch, runId, cost, PR#. No markdown.
|
|
3629
3654
|
|
|
3630
|
-
|
|
3631
|
-
var MEMORY_RULES = `### Memory \u2014 types and when to use each
|
|
3655
|
+
\`neo log\` is your ONLY visible output. Use telegraphic format.
|
|
3632
3656
|
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
| \`feedback\` | Recurring review pattern | After seeing the same reviewer complaint 3+ times | Permanent |
|
|
3639
|
-
| \`episode\` | Run outcome | Auto-created on run completion \u2014 do NOT write manually | Permanent |
|
|
3640
|
-
| \`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>
|
|
3641
3662
|
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
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
|
|
3646
3676
|
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
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>
|
|
3652
3686
|
|
|
3653
|
-
|
|
3654
|
-
Focus
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
\`\`\`
|
|
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>
|
|
3661
3694
|
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
neo memory write --type focus --expires 2h "ACTIVE: 5900a64a developer 'T1: schema+store' branch:feat/task-queue
|
|
3665
|
-
PENDING: T2 'CLI --outcome flag' depends:T1
|
|
3666
|
-
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).
|
|
3667
3697
|
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
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)
|
|
3671
3703
|
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
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)
|
|
3675
3709
|
|
|
3676
|
-
|
|
3677
|
-
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)
|
|
3678
3711
|
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
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>
|
|
3682
3714
|
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
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>
|
|
3688
3721
|
|
|
3689
|
-
|
|
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
|
|
3690
3729
|
neo memory forget <id>
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
#### Work queue workflow (Tasks)
|
|
3697
|
-
After architect/refiner output, create tasks with \`neo memory write --type task\`:
|
|
3698
|
-
- Include \`--category\` with the command to retrieve context (\`neo runs <id>\` or \`cat notes/<file>\`)
|
|
3699
|
-
- Use \`--tags depends:mem_<id>\` for task dependencies
|
|
3700
|
-
- Use \`--tags initiative:<name>\` to group tasks across repos
|
|
3701
|
-
- Update status with \`neo memory update <id> --outcome in_progress|done|blocked|abandoned\`
|
|
3702
|
-
- The queue is shown at every heartbeat \u2014 you will not lose track
|
|
3703
|
-
|
|
3704
|
-
#### Pattern escalation
|
|
3705
|
-
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.
|
|
3706
|
-
|
|
3707
|
-
#### Event deduplication
|
|
3708
|
-
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.
|
|
3709
|
-
|
|
3710
|
-
**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
|
+
}
|
|
3711
3734
|
function buildContextSections(opts) {
|
|
3712
3735
|
const parts = [];
|
|
3713
3736
|
if (opts.repos.length > 0) {
|
|
@@ -3868,7 +3891,7 @@ function getBasename(scopePath) {
|
|
|
3868
3891
|
const parts = scopePath.split("/");
|
|
3869
3892
|
return parts[parts.length - 1] || scopePath;
|
|
3870
3893
|
}
|
|
3871
|
-
var SIGNIFICANT_TYPES = /* @__PURE__ */ new Set(["decision", "action", "dispatch", "error"
|
|
3894
|
+
var SIGNIFICANT_TYPES = /* @__PURE__ */ new Set(["decision", "action", "dispatch", "error"]);
|
|
3872
3895
|
function buildRecentActionsSection(entries) {
|
|
3873
3896
|
const significant = entries.filter((e) => SIGNIFICANT_TYPES.has(e.type));
|
|
3874
3897
|
if (significant.length === 0) return "";
|
|
@@ -3891,7 +3914,7 @@ function buildEventsSection(grouped) {
|
|
|
3891
3914
|
const { messages, webhooks, runCompletions } = grouped;
|
|
3892
3915
|
const totalEvents = messages.length + webhooks.length + runCompletions.length;
|
|
3893
3916
|
if (totalEvents === 0) {
|
|
3894
|
-
return "No new events.
|
|
3917
|
+
return "No new events.";
|
|
3895
3918
|
}
|
|
3896
3919
|
const parts = [];
|
|
3897
3920
|
for (const msg of messages) {
|
|
@@ -3922,15 +3945,33 @@ ${JSON.stringify(event.data.payload ?? {}, null, 2)}
|
|
|
3922
3945
|
return `Internal event: ${event.eventKind}`;
|
|
3923
3946
|
}
|
|
3924
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
|
+
}
|
|
3925
3969
|
function buildStandardPrompt(opts) {
|
|
3926
3970
|
const sections = [];
|
|
3927
3971
|
sections.push(`<role>
|
|
3928
3972
|
${ROLE}
|
|
3929
3973
|
Heartbeat #${opts.heartbeatCount}
|
|
3930
3974
|
</role>`);
|
|
3931
|
-
sections.push(`<commands>
|
|
3932
|
-
${COMMANDS}
|
|
3933
|
-
</commands>`);
|
|
3934
3975
|
const contextParts = [];
|
|
3935
3976
|
const workQueue = buildWorkQueueSection(opts.memories);
|
|
3936
3977
|
if (workQueue) {
|
|
@@ -3951,16 +3992,21 @@ ${buildEventsSection(opts.grouped)}`);
|
|
|
3951
3992
|
sections.push(`<context>
|
|
3952
3993
|
${contextParts.join("\n\n")}
|
|
3953
3994
|
</context>`);
|
|
3995
|
+
sections.push(`<reference>
|
|
3996
|
+
${getCommandsSection(opts.heartbeatCount)}
|
|
3997
|
+
</reference>`);
|
|
3954
3998
|
const instructionParts = [];
|
|
3955
3999
|
instructionParts.push(HEARTBEAT_RULES);
|
|
3956
4000
|
instructionParts.push(REPORTING_RULES);
|
|
3957
|
-
instructionParts.push(
|
|
4001
|
+
instructionParts.push(MEMORY_RULES_CORE);
|
|
3958
4002
|
if (opts.customInstructions) {
|
|
3959
4003
|
instructionParts.push(`### Custom instructions
|
|
3960
4004
|
${opts.customInstructions}`);
|
|
3961
4005
|
}
|
|
4006
|
+
const { messages, webhooks, runCompletions } = opts.grouped;
|
|
4007
|
+
const hasEvents = messages.length + webhooks.length + runCompletions.length > 0;
|
|
3962
4008
|
instructionParts.push(
|
|
3963
|
-
"
|
|
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."
|
|
3964
4010
|
);
|
|
3965
4011
|
sections.push(`<instructions>
|
|
3966
4012
|
${instructionParts.join("\n\n")}
|
|
@@ -3973,9 +4019,6 @@ function buildConsolidationPrompt(opts) {
|
|
|
3973
4019
|
${ROLE}
|
|
3974
4020
|
Heartbeat #${opts.heartbeatCount} (CONSOLIDATION)
|
|
3975
4021
|
</role>`);
|
|
3976
|
-
sections.push(`<commands>
|
|
3977
|
-
${COMMANDS}
|
|
3978
|
-
</commands>`);
|
|
3979
4022
|
const contextParts = [];
|
|
3980
4023
|
const workQueueConsolidation = buildWorkQueueSection(opts.memories);
|
|
3981
4024
|
if (workQueueConsolidation) {
|
|
@@ -3996,10 +4039,14 @@ ${buildEventsSection(opts.grouped)}`);
|
|
|
3996
4039
|
sections.push(`<context>
|
|
3997
4040
|
${contextParts.join("\n\n")}
|
|
3998
4041
|
</context>`);
|
|
4042
|
+
sections.push(`<reference>
|
|
4043
|
+
${getCommandsSection(opts.heartbeatCount)}
|
|
4044
|
+
</reference>`);
|
|
3999
4045
|
const instructionParts = [];
|
|
4000
4046
|
instructionParts.push(HEARTBEAT_RULES);
|
|
4001
4047
|
instructionParts.push(REPORTING_RULES);
|
|
4002
|
-
instructionParts.push(
|
|
4048
|
+
instructionParts.push(MEMORY_RULES_CORE);
|
|
4049
|
+
instructionParts.push(MEMORY_RULES_EXAMPLES);
|
|
4003
4050
|
if (opts.customInstructions) {
|
|
4004
4051
|
instructionParts.push(`### Custom instructions
|
|
4005
4052
|
${opts.customInstructions}`);
|
|
@@ -4016,13 +4063,7 @@ If there IS active work, your job:
|
|
|
4016
4063
|
2. **Update focus** \u2014 rewrite focus using the MANDATORY structured format (ACTIVE/PENDING/WAITING/PROCESSED). Remove resolved items. Add new context.
|
|
4017
4064
|
3. **Pattern escalation** \u2014 if agents hit the same issue 3+ times (check recent actions), write a \`procedure\` to prevent recurrence.
|
|
4018
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.
|
|
4019
|
-
|
|
4020
|
-
\`\`\`bash
|
|
4021
|
-
neo memory write --type procedure --scope /repo "Before re-dispatching after orphan, check gh pr view first"
|
|
4022
|
-
neo memory write --type focus --expires 4h "ACTIVE: abc123 developer 'T3: prompt injection' branch:feat/task-queue
|
|
4023
|
-
PROCESSED: 5900a64a \u2192 PR#70 APPROVED"
|
|
4024
|
-
neo memory forget <stale-id>
|
|
4025
|
-
\`\`\``
|
|
4066
|
+
5. **Prune done tasks** \u2014 forget tasks with outcome \`done\` or \`abandoned\` older than 7 days.`
|
|
4026
4067
|
);
|
|
4027
4068
|
sections.push(`<instructions>
|
|
4028
4069
|
${instructionParts.join("\n\n")}
|
|
@@ -4035,9 +4076,6 @@ function buildCompactionPrompt(opts) {
|
|
|
4035
4076
|
${ROLE}
|
|
4036
4077
|
Heartbeat #${opts.heartbeatCount} (COMPACTION)
|
|
4037
4078
|
</role>`);
|
|
4038
|
-
sections.push(`<commands>
|
|
4039
|
-
${COMMANDS}
|
|
4040
|
-
</commands>`);
|
|
4041
4079
|
const contextParts = [];
|
|
4042
4080
|
contextParts.push(...buildContextSections(opts));
|
|
4043
4081
|
contextParts.push(buildMemorySection(opts.memories, opts.supervisorDir));
|
|
@@ -4048,10 +4086,14 @@ ${COMMANDS}
|
|
|
4048
4086
|
sections.push(`<context>
|
|
4049
4087
|
${contextParts.join("\n\n")}
|
|
4050
4088
|
</context>`);
|
|
4089
|
+
sections.push(`<reference>
|
|
4090
|
+
${getCommandsSection(opts.heartbeatCount)}
|
|
4091
|
+
</reference>`);
|
|
4051
4092
|
const instructionParts = [];
|
|
4052
4093
|
instructionParts.push(HEARTBEAT_RULES);
|
|
4053
4094
|
instructionParts.push(REPORTING_RULES);
|
|
4054
|
-
instructionParts.push(
|
|
4095
|
+
instructionParts.push(MEMORY_RULES_CORE);
|
|
4096
|
+
instructionParts.push(MEMORY_RULES_EXAMPLES);
|
|
4055
4097
|
if (opts.customInstructions) {
|
|
4056
4098
|
instructionParts.push(`### Custom instructions
|
|
4057
4099
|
${opts.customInstructions}`);
|
|
@@ -4064,8 +4106,9 @@ This is a COMPACTION heartbeat. Deep-clean your ENTIRE memory.
|
|
|
4064
4106
|
3. **Remove trivial facts** \u2014 file counts, line numbers, structural details that \`ls\` or \`cat package.json\` can answer. These waste context.
|
|
4065
4107
|
4. **Merge duplicates** \u2014 combine similar facts within the same scope into one.
|
|
4066
4108
|
5. **Clean up focus** \u2014 forget resolved items, rewrite remaining in structured format.
|
|
4067
|
-
6. **
|
|
4068
|
-
7. **
|
|
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.
|
|
4069
4112
|
|
|
4070
4113
|
Flag contradictions: if two facts contradict, keep the newer one.
|
|
4071
4114
|
|
|
@@ -4376,6 +4419,12 @@ var HeartbeatLoop = class {
|
|
|
4376
4419
|
modeLabel: "consolidation"
|
|
4377
4420
|
};
|
|
4378
4421
|
}
|
|
4422
|
+
if (isIdleHeartbeat(sharedOpts)) {
|
|
4423
|
+
return {
|
|
4424
|
+
prompt: buildIdlePrompt(sharedOpts),
|
|
4425
|
+
modeLabel: "idle"
|
|
4426
|
+
};
|
|
4427
|
+
}
|
|
4379
4428
|
return {
|
|
4380
4429
|
prompt: buildStandardPrompt(sharedOpts),
|
|
4381
4430
|
modeLabel: "standard"
|
|
@@ -4403,7 +4452,6 @@ var HeartbeatLoop = class {
|
|
|
4403
4452
|
}
|
|
4404
4453
|
const queryOptions = {
|
|
4405
4454
|
cwd: homedir2(),
|
|
4406
|
-
maxTurns: 15,
|
|
4407
4455
|
allowedTools,
|
|
4408
4456
|
permissionMode: "bypassPermissions",
|
|
4409
4457
|
allowDangerouslySkipPermissions: true,
|