agent-accelerator 0.4.5 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +149 -57
- package/bunfig.toml +1 -0
- package/package.json +2 -3
- package/src/agent/agent.ts +401 -120
- package/src/agent/context.ts +11 -16
- package/src/agent/delegation.ts +111 -274
- package/src/agent/loop.ts +253 -98
- package/src/agent/subagent.ts +4 -12
- package/src/agent/worker.ts +435 -0
- package/src/index.ts +39 -5
- package/src/models/catalog-cache.ts +65 -25
- package/src/models/catalog.ts +21 -2
- package/src/providers/google.ts +282 -74
- package/src/providers/openai-compat.ts +61 -41
- package/src/providers/openai.ts +46 -31
- package/src/providers/openrouter.ts +42 -31
- package/src/providers/shared.ts +110 -16
- package/src/providers.ts +119 -6
- package/src/scheduler/scheduled-actions.md +41 -0
- package/src/scheduler/scheduler.ts +814 -0
- package/src/scheduler/store.ts +263 -0
- package/src/scheduler/time.ts +148 -0
- package/src/scheduler/tool.ts +151 -0
- package/src/session/store.ts +211 -104
- package/src/streaming/event-stream.ts +6 -4
- package/src/telemetry-file.ts +31 -0
- package/src/telemetry.ts +65 -0
- package/src/tools/executor.ts +163 -38
- package/src/tools/subagent-result.ts +60 -0
- package/src/types/agent.ts +57 -20
- package/src/types/model.ts +18 -0
- package/src/types/response.ts +2 -0
- package/src/types/scheduler.ts +81 -0
- package/src/types/tool.ts +15 -0
- package/src/utils/cache.ts +14 -5
- package/src/utils/documents.ts +112 -52
- package/src/utils/errors.ts +11 -0
- package/src/utils/headers.ts +15 -15
- package/src/utils/home.ts +30 -0
- package/src/utils/media.ts +57 -12
- package/src/utils/raw-capture.ts +76 -0
- package/src/utils/retry.ts +11 -3
- package/src/utils/safe-io.ts +252 -0
- package/src/utils/session.ts +7 -8
- package/src/data/README.md +0 -84
package/src/providers/shared.ts
CHANGED
|
@@ -6,8 +6,11 @@
|
|
|
6
6
|
* across the four REST transports, plus timeout/signal and id helpers.
|
|
7
7
|
*/
|
|
8
8
|
import { toConciseProviderError } from "../utils/errors.ts";
|
|
9
|
+
import { withRetries } from "../utils/retry.ts";
|
|
10
|
+
import { redactHeadersCentral } from "../utils/cache.ts";
|
|
11
|
+
import { TRUNCATED_ARGS_KEY, isTruncatedArguments } from "../types/tool.ts";
|
|
9
12
|
|
|
10
|
-
/** Parses a JSON tool-argument string; non-objects degrade to
|
|
13
|
+
/** Parses a JSON tool-argument string; non-objects degrade to the truncated-arguments marker. */
|
|
11
14
|
export function parseArguments(raw: string | undefined): Record<string, unknown> {
|
|
12
15
|
if (!raw) return {};
|
|
13
16
|
try {
|
|
@@ -15,9 +18,9 @@ export function parseArguments(raw: string | undefined): Record<string, unknown>
|
|
|
15
18
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
16
19
|
return parsed as Record<string, unknown>;
|
|
17
20
|
}
|
|
18
|
-
return { raw };
|
|
21
|
+
return { [TRUNCATED_ARGS_KEY]: raw };
|
|
19
22
|
} catch {
|
|
20
|
-
return { raw };
|
|
23
|
+
return { [TRUNCATED_ARGS_KEY]: raw };
|
|
21
24
|
}
|
|
22
25
|
}
|
|
23
26
|
|
|
@@ -30,17 +33,13 @@ export function normalizeThinkingParts(parts: string[]): string | undefined {
|
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
/**
|
|
33
|
-
* Redacts secret-bearing headers for the raw audit trail
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
+
* Redacts secret-bearing headers for the raw audit trail via the central
|
|
37
|
+
* deny-list: any header whose NAME contains key / token / secret / cookie /
|
|
38
|
+
* authorization (so `x-api-key`, `api-key`, `cf-aig-authorization` …), plus any
|
|
39
|
+
* value that looks like a credential. Everything else passes through.
|
|
36
40
|
*/
|
|
37
41
|
export function redactedHeaders(headers: Record<string, string>): Record<string, string> {
|
|
38
|
-
|
|
39
|
-
for (const [k, v] of Object.entries(headers)) {
|
|
40
|
-
const lower = k.toLowerCase();
|
|
41
|
-
out[k] = lower === "authorization" || lower === "x-goog-api-key" ? "[REDACTED]" : v;
|
|
42
|
-
}
|
|
43
|
-
return out;
|
|
42
|
+
return redactHeadersCentral(headers) as Record<string, string>;
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
/**
|
|
@@ -92,6 +91,30 @@ export function incompleteStreamError(provider: string, modelId: string): Error
|
|
|
92
91
|
return err;
|
|
93
92
|
}
|
|
94
93
|
|
|
94
|
+
/**
|
|
95
|
+
* True when an error signals an evicted/invalid chained interaction
|
|
96
|
+
* (Google Interactions `404` / `{code: not_found}` on REST, `{event_type:
|
|
97
|
+
* error, error: {code: not_found}}` on SSE). Structured-data only: HTTP
|
|
98
|
+
* status, `code`, or `errorType` — never message text.
|
|
99
|
+
*/
|
|
100
|
+
export function isNotFoundChainError(error: unknown): boolean {
|
|
101
|
+
if (!error || typeof error !== "object") return false;
|
|
102
|
+
const err = error as Record<string, unknown>;
|
|
103
|
+
const status = Number(
|
|
104
|
+
(err["status"] as number | undefined) ??
|
|
105
|
+
(err["statusCode"] as number | undefined) ??
|
|
106
|
+
((err["response"] as Record<string, unknown> | undefined)?.["status"] as number | undefined) ??
|
|
107
|
+
NaN
|
|
108
|
+
);
|
|
109
|
+
if (status === 404) return true;
|
|
110
|
+
const code = String(
|
|
111
|
+
(err["code"] as string | undefined) ??
|
|
112
|
+
(err["errorType"] as string | undefined) ??
|
|
113
|
+
""
|
|
114
|
+
).toLowerCase();
|
|
115
|
+
return code === "not_found";
|
|
116
|
+
}
|
|
117
|
+
|
|
95
118
|
/** Per-request timeout ms from provider options (Q-22). */
|
|
96
119
|
export function timeoutFor(options: { timeoutMs?: number } | undefined): number | undefined {
|
|
97
120
|
const t = (options as { timeoutMs?: unknown } | undefined)?.timeoutMs;
|
|
@@ -152,11 +175,9 @@ export function hasAuthHeader(headers: Record<string, string> | undefined): bool
|
|
|
152
175
|
return false;
|
|
153
176
|
}
|
|
154
177
|
|
|
155
|
-
/** True for truncated streamed tool args (
|
|
178
|
+
/** True for truncated streamed tool args (the `__truncated_raw` marker from `parseStreamedToolArguments`). */
|
|
156
179
|
export function hasTruncatedToolArguments(args: unknown): boolean {
|
|
157
|
-
|
|
158
|
-
const keys = Object.keys(args as Record<string, unknown>);
|
|
159
|
-
return keys.length === 1 && keys[0] === "raw" && typeof (args as Record<string, unknown>).raw === "string";
|
|
180
|
+
return isTruncatedArguments(args);
|
|
160
181
|
}
|
|
161
182
|
|
|
162
183
|
/** Short hash for tool-ID clamping (FNV-1a, 8 hex chars, no deps). */
|
|
@@ -188,3 +209,76 @@ export function clampToolCallId(id: string): string {
|
|
|
188
209
|
const prefix = callId.slice(0, Math.max(1, 40 - hash.length - 1));
|
|
189
210
|
return `${prefix}_${hash}`;
|
|
190
211
|
}
|
|
212
|
+
|
|
213
|
+
/** Plain lower-cased header record from a fetch `Headers` object. */
|
|
214
|
+
export function headersToRecord(headers: Headers): Record<string, string> {
|
|
215
|
+
const out: Record<string, string> = {};
|
|
216
|
+
headers.forEach((v, k) => {
|
|
217
|
+
out[k] = v;
|
|
218
|
+
});
|
|
219
|
+
return out;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Parses a rate-limit hint from response headers into milliseconds:
|
|
224
|
+
* `retry-after-ms`, then `retry-after` (delta-seconds or HTTP-date).
|
|
225
|
+
* Returns undefined when absent or unparseable.
|
|
226
|
+
*/
|
|
227
|
+
export function parseRetryAfterMs(headers: Record<string, string> | undefined, now = Date.now()): number | undefined {
|
|
228
|
+
if (!headers) return undefined;
|
|
229
|
+
const get = (name: string): string | undefined => {
|
|
230
|
+
for (const [k, v] of Object.entries(headers)) if (k.toLowerCase() === name) return v;
|
|
231
|
+
return undefined;
|
|
232
|
+
};
|
|
233
|
+
const ms = get("retry-after-ms");
|
|
234
|
+
if (ms !== undefined && ms.trim() !== "" && Number.isFinite(Number(ms)) && Number(ms) >= 0) return Math.round(Number(ms));
|
|
235
|
+
const ra = get("retry-after");
|
|
236
|
+
if (ra === undefined || ra.trim() === "") return undefined;
|
|
237
|
+
const secs = Number(ra);
|
|
238
|
+
if (Number.isFinite(secs)) return secs >= 0 ? Math.round(secs * 1000) : undefined;
|
|
239
|
+
const at = Date.parse(ra);
|
|
240
|
+
return Number.isFinite(at) ? Math.max(0, at - now) : undefined;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Opens an SSE response, retrying transient failures (429/5xx/network) BEFORE
|
|
245
|
+
* any byte is consumed, so a streaming caller gets the same resilience as
|
|
246
|
+
* `generate()`. Once this resolves the stream is live and never retried.
|
|
247
|
+
* `onHttpError` must throw (usually the adapter's concise `throwIfError`).
|
|
248
|
+
*/
|
|
249
|
+
export function openStreamResponse(args: {
|
|
250
|
+
fetchOnce: () => Promise<Response>;
|
|
251
|
+
onHttpError: (res: Response, bodyText: string) => void;
|
|
252
|
+
noBodyError: () => Error;
|
|
253
|
+
retry: ReturnType<typeof retryOptionsFrom>;
|
|
254
|
+
signal: AbortSignal;
|
|
255
|
+
label: { providerId: string; modelId: string };
|
|
256
|
+
}): Promise<Response> {
|
|
257
|
+
return withRetries(
|
|
258
|
+
async () => {
|
|
259
|
+
const res = await args.fetchOnce();
|
|
260
|
+
if (!res.ok) {
|
|
261
|
+
const text = await res.text().catch(() => "");
|
|
262
|
+
args.onHttpError(res, text);
|
|
263
|
+
}
|
|
264
|
+
if (!res.body) throw args.noBodyError();
|
|
265
|
+
return res;
|
|
266
|
+
},
|
|
267
|
+
{ ...args.retry, signal: args.signal, label: args.label }
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Retry knobs from provider request options, in the shape `withRetries` expects. */
|
|
272
|
+
export function retryOptionsFrom(options: {
|
|
273
|
+
maxRetries?: number;
|
|
274
|
+
maxRetryDelayMs?: number;
|
|
275
|
+
retryBaseDelayMs?: number;
|
|
276
|
+
retryBudgetMs?: number;
|
|
277
|
+
} | undefined): { maxRetries?: number; maxRetryDelayMs?: number; baseDelayMs?: number; retryBudgetMs?: number } {
|
|
278
|
+
return {
|
|
279
|
+
maxRetries: options?.maxRetries,
|
|
280
|
+
maxRetryDelayMs: options?.maxRetryDelayMs,
|
|
281
|
+
baseDelayMs: options?.retryBaseDelayMs,
|
|
282
|
+
retryBudgetMs: options?.retryBudgetMs,
|
|
283
|
+
};
|
|
284
|
+
}
|
package/src/providers.ts
CHANGED
|
@@ -13,6 +13,8 @@ import type { CacheConfig, ServiceTier, ThinkingLevel } from "./types/core.ts";
|
|
|
13
13
|
import type { StructuredOutputSpec } from "./types/model.ts";
|
|
14
14
|
import { zodToJsonSchema } from "./tools/schema.ts";
|
|
15
15
|
import { ValidationError } from "./types/errors.ts";
|
|
16
|
+
import { TRUNCATED_ARGS_KEY } from "./types/tool.ts";
|
|
17
|
+
import { telemetryBus } from "./telemetry.ts";
|
|
16
18
|
|
|
17
19
|
/** Classification of a canonical capability on a given provider. */
|
|
18
20
|
export type ProviderCapabilityStatus =
|
|
@@ -50,11 +52,19 @@ export function emitProviderWarning(options: {
|
|
|
50
52
|
if (emittedWarnings.has(key)) return;
|
|
51
53
|
emittedWarnings.add(key);
|
|
52
54
|
const requested = options.requested ? ` (requested: "${options.requested}")` : "";
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
// Primary channel: the telemetry bus (code `provider_warning`). The legacy
|
|
56
|
+
// global sink and `console.warn` remain for existing capture-based tests and
|
|
57
|
+
// CLIs that predate the bus.
|
|
58
|
+
const msg = `[Agent Accelerator] WARNING [${options.provider}] ${options.capability}${requested}: ${options.reason} Using ${options.fallback} instead.`;
|
|
59
|
+
telemetryBus.emitWarning("provider_warning", msg, {
|
|
60
|
+
provider: options.provider,
|
|
61
|
+
capability: options.capability,
|
|
62
|
+
requested: options.requested,
|
|
63
|
+
reason: options.reason,
|
|
64
|
+
fallback: options.fallback,
|
|
65
|
+
});
|
|
66
|
+
try { (globalThis as any).__agentAccelWarnSink?.(msg, options); } catch {}
|
|
67
|
+
try { console.warn(msg); } catch {}
|
|
58
68
|
}
|
|
59
69
|
|
|
60
70
|
/** Clears deduped-warning state (mainly for tests). */
|
|
@@ -490,7 +500,110 @@ export function parseStreamedToolArguments(startText: string, deltaText: string)
|
|
|
490
500
|
// try next candidate
|
|
491
501
|
}
|
|
492
502
|
}
|
|
493
|
-
return {
|
|
503
|
+
return { [TRUNCATED_ARGS_KEY]: startText + deltaText };
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Returns the wire string that actually parsed in
|
|
508
|
+
* {@link parseStreamedToolArguments} (identical candidate trial order), so
|
|
509
|
+
* `rawArguments` audit trails never store concatenation artifacts like
|
|
510
|
+
* `"{}{...}"` (empty start payload + complete delta). Falls back to the raw
|
|
511
|
+
* concatenation when nothing parses, preserving the truncated-call marker
|
|
512
|
+
* path (`{ __truncated_raw }`, never executed).
|
|
513
|
+
*
|
|
514
|
+
* @example `selectRawArguments("{}", '{"location":"Paris"}') // '{"location":"Paris"}'`
|
|
515
|
+
*/
|
|
516
|
+
export function selectRawArguments(startText: string, deltaText: string): string {
|
|
517
|
+
const candidates = [startText + deltaText, deltaText, startText].filter((c) => c && c.trim());
|
|
518
|
+
for (const candidate of candidates) {
|
|
519
|
+
try {
|
|
520
|
+
const parsed: unknown = JSON.parse(candidate);
|
|
521
|
+
if (isPlainObject(parsed)) return candidate;
|
|
522
|
+
} catch {
|
|
523
|
+
// try next candidate
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return startText + deltaText;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// ---------------------------------------------------------------------------
|
|
530
|
+
// Generation controls (maxOutputTokens / stop / seed)
|
|
531
|
+
// ---------------------------------------------------------------------------
|
|
532
|
+
|
|
533
|
+
/** Canonical generation controls as carried on `ProviderRequestOptions`. */
|
|
534
|
+
export interface GenerationParams {
|
|
535
|
+
maxOutputTokens?: number;
|
|
536
|
+
stop?: string[];
|
|
537
|
+
seed?: number;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const finiteNumber = (v: unknown): v is number => typeof v === "number" && Number.isFinite(v);
|
|
541
|
+
|
|
542
|
+
/** Normalizes the public `stop` option (`string | string[]`) to a clean array, or undefined. */
|
|
543
|
+
export function normalizeStop(stop: string | string[] | undefined): string[] | undefined {
|
|
544
|
+
const list = (Array.isArray(stop) ? stop : stop === undefined ? [] : [stop]).filter(
|
|
545
|
+
(s): s is string => typeof s === "string" && s.length > 0
|
|
546
|
+
);
|
|
547
|
+
return list.length > 0 ? list : undefined;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Standard Chat Completions fields (OpenRouter + generic compat endpoints):
|
|
552
|
+
* `max_tokens`, `stop`, `seed`. Only set values are
|
|
553
|
+
* emitted, so strict endpoints never see anything the caller did not ask for.
|
|
554
|
+
*/
|
|
555
|
+
export function mapGenerationParamsToChat(params: GenerationParams | undefined): Record<string, unknown> {
|
|
556
|
+
const out: Record<string, unknown> = {};
|
|
557
|
+
if (!params) return out;
|
|
558
|
+
if (finiteNumber(params.maxOutputTokens) && params.maxOutputTokens > 0) out["max_tokens"] = Math.floor(params.maxOutputTokens);
|
|
559
|
+
if (params.stop && params.stop.length > 0) out["stop"] = params.stop;
|
|
560
|
+
if (finiteNumber(params.seed)) out["seed"] = Math.floor(params.seed);
|
|
561
|
+
return out;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function warnUnsupportedStopSeed(provider: string, modelRef: string, params: GenerationParams, reasonSuffix: string): void {
|
|
565
|
+
if (params.stop && params.stop.length > 0) {
|
|
566
|
+
emitProviderWarning({
|
|
567
|
+
provider,
|
|
568
|
+
capability: "stop sequences",
|
|
569
|
+
requested: `${params.stop.join("|")} (${modelRef})`,
|
|
570
|
+
reason: `the ${reasonSuffix} defines no stop-sequence parameter.`,
|
|
571
|
+
fallback: "no stop sequences (the model decides when to stop)",
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
if (finiteNumber(params.seed)) {
|
|
575
|
+
emitProviderWarning({
|
|
576
|
+
provider,
|
|
577
|
+
capability: "seed",
|
|
578
|
+
requested: `${params.seed} (${modelRef})`,
|
|
579
|
+
reason: `the ${reasonSuffix} defines no seed parameter.`,
|
|
580
|
+
fallback: "non-deterministic sampling",
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/** OpenAI Responses: `max_output_tokens` is native; `stop` and `seed` do not exist on this wire (warn + drop). */
|
|
586
|
+
export function mapGenerationParamsToOpenAI(
|
|
587
|
+
params: GenerationParams | undefined,
|
|
588
|
+
modelRef: string
|
|
589
|
+
): { max_output_tokens?: number } {
|
|
590
|
+
if (!params) return {};
|
|
591
|
+
warnUnsupportedStopSeed("openai", modelRef, params, "OpenAI Responses API");
|
|
592
|
+
return finiteNumber(params.maxOutputTokens) && params.maxOutputTokens > 0
|
|
593
|
+
? { max_output_tokens: Math.floor(params.maxOutputTokens) }
|
|
594
|
+
: {};
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/** Google Interactions `generation_config`: `max_output_tokens` is documented; `stop` and `seed` are not (warn + drop). */
|
|
598
|
+
export function mapGenerationParamsToGoogle(
|
|
599
|
+
params: GenerationParams | undefined,
|
|
600
|
+
modelRef: string
|
|
601
|
+
): { max_output_tokens?: number } {
|
|
602
|
+
if (!params) return {};
|
|
603
|
+
warnUnsupportedStopSeed("google", modelRef, params, "Gemini Interactions API");
|
|
604
|
+
return finiteNumber(params.maxOutputTokens) && params.maxOutputTokens > 0
|
|
605
|
+
? { max_output_tokens: Math.floor(params.maxOutputTokens) }
|
|
606
|
+
: {};
|
|
494
607
|
}
|
|
495
608
|
|
|
496
609
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
Schedules a sub-agent (a "scheduled action") to run LATER, once or on repeat, on its own. Every run starts blank, does its task with the worker tools, and saves its answer to a file. It does NOT reply in this chat. Use it for deliveries such as "a research brief every day at 7:00 AM" or "a codebase health report every Monday at 9:00 AM".
|
|
2
|
+
|
|
3
|
+
Call get_datetime first whenever you create or reschedule, then pick DD from today's date.
|
|
4
|
+
|
|
5
|
+
COMMANDS (one tool, the `command` field)
|
|
6
|
+
- create: schedule a new action. Needs instructions, task and scheduled_action.{name, id, timeToDeliver, meridiem, startCookingInAdvance}. Optional: role, tools, timeoutMs, scheduled_action.repeatEvery, scheduled_action.retries.
|
|
7
|
+
- list: every action with its metadata and state (waiting, cooking, missed, done).
|
|
8
|
+
- get: one action by `id`, with its latest run result.
|
|
9
|
+
- update: change instructions, task, tools, timeoutMs, role or any scheduled_action field of the action `id`.
|
|
10
|
+
- kill: delete the action `id` for good. If the answer says it is running, ask the user before retrying with force: true. purge: true also deletes its past run files.
|
|
11
|
+
- run_now: run the action `id` immediately, outside its schedule. Also fixes a missed run.
|
|
12
|
+
- skip: ignore a missed run of `id` and wait for the next scheduled time.
|
|
13
|
+
|
|
14
|
+
SCHEDULED_ACTION FIELDS
|
|
15
|
+
- name: human label.
|
|
16
|
+
- id: lowercase kebab-case, at most 48 characters, e.g. "daily-ai-brief". The SDK appends 6 characters ("daily-ai-brief-d64aj2"); use that full id in every later command.
|
|
17
|
+
- timeToDeliver: "DD:HH:MM". DD = days from today (00 today, 01 tomorrow, 55 = 55 days ahead). HH:MM = clock time on a 12-hour clock, HH 01-12.
|
|
18
|
+
- meridiem: "AM" or "PM" for timeToDeliver.
|
|
19
|
+
- startCookingInAdvance: "DD:HH:MM" as a duration (HH 00-23): how long before delivery the run starts. "00:00:10" = 10 minutes, "00:01:00" = 1 hour.
|
|
20
|
+
- repeatEvery (optional): "DD:HH:MM" gap between deliveries. "01:00:00" = every day, "07:00:00" = every week. Omit to run once.
|
|
21
|
+
- retries (optional): extra attempts if a run fails, used only while there is still time before delivery. Default 0.
|
|
22
|
+
|
|
23
|
+
EXAMPLES
|
|
24
|
+
Daily brief, first delivery tomorrow at 7:00 AM, starting 30 minutes ahead:
|
|
25
|
+
{"command":"create","instructions":"You write a short, sourced AI research brief.","task":"Summarize the most important AI news of the last day in 5 bullets.","scheduled_action":{"name":"Daily AI Research Brief","id":"daily-ai-brief","timeToDeliver":"01:07:00","meridiem":"AM","startCookingInAdvance":"00:00:30","repeatEvery":"01:00:00"}}
|
|
26
|
+
|
|
27
|
+
Weekly report, every Monday at 9:00 AM (use get_datetime to count the days to the next Monday), starting 2 hours ahead:
|
|
28
|
+
{"command":"create","instructions":"You review a codebase and report its health.","task":"Report test health, open risks and notable changes this week.","scheduled_action":{"name":"Weekly Codebase Health Report","id":"weekly-code-health","timeToDeliver":"03:09:00","meridiem":"AM","startCookingInAdvance":"00:02:00","repeatEvery":"07:00:00"}}
|
|
29
|
+
|
|
30
|
+
TIMING ADVICE (do not guess; talk to the user)
|
|
31
|
+
- A run must finish before the next one is due, so startCookingInAdvance must be shorter than repeatEvery.
|
|
32
|
+
- Pick the lead time from the job: a quick lookup fits the 10-minute lead ("00:00:10"); research with many tools needs much more.
|
|
33
|
+
- If the job looks too heavy for the gap the user asked for, do not schedule it blindly. Say so and suggest a wider gap, an earlier start, or a lighter task.
|
|
34
|
+
- A run still cooking when the next one is due is skipped, never stacked, so a gap that is too small means lost runs.
|
|
35
|
+
|
|
36
|
+
RULES
|
|
37
|
+
- Each run starts blank and remembers nothing from earlier runs: put everything the worker needs in instructions and task.
|
|
38
|
+
- Results go to files, not into this chat. Tell the user the action is scheduled and when it will be delivered.
|
|
39
|
+
- Never put secrets in instructions or task: they are stored as plain text.
|
|
40
|
+
- Actions run only while the host program is running.
|
|
41
|
+
- Worker tools you may grant through `tools`: {{TOOLS}}.
|