niahere 0.2.56 → 0.2.57
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 +2 -2
- package/package.json +1 -1
- package/src/cli/job.ts +10 -3
- package/src/core/runner.ts +43 -2
- package/src/db/migrations/012_jobs_stateless.ts +7 -0
- package/src/db/models/job.ts +12 -8
- package/src/mcp/server.ts +3 -1
- package/src/mcp/tools.ts +7 -3
- package/src/prompts/environment.md +10 -1
- package/src/types/job.ts +1 -0
package/README.md
CHANGED
|
@@ -63,8 +63,8 @@ nia update — update to latest version (auto-backup + resta
|
|
|
63
63
|
nia job list — list all jobs
|
|
64
64
|
nia job show [name] — full details + recent runs
|
|
65
65
|
nia job status [name] — quick status check
|
|
66
|
-
nia job add <n> <s> <p> — add a job (--type, --always, --agent, --prompt-file)
|
|
67
|
-
nia job update <name> — update a job (--schedule, --prompt, --prompt-file, --type, --always, --agent)
|
|
66
|
+
nia job add <n> <s> <p> — add a job (--type, --always, --agent, --stateless, --prompt-file)
|
|
67
|
+
nia job update <name> — update a job (--schedule, --prompt, --prompt-file, --type, --always, --agent, --stateless)
|
|
68
68
|
nia job remove <name> — delete a job
|
|
69
69
|
nia job enable / disable <n> — toggle a job
|
|
70
70
|
nia job run <name> — run a job once
|
package/package.json
CHANGED
package/src/cli/job.ts
CHANGED
|
@@ -23,6 +23,7 @@ Commands:
|
|
|
23
23
|
--type cron|interval|once Schedule type (default: cron)
|
|
24
24
|
--always Run 24/7 regardless of active hours
|
|
25
25
|
--agent <name> Assign an agent to the job
|
|
26
|
+
--stateless yes|no Disable working memory for this job
|
|
26
27
|
update <name> Update a job
|
|
27
28
|
--schedule <schedule> New schedule
|
|
28
29
|
--prompt <text> New prompt
|
|
@@ -30,6 +31,7 @@ Commands:
|
|
|
30
31
|
--type cron|interval|once Change schedule type
|
|
31
32
|
--always / --no-always Toggle 24/7 mode
|
|
32
33
|
--agent <name> Assign agent (--no-agent to remove)
|
|
34
|
+
--stateless yes|no Toggle working memory
|
|
33
35
|
remove <name> Delete a job
|
|
34
36
|
enable <name> Enable a job
|
|
35
37
|
disable <name> Disable a job
|
|
@@ -100,6 +102,8 @@ export async function jobCommand(): Promise<void> {
|
|
|
100
102
|
}
|
|
101
103
|
|
|
102
104
|
const always = args.getBool("always") ?? false;
|
|
105
|
+
const statelessRaw = args.getString("stateless");
|
|
106
|
+
const stateless = statelessRaw ? ["yes", "y", "true", "t", "1"].includes(statelessRaw.toLowerCase()) : false;
|
|
103
107
|
const agent = args.getString("agent");
|
|
104
108
|
|
|
105
109
|
const [name, schedule, ...promptParts] = args.positional;
|
|
@@ -125,7 +129,7 @@ export async function jobCommand(): Promise<void> {
|
|
|
125
129
|
const config = getConfig();
|
|
126
130
|
const nextRunAt = computeInitialNextRun(scheduleType, schedule, config.timezone);
|
|
127
131
|
await withDb(async () => {
|
|
128
|
-
await Job.create(name, schedule, prompt, always, scheduleType, nextRunAt, agent);
|
|
132
|
+
await Job.create(name, schedule, prompt, always, scheduleType, nextRunAt, agent, stateless);
|
|
129
133
|
console.log(`Job "${name}" added (${scheduleType}: ${schedule}).${always ? " (runs 24/7)" : ""}`);
|
|
130
134
|
});
|
|
131
135
|
} catch (err) {
|
|
@@ -176,7 +180,7 @@ export async function jobCommand(): Promise<void> {
|
|
|
176
180
|
fail('Example: nia job update curator --schedule "4h" --prompt "New prompt"');
|
|
177
181
|
}
|
|
178
182
|
|
|
179
|
-
const fields: Partial<{ schedule: string; prompt: string; always: boolean; scheduleType: ScheduleType; agent: string | null }> = {};
|
|
183
|
+
const fields: Partial<{ schedule: string; prompt: string; always: boolean; stateless: boolean; scheduleType: ScheduleType; agent: string | null }> = {};
|
|
180
184
|
const schedule = args.getString("schedule");
|
|
181
185
|
const promptFile = args.getString("prompt-file");
|
|
182
186
|
let prompt = args.getString("prompt");
|
|
@@ -187,6 +191,7 @@ export async function jobCommand(): Promise<void> {
|
|
|
187
191
|
}
|
|
188
192
|
const scheduleType = args.getString("type") as ScheduleType | undefined;
|
|
189
193
|
const always = args.getBool("always");
|
|
194
|
+
const statelessRaw = args.getString("stateless");
|
|
190
195
|
const agent = args.getString("agent");
|
|
191
196
|
const noAgent = args.getBool("agent");
|
|
192
197
|
|
|
@@ -199,11 +204,12 @@ export async function jobCommand(): Promise<void> {
|
|
|
199
204
|
fields.scheduleType = scheduleType;
|
|
200
205
|
}
|
|
201
206
|
if (always !== undefined) fields.always = always;
|
|
207
|
+
if (statelessRaw) fields.stateless = ["yes", "y", "true", "t", "1"].includes(statelessRaw.toLowerCase());
|
|
202
208
|
if (agent) fields.agent = agent;
|
|
203
209
|
if (noAgent === false) fields.agent = null;
|
|
204
210
|
|
|
205
211
|
if (Object.keys(fields).length === 0) {
|
|
206
|
-
fail("Nothing to update. Pass at least one flag (--schedule, --prompt, --type, --always, --agent).");
|
|
212
|
+
fail("Nothing to update. Pass at least one flag (--schedule, --prompt, --type, --always, --stateless, --agent).");
|
|
207
213
|
}
|
|
208
214
|
|
|
209
215
|
try {
|
|
@@ -231,6 +237,7 @@ export async function jobCommand(): Promise<void> {
|
|
|
231
237
|
console.log(` enabled: ${job.enabled}`);
|
|
232
238
|
console.log(` always: ${job.always}`);
|
|
233
239
|
if (job.agent) console.log(` agent: ${job.agent}`);
|
|
240
|
+
if (job.stateless) console.log(` stateless: true`);
|
|
234
241
|
console.log(` prompt: ${job.prompt}`);
|
|
235
242
|
|
|
236
243
|
const state = readState();
|
package/src/core/runner.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { homedir } from "os";
|
|
2
|
-
import { existsSync } from "fs";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
3
4
|
import { randomUUID } from "crypto";
|
|
4
5
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
5
6
|
import type { JobInput, JobResult } from "../types";
|
|
@@ -11,6 +12,7 @@ import { scanAgents } from "./agents";
|
|
|
11
12
|
import { truncate, formatToolUse } from "../utils/format-activity";
|
|
12
13
|
import { getMcpServers } from "../mcp";
|
|
13
14
|
import { ActiveEngine } from "../db/models";
|
|
15
|
+
import { getPaths } from "../utils/paths";
|
|
14
16
|
import { log } from "../utils/log";
|
|
15
17
|
|
|
16
18
|
export type ActivityCallback = (line: string) => void;
|
|
@@ -222,6 +224,42 @@ export async function runTask(opts: TaskOptions): Promise<RunnerOutput> {
|
|
|
222
224
|
}
|
|
223
225
|
}
|
|
224
226
|
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
// Working memory
|
|
229
|
+
// ---------------------------------------------------------------------------
|
|
230
|
+
|
|
231
|
+
/** Build the working memory block for a stateful job. Returns empty string for stateless jobs. */
|
|
232
|
+
export function buildWorkingMemory(jobName: string, stateless?: boolean): string {
|
|
233
|
+
if (stateless) return "";
|
|
234
|
+
|
|
235
|
+
const jobDir = join(getPaths().jobsDir, jobName);
|
|
236
|
+
mkdirSync(jobDir, { recursive: true });
|
|
237
|
+
const statePath = join(jobDir, "state.md");
|
|
238
|
+
let stateContent = "";
|
|
239
|
+
if (existsSync(statePath)) {
|
|
240
|
+
try {
|
|
241
|
+
stateContent = readFileSync(statePath, "utf8").trim();
|
|
242
|
+
} catch {
|
|
243
|
+
stateContent = "";
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const stateBlock = stateContent
|
|
248
|
+
? `\n${stateContent}\n`
|
|
249
|
+
: "(first run — no prior state)";
|
|
250
|
+
|
|
251
|
+
return `
|
|
252
|
+
|
|
253
|
+
## Working Memory
|
|
254
|
+
|
|
255
|
+
You have a persistent workspace at \`${jobDir}/\`. This directory is yours — create files, organize data, track history, maintain state however you need.
|
|
256
|
+
|
|
257
|
+
Your \`state.md\` from last run:
|
|
258
|
+
${stateBlock}
|
|
259
|
+
|
|
260
|
+
Before finishing, update \`state.md\` with: what you did this run, what you noticed, and what to do or focus on next time. Keep it concise — a working notebook, not a log.`;
|
|
261
|
+
}
|
|
262
|
+
|
|
225
263
|
// ---------------------------------------------------------------------------
|
|
226
264
|
// Public API
|
|
227
265
|
// ---------------------------------------------------------------------------
|
|
@@ -256,10 +294,13 @@ export async function runJob(job: JobInput, onActivity?: ActivityCallback): Prom
|
|
|
256
294
|
systemPrompt = buildSystemPrompt("job");
|
|
257
295
|
}
|
|
258
296
|
|
|
259
|
-
|
|
297
|
+
let jobPrompt = job.prompt
|
|
260
298
|
? `Job: ${job.name} (schedule: ${job.schedule})\n\n${job.prompt}`
|
|
261
299
|
: `Job: ${job.name} (schedule: ${job.schedule})\n\nExecute your scheduled tasks.`;
|
|
262
300
|
|
|
301
|
+
// Working memory: give stateful jobs a persistent workspace
|
|
302
|
+
jobPrompt += buildWorkingMemory(job.name, job.stateless);
|
|
303
|
+
|
|
263
304
|
if (config.runner === "codex") {
|
|
264
305
|
const fullPrompt = `${systemPrompt}\n\n---\n\n${jobPrompt}`;
|
|
265
306
|
output = await runJobWithCodex(fullPrompt, cwd, agentModel || config.model);
|
package/src/db/models/job.ts
CHANGED
|
@@ -38,6 +38,7 @@ export interface Job {
|
|
|
38
38
|
always: boolean;
|
|
39
39
|
scheduleType: ScheduleType;
|
|
40
40
|
agent: string | null;
|
|
41
|
+
stateless: boolean;
|
|
41
42
|
nextRunAt: string | null;
|
|
42
43
|
lastRunAt: string | null;
|
|
43
44
|
createdAt: string;
|
|
@@ -53,6 +54,7 @@ function toJob(r: Record<string, any>): Job {
|
|
|
53
54
|
always: r.always ?? false,
|
|
54
55
|
scheduleType: r.schedule_type || "cron",
|
|
55
56
|
agent: r.agent || null,
|
|
57
|
+
stateless: r.stateless ?? false,
|
|
56
58
|
nextRunAt: r.next_run_at ? String(r.next_run_at) : null,
|
|
57
59
|
lastRunAt: r.last_run_at ? String(r.last_run_at) : null,
|
|
58
60
|
createdAt: String(r.created_at),
|
|
@@ -73,6 +75,7 @@ export async function create(
|
|
|
73
75
|
scheduleType: ScheduleType = "cron",
|
|
74
76
|
nextRunAt?: Date,
|
|
75
77
|
agent?: string,
|
|
78
|
+
stateless = false,
|
|
76
79
|
): Promise<void> {
|
|
77
80
|
validateSchedule(schedule, scheduleType);
|
|
78
81
|
const existing = await get(name);
|
|
@@ -81,27 +84,27 @@ export async function create(
|
|
|
81
84
|
}
|
|
82
85
|
const sql = getSql();
|
|
83
86
|
await sql`
|
|
84
|
-
INSERT INTO jobs (name, schedule, prompt, always, schedule_type, next_run_at, agent)
|
|
85
|
-
VALUES (${name}, ${schedule}, ${prompt}, ${always}, ${scheduleType}, ${nextRunAt ?? null}, ${agent ?? null})
|
|
87
|
+
INSERT INTO jobs (name, schedule, prompt, always, schedule_type, next_run_at, agent, stateless)
|
|
88
|
+
VALUES (${name}, ${schedule}, ${prompt}, ${always}, ${scheduleType}, ${nextRunAt ?? null}, ${agent ?? null}, ${stateless})
|
|
86
89
|
`;
|
|
87
90
|
await notifyChange();
|
|
88
91
|
}
|
|
89
92
|
|
|
90
93
|
export async function list(): Promise<Job[]> {
|
|
91
94
|
const sql = getSql();
|
|
92
|
-
const rows = await sql`SELECT name, schedule, prompt, enabled, always, schedule_type, agent, next_run_at, last_run_at, created_at, updated_at FROM jobs ORDER BY name`;
|
|
95
|
+
const rows = await sql`SELECT name, schedule, prompt, enabled, always, schedule_type, agent, stateless, next_run_at, last_run_at, created_at, updated_at FROM jobs ORDER BY name`;
|
|
93
96
|
return rows.map(toJob);
|
|
94
97
|
}
|
|
95
98
|
|
|
96
99
|
export async function get(name: string): Promise<Job | null> {
|
|
97
100
|
const sql = getSql();
|
|
98
|
-
const rows = await sql`SELECT name, schedule, prompt, enabled, always, schedule_type, agent, next_run_at, last_run_at, created_at, updated_at FROM jobs WHERE name = ${name}`;
|
|
101
|
+
const rows = await sql`SELECT name, schedule, prompt, enabled, always, schedule_type, agent, stateless, next_run_at, last_run_at, created_at, updated_at FROM jobs WHERE name = ${name}`;
|
|
99
102
|
return rows.length > 0 ? toJob(rows[0]) : null;
|
|
100
103
|
}
|
|
101
104
|
|
|
102
105
|
export async function update(
|
|
103
106
|
name: string,
|
|
104
|
-
fields: Partial<{ schedule: string; prompt: string; enabled: boolean; always: boolean; agent: string | null; scheduleType: ScheduleType }>,
|
|
107
|
+
fields: Partial<{ schedule: string; prompt: string; enabled: boolean; always: boolean; agent: string | null; stateless: boolean; scheduleType: ScheduleType }>,
|
|
105
108
|
): Promise<boolean> {
|
|
106
109
|
const sql = getSql();
|
|
107
110
|
const existing = await get(name);
|
|
@@ -113,6 +116,7 @@ export async function update(
|
|
|
113
116
|
const enabled = fields.enabled ?? existing.enabled;
|
|
114
117
|
const always = fields.always ?? existing.always;
|
|
115
118
|
const agent = fields.agent !== undefined ? fields.agent : existing.agent;
|
|
119
|
+
const stateless = fields.stateless ?? existing.stateless;
|
|
116
120
|
|
|
117
121
|
if (fields.schedule || fields.scheduleType) {
|
|
118
122
|
validateSchedule(schedule, scheduleType);
|
|
@@ -120,7 +124,7 @@ export async function update(
|
|
|
120
124
|
|
|
121
125
|
await sql`
|
|
122
126
|
UPDATE jobs
|
|
123
|
-
SET schedule = ${schedule}, schedule_type = ${scheduleType}, prompt = ${prompt}, enabled = ${enabled}, always = ${always}, agent = ${agent}, updated_at = NOW()
|
|
127
|
+
SET schedule = ${schedule}, schedule_type = ${scheduleType}, prompt = ${prompt}, enabled = ${enabled}, always = ${always}, agent = ${agent}, stateless = ${stateless}, updated_at = NOW()
|
|
124
128
|
WHERE name = ${name}
|
|
125
129
|
`;
|
|
126
130
|
await notifyChange();
|
|
@@ -136,14 +140,14 @@ export async function remove(name: string): Promise<boolean> {
|
|
|
136
140
|
|
|
137
141
|
export async function listEnabled(): Promise<Job[]> {
|
|
138
142
|
const sql = getSql();
|
|
139
|
-
const rows = await sql`SELECT name, schedule, prompt, enabled, always, schedule_type, agent, next_run_at, last_run_at, created_at, updated_at FROM jobs WHERE enabled = TRUE ORDER BY name`;
|
|
143
|
+
const rows = await sql`SELECT name, schedule, prompt, enabled, always, schedule_type, agent, stateless, next_run_at, last_run_at, created_at, updated_at FROM jobs WHERE enabled = TRUE ORDER BY name`;
|
|
140
144
|
return rows.map(toJob);
|
|
141
145
|
}
|
|
142
146
|
|
|
143
147
|
export async function listDue(): Promise<Job[]> {
|
|
144
148
|
const sql = getSql();
|
|
145
149
|
const rows = await sql`
|
|
146
|
-
SELECT name, schedule, prompt, enabled, always, schedule_type, agent, next_run_at, last_run_at, created_at, updated_at
|
|
150
|
+
SELECT name, schedule, prompt, enabled, always, schedule_type, agent, stateless, next_run_at, last_run_at, created_at, updated_at
|
|
147
151
|
FROM jobs
|
|
148
152
|
WHERE enabled = TRUE AND next_run_at <= NOW()
|
|
149
153
|
ORDER BY next_run_at
|
package/src/mcp/server.ts
CHANGED
|
@@ -25,6 +25,7 @@ export function createNiaMcpServer() {
|
|
|
25
25
|
schedule_type: z.enum(["cron", "interval", "once"]).default("cron").describe("Schedule type"),
|
|
26
26
|
always: z.boolean().default(false).describe("If true, runs 24/7 ignoring active hours"),
|
|
27
27
|
agent: z.string().optional().describe("Agent name to use for this job (loads agent's AGENT.md as system prompt)"),
|
|
28
|
+
stateless: z.boolean().default(false).describe("If true, disables working memory (no state.md injection or workspace)"),
|
|
28
29
|
},
|
|
29
30
|
async (args) => ({
|
|
30
31
|
content: [{ type: "text" as const, text: await handlers.addJob(args) }],
|
|
@@ -32,13 +33,14 @@ export function createNiaMcpServer() {
|
|
|
32
33
|
),
|
|
33
34
|
tool(
|
|
34
35
|
"update_job",
|
|
35
|
-
"Update an existing job's schedule, prompt, always flag, agent, or schedule_type. Only pass fields you want to change.",
|
|
36
|
+
"Update an existing job's schedule, prompt, always flag, agent, stateless, or schedule_type. Only pass fields you want to change.",
|
|
36
37
|
{
|
|
37
38
|
name: z.string().describe("Job name to update"),
|
|
38
39
|
schedule: z.string().optional().describe("New schedule (cron expression, interval duration, or ISO timestamp)"),
|
|
39
40
|
prompt: z.string().optional().describe("New prompt"),
|
|
40
41
|
always: z.boolean().optional().describe("If true, runs 24/7 ignoring active hours"),
|
|
41
42
|
agent: z.string().nullable().optional().describe("Agent name (set null to remove agent)"),
|
|
43
|
+
stateless: z.boolean().optional().describe("If true, disables working memory (no state.md injection or workspace)"),
|
|
42
44
|
schedule_type: z.enum(["cron", "interval", "once"]).optional().describe("Schedule type (must match the schedule format)"),
|
|
43
45
|
},
|
|
44
46
|
async (args) => ({
|
package/src/mcp/tools.ts
CHANGED
|
@@ -23,13 +23,15 @@ export async function addJob(args: {
|
|
|
23
23
|
schedule_type?: ScheduleType;
|
|
24
24
|
always?: boolean;
|
|
25
25
|
agent?: string;
|
|
26
|
+
stateless?: boolean;
|
|
26
27
|
}): Promise<string> {
|
|
27
28
|
const scheduleType = args.schedule_type || "cron";
|
|
28
29
|
const always = args.always || false;
|
|
30
|
+
const stateless = args.stateless || false;
|
|
29
31
|
const config = getConfig();
|
|
30
32
|
|
|
31
33
|
const nextRunAt = computeInitialNextRun(scheduleType, args.schedule, config.timezone);
|
|
32
|
-
await Job.create(args.name, args.schedule, args.prompt, always, scheduleType, nextRunAt, args.agent);
|
|
34
|
+
await Job.create(args.name, args.schedule, args.prompt, always, scheduleType, nextRunAt, args.agent, stateless);
|
|
33
35
|
const agentNote = args.agent ? ` [agent: ${args.agent}]` : "";
|
|
34
36
|
return `Job "${args.name}" created (${scheduleType}: ${args.schedule})${agentNote}. Next run: ${nextRunAt.toISOString()}`;
|
|
35
37
|
}
|
|
@@ -40,16 +42,18 @@ export async function updateJob(args: {
|
|
|
40
42
|
prompt?: string;
|
|
41
43
|
always?: boolean;
|
|
42
44
|
agent?: string | null;
|
|
45
|
+
stateless?: boolean;
|
|
43
46
|
schedule_type?: "cron" | "interval" | "once";
|
|
44
47
|
}): Promise<string> {
|
|
45
|
-
const fields: Partial<{ schedule: string; prompt: string; always: boolean; agent: string | null; scheduleType: "cron" | "interval" | "once" }> = {};
|
|
48
|
+
const fields: Partial<{ schedule: string; prompt: string; always: boolean; stateless: boolean; agent: string | null; scheduleType: "cron" | "interval" | "once" }> = {};
|
|
46
49
|
if (args.schedule) fields.schedule = args.schedule;
|
|
47
50
|
if (args.prompt) fields.prompt = args.prompt;
|
|
48
51
|
if (args.always !== undefined) fields.always = args.always;
|
|
52
|
+
if (args.stateless !== undefined) fields.stateless = args.stateless;
|
|
49
53
|
if (args.agent !== undefined) fields.agent = args.agent;
|
|
50
54
|
if (args.schedule_type) fields.scheduleType = args.schedule_type;
|
|
51
55
|
|
|
52
|
-
if (Object.keys(fields).length === 0) return "Nothing to update. Pass at least one field (schedule, prompt, always, agent, or schedule_type).";
|
|
56
|
+
if (Object.keys(fields).length === 0) return "Nothing to update. Pass at least one field (schedule, prompt, always, stateless, agent, or schedule_type).";
|
|
53
57
|
|
|
54
58
|
const updated = await Job.update(args.name, fields);
|
|
55
59
|
if (!updated) return `Job "${args.name}" not found.`;
|
|
@@ -25,7 +25,8 @@ You have MCP tools for managing jobs directly (preferred over CLI for speed):
|
|
|
25
25
|
- `interval`: duration string (e.g., "5m", "2h", "1d" = every 5 min/2 hours/1 day)
|
|
26
26
|
- `once`: ISO timestamp for one-time execution (e.g., "2026-03-14T10:00:00")
|
|
27
27
|
- Set `always: true` to run 24/7 (ignores active hours)
|
|
28
|
-
-
|
|
28
|
+
- Set `stateless: true` to disable working memory (no state.md or workspace)
|
|
29
|
+
- **update_job** — update an existing job's schedule, prompt, always, stateless, or agent
|
|
29
30
|
- **remove_job** — delete a job by name
|
|
30
31
|
- **enable_job** / **disable_job** — toggle a job on or off
|
|
31
32
|
- **run_job** — trigger a job to run immediately
|
|
@@ -43,6 +44,14 @@ You have MCP tools for managing jobs directly (preferred over CLI for speed):
|
|
|
43
44
|
|
|
44
45
|
Active hours: {{activeStart}}–{{activeEnd}} ({{timezone}}). Jobs respect this; crons (always=true) don't.
|
|
45
46
|
|
|
47
|
+
### Job Working Memory
|
|
48
|
+
|
|
49
|
+
Jobs are **stateful by default**. Each job gets a persistent workspace at `~/.niahere/jobs/<job-name>/`. Before each run, the runner reads `state.md` from that directory and injects it into the prompt. The agent should update `state.md` at the end of each run with what it did, what it noticed, and what to focus on next time.
|
|
50
|
+
|
|
51
|
+
The workspace is freeform — the agent can create any files it needs (data, cache, history, etc.). `state.md` is the convention for the runner to inject automatically; everything else is the agent's to organize.
|
|
52
|
+
|
|
53
|
+
To disable working memory for a specific job, set `stateless: true` when creating or updating it.
|
|
54
|
+
|
|
46
55
|
## Managing Config
|
|
47
56
|
|
|
48
57
|
Config file: `{{configPath}}`
|