feat(cron): per-agent cron + session messaging + tool consolidation #111
No reviewers
Labels
No labels
bug
commercial
documentation
duplicate
enhancement
feature
good first issue
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
jasoncouture/llama-shears!111
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/cron"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
CRON.mdsub-agent via the sharedIPromptedAgentSpawner; supports one-shot jobs.session_list/session_sendMCP tools so transient sub-agents (heartbeat, cron) can deliver a message back to their parent session over the same path as a user-typed chat input.CRON.mdandHEARTBEAT.mdnow referencellamashears__session_sendwith{{ session_path.parent }}, and stop pointing sub-agents at the removedmessage_sendtool.CronTools,MemoryTools, andFileToolspartial classes; DI registrations collapsed to oneWithTools<…>()each.session_send(the actual tool name), not the removedmessage_send.Test plan
dotnet buildcleandotnet test— 572 passingsession_send, parent session receives the message, job auto-disablessession_sendonly when there is real signalPull request overview
This PR adds per-agent cron execution via transient sub-agents, introduces session messaging tools for parent/child session communication, and consolidates MCP tool implementations for cron, memory, and filesystem operations.
Changes:
CRON.mdtransient sub-agents, and support one-shot scheduling.session_list/session_sendtools allow transient agents to message live sessions.Reviewed changes
Copilot reviewed 61 out of 61 changed files in this pull request and generated 7 comments.
Show a summary per file
tests/LlamaShears.UnitTests/Cron/JsonCronStoreTests.cstests/LlamaShears.UnitTests/Cron/CronSchedulerTests.cstests/LlamaShears.UnitTests/Api/Tools/ModelContextProtocol/Filesystem/WriteFileToolTests.csFileTools.tests/LlamaShears.UnitTests/Api/Tools/ModelContextProtocol/Filesystem/RegexReplaceFileToolTests.csFileTools.tests/LlamaShears.UnitTests/Api/Tools/ModelContextProtocol/Filesystem/ReadFileToolTests.csFileTools.tests/LlamaShears.UnitTests/Api/Tools/ModelContextProtocol/Filesystem/GrepToolTests.csFileTools.tests/LlamaShears.UnitTests/Api/Tools/ModelContextProtocol/Filesystem/DeleteFileToolTests.csFileTools.tests/LlamaShears.UnitTests/Api/Tools/ModelContextProtocol/Filesystem/AppendFileToolTests.csFileTools.tests/LlamaShears.UnitTests/Agent/Core/TransientAgentTests.csmessage_sendtosession_send.tests/LlamaShears.UnitTests/Agent/Core/AgentHeartbeatServiceTests.cssrc/public/LlamaShears.Core.Abstractions/Agent/Sessions/SessionId.cssrc/public/LlamaShears.Core.Abstractions/Agent/PromptedAgentStartInformation.cssrc/public/LlamaShears.Core.Abstractions/Agent/ITransientAgentSpawner.cssrc/public/LlamaShears.Core.Abstractions/Agent/IPromptedAgentSpawner.cssrc/LlamaShears/content/templates/workspace/system/HEARTBEAT.mdsession_send.src/LlamaShears/content/templates/workspace/system/CRON.mdsrc/LlamaShears/content/templates/workspace/system/context/CRON.mdsrc/LlamaShears.Core/TransientAgent.cssession_send.src/LlamaShears.Core/PromptedAgentSpawner.cssrc/LlamaShears.Core/Cron/JsonCronStore.cssrc/LlamaShears.Core/Cron/ICronStore.cssrc/LlamaShears.Core/Cron/ICronScheduler.cssrc/LlamaShears.Core/Cron/CronServiceCollectionExtensions.cssrc/LlamaShears.Core/Cron/CronScheduler.cssrc/LlamaShears.Core/Cron/CronJobEdit.cssrc/LlamaShears.Core/Cron/CronJob.cssrc/LlamaShears.Core/Cron/CronExecutor.cssrc/LlamaShears.Core/Cron/AgentCronService.cssrc/LlamaShears.Core/CoreServiceCollectionExtensions.cssrc/LlamaShears.Core/AgentHeartbeatService.cssrc/LlamaShears.Api/Tools/ModelContextProtocol/Session/SessionTools.cssrc/LlamaShears.Api/Tools/ModelContextProtocol/Session/SessionSendResult.cssrc/LlamaShears.Api/Tools/ModelContextProtocol/Session/SessionListResult.cssrc/LlamaShears.Api/Tools/ModelContextProtocol/Session/SessionEntry.cssrc/LlamaShears.Api/Tools/ModelContextProtocol/ModelContextProtocolServiceCollectionExtensions.cssrc/LlamaShears.Api/Tools/ModelContextProtocol/Memory/StoreMemoryTool.cssrc/LlamaShears.Api/Tools/ModelContextProtocol/Memory/SearchMemoryTool.cssrc/LlamaShears.Api/Tools/ModelContextProtocol/Memory/MemoryTools.cssrc/LlamaShears.Api/Tools/ModelContextProtocol/Memory/IndexMemoryTool.cssrc/LlamaShears.Api/Tools/ModelContextProtocol/Filesystem/WriteFileTool.cssrc/LlamaShears.Api/Tools/ModelContextProtocol/Filesystem/RegexReplaceFileTool.cssrc/LlamaShears.Api/Tools/ModelContextProtocol/Filesystem/ReadFileTool.cssrc/LlamaShears.Api/Tools/ModelContextProtocol/Filesystem/MoveFileTool.cssrc/LlamaShears.Api/Tools/ModelContextProtocol/Filesystem/ListFilesTool.cssrc/LlamaShears.Api/Tools/ModelContextProtocol/Filesystem/GrepTool.cssrc/LlamaShears.Api/Tools/ModelContextProtocol/Filesystem/FileTools.cssrc/LlamaShears.Api/Tools/ModelContextProtocol/Filesystem/DeleteFileTool.cssrc/LlamaShears.Api/Tools/ModelContextProtocol/Filesystem/AppendFileTool.cssrc/LlamaShears.Api/Tools/ModelContextProtocol/Cron/TriggerCronTool.cssrc/LlamaShears.Api/Tools/ModelContextProtocol/Cron/ScheduleCronTool.cssrc/LlamaShears.Api/Tools/ModelContextProtocol/Cron/ListCronTool.cssrc/LlamaShears.Api/Tools/ModelContextProtocol/Cron/EditCronTool.cssrc/LlamaShears.Api/Tools/ModelContextProtocol/Cron/CronTools.cssrc/LlamaShears.Api/Tools/ModelContextProtocol/Cron/CancelCronTool.cssrc/LlamaShears.Api.Web/Services/ChatSession.csdocs/api/LlamaShears.Core.Abstractions/LlamaShears/Core/Abstractions/Agent/Sessions/SessionId.mdSessionId.docs/api/LlamaShears.Core.Abstractions/LlamaShears/Core/Abstractions/Agent/PromptedAgentStartInformation.mddocs/api/LlamaShears.Core.Abstractions/LlamaShears/Core/Abstractions/Agent/ITransientAgentSpawner.mddocs/api/LlamaShears.Core.Abstractions/LlamaShears/Core/Abstractions/Agent/IPromptedAgentSpawner.mddocs/api/LlamaShears.Core.Abstractions/LlamaShears/Core/Abstractions/Agent/index.mddocs/api/LlamaShears.Core.Abstractions/index.md💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
@ -0,0 +26,4 @@public async Task<CronScheduleResult> ScheduleCron([Description("Human-readable handle for this job. Used in list output and log messages.")] string name,[Description("Cron expression in 5-field form (e.g. '0 9 * * 1-5' for 09:00 UTC weekdays).")] string cronExpression,[Description("Instructions you (the scheduling agent) are giving to the future cron sub-agent. Write it as a directive, not as the literal output you want produced. Example: when the user says \"have it say hello in chat every minute\", the prompt is \"Send the message 'hello' to the user's chat channel via the message_send tool.\" — NOT just \"hello\". The sub-agent will read this string as its initial user-role turn and must figure out what tools to call to satisfy it.")] string prompt,The prompt guidance still tells callers to use the removed
message_sendtool. This contradicts the newsession_sendflow and will cause cron prompts generated from this description to ask sub-agents for a tool that is no longer registered; update the example to usesession_send/session_list.@ -0,0 +41,4 @@var job = await _scheduler.ScheduleAsync(name, cronExpression, prompt, oneShot, cancellationToken);return new CronScheduleResult(Scheduled: true,Job: new CronJobSummary(The new
oneShotflag is accepted when scheduling, but the returned job summary omits it (and the list/edit summaries do the same). Callers cannot verify whether a job is one-shot after scheduling or listing it; includeOneShotin the cron summary payloads and populate it fromCronJob.OneShot.@ -0,0 +111,4 @@[Description("Cron job id (GUID, format-D).")] string id,[Description("New human-readable name. Leave null to keep current.")] string? name = null,[Description("New 5-field cron expression. Leave null to keep current.")] string? cronExpression = null,[Description("New instruction text the future cron sub-agent will execute. Write it as a directive FROM YOU TO THE SUB-AGENT — describe what action to take and which tools to use, not the literal output you want. Example: \"Send 'hello' to the user's chat via message_send\" — NOT just \"hello\". Leave null to keep current.")] string? prompt = null,This edit-tool description still points cron sub-agents at
message_send, but that tool was removed and the transient auto-forward suppression now keys offsession_send. Update the example so edited cron prompts don't preserve instructions for a nonexistent tool.@ -0,0 +125,4 @@return new CronEditResult(JobId: null, Edited: false, Job: null, Error: $"Refused: '{id}' is not a valid GUID.");}var edit = new CronJobEdit(name, cronExpression, prompt, enabled);CronJobEditnow supportsOneShot, but this tool constructs the edit without ever accepting or forwarding that field, so users cannot toggle a job between recurring and one-shot after creation. Add a nullableoneShotparameter and pass it intoCronJobEdit.@ -63,2 +47,4 @@.WithTools<MemoryTools>().WithTools<CronTools>().WithTools<TodoTools>().WithTools<ShellTools>()This policy block drops the previous write protection for
AGENTS.mdand removes the rootTOOLS.md/TODO.mdprotections while consolidating tools. That makes the generic file write/append/regex-replace tools able to modify host-owned guidance and todo files that were previously protected; restore the removedProtectionMode.Write/read/write/delete rules unless this security boundary is intentionally being redesigned with replacement safeguards.@ -0,0 +82,4 @@Event.WellKnown.Channel.Message with { Id = target },payload,cancellationToken);return new SessionSendResult(SessionId: target.ToString(), Sent: true);session_sendreturnsSent = truefor any parseable same-agent session id, even if that session is not currently registered in_instances. Because publishing to an unhandled event has no receiver, callers can get a success response while the message is dropped; check_instances.GetAllAgents()for the target session before publishing and return an error when it is not live.@ -40,3 +41,4 @@return [.. bucket.Values];}finally{Changing the store path from the existing
Data/cron.jsonfile to per-agent files underData/cron/makes previously scheduled jobs invisible after upgrade. Add a one-time migration that reads the old global file, partitions jobs byAgentId, persists them to the new per-agent files, and removes/archives the legacy file after success.