FN-8850: capture task completion recommendations
Capture bounded, task-ready executor follow-ups at accepted completion. - Guide executor prompts to submit recommendations or an explicit empty list at completion. - Enforce default and disabled recommendation caps in completion handling. - Document recommendation behavior and cover prompt and validation contracts. Files changed: .../fn-8850-populate-task-recommendations.md | 7 +++ docs/dashboard-guide.md | 2 +- packages/core/src/agents/agent-prompts.ts | 16 ++++-- .../__tests__/ephemeral-task-create-gate.test.ts | 3 +- .../engine/src/__tests__/executor-prompt.test.ts | 67 ++++++++++++++++++++++ .../executor-task-recommendations.test.ts | 39 ++++++++++++- packages/engine/src/executor.ts | 44 ++++++++++++-- 7 files changed, 162 insertions(+), 16 deletions(-) Fusion-Task-Id: FN-8850 Fusion-Task-Lineage: 55a99d05-33fa-464c-869c-18a9cf7e495a Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-8850-populate-task-recommendations.md
Normal file
7
.changeset/fn-8850-populate-task-recommendations.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Capture executor-discovered completion recommendations for completed tasks.
|
||||
category: fix
|
||||
dev: Executor prompts now require bounded task-ready recommendations or an explicit empty list.
|
||||
@@ -1487,7 +1487,7 @@ Inspect task definition, logs, review feedback, comments, artifacts, workflow ou
|
||||
- The top-level **Chat** tab appears first for active task details and is the default landing tab for non-`done` tasks. It uses the task's effective planning model, but opening the tab is lookup-only: Fusion creates the task-scoped planner chat only after you send a composer message, starter prompt, or planner-question answer. Once a user message exists, the resumable planner chat can appear in the global Chat list; interacted chats are kept when the task reaches `done` and removed when the task is archived. Each send includes bounded server-built task context so the planner can answer current status, progress, recent activity, dependency, and task definition questions. It shows starter prompts for common planning questions, can render structured planner questions, and converts only explicit operator steering intent through the scoped steering tool. The composer stays pinned while the transcript, loading, error, starter, history, and streaming states scroll internally; on mobile/narrow task detail, the default focused Chat layout hides nonessential title/metadata/tab/action rows until you collapse it from the in-view expand control.
|
||||
- The **Activity → Live**, **Feed**, and **Raw Logs** segments remain immediately after **Chat** and share an expand/collapse control that lets the active Activity segment fill the task-detail modal, then restores the normal header, tabs, and action footer when collapsed.
|
||||
- The **Summary** tab appears for `done` tasks and remains their default landing tab. It shows the recorded completion summary, the **Merge Details** card (merge status, commit, PR, timestamp, and message), changed-file/merge stats when available, completed steps, workflow results, retry counts, and a token usage & cost section broken down by model from the already-loaded task detail; unpriced models show cost as unavailable rather than `$0`.
|
||||
- The **Recommendations** tab appears on every completed task. When no recommendations were produced, it shows a clear empty state; otherwise each row shows a task-ready title, category, and description. **Create task** uses the normal guarded intake policy (including duplicate checks), so a duplicate conflict creates no child and leaves the recommendation available to retry; successful repeated clicks reuse the same linked triage task.
|
||||
- The **Recommendations** tab appears on every completed task. At accepted completion, executors evaluate optional, non-blocking out-of-scope findings and submit task-ready recommendations; an explicit `[]` means none qualified, not that filler should be invented. The project cap bounds captured results, and `maxRecommendationsPerTask: 0` disables capture. Recommendations are distinct from immediately created/delegated tasks, which remain appropriate for required dependency coordination, explicit task requirements, or operator-directed filing. When no recommendations were produced, the tab shows a clear empty state; otherwise each row shows a task-ready title, category, and description. **Create task** uses the normal guarded intake policy (including duplicate checks), so a duplicate conflict creates no child and leaves the recommendation available to retry; successful repeated clicks reuse the same linked triage task.
|
||||
- The **Cost** tab is available for tasks in every column and sits immediately after **Comments → Terminal** in the tab strip. It shows the read-time derived per-model cost breakdown (input, output, cached, cache-write, total tokens, derived USD) and a task total; no token usage shows an explicit empty state, while unpriced or zero-usage rows use `—` instead of a guessed `$0`.
|
||||
<!-- FNXC:Settings-ThinkingLevel 2026-07-13-00:27: The task-detail Models tab now persists validatorThinkingLevel and planningThinkingLevel separately so Reviewer and Planning lanes can choose reasoning effort without changing the Executor lane's task.thinkingLevel. -->
|
||||
- The **Models** tab exposes inline **Thinking Level** selectors for **Executor Model**, **Reviewer Model**, and **Planning Model**. Executor saves the shared task thinking level, while Reviewer and Planning save independent per-lane overrides; leaving either lane on **Default** inherits the shared task thinking level and then the configured workflow/project defaults.
|
||||
|
||||
@@ -106,8 +106,15 @@ You have tools to report progress. The board updates in real-time.
|
||||
|
||||
**Logging important actions:** \`task_log(message="what happened")\`
|
||||
|
||||
**Out-of-scope work found during execution:** \`task_create(description="what needs doing")\`
|
||||
When creating multiple related tasks, declare dependencies between them:
|
||||
/*
|
||||
FNXC:TaskRecommendations 2026-08-09-04:06:
|
||||
FN-8850 requires optional, non-blocking discoveries to be captured only at the explicit accepted
|
||||
completion boundary. Immediate task creation remains for required dependency coordination or an
|
||||
operator-directed filing, while workflow step sessions remain unable to write recommendations.
|
||||
*/
|
||||
**Out-of-scope findings at completion:** Do not automatically create a task for optional, non-blocking work discovered outside this task. When recommendation capture is enabled, at the final accepted \`fn_task_done(outcome="completed")\` checkpoint evaluate genuine task-ready follow-ups and send \`recommendations\` (or \`recommendations: []\` when none qualify). Each recommendation needs a stable unique \`id\`, \`title\`, \`description\`, and \`category\`; never use it for a required current-task fix, blocker, secret, executable command, reasoning transcript, or filler.
|
||||
|
||||
Use \`task_create\` or \`fn_delegate_task\` only when the task explicitly requires immediate filing, necessary dependency coordination, or the operator directs it. When creating multiple related tasks, declare dependencies between them:
|
||||
\`task_create(description="load door sounds", dependencies=[])\` → returns KB-050
|
||||
\`task_create(description="play sound on door open/close", dependencies=["KB-050"])\`
|
||||
|
||||
@@ -941,10 +948,7 @@ You have tools to report progress. The board updates in real-time.
|
||||
|
||||
**Logging important actions:** \`task_log(message="what happened")\`
|
||||
|
||||
**Out-of-scope work found during execution:** \`task_create(description="what needs doing")\`
|
||||
When creating multiple related tasks, declare dependencies between them:
|
||||
\`task_create(description="load door sounds", dependencies=[])\` → returns KB-050
|
||||
\`task_create(description="play sound on door open/close", dependencies=["KB-050"])\`
|
||||
**Out-of-scope findings at completion:** When recommendation capture is enabled, retain optional, non-blocking discoveries as task-ready \`fn_task_done\` recommendations at accepted completion, or send \`recommendations: []\` when none qualify. Use \`task_create\` only for explicit immediate filing, dependency coordination, or operator direction; never recommend required current-task work, blockers, secrets, commands, reasoning, or filler.
|
||||
|
||||
**Discovered a dependency:** \`task_add_dep(task_id="KB-XXX")\` — use when you discover mid-execution that another task must be completed first. This will return a warning first — you must call again with \`confirm=true\` to proceed. Adding a dependency stops execution, discards current work, and moves the task to triage for re-specification.
|
||||
|
||||
|
||||
@@ -205,7 +205,8 @@ describe("executor session tool list (behavioral)", () => {
|
||||
it("tells the agent the tool is withheld by policy and what to do instead", async () => {
|
||||
const { systemPrompt } = await captureExecutorSession("deny");
|
||||
expect(systemPrompt).toContain("Follow-up task creation is disabled for this session");
|
||||
expect(systemPrompt).toContain("fn_task_log");
|
||||
expect(systemPrompt).toContain("completion recommendation route");
|
||||
expect(systemPrompt).toContain("recommendations: []");
|
||||
});
|
||||
|
||||
it("adds no withheld-tool guidance when creation is allowed", async () => {
|
||||
|
||||
@@ -3011,3 +3011,70 @@ describe("executor base prompt runtime self-awareness", () => {
|
||||
expect(layers.dynamic).not.toContain(FUSION_RUNTIME_SELF_AWARENESS);
|
||||
});
|
||||
});
|
||||
|
||||
describe("completion recommendation prompt contract", () => {
|
||||
it.each([
|
||||
["built-in default", { agentPrompts: undefined }],
|
||||
["custom executor prompt", {
|
||||
agentPrompts: {
|
||||
templates: [{ id: "custom-executor", name: "Custom", role: "executor", prompt: "Operator custom executor prompt.", builtIn: false }],
|
||||
roleAssignments: { executor: "custom-executor" },
|
||||
},
|
||||
}],
|
||||
])("appends populated and empty completion guidance for %s", async (_label, settings) => {
|
||||
const { getExecutorSystemPrompt } = await import("../executor.js");
|
||||
const prompt = getExecutorSystemPrompt({ ...settings, maxRecommendationsPerTask: 2 } as any);
|
||||
|
||||
expect(prompt).toContain("at most 2 task-ready recommendations");
|
||||
expect(prompt).toContain("recommendations: []");
|
||||
expect(prompt).toContain('id: "follow-up-export"');
|
||||
expect(prompt).toContain('outcome="blocked"');
|
||||
expect(prompt).not.toContain("Out-of-scope work found during execution");
|
||||
});
|
||||
|
||||
it("uses the default cap when unset and disables every recommendation request at cap zero", async () => {
|
||||
const { getExecutorSystemPrompt } = await import("../executor.js");
|
||||
const defaultPrompt = getExecutorSystemPrompt({ agentPrompts: undefined } as any);
|
||||
expect(defaultPrompt).toContain("at most 3 task-ready recommendations");
|
||||
|
||||
const disabledPrompt = getExecutorSystemPrompt({ agentPrompts: undefined, maxRecommendationsPerTask: 0 } as any);
|
||||
expect(disabledPrompt).toContain("Recommendation capture is disabled");
|
||||
expect(disabledPrompt).toContain("Ignore any earlier generic recommendation guidance");
|
||||
expect(disabledPrompt).not.toContain("at most 0 task-ready recommendations");
|
||||
expect(disabledPrompt).toContain("When recommendation capture is enabled, at the final accepted");
|
||||
|
||||
const customDisabledPrompt = getExecutorSystemPrompt({
|
||||
maxRecommendationsPerTask: 0,
|
||||
agentPrompts: {
|
||||
templates: [{ id: "stale-custom-executor", name: "Custom", role: "executor", prompt: "Always send recommendations.", builtIn: false }],
|
||||
roleAssignments: { executor: "stale-custom-executor" },
|
||||
},
|
||||
} as any);
|
||||
expect(customDisabledPrompt).toContain("Always send recommendations.");
|
||||
expect(customDisabledPrompt).toMatch(/Always send recommendations\.[\s\S]*Ignore any earlier generic recommendation guidance/);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["only task creation is withheld", { taskCreateWithheld: true }],
|
||||
["only delegation is withheld", { delegateWithheld: true }],
|
||||
["task creation and delegation are withheld", { taskCreateWithheld: true, delegateWithheld: true }],
|
||||
])("keeps enabled recommendation guidance available when %s", async (_label, availability) => {
|
||||
const { getExecutorSystemPrompt } = await import("../executor.js");
|
||||
const prompt = getExecutorSystemPrompt({ agentPrompts: undefined, maxRecommendationsPerTask: 3 } as any, availability);
|
||||
|
||||
expect(prompt).toContain("Follow-up task creation is disabled for this session");
|
||||
expect(prompt).toContain("completion recommendation route");
|
||||
expect(prompt).toContain("at most 3 task-ready recommendations");
|
||||
});
|
||||
|
||||
it("does not present recommendations as an available fallback when capture is disabled and creation is withheld", async () => {
|
||||
const { getExecutorSystemPrompt } = await import("../executor.js");
|
||||
const prompt = getExecutorSystemPrompt(
|
||||
{ agentPrompts: undefined, maxRecommendationsPerTask: 0 } as any,
|
||||
{ taskCreateWithheld: true, delegateWithheld: true },
|
||||
);
|
||||
|
||||
expect(prompt).toContain("Recommendation capture is disabled, so retain non-blocking context");
|
||||
expect(prompt).not.toContain("use the available completion recommendation route");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,7 +29,7 @@ function completionTask() {
|
||||
};
|
||||
}
|
||||
|
||||
function createProductionTaskDoneTool() {
|
||||
function createProductionTaskDoneTool(maximum: number | undefined = 3) {
|
||||
const store = createMockStore();
|
||||
const task = completionTask();
|
||||
store._setRow(task.id, task);
|
||||
@@ -40,7 +40,7 @@ function createProductionTaskDoneTool() {
|
||||
groupOverlappingFiles: false,
|
||||
autoMerge: false,
|
||||
worktreeInitCommand: undefined,
|
||||
maxRecommendationsPerTask: 3,
|
||||
...(maximum === undefined ? {} : { maxRecommendationsPerTask: maximum }),
|
||||
});
|
||||
const executor = new TaskExecutor(store as any, "/repo");
|
||||
const tool = (executor as any).createTaskDoneTool(
|
||||
@@ -91,6 +91,41 @@ describe("fn_task_done recommendation validation", () => {
|
||||
expect((await store.getTask(task.id)).recommendations).toEqual([replacement]);
|
||||
});
|
||||
|
||||
|
||||
it("persists an honest empty list and uses the default cap when the setting is absent", async () => {
|
||||
const { store, task, tool } = createProductionTaskDoneTool(undefined);
|
||||
const empty = await tool.execute("call-empty", { recommendations: [] });
|
||||
expect(empty.content[0].text).toContain("Task marked complete");
|
||||
expect((await store.getTask(task.id)).recommendations).toEqual([]);
|
||||
|
||||
const { store: defaultStore, task: defaultTask, tool: defaultTool } = createProductionTaskDoneTool(undefined);
|
||||
await defaultTool.execute("call-default", { recommendations: [recommendation, { ...recommendation, id: "rec-second" }, { ...recommendation, id: "rec-third" }] });
|
||||
expect((await defaultStore.getTask(defaultTask.id)).recommendations).toHaveLength(3);
|
||||
});
|
||||
|
||||
it("accepts an empty list but rejects populated input when capture is disabled", async () => {
|
||||
const { store, task, tool } = createProductionTaskDoneTool(0);
|
||||
const empty = await tool.execute("call-disabled-empty", { recommendations: [] });
|
||||
expect(empty.content[0].text).toContain("Task marked complete");
|
||||
expect((await store.getTask(task.id)).recommendations).toEqual([]);
|
||||
|
||||
const { store: rejectedStore, task: rejectedTask, tool: rejectedTool } = createProductionTaskDoneTool(0);
|
||||
const rejected = await rejectedTool.execute("call-disabled-populated", { recommendations: [recommendation] });
|
||||
expect(rejected.content[0].text).toContain("maximum of 0");
|
||||
expect((await rejectedStore.getTask(rejectedTask.id)).recommendations).toBeUndefined();
|
||||
});
|
||||
|
||||
it("documents the prompted payload and persists its equivalent through the production tool", async () => {
|
||||
const { store, task, tool } = createProductionTaskDoneTool();
|
||||
expect(tool.description).toContain("recommendations: []");
|
||||
expect(tool.description).toContain("empty list is accepted for compatibility");
|
||||
expect(tool.parameters.properties.recommendations.description).toContain("unique stable ids");
|
||||
expect(tool.parameters.properties.recommendations.description).toContain("populated input is rejected");
|
||||
|
||||
await tool.execute("call-prompt-shape", { recommendations: [recommendation] });
|
||||
expect((await store.getTask(task.id)).recommendations).toEqual([recommendation]);
|
||||
});
|
||||
|
||||
it("does not persist recommendations when production completion is refused or blocked", async () => {
|
||||
const { store, task, tool } = createProductionTaskDoneTool();
|
||||
const refused = await tool.execute("call-refused", {
|
||||
|
||||
@@ -1559,8 +1559,15 @@ This path exists specifically to prevent the executor from looping when PROMPT.m
|
||||
|
||||
**Logging important actions:** \`fn_task_log(message="what happened")\`
|
||||
|
||||
**Out-of-scope work found during execution:** \`fn_task_create(description="what needs doing")\`
|
||||
When creating multiple related tasks, declare dependencies between them:
|
||||
/*
|
||||
FNXC:TaskRecommendations 2026-08-09-04:06:
|
||||
FN-8850 requires optional, non-blocking discoveries to be captured only at the explicit accepted
|
||||
completion boundary. Immediate task creation remains for required dependency coordination or an
|
||||
operator-directed filing, while workflow step sessions remain unable to write recommendations.
|
||||
*/
|
||||
**Out-of-scope findings at completion:** Do not automatically create a task for optional, non-blocking work discovered outside this task. When recommendation capture is enabled, at the final accepted \`fn_task_done(outcome="completed")\` checkpoint evaluate genuine task-ready follow-ups and send \`recommendations\` (or \`recommendations: []\` when none qualify). Each recommendation needs a stable unique \`id\`, \`title\`, \`description\`, and \`category\`; never use it for a required current-task fix, blocker, secret, executable command, reasoning transcript, or filler.
|
||||
|
||||
Use \`fn_task_create\` or \`fn_delegate_task\` only when the task explicitly requires immediate filing, necessary dependency coordination, or the operator directs it. When creating multiple related tasks, declare dependencies between them:
|
||||
\`fn_task_create(description="load door sounds", dependencies=[])\` → returns KB-050
|
||||
\`fn_task_create(description="play sound on door open/close", dependencies=["KB-050"])\`
|
||||
|
||||
@@ -1734,12 +1741,31 @@ policy rather than malfunction. It is appended last so it wins over the base tex
|
||||
applies to a custom operator prompt too (an operator who overrode the prompt still gets a
|
||||
truthful statement of what this session may do).
|
||||
*/
|
||||
function getWithheldTaskCreationGuidance(taskCreateWithheld: boolean, delegateWithheld: boolean): string {
|
||||
function getCompletionRecommendationGuidance(maximum: number): string {
|
||||
/*
|
||||
FNXC:TaskRecommendations 2026-08-09-04:06:
|
||||
Engine-appended guidance preserves the accepted-completion recommendation contract even when an
|
||||
operator customizes the executor prompt. A disabled cap must not invite unavailable writes.
|
||||
*/
|
||||
if (maximum === 0) {
|
||||
return `## Completion recommendations
|
||||
|
||||
Recommendation capture is disabled for this project (maxRecommendationsPerTask is 0). Ignore any earlier generic recommendation guidance: do not send recommendations, including \`recommendations: []\`; use an honest summary or task log for non-blocking context, and do not fabricate a finding.`;
|
||||
}
|
||||
return `## Completion recommendations
|
||||
|
||||
At the final accepted \`fn_task_done(outcome="completed")\` checkpoint, evaluate optional, non-blocking work discovered outside this task. Send at most ${maximum} task-ready recommendations, each with a stable unique \`id\`, \`title\`, \`description\`, and \`category\`, or explicitly send \`recommendations: []\` when none genuinely qualify. Example populated payload: \`recommendations: [{ id: "follow-up-export", title: "Add task export", description: "Provide a CSV export for completed tasks.", category: "feature" }]\`. Do not fabricate filler or include required current-task work, blockers, secrets, executable commands, reasoning, or duplicate ids. Recommendations are only for completed outcomes; never send them with \`outcome="blocked"\`. Use immediate task creation/delegation only for an explicit task requirement, necessary dependency coordination, or operator direction.`;
|
||||
}
|
||||
|
||||
function getWithheldTaskCreationGuidance(taskCreateWithheld: boolean, delegateWithheld: boolean, maximum: number): string {
|
||||
if (!taskCreateWithheld && !delegateWithheld) return "";
|
||||
const withheld = [
|
||||
...(taskCreateWithheld ? ["`fn_task_create`"] : []),
|
||||
...(delegateWithheld ? ["`fn_delegate_task`"] : []),
|
||||
].join(" and ");
|
||||
const recommendationRoute = maximum > 0
|
||||
? `For optional, non-blocking discoveries, use the available completion recommendation route at accepted completion (or \`recommendations: []\` if none qualify).`
|
||||
: "Recommendation capture is disabled, so retain non-blocking context in an honest task log or completion summary without inventing a follow-up.";
|
||||
return `## Follow-up task creation is disabled for this session
|
||||
|
||||
This project's "Ephemeral agent follow-up tasks" policy withholds ${withheld}. ${
|
||||
@@ -1748,7 +1774,7 @@ This project's "Ephemeral agent follow-up tasks" policy withholds ${withheld}. $
|
||||
taskCreateWithheld && delegateWithheld ? "them" : "it"
|
||||
}, and do not retry.
|
||||
|
||||
Ignore any instruction above that tells you to file follow-up work with ${withheld}. When you find out-of-scope work, record it instead with \`fn_task_log(message="follow-up: ...")\` and include it in your \`fn_task_done\` summary so the operator sees it. If the work genuinely blocks this task, use \`fn_task_done(outcome="blocked", reason="...")\` rather than trying to create a task for it.`;
|
||||
Ignore any instruction above that tells you to file follow-up work with ${withheld}. ${recommendationRoute} If the work genuinely blocks this task, use \`fn_task_done(outcome="blocked", reason="...")\` rather than trying to create a task for it.`;
|
||||
}
|
||||
|
||||
/** Resolve the executor system prompt from settings, falling back to the hardcoded constant. */
|
||||
@@ -1758,12 +1784,15 @@ export function getExecutorSystemPrompt(
|
||||
): string {
|
||||
const customPrompt = resolveAgentPrompt("executor", settings.agentPrompts);
|
||||
const basePrompt = customPrompt || EXECUTOR_SYSTEM_PROMPT;
|
||||
const maximumRecommendations = settings.maxRecommendationsPerTask ?? 3;
|
||||
const sections = [
|
||||
basePrompt,
|
||||
isResearchToolSurfaceEnabled(settings) ? getResearchGuidanceForSurface("executor") : "",
|
||||
getCompletionRecommendationGuidance(maximumRecommendations),
|
||||
getWithheldTaskCreationGuidance(
|
||||
toolAvailability?.taskCreateWithheld === true,
|
||||
toolAvailability?.delegateWithheld === true,
|
||||
maximumRecommendations,
|
||||
),
|
||||
].filter((section) => section.trim());
|
||||
return sections.join("\n\n");
|
||||
@@ -18397,7 +18426,10 @@ export class TaskExecutor {
|
||||
description:
|
||||
"End the task. With outcome=\"completed\" (default): signal that all steps are complete, tests pass, and " +
|
||||
"documentation is updated — call as the final action after finishing all work; automatically marks all " +
|
||||
"remaining steps as done; optionally provide a summary of what was changed/fixed. " +
|
||||
"remaining steps as done. At this accepted final checkpoint, when recommendation capture is enabled, submit up to " +
|
||||
"the project cap of genuine, task-ready out-of-scope recommendations with stable unique ids, or explicitly send " +
|
||||
"recommendations: [] when none qualify; at cap 0, omit recommendations (an empty list is accepted for compatibility). " +
|
||||
"Do not use recommendations for required fixes, blockers, secrets, commands, or reasoning. " +
|
||||
"With outcome=\"blocked\": honestly park the task when the work genuinely cannot proceed (upstream API break, " +
|
||||
"missing dependency task, unresolvable external blocker). Blocked is NOT a completion claim — it does not " +
|
||||
"trip the review/completion gates, does not auto-complete or auto-skip steps, and preserves your worktree/" +
|
||||
@@ -18412,7 +18444,7 @@ export class TaskExecutor {
|
||||
title: Type.String(),
|
||||
description: Type.String(),
|
||||
category: Type.Union([Type.Literal("improvement"), Type.Literal("feature"), Type.Literal("bug"), Type.Literal("other")]),
|
||||
}), { description: "Optional bounded out-of-scope, task-ready follow-up suggestions. Do not include mandatory fixes, secrets, commands, or execution reasoning." })),
|
||||
}), { description: "For accepted completed outcomes when capture is enabled: submit at most the project cap of task-ready out-of-scope suggestions with unique stable ids, or [] when none qualify. At cap 0, omit this field; an empty list is accepted for compatibility but populated input is rejected. Never send for blocked/refused outcomes or include mandatory fixes, secrets, executable commands, or reasoning." })),
|
||||
/*
|
||||
FNXC:Lifecycle 2026-07-16-10:20:
|
||||
FN-8141 laundered a genuinely-impossible task into `done`: fn_task_done only expressed success, the bulk-completion
|
||||
|
||||
Reference in New Issue
Block a user