feat: ephemeral sessions + agent manager refactor #92
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!92
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/ephemeral-sessions"
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
Ephemeral sessions — short-lived child sessions of an agent. Persist under
<agentId>/<sessionGuid>/, never auto-loaded, caller-owned. Reply to parent viasession_replyMCP tool (publisheschannel:messageon the bus); fallback path auto-sends the last assistant content if the tool is never called. Pre-requisite for cron firing and sub-agent tooling.Agent manager split —
AgentConfigSupervisor(hosted) watches disk, emitscommand:agent-load/command:agent-unload.AgentManager(nowBackgroundService) subscribes and owns slot lifecycle. Single-purpose classes; communicate via bus only.Plumbing changes
IAgentIterationRunnerextracted fromAgent.ProcessIterationAsync— shared seam, returns tool-result turns rather than enqueueing.IContextStoregains a nullablesessionIddimension;null= default session (existing path, backward compat). New layout:<agentId>/<sessionGuid:n>/current.json.ChannelMessage.SessionId+ModelTurn.SessionId+AgentState.SessionId(all nullable) — sender / persistence routing metadata.CompactionAgentServiceis now anIAgentService; discard-resolve inBuildSlotAsyncdropped.IAgentManager.Get(string)removed — leaked scoped lifetime and broke AsyncLocal isolation.Containsis the only safe query.CoreServiceCollectionExtensions.AddCoresplit into per-component private helpers.Test plan
dotnet buildcleandotnet test— 526 / 526 passingOut of scope (follow-ups)
FireSingleAsyncnon-stubAgentManagersupervising ephemeral sessions (currently caller-owned)ILanguageModeldiscard-resolve inBuildSlotAsync→ explicit config validationPull request overview
This PR adds a first-class “ephemeral session” primitive (caller-owned, per-session persistence under
<agentId>/<sessionGuid>/, reply via the newsession_replyMCP tool + fallback) and refactors agent hosting by splitting config reconciliation (AgentConfigSupervisor) from slot lifecycle (AgentManager), with coordination purely via bus commands. It also extractsIAgentIterationRunneras a shared seam and extends persistence/event metadata to carry an optionalSessionIdfor correct routing.Changes:
session_replyMCP tool + unit tests.AgentConfigSupervisorpublishescommand:agent-load/unload;AgentManagerbecomes aBackgroundServicethat subscribes and manages slots.IContextStoregainsGuid? sessionId, model/event types gain nullableSessionId, andJsonLineContextStorewrites non-default sessions under GUID subfolders.Reviewed changes
Copilot reviewed 138 out of 138 changed files in this pull request and generated 4 comments.
Show a summary per file
IContextStoreto session-aware signatures.AgentIterationRunnerseam.AgentIterationRunnerseam.AgentIterationRunnerseam.AgentIterationRunnerseam.AgentIterationRunnerseam.SessionIdrouting metadata.extension(...)blocks.extension(...)blocks.extension(...)blocks.SessionIdfor audit/routing metadata.extension(...)blocks.SessionIdto context surface (default = null).SessionIdto archive identity.Get(string); leavesContains.extension(...)blocks.SessionIdmetadata to agent state.extension(...)blocks.partialmodifier.IEphemeralSessionFactory.SessionIdon the concrete context.returnin canceled catch block.IAgentServiceto participate in slot lifecycle.IAgentIterationRunner.IAgentIterationRunnerand enqueues returned tool turns; removes embedded iteration logic.Get()toContains()for loaded-agent checks.session_replyto publish child replies.SessionReplyTool.@implementsbelow@using.CultureInfoimport instead of fully-qualified reference.SessionIdproperty.SessionIdproperty.💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
@ -0,0 +152,4 @@IsError = outcome.ToolResults[i].IsError,SessionId = sessionId,});}InferenceRunneralready publishes tool-resultModelTurns viaPublishCompletedToolCallAsync(Event.WellKnown.Agent.Turn), andAgentTurnContextPersisterpersists those turns. Returning another set of tool turns here (which will then be published again on the next iteration as part of the inbound batch) risks duplicating tool results in the persisted transcript/prompt. Consider returning references to the already-emitted turns, or suppressing/persist-filtering theInferenceRunnertool-turn publish, or otherwise ensuring tool results are only persisted/prompted once (and that the fed-back tool turns preserve the correctChannelId).@ -78,16 +79,17 @@ public sealed class JsonLineContextStore : IContextStore.Select(Path.GetFileName)ListAgentsAsyncfilters out any top-level directory whose name parses as a 32-char GUID ("N" format). Since agent IDs are not otherwise constrained, this can accidentally hide legitimate agents whose ID happens to match that pattern (e.g., an agent named with a GUID). Consider either enforcing/disallowing GUID-N agent IDs at config/creation time, or tightening the heuristic (e.g., only skip GUID folders that are not valid agent folders), or removing the filter and addressing the original rogue-folder scenario differently.@ -95,0 +83,4 @@}if (element.ValueKind == JsonValueKind.Null){return default(T) is null;Switching these public extension helpers to the new
extension(...) { ... }block syntax appears to cause the generated API docs in this PR to surface synthetic mangled types/namespaces (e.g.<G>$...) rather than clean extension-method entries. If the docs underdocs/api/are intended for public consumption, consider reverting to classicpublic staticextension methods withthisparameters (or updating the doc generator) to keep the published API surface readable and stable.@ -10,3 +9,3 @@new ConcurrentDictionary<string, IAgentContext>(StringComparer.Ordinal);private readonly ConcurrentDictionary<(string AgentId, Guid? SessionId), IAgentContext> _contexts = new();public FakeContextStore With(string agentId, IAgentContext context)This introduces target-typed
new()for_contexts. Repo style disables IDE0090 and explicitly prefersnew ConcurrentDictionary<...>(...)for readability (see.editorconfigIDE0090 section). Consider switching back to an explicit constructor call here for consistency with the configured style.