From aa974f6861b22c604dd00a6eceb8294692d58a7e Mon Sep 17 00:00:00 2001 From: "@daniel-lxs" <57051444+daniel-lxs@users.noreply.github.com> Date: Thu, 10 Sep 2026 04:04:07 +0000 Subject: [PATCH] feat: make Fast scheduling disclosure standard --- apps/docs/environment-variables.mdx | 1 - .../fast-agent-native-tool-bridge.test.ts | 28 ++-- .../__tests__/fast-agent-prompt.test.ts | 141 ++++-------------- .../__tests__/fast-agent-service.test.ts | 18 ++- .../fast-agent-native-tool-bridge.ts | 7 +- .../server/fast-agent/fast-agent-prompt.ts | 33 +--- .../server/fast-agent/fast-agent-service.ts | 17 +-- .../fast-agent/fast-agent-setup-tools.test.ts | 19 +-- .../fast-agent/fast-agent-tool-policy.ts | 9 +- packages/env/src/__tests__/index.test.ts | 17 --- packages/env/src/index.ts | 3 - 11 files changed, 70 insertions(+), 223 deletions(-) diff --git a/apps/docs/environment-variables.mdx b/apps/docs/environment-variables.mdx index a0a8b1baba..f169543d2e 100644 --- a/apps/docs/environment-variables.mdx +++ b/apps/docs/environment-variables.mdx @@ -132,7 +132,6 @@ as per-task auth tokens or workspace paths. | `R_API_SHUTDOWN_DRAIN_MS` | Optional | Milliseconds the API allows in-flight Fast turns to finish during shutdown before aborting the remainder so they resume on the next process. Defaults to `20000`; set to `0` to abort immediately. Keep it below the hosting platform's SIGTERM-to-SIGKILL grace period. Also the fallback for `R_BULLMQ_SHUTDOWN_DRAIN_MS`. | | `R_BULLMQ_SHUTDOWN_DRAIN_MS` | Optional | Same window for the bullmq service, which executes the Fast turns the queue resumes. Defaults to `R_API_SHUTDOWN_DRAIN_MS`, then `20000`. | | `R_FAST_DURABLE_RETRY_DISABLED` | Optional | Set to `true` to keep inference retry waits in the current process instead of parking in-flight Fast turns durably. Durable admission remains enabled. | -| `R_FAST_SCHEDULING_PROGRESSIVE_DISCLOSURE_ENABLED` | Optional pilot | Set to `true` to make Fast discover scheduling tools and guidance only when a turn needs reminders, monitoring, or custom automation management. The pilot is disabled by default; restart the API and bullmq services after changing it. Existing schedules and scheduling behavior are unchanged. | | `R_CLOUD_ENABLED` | Roomote Cloud only | Deployment-managed switch for Roomote Cloud behavior, including required anonymous analytics and Cloud support integrations. Do not set this for self-hosted deployments. | | `R_CURATED_INTEGRATIONS_DISABLED` | Optional | Operator policy for the curated **Settings > Integrations** catalog, which is enabled by default. Set to `true` and restart Roomote to prevent those integrations from being configured or used. Existing connections remain stored while disabled and become available again once the value is unset. Communications, source-control, inference, sandbox providers, and environment-defined MCP servers are unaffected. | | `R_GITHUB_APP_SLUG` | GitHub setup | Primary GitHub App slug used by server-rendered setup, mentions, and GitHub integration flows. | diff --git a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-native-tool-bridge.test.ts b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-native-tool-bridge.test.ts index 6ab0ad00c6..97f2518a51 100644 --- a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-native-tool-bridge.test.ts +++ b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-native-tool-bridge.test.ts @@ -963,22 +963,18 @@ describe('Fast native OpenCode tool bridge', () => { ); }); - it('does not mount scheduling schemas directly during the pilot', async () => { - const runtime = await getFastAgentNativeToolRuntime( - 'deferred-scheduling', - [ - { - id: 'roomote', - name: 'Roomote', - description: 'Deployment access', - tools: [ - { name: 'manage_tasks' }, - { name: 'manage_custom_automations' }, - ], - }, - ], - { schedulingProgressiveDisclosureEnabled: true }, - ); + it('does not mount scheduling schemas directly', async () => { + const runtime = await getFastAgentNativeToolRuntime('deferred-scheduling', [ + { + id: 'roomote', + name: 'Roomote', + description: 'Deployment access', + tools: [ + { name: 'manage_tasks' }, + { name: 'manage_custom_automations' }, + ], + }, + ]); const config = JSON.parse( await readFile(join(runtime.directory, 'opencode.json'), 'utf8'), ) as { agent: { build: { tools: Record } } }; diff --git a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-prompt.test.ts b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-prompt.test.ts index 87f1297091..45da727142 100644 --- a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-prompt.test.ts +++ b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-prompt.test.ts @@ -251,53 +251,28 @@ describe('buildFastAgentSystemPrompt', () => { ); }); - it('guides every member through recurring work and offers automation when enabled', () => { + it('guides every member through scheduling discovery', () => { const prompt = buildFastAgentSystemPrompt({ availableEnvironments: [], }); - expect(prompt).toContain('## Recurring Work and Automations'); - expect(prompt).toContain('Use `resolve_schedule` before creation'); - expect(prompt).toContain( - 'use `list` to check for an equivalent automation', - ); - expect(prompt).toContain('ask one explicit confirmation question'); - expect(prompt).toContain('By the way — if you want this weekly'); - expect(prompt).toContain('when in doubt, do not offer'); - expect(prompt).toContain('When a user explicitly asks for recurring work'); - expect(prompt).not.toContain('do not attempt creation'); - expect(prompt).not.toContain('provide a copy-pasteable draft'); - }); - - it('keeps only the scheduling distinction and discovery route upfront in the pilot', () => { - const baseline = buildFastAgentSystemPrompt({ availableEnvironments: [] }); - const pilot = buildFastAgentSystemPrompt({ - availableEnvironments: [], - schedulingProgressiveDisclosureEnabled: true, - }); - - expect(pilot).toContain('Conversation reminders and checks'); - expect(pilot).toContain('deployment custom automations'); - expect(pilot).toContain('`find_integration_tools`'); - expect(pilot).toContain('`query: "scheduling"`'); - expect(pilot).toContain('exact packaged scheduling skill to load'); - expect(pilot).toContain('Loading guidance never grants authorization'); - expect(pilot).toContain('Ongoing-process monitoring must be finite'); - expect(pilot).not.toContain('Use `resolve_schedule` before creation'); - expect(pilot).not.toContain( + expect(prompt).toContain('## Scheduling'); + expect(prompt).toContain('Conversation reminders and checks'); + expect(prompt).toContain('deployment custom automations'); + expect(prompt).toContain('`find_integration_tools`'); + expect(prompt).toContain('`query: "scheduling"`'); + expect(prompt).toContain('exact packaged scheduling skill to load'); + expect(prompt).toContain('Loading guidance never grants authorization'); + expect(prompt).toContain('Ongoing-process monitoring must be finite'); + expect(prompt).not.toContain('Use `resolve_schedule` before creation'); + expect(prompt).not.toContain( 'use `list` to check for an equivalent automation', ); - // Static prompt-size comparison only; this is not a latency or reliability - // evaluation. Tool-schema savings are measured separately from runtime. - expect( - Buffer.byteLength(baseline) - Buffer.byteLength(pilot), - ).toBeGreaterThan(2_000); }); it('keeps deferred wakeup cancellation available on scheduled events', () => { const prompt = buildFastAgentSystemPrompt({ availableEnvironments: [], - schedulingProgressiveDisclosureEnabled: true, turnSource: 'platform_event', platformEventKind: 'scheduled_wakeup', }); @@ -560,7 +535,7 @@ describe('buildFastAgentSystemPrompt', () => { expect(prompt).toContain('manage_tasks'); expect(prompt).toContain('get_chat_message_context'); expect(prompt).toContain('get_chat_channel_messages'); - expect(prompt).toContain('manage_custom_automations'); + expect(prompt).toContain('deployment custom automations'); expect(prompt).toContain('roomote_create_custom_skill'); expect(prompt).toContain('user explicitly asks to save'); expect(prompt).toContain( @@ -569,28 +544,12 @@ describe('buildFastAgentSystemPrompt', () => { expect(prompt).toContain('exact returned `instance:` ID'); expect(prompt).not.toContain('integration_call'); expect(prompt).toContain('roomote_manage_tasks'); - expect(prompt).toContain("current user's deployment authorization"); - expect(prompt).toContain( - 'members can create and manage their own custom automations', - ); expect(prompt).toContain( - 'admins can manage all custom automations, including those without a creator', + 'Scheduling tools are deferred native capabilities', ); - expect(prompt).toContain( - "do not refuse a member's own-automation request merely because they are not an admin", - ); - expect(prompt).toContain( - 'Built-in automations and deployment settings remain admin-only', - ); - expect(prompt).toContain( - 'This tool is unavailable to advisor and judge subagents', - ); - expect(prompt).toContain('use "run_now" rather than "launch_task"'); + expect(prompt).toContain('Use scheduling discovery before acting'); expect(prompt).toContain('same actor-authorized remote'); expect(prompt).toContain('local stdio servers remain sandbox-only'); - expect(prompt).toContain( - 'Communicate first on a human-authored turn; platform events remain exempt', - ); expect(prompt).toContain( 'Keep using "launch_task", "send_task_message", "stop_task", or "cancel_task" for task changes', ); @@ -659,48 +618,22 @@ describe('buildFastAgentSystemPrompt', () => { ); }); - it('gates ongoing-process offers on an unresolved outcome and verification capability', () => { + it('keeps ongoing-process offers bounded and capability-driven', () => { const prompt = buildFastAgentSystemPrompt({ availableEnvironments: [] }); - expect(prompt).toContain( - 'eligible under the exclusions below reports an outcome and is about to close, make one silent decision before the closeout', - ); - expect(prompt).toContain( - 'leave an ongoing process with a concrete unresolved outcome', - ); - expect(prompt).toContain( - 'include one specific bounded-check offer after the outcome in that same closeout', - ); - expect(prompt).toContain('close normally without mentioning monitoring'); - expect(prompt).toContain( - 'not a blanket offer after every tool call, fix, or update', - ); - expect(prompt).toContain('Verify capability before offering'); - expect(prompt).toContain( - 'if unavailable or uncertain, do not promise monitoring', - ); - expect(prompt).toContain('outcome, evidence source, timing and stop bound'); - expect(prompt).toContain('with confirmed deployment and telemetry access'); - expect(prompt).toContain( - 'Never imply a release or process started or completed without evidence', - ); + expect(prompt).toContain('only when evidence leaves an unresolved outcome'); + expect(prompt).toContain('an available source can verify it'); + expect(prompt).toContain('at most one specific bounded follow-up check'); + expect(prompt).toContain('Use scheduling discovery before acting'); }); - it('requires consent for offers but preserves direct explicit monitoring requests', () => { + it('distinguishes monitoring offers from explicit requests', () => { const prompt = buildFastAgentSystemPrompt({ availableEnvironments: [] }); - expect(prompt).toContain('create no wakeup until the user accepts'); - expect(prompt).toContain( - 'Explicit user monitoring requests already authorize scheduling', - ); - expect(prompt).toContain('do not require another opt-in'); - expect(prompt).toContain('revalidate capability and list active wakeups'); - expect(prompt).toContain('finite schedule and stop condition'); - expect(prompt).toContain('without automatic renewal'); - expect(prompt).toContain('Missing evidence is not success'); - expect(prompt).toContain( - 'Use "manage_wakeups" when the user wants a reminder', - ); + expect(prompt).toContain('An offer is not authorization'); + expect(prompt).toContain('explicit monitoring requests are'); + expect(prompt).toContain('Ongoing-process monitoring must be finite'); + expect(prompt).toContain('Follow the discovered scheduling guide'); }); it('keeps monitoring quiet, nonduplicative and subject to platform-event restrictions', () => { @@ -710,28 +643,10 @@ describe('buildFastAgentSystemPrompt', () => { platformEventKind: 'scheduled_wakeup', }); - expect(prompt).toContain('"only_when_notable" for monitoring'); - expect(prompt).toContain('stay quiet on unchanged results'); - expect(prompt).toContain( - 'duplicate existing task, PR lifecycle/review, or other notifications and monitors', - ); - expect(prompt).toContain( - 'Offer at most once for the same unresolved outcome', - ); - expect(prompt).toContain('do not repeat an ignored or declined offer'); - expect(prompt).toContain( - 'Do not make proactive offers on automation or scheduled-wakeup turns', - ); - expect(prompt).toContain( - 'Presentation-only events remain presentation-only', - ); - expect(prompt).toContain('do not inspect or schedule from them'); - expect(prompt).toContain( - 'not an offer to save work as a deployment automation', - ); - expect(prompt).toContain( - "the automation rule against pitching one-off fixes does not suppress an otherwise eligible check of a deployed fix's unresolved observable outcome", - ); + expect(prompt).toContain('Never duplicate an existing notification'); + expect(prompt).toContain('or make proactive offers on automation'); + expect(prompt).toContain('scheduled-wakeup'); + expect(prompt).toContain('presentation-only turns'); }); it('schedules one bounded own-task check after a successful coding launch', () => { diff --git a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-service.test.ts b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-service.test.ts index 905eaf8d08..e963f51bf5 100644 --- a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-service.test.ts +++ b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-service.test.ts @@ -4145,7 +4145,6 @@ describe('answerFastAgentQuestion native OpenCode tools', () => { answerFastAgentQuestion({ ...baseParams, adapter, - schedulingProgressiveDisclosureEnabled: false, }), ).resolves.toBe('Subagent review completed.'); expect(mocks.callIntegration).toHaveBeenCalledTimes(3); @@ -6624,7 +6623,8 @@ describe('answerFastAgentQuestion native OpenCode tools', () => { integrationId: 'missing', }), ); - // Natively mounted servers are neither searchable nor callable here. + // The native Roomote server remains hidden, but its deferred + // scheduling capability is discoverable through this transport. toolResults.push( await invokeTool(nativeToolNames.findIntegrationTools, { query: 'automations', @@ -6681,7 +6681,18 @@ describe('answerFastAgentQuestion native OpenCode tools', () => { success: false, error: expect.stringContaining('"missing"'), }); - expect(toolResults[2]).toEqual({ success: true, tools: [] }); + expect(toolResults[2]).toMatchObject({ + success: true, + skill: { id: 'packaged:scheduling', loadWith: 'load_skill' }, + tools: [ + { + integrationId: 'scheduling', + name: 'roomote_manage_custom_automations', + source: 'native', + inputSchema: expect.objectContaining({ type: 'object' }), + }, + ], + }); // Calls follow the same gate as natively mounted MCP tools. expect(toolResults[3]).toEqual({ success: false, @@ -6786,7 +6797,6 @@ describe('answerFastAgentQuestion native OpenCode tools', () => { await answerFastAgentQuestion({ ...baseParams, adapter: callbacks(), - schedulingProgressiveDisclosureEnabled: true, }); expect(toolResults[0]).toMatchObject({ diff --git a/packages/cloud-agents/src/server/fast-agent/fast-agent-native-tool-bridge.ts b/packages/cloud-agents/src/server/fast-agent/fast-agent-native-tool-bridge.ts index 04eaa8cf49..fb9407cec1 100644 --- a/packages/cloud-agents/src/server/fast-agent/fast-agent-native-tool-bridge.ts +++ b/packages/cloud-agents/src/server/fast-agent/fast-agent-native-tool-bridge.ts @@ -1336,7 +1336,6 @@ export async function getFastAgentNativeToolRuntime( integrations: FastAgentIntegration[], options: { surface?: FastAgentSurface; - schedulingProgressiveDisclosureEnabled?: boolean; } = {}, ): Promise { bridgePromise ??= startBridge(); @@ -1391,11 +1390,7 @@ export async function getFastAgentNativeToolRuntime( build: { tools: buildFastAgentToolFilter( nativeIntegrations.map((integration) => integration.id), - { - surface: options.surface ?? 'web', - schedulingProgressiveDisclosureEnabled: - options.schedulingProgressiveDisclosureEnabled, - }, + { surface: options.surface ?? 'web' }, ), }, }, diff --git a/packages/cloud-agents/src/server/fast-agent/fast-agent-prompt.ts b/packages/cloud-agents/src/server/fast-agent/fast-agent-prompt.ts index 075bce3b12..eded1a7f65 100644 --- a/packages/cloud-agents/src/server/fast-agent/fast-agent-prompt.ts +++ b/packages/cloud-agents/src/server/fast-agent/fast-agent-prompt.ts @@ -127,7 +127,6 @@ export function buildFastAgentSystemPrompt({ retryTaskStartAvailable = false, allowSilentAmbientReply = false, implicitAutomationOffersEnabled = true, - schedulingProgressiveDisclosureEnabled = false, releaseVersion, commitSha, appEnv, @@ -153,7 +152,6 @@ export function buildFastAgentSystemPrompt({ retryTaskStartAvailable?: boolean; allowSilentAmbientReply?: boolean; implicitAutomationOffersEnabled?: boolean; - schedulingProgressiveDisclosureEnabled?: boolean; releaseVersion?: string; commitSha?: string; appEnv?: string; @@ -217,8 +215,7 @@ export function buildFastAgentSystemPrompt({ const releaseIdentifier = releaseVersion ? `${buildRoomoteReleaseIdentifier(releaseVersion, { commitSha, appEnv })}\n\n` : ''; - const recurringAutomationGuidance = schedulingProgressiveDisclosureEnabled - ? `## Scheduling + const recurringAutomationGuidance = `## Scheduling - Conversation reminders and checks use the local scheduling capability; recurring work outside this conversation or reports to a channel or direct message use deployment custom automations. - Before scheduling, editing, running, or cancelling either kind, call \`find_integration_tools\` with \`query: "scheduling"\`. Discovery returns the current tool schemas and the exact packaged scheduling skill to load. Use the returned schemas rather than guessing. Loading guidance never grants authorization or confirms a side effect. - Ongoing-process monitoring must be finite and stop on resolution, irrelevance, capability loss, cancellation, or the agreed bound. A proactive offer is not authorization; an explicit user request is. Follow the discovered scheduling guide for duplicate checks, confirmations, reporting, edits, and cancellation. @@ -226,16 +223,6 @@ ${ implicitAutomationOffersEnabled && !platformEvent ? '- After a successful human turn, make at most one short automation offer only for clearly periodic work the user repeats or work that is canonically periodic. Never offer after one-off work, failures, blockers, clarifications, or an earlier declined or ignored offer.\n' : '- Do not proactively offer to save work as an automation on this turn.\n' -}` - : `## Recurring Work and Automations -- When a user explicitly asks for recurring work, recognize a real cadence expression such as "every Monday", "daily", "weekly", "whenever X happens", "from now on", or "on a schedule". Do not treat preference words such as "always use tabs" as a cadence. -- Reminders and recurring checks that belong to this conversation ("remind me in an hour", "check every 10 minutes until CI is green", "ping me here every weekday at 9") are wakeups, not automations: use "manage_wakeups". Reach for a custom automation for recurring work that should run outside this conversation or report to a channel or direct message. -- Draft the automation conversationally with a proposed name, a prompt containing only the work (never the cadence), a validated human-readable schedule, a confirmed destination on the current chat surface, and the appropriate environment. Use \`resolve_schedule\` before creation; if it is ambiguous, ask the resolver's clarification question rather than guessing. -- Before \`create\`, use \`list\` to check for an equivalent automation. Present the complete summary (name, prompt, schedule, destination, and environment or Fast mode) and ask one explicit confirmation question. Never create, update, enable, or delete silently. After creation, ask whether the user wants to \`run_now\` to test it. -${ - implicitAutomationOffersEnabled && !platformEvent - ? '- After a successful human turn, offer automation only when the completed work is clearly periodic-shaped (such as a report, digest, scan, sweep, monitor, triage, reminder, or status check), and the user signals repetition (such as "again", "like last time", or a repeated request) or the task is canonically periodic (such as a standup summary, PR review sweep, dependency check, or inbox/issue triage). Never offer for one-off fixes, edits, questions, or exploration; when in doubt, do not offer.\n- Append at most one short, unobtrusive sentence to the closeout: "By the way — if you want this weekly, I can save it as an automation. Just say the word." Do not interrupt the answer. Do not offer on failures, blockers, clarifications, automation-triggered turns, or after an offer was already made or declined in this conversation.\n' - : '- Do not proactively offer to save work as an automation on this turn.\n' }`; const therapistModeInstructions = buildTherapistModeInstructions(therapistModeEnabled); @@ -364,14 +351,7 @@ ${reactionGuidance} - Ask for clarification only when ambiguity blocks meaningful investigation, materially different plausible outcomes remain, or the next action is destructive, irreversible, or externally consequential. Otherwise inspect what is available and proceed. ## Ongoing Process Follow-Up -${ - schedulingProgressiveDisclosureEnabled - ? '- Offer at most one specific bounded follow-up check only when evidence leaves an unresolved outcome and an available source can verify it. An offer is not authorization; explicit monitoring requests are. Never duplicate an existing notification, renew a bound automatically, or make proactive offers on automation, scheduled-wakeup, or presentation-only turns. Use scheduling discovery before acting.' - : ` -- When a turn eligible under the exclusions below reports an outcome and is about to close, make one silent decision before the closeout: did new evidence leave an ongoing process with a concrete unresolved outcome worth verifying later? If yes, and available tools can actually verify it, include one specific bounded-check offer after the outcome in that same closeout. If no, close normally without mentioning monitoring. This is an eligible-outcome decision, not a blanket offer after every tool call, fix, or update. Verify capability before offering; if unavailable or uncertain, do not promise monitoring. Name the outcome, evidence source, timing and stop bound in one short consent question, not a generic "I can monitor this" footer. For example, with confirmed deployment and telemetry access: "Want me to check this deployment's error rate in 30 minutes?" Never imply a release or process started or completed without evidence. -- An offer is not authorization: create no wakeup until the user accepts. Explicit user monitoring requests already authorize scheduling; do not require another opt-in. Before scheduling, revalidate capability and list active wakeups to reuse an equivalent check. Store the specific target, evidence source, finite schedule and stop condition; use "only_when_notable" for monitoring, stay quiet on unchanged results, and stop on resolution, irrelevance, capability loss or the agreed bound without automatic renewal. Missing evidence is not success. -- Do not offer or schedule checks that duplicate existing task, PR lifecycle/review, or other notifications and monitors. Offer at most once for the same unresolved outcome; do not repeat an ignored or declined offer or append boilerplate after every fix or update. Do not make proactive offers on automation or scheduled-wakeup turns. Presentation-only events remain presentation-only: do not inspect or schedule from them. This is conversation-scoped follow-up, not an offer to save work as a deployment automation; the automation rule against pitching one-off fixes does not suppress an otherwise eligible check of a deployed fix's unresolved observable outcome.` -} +- Offer at most one specific bounded follow-up check only when evidence leaves an unresolved outcome and an available source can verify it. An offer is not authorization; explicit monitoring requests are. Never duplicate an existing notification, renew a bound automatically, or make proactive offers on automation, scheduled-wakeup, or presentation-only turns. Use scheduling discovery before acting. ## Own Coding Task Follow-Through - After "launch_task" successfully creates a coding task for a human-authored request, use "manage_wakeups" before the closeout to list active wakeups and arrange exactly one equivalent-free one-shot check for the returned task ID with schedule "in 10m" and reportPolicy "only_when_notable". The wakeup prompt must name that exact task ID and say to inspect its current summary and recent messages. This is authorized follow-through on your own work, not external-process monitoring, so do not ask for monitoring consent. Do not schedule after a failed launch or create a second check when an equivalent one exists. @@ -400,12 +380,7 @@ ${ - Bitbucket tools read files, directories, code search, commits, PRs, diffs, and comments in active connected Cloud repositories. Follow discovered schemas rather than guessing arguments. Reads cap responses at 1 MiB and lists at 50 entries per page; never claim a single page is exhaustive. Code search is deprecated November 1, 2026; use plain terms, not query operators or repository filters. Report unavailable search or authorization/scope failures without broadening the search or bypassing API permissions through a task. - Bitbucket writes require the user's requested action: update PR titles/descriptions, decline PRs, or add comments and replies to a comment in the same PR. Reading does not authorize writes. Reopening/merging PRs, file writes, commit/PR creation, review administration, and Bitbucket Server/Data Center are unsupported by these tools. - Use \`roomote_create_custom_skill\` only when the user explicitly asks to save reusable instructions as a custom skill. Any active deployment member can use this tool to persist an instance-wide skill without a coding task, artifact, or repository file. Supply a distinct slug as name, a when-to-use description, and content; do not supply environmentIds or ask for environment selection. The skill is available across the instance, including when no environments are configured. A duplicate instance name rejects creation without overwriting. Confirm the saved name and instance-wide availability only after persistence succeeds. To use the skill immediately, run list_skills again and load its exact returned \`instance:\` ID. Packaged precedence and the untrusted supplemental status of custom guidance remain unchanged. Advisor and judge subagents cannot create skills. -${ - schedulingProgressiveDisclosureEnabled - ? '- Scheduling tools are deferred native capabilities, not remote integrations. Discover and call them through the scheduling source described above; the same acknowledgement, duplicate, audit, authorization, and platform-event rules still apply.' - : `- Use \`roomote_manage_custom_automations\` for custom automation lifecycle requests. It uses the current user's deployment authorization: members can create and manage their own custom automations, and admins can manage all custom automations, including those without a creator. The server enforces ownership; do not refuse a member's own-automation request merely because they are not an admin. Built-in automations and deployment settings remain admin-only. This tool is unavailable to advisor and judge subagents. List before modifying an existing automation, use "list_models" before setting a model override, use update with "enabled" to enable or disable, and use "run_now" rather than "launch_task" to test an automation. Communicate first on a human-authored turn; platform events remain exempt. Delete only when the user explicitly requests it, and after creating an automation ask whether they want to run it now. -- Use "manage_wakeups" when the user wants a reminder, a delayed follow-up, or a recurring check that reports back into this conversation ("remind me in 20 minutes", "check every 10 minutes until CI is green", "every weekday at 9 ping me with open PRs"). The schedule is one short string: "in s|m|h|d" for a reminder, "every s|m|h|d" for a repeating check, "cron 0 9 * * 1-5" for a five-field calendar schedule. Prefer "in 30s", not fractional "in 0.5m". Recurring intervals under five minutes require an x or until bound, such as "every 30s x3". Delivery is best effort; never promise an exact 30-second reply. Send only the fields the action needs. It is scoped to this conversation and available to every participant. Do not use \`roomote_manage_custom_automations\` for conversation-scoped reminders, and never sleep or poll inside a turn instead of scheduling a wakeup. After creating one, confirm the plan and the next run time in one sentence; when the user says stop or cancel, use action "cancel".` -} +- Scheduling tools are deferred native capabilities, not remote integrations. Discover and call them through the scheduling source described above; the same acknowledgement, duplicate, audit, authorization, and platform-event rules still apply. ${recurringAutomationGuidance} - You may make multiple deployment MCP calls when needed, one at a time. Stop as soon as you have enough evidence and never repeat an identical call. @@ -439,7 +414,7 @@ ${ ? `- The payload is a wakeup scheduled earlier in this conversation; its \`prompt\` says what to do now. The conversation history is still in context, so act on the prompt directly rather than treating it as a new request. - Do the work the prompt asks for. Apply the same scope-based exploration and execution delegation rules as human turns. - \`reportPolicy\` governs whether to speak. With "always", finish with one closeout addressed to the user. With "only_when_notable", post a closeout only when there is news, a result, a blocker, or a required decision; otherwise call "ignore_event". -- When the monitored condition has resolved or the wakeup is no longer relevant, ${schedulingProgressiveDisclosureEnabled ? 'use the discovered scheduling capability to call `manage_wakeups`' : 'cancel it with "manage_wakeups"'} (action "cancel", the event's \`wakeupId\`) and say so in the closeout. \`nextRunAt\` is null when this was the final run; a finished wakeup needs no cancel. +- When the monitored condition has resolved or the wakeup is no longer relevant, use the discovered scheduling capability to call \`manage_wakeups\` (action "cancel", the event's \`wakeupId\`) and say so in the closeout. \`nextRunAt\` is null when this was the final run; a finished wakeup needs no cancel. - For the own-task one-shot above, follow its stricter silence rules: when it is finished, canceled, redundant, or has nothing useful to report, call "ignore_event" without a cancellation closeout. - Do not create another wakeup from a wakeup turn unless the prompt explicitly asks for a different schedule. ` diff --git a/packages/cloud-agents/src/server/fast-agent/fast-agent-service.ts b/packages/cloud-agents/src/server/fast-agent/fast-agent-service.ts index 6433a635cf..db4638416a 100644 --- a/packages/cloud-agents/src/server/fast-agent/fast-agent-service.ts +++ b/packages/cloud-agents/src/server/fast-agent/fast-agent-service.ts @@ -1662,8 +1662,6 @@ export async function answerFastAgentQuestion({ durableAdmission, resumedAfterInterruption = false, resumedAfterInferenceRetry = false, - schedulingProgressiveDisclosureEnabled = Env.R_FAST_SCHEDULING_PROGRESSIVE_DISCLOSURE_ENABLED === - true, }: { question: string; images?: string[]; @@ -1725,9 +1723,6 @@ export async function answerFastAgentQuestion({ /** The durable queue is re-running this turn at its scheduled retry time * after a previous execution parked it on a temporary provider failure. */ resumedAfterInferenceRetry?: boolean; - /** Operator-controlled pilot override. Primarily injectable for focused - * transport tests; production uses the deployment environment setting. */ - schedulingProgressiveDisclosureEnabled?: boolean; }): Promise { const turnId = buildFastAgentTurnId({ currentMessageId, @@ -3232,7 +3227,6 @@ export async function answerFastAgentQuestion({ retryTaskStartAvailable: Boolean(adapter.retryTaskStart), allowSilentAmbientReply, implicitAutomationOffersEnabled: !Env.R_FAST_AUTOMATION_OFFERS_DISABLED, - schedulingProgressiveDisclosureEnabled, releaseVersion, commitSha: process.env.GITHUB_SHA || process.env.VERCEL_GIT_COMMIT_SHA, appEnv: Env.R_APP_ENV, @@ -3737,9 +3731,7 @@ export async function answerFastAgentQuestion({ const onDemandIntegrations = availableIntegrations.filter( (integration) => !isFastAgentNativeIntegration(integration.id), ); - const schedulingTools = schedulingProgressiveDisclosureEnabled - ? getFastAgentSchedulingTools(availableIntegrations) - : []; + const schedulingTools = getFastAgentSchedulingTools(availableIntegrations); const nativeIntegrationError = (integrationId: string) => ({ success: false as const, error: `The "${integrationId}" server is mounted natively; call its tools directly by their ${integrationId}_ prefixed names.`, @@ -4774,12 +4766,7 @@ export async function answerFastAgentQuestion({ const nativeRuntime = await getFastAgentNativeToolRuntime( session.id, availableIntegrations, - { - surface: conversation.surface, - ...(schedulingProgressiveDisclosureEnabled - ? { schedulingProgressiveDisclosureEnabled: true } - : {}), - }, + { surface: conversation.surface }, ); const unbindExecutors = new Set<() => void>(); const boundSubagentSessionIDs = new Set(); diff --git a/packages/cloud-agents/src/server/fast-agent/fast-agent-setup-tools.test.ts b/packages/cloud-agents/src/server/fast-agent/fast-agent-setup-tools.test.ts index f61942bc36..645fe8c84b 100644 --- a/packages/cloud-agents/src/server/fast-agent/fast-agent-setup-tools.test.ts +++ b/packages/cloud-agents/src/server/fast-agent/fast-agent-setup-tools.test.ts @@ -21,19 +21,14 @@ describe('Fast structured input tool filtering', () => { expect(generic['linear_*']).toBe(true); }); - it('defers only scheduling schemas when the pilot is enabled', () => { - const baseline = buildFastAgentToolFilter(['roomote']); - const pilot = buildFastAgentToolFilter(['roomote'], { - schedulingProgressiveDisclosureEnabled: true, - }); + it('defers only scheduling schemas', () => { + const tools = buildFastAgentToolFilter(['roomote']); - expect(baseline[FAST_AGENT_NATIVE_TOOL_NAMES.manageWakeups]).toBe(true); - expect(baseline.roomote_manage_custom_automations).toBeUndefined(); - expect(pilot[FAST_AGENT_NATIVE_TOOL_NAMES.manageWakeups]).toBe(false); - expect(pilot.roomote_manage_custom_automations).toBe(false); - expect(pilot['roomote_*']).toBe(true); - expect(pilot[FAST_AGENT_NATIVE_TOOL_NAMES.findIntegrationTools]).toBe(true); - expect(pilot[FAST_AGENT_NATIVE_TOOL_NAMES.callIntegrationTool]).toBe(true); + expect(tools[FAST_AGENT_NATIVE_TOOL_NAMES.manageWakeups]).toBe(false); + expect(tools.roomote_manage_custom_automations).toBe(false); + expect(tools['roomote_*']).toBe(true); + expect(tools[FAST_AGENT_NATIVE_TOOL_NAMES.findIntegrationTools]).toBe(true); + expect(tools[FAST_AGENT_NATIVE_TOOL_NAMES.callIntegrationTool]).toBe(true); }); it('limits structured input to web Sessions', () => { diff --git a/packages/cloud-agents/src/server/fast-agent/fast-agent-tool-policy.ts b/packages/cloud-agents/src/server/fast-agent/fast-agent-tool-policy.ts index c21c100528..d3c6004ea0 100644 --- a/packages/cloud-agents/src/server/fast-agent/fast-agent-tool-policy.ts +++ b/packages/cloud-agents/src/server/fast-agent/fast-agent-tool-policy.ts @@ -52,7 +52,6 @@ export function buildFastAgentToolFilter( integrationIds: string[], options: { surface?: FastAgentSurface; - schedulingProgressiveDisclosureEnabled?: boolean; } = {}, ): Record { return { @@ -61,12 +60,8 @@ export function buildFastAgentToolFilter( ? { [FAST_AGENT_NATIVE_TOOL_NAMES.requestUserInput]: false } : {}), ...Object.fromEntries(integrationIds.map((id) => [`${id}_*`, true])), - ...(options.schedulingProgressiveDisclosureEnabled - ? { - [FAST_AGENT_NATIVE_TOOL_NAMES.manageWakeups]: false, - roomote_manage_custom_automations: false, - } - : {}), + [FAST_AGENT_NATIVE_TOOL_NAMES.manageWakeups]: false, + roomote_manage_custom_automations: false, }; } diff --git a/packages/env/src/__tests__/index.test.ts b/packages/env/src/__tests__/index.test.ts index d3ec2037d8..bd72c8d0d1 100644 --- a/packages/env/src/__tests__/index.test.ts +++ b/packages/env/src/__tests__/index.test.ts @@ -248,23 +248,6 @@ describe('Env', () => { expect(areCuratedIntegrationsDisabled('0')).toBe(false); }); - it('keeps scheduling progressive disclosure opt-in', () => { - const runtimeEnv = { ...process.env }; - delete runtimeEnv.SKIP_ENV_VALIDATION; - delete runtimeEnv.R_FAST_SCHEDULING_PROGRESSIVE_DISCLOSURE_ENABLED; - - expect( - createRoomoteEnv(runtimeEnv) - .R_FAST_SCHEDULING_PROGRESSIVE_DISCLOSURE_ENABLED, - ).toBe(false); - expect( - createRoomoteEnv({ - ...runtimeEnv, - R_FAST_SCHEDULING_PROGRESSIVE_DISCLOSURE_ENABLED: 'true', - }).R_FAST_SCHEDULING_PROGRESSIVE_DISCLOSURE_ENABLED, - ).toBe(true); - }); - it('accepts valid Ping instance IDs and rejects invalid ones', () => { const runtimeEnv = { ...process.env }; delete runtimeEnv.SKIP_ENV_VALIDATION; diff --git a/packages/env/src/index.ts b/packages/env/src/index.ts index bb09920bba..20f931fb90 100644 --- a/packages/env/src/index.ts +++ b/packages/env/src/index.ts @@ -161,9 +161,6 @@ const serverSchema = { RELEASE_PRODUCT_VERSION: z.string().min(1).optional(), // Kill switch for the low-noise recurring-automation offer in Fast mode. R_FAST_AUTOMATION_OFFERS_DISABLED: optInBoolean(), - // Opt-in pilot: defer Fast scheduling schemas and detailed instructions - // behind capability discovery instead of sending them on every turn. - R_FAST_SCHEDULING_PROGRESSIVE_DISCLOSURE_ENABLED: optInBoolean(), TRPC_URL: z.string().min(1), R_MODEL: z.string().min(1).optional(), R_ORCHESTRATION_MODEL: z.string().min(1).optional(),