@fickydev/pigent 0.1.20 → 0.1.22

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/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.22 - 2026-05-18
4
+
5
+ ### Added
6
+
7
+ - Added `/skills` command to list all known skills with active/inactive flags and source labels for the current chat session.
8
+ - Added session-scoped dynamic skill loading with `/skills add <skill>`, `/skills remove <skill>`, and `/skills clear`.
9
+ - Added current skill inventory to each agent prompt so users can ask natural questions like "what skills do you have?".
10
+ - Added `agent_sessions.session_skills` persistence and migration.
11
+
12
+ ## 0.1.21 - 2026-05-18
13
+
14
+ ### Fixed
15
+
16
+ - Strengthened task-management skill/tool guidance so agents use scheduled task tools for future reminders/notifications instead of refusing with "can't proactively send messages".
17
+
3
18
  ## 0.1.20 - 2026-05-18
4
19
 
5
20
  ### Fixed
package/TODO.md CHANGED
@@ -146,6 +146,9 @@
146
146
  - [x] Make README more end-user facing
147
147
  - [x] Confirm per-agent system prompt injection
148
148
  - [x] Confirm per-agent skills loading
149
+ - [x] Add `/skills` command for current session skills with active/inactive flags
150
+ - [x] Add session-scoped dynamic skill loading via `/skills add/remove/clear`
151
+ - [x] Add skill inventory context so agents can answer natural-language skill questions
149
152
  - [x] Confirm per-agent extensions loading
150
153
  - [ ] Confirm per-agent custom tools loading
151
154
  - [x] Prototype one prompt through Pi SDK
@@ -170,6 +173,7 @@
170
173
  - [x] Add max task runs per hour rate limit
171
174
  - [x] Add `/task status` or similar command
172
175
  - [x] Add task management as agent skill + tools (natural language task CRUD)
176
+ - [x] Improve task-management prompt guidance for reminder/proactive-message refusal
173
177
 
174
178
  ## Policy And Safety
175
179
 
@@ -0,0 +1 @@
1
+ ALTER TABLE `agent_sessions` ADD `session_skills` text DEFAULT '[]' NOT NULL;
@@ -50,6 +50,13 @@
50
50
  "when": 1779117599104,
51
51
  "tag": "0006_flippant_bruce_banner",
52
52
  "breakpoints": true
53
+ },
54
+ {
55
+ "idx": 7,
56
+ "version": "6",
57
+ "when": 1779120000000,
58
+ "tag": "0007_session_skills",
59
+ "breakpoints": true
53
60
  }
54
61
  ]
55
62
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fickydev/pigent",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Autonomous multi-agent daemon using Pi as core execution engine.",
@@ -8,17 +8,24 @@ disable-model-invocation: false
8
8
 
9
9
  You can manage scheduled recurring tasks for this agent and chat.
10
10
 
11
+ Important behavior:
12
+
13
+ - If the user asks you to remind, notify, check, monitor, or run something later or repeatedly, use the task tools.
14
+ - Do not refuse by saying you cannot proactively send messages. Pigent's scheduler can run the task later and send the result to the chat.
15
+ - You are not personally sending a future message from this turn. You are creating/updating a scheduled task that Pigent will execute later.
16
+ - After creating or updating a task, briefly confirm what will run and how often.
17
+
11
18
  ## Available Tools
12
19
 
13
20
  ### `task_list`
14
21
  List all scheduled tasks for this agent and chat.
15
22
 
16
23
  ### `task_create`
17
- Create a new scheduled task. Provide:
24
+ Create a new scheduled task. Use this whenever the user asks for a future or recurring reminder, notification, check, monitor, report, or follow-up. Provide:
18
25
  - `prompt` (required): what to do at each interval. Be specific.
19
26
  - `intervalMs` (optional): how often to run. Default 600000 (10 minutes). Minimum 60000 (1 minute).
20
27
 
21
- When the task runs, if the result is "NOOP", nothing is sent to the user.
28
+ When the task runs, Pigent sends the agent result to the chat. If the result is "NOOP", nothing is sent to the user.
22
29
 
23
30
  ### `task_update`
24
31
  Update an existing task's prompt or interval. Provide:
@@ -38,9 +45,20 @@ Remove a scheduled task by ID.
38
45
 
39
46
  - User asks to "check something periodically" or "monitor something"
40
47
  - User asks to "run something every X minutes/hours"
48
+ - User asks for reminders, notifications, reports, or follow-ups later
41
49
  - User wants to change or stop a recurring task
42
50
  - User asks "what tasks do I have?" or "what's running?"
43
51
 
52
+ ## What Not To Do
53
+
54
+ Do not say:
55
+
56
+ - "I can't proactively send messages"
57
+ - "I can't remind you later"
58
+ - "I can't monitor this in the background"
59
+
60
+ Instead, call `task_create` (or `task_update`/`task_remove` for existing tasks).
61
+
44
62
  ## Examples
45
63
 
46
64
  User: "check my build every 5 minutes"
@@ -49,6 +67,9 @@ User: "check my build every 5 minutes"
49
67
  User: "remind me to review PRs every hour"
50
68
  → task_create({ prompt: "Check for open pull requests that need review. List them. NOOP if none.", intervalMs: 3600000 })
51
69
 
70
+ User: "notify me every morning to drink water"
71
+ → task_create({ prompt: "Remind the user to drink water.", intervalMs: 86400000 })
72
+
52
73
  User: "change the build checker to run every 2 minutes"
53
74
  → task_list() to find the task, then task_update({ id: "abc123", intervalMs: 120000 })
54
75
 
@@ -1,7 +1,9 @@
1
1
  import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
2
2
  import type { InboundMessage } from "../channels/types";
3
3
  import type { LoadedAgentConfig } from "../config/schemas";
4
+ import { hasSkillRef, listBuiltInSkills, parseSessionSkills, skillDisplayName, uniqueSkillRefs } from "../config/skillRefs";
4
5
  import type { Repositories } from "../db/repositories";
6
+ import type { AgentSessionRow } from "../db/schema";
5
7
  import { logger } from "../logging/logger";
6
8
  import { PiAgentRunner } from "../pi/PiAgentRunner";
7
9
  import { createTaskTools, type TaskToolContext } from "../pi/tools/taskTools";
@@ -117,8 +119,8 @@ export class AgentRunner {
117
119
  agent,
118
120
  profile: this.registry.getProfile(agent.profile),
119
121
  session,
120
- prompt: this.composePrompt(input, chatInstructions),
121
- customTools: this.buildCustomTools(agent, input.message),
122
+ prompt: this.composePrompt(input, session, chatInstructions),
123
+ customTools: this.buildCustomTools(agent, session, input.message),
122
124
  });
123
125
 
124
126
  if (session.piSessionId !== result.piSessionId || session.piSessionPath !== result.piSessionPath) {
@@ -145,10 +147,10 @@ export class AgentRunner {
145
147
  }
146
148
  }
147
149
 
148
- private buildCustomTools(agent: LoadedAgentConfig, message: InboundMessage): ToolDefinition[] | undefined {
150
+ private buildCustomTools(agent: LoadedAgentConfig, session: AgentSessionRow, message: InboundMessage): ToolDefinition[] | undefined {
149
151
  const tools: ToolDefinition[] = [];
150
152
 
151
- if (this.hasSkill(agent, "task-management") && this.scheduler) {
153
+ if (this.hasSkill(agent, session, "task-management") && this.scheduler) {
152
154
  const ctx: TaskToolContext = {
153
155
  agentId: agent.id,
154
156
  channel: message.channel,
@@ -173,18 +175,17 @@ export class AgentRunner {
173
175
  return tools.length > 0 ? tools : undefined;
174
176
  }
175
177
 
176
- private hasSkill(agent: LoadedAgentConfig, skillName: string): boolean {
178
+ private hasSkill(agent: LoadedAgentConfig, session: AgentSessionRow, skillName: string): boolean {
177
179
  const allSkills = [
178
180
  ...(agent.profile ? (this.registry.getProfile(agent.profile)?.defaultSkills ?? []) : []),
179
181
  ...agent.skills,
182
+ ...parseSessionSkills(session.sessionSkills),
180
183
  ];
181
- return allSkills.some((s) => {
182
- const base = s.split("/").pop() ?? s;
183
- return base === skillName || s === skillName;
184
- });
184
+
185
+ return hasSkillRef(allSkills, skillName);
185
186
  }
186
187
 
187
- private composePrompt(input: AgentRunInput, chatInstructions: string): string {
188
+ private composePrompt(input: AgentRunInput, session: AgentSessionRow, chatInstructions: string): string {
188
189
  const parts: string[] = [
189
190
  `[Channel]\n${input.message.channel}`,
190
191
  `[Chat]\n${input.message.chatId}`,
@@ -202,11 +203,48 @@ export class AgentRunner {
202
203
  parts.push(`[Chat Instructions]\n${chatInstructions}`);
203
204
  }
204
205
 
206
+ const skillsContext = this.skillsContext(input.agentId, session);
207
+ if (skillsContext) {
208
+ parts.push(`[Skills]\n${skillsContext}`);
209
+ }
210
+
205
211
  parts.push(`[Message]\n${input.text}`);
206
212
 
207
213
  return parts.join("\n\n");
208
214
  }
209
215
 
216
+ private skillsContext(agentId: string, session: AgentSessionRow): string {
217
+ const agent = this.registry.getAgent(agentId);
218
+ if (!agent) return "";
219
+
220
+ const profile = this.registry.getProfile(agent.profile);
221
+ const profileSkills = profile?.defaultSkills ?? [];
222
+ const agentSkills = agent.skills;
223
+ const sessionSkills = parseSessionSkills(session.sessionSkills);
224
+ const builtInSkills = listBuiltInSkills(import.meta.dir);
225
+ const activeSkills = uniqueSkillRefs([...profileSkills, ...agentSkills, ...sessionSkills]);
226
+ const knownSkills = uniqueSkillRefs([...builtInSkills, ...activeSkills]).sort((a, b) =>
227
+ skillDisplayName(a).localeCompare(skillDisplayName(b)),
228
+ );
229
+
230
+ if (knownSkills.length === 0) return "No skills are configured or available.";
231
+
232
+ return knownSkills
233
+ .map((skill) => {
234
+ const name = skillDisplayName(skill);
235
+ const active = hasSkillRef(activeSkills, name);
236
+ const sources = [];
237
+
238
+ if (hasSkillRef(profileSkills, name)) sources.push("profile");
239
+ if (hasSkillRef(agentSkills, name)) sources.push("agent");
240
+ if (hasSkillRef(sessionSkills, name)) sources.push("session");
241
+ if (hasSkillRef(builtInSkills, name)) sources.push("built-in");
242
+
243
+ return `- ${name}: ${active ? "active" : "available, inactive"}${sources.length > 0 ? ` (${sources.join(", ")})` : ""}`;
244
+ })
245
+ .join("\n");
246
+ }
247
+
210
248
  private async chatInstructions(input: AgentRunInput): Promise<string> {
211
249
  if (input.message.channel !== "telegram") return "";
212
250
 
@@ -1,6 +1,7 @@
1
1
  import { AuthStorage, ModelRegistry } from "@earendil-works/pi-coding-agent";
2
2
  import type { InboundMessage, InlineKeyboardButton } from "../channels/types";
3
3
  import type { LoadedAgentConfig, LoadedProfileConfig, ModelChoiceConfig } from "../config/schemas";
4
+ import { hasSkillRef, listBuiltInSkills, parseSessionSkills, skillDisplayName, uniqueSkillRefs } from "../config/skillRefs";
4
5
  import type { AgentSessionRow } from "../db/schema";
5
6
  import type { Repositories } from "../db/repositories";
6
7
  import { PiAgentRunner, type PiContextUsage } from "../pi/PiAgentRunner";
@@ -47,6 +48,8 @@ export class BotCommandHandler {
47
48
  return { handled: true, text: await this.newSessionText(message) };
48
49
  case "/status":
49
50
  return { handled: true, text: await this.statusText(message) };
51
+ case "/skills":
52
+ return { handled: true, text: await this.skillsText(message) };
50
53
  case "/model":
51
54
  return await this.modelResult(message);
52
55
  case "/thinking":
@@ -71,6 +74,9 @@ export class BotCommandHandler {
71
74
  "/agents - list agents for this chat",
72
75
  "/new - start a fresh session for this chat",
73
76
  "/status - show current session status",
77
+ "/skills - list skills for this chat session",
78
+ "/skills add <skill> - load a skill for this session",
79
+ "/skills remove <skill> - unload a session-loaded skill",
74
80
  "/model - show model picker for this chat session",
75
81
  "/model <provider/modelId> - set model for this chat session",
76
82
  "/model default - clear model override for this chat session",
@@ -191,6 +197,83 @@ export class BotCommandHandler {
191
197
  };
192
198
  }
193
199
 
200
+ private async skillsText(message: InboundMessage): Promise<string> {
201
+ const sessionResult = await this.getDefaultSession(message);
202
+ if (!sessionResult.ok) return sessionResult.message;
203
+
204
+ const [, actionRaw, ...args] = message.text.trim().split(/\s+/);
205
+ const action = actionRaw?.toLowerCase() ?? "list";
206
+ const skillRef = args.join(" ").trim();
207
+
208
+ if (action === "add") {
209
+ if (!skillRef) return "Usage: /skills add <skill>";
210
+
211
+ const current = parseSessionSkills(sessionResult.session.sessionSkills);
212
+ const next = uniqueSkillRefs([...current, skillRef]);
213
+ await this.repositories.sessions.updateSessionSkills(sessionResult.session.id, next);
214
+
215
+ return [
216
+ "Skill loaded for current session: " + skillRef,
217
+ "",
218
+ "It will be included on the next agent response in this chat session.",
219
+ "Use /skills to see active skills.",
220
+ ].join("\n");
221
+ }
222
+
223
+ if (action === "remove") {
224
+ if (!skillRef) return "Usage: /skills remove <skill>";
225
+
226
+ const current = parseSessionSkills(sessionResult.session.sessionSkills);
227
+ const next = current.filter((item) => item !== skillRef);
228
+ await this.repositories.sessions.updateSessionSkills(sessionResult.session.id, next);
229
+
230
+ return next.length === current.length
231
+ ? "Session skill not found: " + skillRef
232
+ : "Skill removed from current session: " + skillRef;
233
+ }
234
+
235
+ if (action === "clear") {
236
+ await this.repositories.sessions.updateSessionSkills(sessionResult.session.id, []);
237
+ return "Session-loaded skills cleared.";
238
+ }
239
+
240
+ if (action !== "list") {
241
+ return [
242
+ "Usage:",
243
+ "/skills - list skills",
244
+ "/skills add <skill> - load skill for this session",
245
+ "/skills remove <skill> - unload session skill",
246
+ "/skills clear - clear session-loaded skills",
247
+ ].join("\n");
248
+ }
249
+
250
+ const agent = sessionResult.agent;
251
+ const profile = this.registry.getProfile(agent.profile);
252
+ const profileSkills = profile?.defaultSkills ?? [];
253
+ const agentSkills = agent.skills;
254
+ const sessionSkills = parseSessionSkills(sessionResult.session.sessionSkills);
255
+ const builtInSkills = listBuiltInSkills(import.meta.dir);
256
+ const activeSkills = uniqueSkillRefs([...profileSkills, ...agentSkills, ...sessionSkills]);
257
+ const knownSkills = uniqueSkillRefs([...builtInSkills, ...activeSkills]).sort((a, b) =>
258
+ skillDisplayName(a).localeCompare(skillDisplayName(b)),
259
+ );
260
+
261
+ return [
262
+ "Skills for current session",
263
+ "",
264
+ "Agent: " + agent.id,
265
+ "Session: " + sessionResult.session.id,
266
+ "",
267
+ "Available skills:",
268
+ ...formatSkillRows(knownSkills, { profileSkills, agentSkills, sessionSkills, builtInSkills }),
269
+ "",
270
+ "Legend: ✅ active, ⬜ available but inactive",
271
+ "Load on the fly: /skills add <skill>",
272
+ "Unload: /skills remove <skill>",
273
+ "Clear session-loaded: /skills clear",
274
+ ].join("\n");
275
+ }
276
+
194
277
  private async modelResult(message: InboundMessage): Promise<BotCommandResult> {
195
278
  const sessionResult = await this.getDefaultSession(message);
196
279
  if (!sessionResult.ok) return { handled: true, text: sessionResult.message };
@@ -511,6 +594,33 @@ function formatDuration(ms: number): string {
511
594
  return Math.floor(hours / 24) + "d " + (hours % 24) + "h";
512
595
  }
513
596
 
597
+ function formatSkillRows(
598
+ skills: string[],
599
+ sources: { profileSkills: string[]; agentSkills: string[]; sessionSkills: string[]; builtInSkills: string[] },
600
+ ): string[] {
601
+ if (skills.length === 0) return ["none"];
602
+
603
+ return skills.map((skill) => {
604
+ const name = skillDisplayName(skill);
605
+ const sourceLabels = [];
606
+
607
+ if (hasSkillRef(sources.profileSkills, name)) sourceLabels.push("profile");
608
+ if (hasSkillRef(sources.agentSkills, name)) sourceLabels.push("agent");
609
+ if (hasSkillRef(sources.sessionSkills, name)) sourceLabels.push("session");
610
+ if (hasSkillRef(sources.builtInSkills, name)) sourceLabels.push("built-in");
611
+
612
+ const active = sourceLabels.some((source) => source !== "built-in");
613
+ const marker = active ? "✅" : "⬜";
614
+ const sourceText = sourceLabels.length > 0 ? sourceLabels.join(", ") : "configured";
615
+
616
+ return `${marker} ${name} (${active ? "active" : "inactive"}; ${sourceText})`;
617
+ });
618
+ }
619
+
620
+ function formatList(items: string[]): string {
621
+ return items.length > 0 ? items.join(", ") : "none";
622
+ }
623
+
514
624
  function formatPath(path: string): string {
515
625
  const home = process.env.HOME || process.env.USERPROFILE;
516
626
  if (home && path.startsWith(home)) {
@@ -0,0 +1,50 @@
1
+ import { existsSync, readdirSync, statSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+
4
+ export function parseSessionSkills(raw: string | null | undefined): string[] {
5
+ if (!raw) return [];
6
+
7
+ try {
8
+ const parsed = JSON.parse(raw);
9
+ if (!Array.isArray(parsed)) return [];
10
+
11
+ return parsed.filter((item): item is string => typeof item === "string" && item.trim().length > 0).map((item) => item.trim());
12
+ } catch {
13
+ return [];
14
+ }
15
+ }
16
+
17
+ export function skillDisplayName(ref: string): string {
18
+ const normalized = ref.replace(/\\/g, "/").replace(/\/$/, "");
19
+ return normalized.split("/").pop() || ref;
20
+ }
21
+
22
+ export function hasSkillRef(refs: string[], skillName: string): boolean {
23
+ return refs.some((ref) => ref === skillName || skillDisplayName(ref) === skillName);
24
+ }
25
+
26
+ export function uniqueSkillRefs(refs: string[]): string[] {
27
+ const seen = new Set<string>();
28
+ const unique: string[] = [];
29
+
30
+ for (const ref of refs.map((item) => item.trim()).filter(Boolean)) {
31
+ if (seen.has(ref)) continue;
32
+ seen.add(ref);
33
+ unique.push(ref);
34
+ }
35
+
36
+ return unique;
37
+ }
38
+
39
+ export function listBuiltInSkills(fromDir: string): string[] {
40
+ const skillsDir = resolve(fromDir, "../../skills");
41
+
42
+ if (!existsSync(skillsDir)) return [];
43
+
44
+ return readdirSync(skillsDir)
45
+ .filter((name) => {
46
+ const path = resolve(skillsDir, name);
47
+ return statSync(path).isDirectory() && existsSync(resolve(path, "SKILL.md"));
48
+ })
49
+ .sort();
50
+ }
@@ -181,6 +181,7 @@ function telegramCommands() {
181
181
  { command: "agents", description: "List agents available in this chat" },
182
182
  { command: "new", description: "Start a fresh chat session" },
183
183
  { command: "status", description: "Show current session status" },
184
+ { command: "skills", description: "List or load skills for this session" },
184
185
  { command: "model", description: "Choose model for this chat session" },
185
186
  { command: "thinking", description: "Choose thinking level for this chat session" },
186
187
  { command: "agent", description: "Send message to a specific agent" },
package/src/db/client.ts CHANGED
@@ -30,6 +30,7 @@ function ensureRuntimeSchema(): void {
30
30
  ensureColumn("agent_sessions", "active", "ALTER TABLE `agent_sessions` ADD `active` integer DEFAULT true NOT NULL");
31
31
  ensureColumn("agent_sessions", "ended_at", "ALTER TABLE `agent_sessions` ADD `ended_at` integer");
32
32
  ensureColumn("agent_sessions", "pi_session_path", "ALTER TABLE `agent_sessions` ADD `pi_session_path` text");
33
+ ensureColumn("agent_sessions", "session_skills", "ALTER TABLE `agent_sessions` ADD `session_skills` text DEFAULT '[]' NOT NULL");
33
34
  }
34
35
 
35
36
  function ensureColumn(table: string, column: string, statement: string): void {
@@ -75,6 +75,18 @@ export class SessionRepository {
75
75
  return this.requireById(id);
76
76
  }
77
77
 
78
+ async updateSessionSkills(id: string, skills: string[]): Promise<AgentSessionRow> {
79
+ await this.db
80
+ .update(agentSessions)
81
+ .set({
82
+ sessionSkills: JSON.stringify(skills),
83
+ updatedAt: Date.now(),
84
+ })
85
+ .where(eq(agentSessions.id, id));
86
+
87
+ return this.requireById(id);
88
+ }
89
+
78
90
  async findById(id: string): Promise<AgentSessionRow | null> {
79
91
  const row = await this.db.query.agentSessions.findFirst({
80
92
  where: eq(agentSessions.id, id),
package/src/db/schema.ts CHANGED
@@ -51,6 +51,7 @@ export const agentSessions = sqliteTable(
51
51
  instructionsHash: text("instructions_hash"),
52
52
  model: text("model"),
53
53
  thinkingLevel: text("thinking_level", { enum: ["off", "low", "medium", "high"] }),
54
+ sessionSkills: text("session_skills").notNull().default("[]"),
54
55
  active: integer("active", { mode: "boolean" }).notNull().default(true),
55
56
  endedAt: integer("ended_at"),
56
57
  createdAt: integer("created_at").notNull(),
@@ -10,6 +10,7 @@ import {
10
10
  import { mkdir } from "node:fs/promises";
11
11
  import { resolve } from "node:path";
12
12
  import type { LoadedAgentConfig, LoadedProfileConfig } from "../config/schemas";
13
+ import { parseSessionSkills } from "../config/skillRefs";
13
14
  import type { AgentSessionRow } from "../db/schema";
14
15
  import { resolveModelSelection } from "./PiModelResolver";
15
16
  import { loadOrCreatePiSession } from "./PiSessionFactory";
@@ -82,7 +83,7 @@ export class PiAgentRunner {
82
83
  compaction: { enabled: false },
83
84
  });
84
85
  const agentDir = getAgentDir();
85
- const skillPaths = resolveSkillPaths(input.agent, input.profile);
86
+ const skillPaths = resolveSkillPaths(input.agent, input.profile, input.session);
86
87
  const resourceLoader = new DefaultResourceLoader({
87
88
  cwd: workspace,
88
89
  agentDir,
@@ -114,7 +115,7 @@ export class PiAgentRunner {
114
115
  }
115
116
  }
116
117
 
117
- function resolveSkillPaths(agent: LoadedAgentConfig, profile: LoadedProfileConfig | null): string[] {
118
+ function resolveSkillPaths(agent: LoadedAgentConfig, profile: LoadedProfileConfig | null, session: AgentSessionRow): string[] {
118
119
  const paths: string[] = [];
119
120
 
120
121
  if (profile) {
@@ -127,6 +128,10 @@ function resolveSkillPaths(agent: LoadedAgentConfig, profile: LoadedProfileConfi
127
128
  paths.push(resolveSkillPath(s, agent.baseDir));
128
129
  }
129
130
 
131
+ for (const s of parseSessionSkills(session.sessionSkills)) {
132
+ paths.push(resolveSkillPath(s, agent.baseDir));
133
+ }
134
+
130
135
  return paths;
131
136
  }
132
137
 
@@ -54,11 +54,17 @@ function createTaskCreateTool(ctx: TaskToolContext): ToolDefinition {
54
54
  name: "task_create",
55
55
  label: "Create Scheduled Task",
56
56
  description:
57
- "Create a new scheduled task for the current agent and chat. " +
58
- "The task will run the given prompt at the specified interval. " +
57
+ "Create a scheduled task/reminder/monitor for the current agent and chat. " +
58
+ "Use this when the user asks to be reminded or notified later, or asks you to check/monitor something repeatedly. " +
59
+ "Pigent will run the given prompt at the specified interval and send the result to the chat. " +
60
+ "Do not refuse by saying you cannot proactively send messages; use this tool instead. " +
59
61
  "If the prompt result is NOOP, nothing is sent to the chat. " +
60
62
  `Maximum ${MAX_TASKS_PER_CHAT} tasks per agent per chat.`,
61
- promptSnippet: "Create a scheduled task with a prompt and interval",
63
+ promptSnippet: "Create a scheduled reminder, notification, monitor, or recurring task with a prompt and interval",
64
+ promptGuidelines: [
65
+ "When the user asks for a future or recurring reminder/notification/check/monitor, call task_create instead of saying you cannot proactively send messages.",
66
+ "Pigent's scheduler will execute task_create tasks later and send non-NOOP results to the current chat.",
67
+ ],
62
68
  parameters: Type.Object({
63
69
  prompt: Type.String({ description: "The prompt to run at each interval. Be specific about what to check or do." }),
64
70
  intervalMs: Type.Optional(