Cortex is Synergy's child-session execution layer. It turns a delegated unit of work into a separately observable session with its own prompt, agent, model, progress, output contract, cancellation tree, and optional worktree.
Delegation is implemented as a tree of sessions, rather than as hidden work inside one message history. A parent session assigns a bounded task, Cortex creates or reuses a child session to perform it, and the child returns an explicit result while keeping its own history.
A Cortex task records:
- its task ID and child session ID
- the parent session and parent message that launched it
- a description and full prompt
- the selected agent, execution role, category, and optional DAG node
- status and live progress
- visibility, durable parent-notification intent, and optional parent-handoff timestamp
- output mode and resolved output
Task status moves through queued, running, and one terminal state: completed, error, cancelled, or interrupted. A Task is created only when it has entered the Cortex queue, so there is no separate pending lifecycle state. interrupted means durable metadata says work was active, but no live runtime survived restart; it is distinct from an execution error.
The child session is the durable record. The in-memory task entry coordinates live execution and is eventually evicted; the child session retains its Cortex metadata, messages, model, terminal status, output, notifyParentOnComplete decision, and optional deliveryNotifiedAt parent-handoff timestamp.
Plugin-owned tasks additionally persist plugin ID, plugin generation, Scope ID, and a plugin-defined correlation ID. These fields let a plugin resume its own domain workflow without treating the in-memory Cortex map as durable state.
The Plugin Task Host can resolve the task that owns the current invocation directly from this durable child-Session metadata. Ownership is committed before child execution begins, so an internal plugin tool can bind through the correlation ID even before the original start() call has returned to the plugin.
Synergy has one Cortex launch mechanism and two authorization entry paths:
- Model-directed delegation through the native
tasktool requires a non-hidden subagent that is visible to the caller and permitted by its delegation policy. The same predicate determines which Agents appear in the caller's prompt. - Host-owned workflows may programmatically launch a private hidden subagent after their owning subsystem establishes authorization. Built-in workflows own their internal Agents. A plugin owns only the Agent contributions resolved from that same plugin generation, and its approved
task.delegateallowlist must include the target.
Both paths enter Cortex.launch() and create the same Task and child Session. Plugins do not get a second Agent registry, task scheduler, transcript store, or execution loop.
Cortex creates a child session in the parent's Scope and inherits its canonical Workspace reference through Session.create, including an unresolved reference whose local projection is null. An explicitly absent Workspace remains absent. Reusing an idle compatible child preserves its transcript while refreshing its durable task controls, including the tool allowlist, output contract, and visibility. If a new worktree is requested, Cortex creates one for the child; a child of an existing worktree inherits that worktree instead of nesting another.
Tasks are admitted through both per-agent and process-global concurrency limits. Each concurrency key allows at most eight running tasks. The global maximum defaults to eight and can be set with the global cortex.maxConcurrentTasks configuration or overridden for the process by SYNERGY_CORTEX_GLOBAL_CONCURRENCY. Lowering the maximum does not cancel running tasks; it queues new work until capacity is available, while raising it wakes eligible queued work.
Memory pressure lowers new-task admission to four under elevated pressure or two under critical pressure. The scheduler uses the shared session memory classification, plus earlier ArrayBuffer thresholds at 1 GiB and 2 GiB, so it converges before Bun stream allocations fail. The configured, environment-provided, or default value remains the user-selected maximum; the effective admission limit is the lower of that maximum and the current memory-pressure ceiling. Running tasks are not cancelled. Queued tasks are reconsidered when capacity is released and while pressure is active, so they resume after memory recovers. The read-only cortex.concurrency API reports configured, environment, effective, memory-pressure limit and reason, source, per-agent, running, and queued values.
An explicit task model wins. Otherwise the native task tool inherits the parent assistant message's model when it is still available to preserve the parent's provider choice. Only when the parent model is no longer available does resolution fall back to the selected agent's configured model, with the parent model retained as the final reference. Other Cortex callers retain the shared manager fallback: explicit model, available agent model, then the parent session model or provider default. The resolved model is persisted on the child session.
The launcher can pass maxOutputTokens to cap the child session's model output and maxCost to discard task output when final measured usage exceeds a cost ceiling. maxOutputTokens is passed through to SessionInvoke.invokeInternal() for both the initial call and any structured-output repair turns; maxCost is checked before Cortex publishes terminal output. The GitHub shadow proposer uses both fields to enforce its proposal budget. When either field is absent, that per-task limit is not applied.
The ordinary role is delegated_subagent. It is intentionally narrower than a primary session:
- permission questions are denied rather than routed back interactively
- task delegation and task inspection tools are removed
- DAG mutation tools are removed
- configured primary-only tools are removed
- deferred tool discovery and expansion remain available, but they expose only tools already allowed by the selected agent's permission rules
- native subagents may expand the deferred Browser group on demand; Browser tools remain hidden until expansion and still pass through the normal capability and permission pipeline
This keeps a delegated task bounded and prevents accidental recursive orchestration. Hidden internal reviewers can be given an explicit delegationGroup so they can call selected specialists while remaining hidden and unavailable as direct user targets.
The child still uses the normal session loop, control-profile resolution, capability gate, permission rules, and sandbox pipeline.
A background task returns its identity immediately and continues independently. A foreground task waits for the child result for up to 300 seconds. If the wait expires, the task keeps running in the background rather than being cancelled.
Completion uses a two-phase protocol (see "Two-Phase Completion Protocol" below). When a synchronous waiter exists, Cortex resolves that waiter directly and durably suppresses the parent notification. Otherwise, an eligible visible task writes one lightweight steer item to the parent's persistent Inbox with a stable task-derived delivery key, then requests SessionDrive to process it. The notification does NOT contain the final result — it tells the parent to retrieve it once with task_output(mode="full"). Hidden reviewer tasks normally suppress parent-facing task events and notifications.
When the parent explicitly reads a terminal task through task_output(mode="full") (or the default mode), afterPersist calls acknowledgeParentCompletion(), which disables future notification recovery and removes the still-pending Inbox item. Diagnostic modes (progress, tail, summary) do not acknowledge completion; the notification persists until the parent retrieves the full result.
Cortex observes the child session while it runs. Progress includes:
- the latest textual activity
- current and recent tool calls with their states
- elapsed time and update time
Recent tool history is bounded, and progress events are throttled to avoid turning streaming activity into excessive durable or frontend updates. Progress is observability, not a separate execution log; the child message history remains authoritative.
The launcher selects one of three output modes:
summary— the default; returns a compact trajectory summary for internal agents, or bounded assistant text for external agentsfinal_response— returns the final assistant response without trajectory summarizationstructured— requires a JSON Schema object and returns validated structured data
Structured output is implemented through an ephemeral result tool and JSON Schema validation. The caller can permit zero to three repair turns when a result does not validate. External agents do not support structured Cortex output.
Large external-agent outputs are bounded while preserving useful head and tail content. Output normalization is part of task completion and is stored with the child session.
Automatic completion is a two-phase protocol: notification and result retrieval.
Phase 1 — Notification: When a background task reaches a terminal state and no synchronous waiter exists, Cortex writes one lightweight steer Inbox item to the parent session. The notification identifies the task and its outcome but does not contain the final result. Its user-message origin captures the source Session, task identifier and task title. Historical task identity is projected only from the established Inbox delivery key. Consumers match both the task and parent Session before reading saved output from a reused child Session; viewing a notification does not acknowledge delivery. The message instructs the parent to retrieve the result once with task_output(task_id="...", mode="full"). The notification is persisted idempotently with a stable task-derived delivery key, so a crash or restart before the parent reads it causes the notification to be re-requested rather than lost.
Phase 2 — Result retrieval and acknowledgement: The parent reads the final result with task_output(mode="full") (or the default mode). This persisted read acknowledges the task completion: Cortex calls acknowledgeParentCompletion(), which sets notifyParentOnComplete to false on both the in-memory task entry and the durable child session, and removes the pending Inbox item. Future notification recovery is disabled for this task.
Diagnostic reads preserve notification: Modes progress, tail, and summary are one-shot snapshots for live status inspection. They do not call acknowledgeParentCompletion(), do not clear the Inbox item, and do not suppress future notifications. The notification persists until the parent explicitly retrieves the full result. This prevents a diagnostic poll from silently consuming the wake-up.
block=true is full-only: The block parameter is valid only with mode="full" or the default mode. Diagnostic modes with block=true are rejected at the Zod schema level.
Foreground path: A synchronous waiter (foreground task() or task_output with block=true) receives the result directly through Cortex.waitFor(). When a synchronous waiter exists, parent notification is durably suppressed (notifyParentOnComplete = false), making delivery and acknowledgement mutually exclusive per task.
Durable output after eviction: The in-memory task entry is eventually compacted and evicted, but the child session remains the durable record. After eviction, task_output(mode="full") can still retrieve terminal output from the child session's persisted Cortex metadata, including its output field and terminal status. This means the parent can retrieve a task's result even after the live task handle has been removed.
Parent delivery and child persistence are separate:
- the child session reaches a terminal state
- Cortex resolves and persists the configured output and notification intent
- a foreground waiter receives it directly, or an eligible parent notification is persisted idempotently in the parent Inbox
SessionDriveprocesses runnable parent Inbox work when the parent is idle- a linked DAG node is updated and may be auto-promoted
- temporary child-worktree resources are cleaned up when appropriate
A running parent is not interrupted with another model call in the middle of its turn. Its completion notice may already be durable in the Inbox; releasing the active session lease asks the shared drive to process that item before any workflow continuation proposal. Callers that need a direct result should await the task, while orchestration features can bind the task to a DAG node.
When the parent session is an external Channel endpoint, Cortex follows the task's parent-message lineage to the originating root user message and copies its channelReplyToMessageId onto the completion notice together with channelPush: true and channelReply: true. The outbound delivery system then replies to that specific Channel topic rather than consulting mutable session endpoint state. Cortex only opts into reply delivery for a non-app Channel with an accountId and a valid message-level anchor.
At Synergy startup, durable child sessions left in queued or running state are changed to interrupted and emit the same observer so plugin control planes can make an explicit recovery decision. Cortex then reconciles eligible terminal notifications from child-session state: a missing delivery is restored with the stable key, while an already persisted but unprocessed Inbox item only re-requests the parent drive. Hidden tasks and old or explicitly suppressed records whose notification intent is not true do not create or recover parent notifications; workflow-owned silent handoffs follow the separate recovery path below.
Silent Cortex completions do not create parent notifications, but workflow parents still require a durable handoff after the last blocking child becomes terminal. Cortex records a successful parent drive in the child's deliveryNotifiedAt field. Startup reconciliation retries unmarked silent handoffs only for the parent's current terminal assistant message and only while BlueprintLoop, Light Loop, or Lattice continuation remains active. The shared continuation gate and Inbox deduplication make this recovery idempotent while preventing old child tasks from waking a later workflow round.
Plugin Host delegation is always handle-based: start() returns immediately, while get() and the cortex.task.after observer expose completion.
Cancelling a task traverses its descendant task tree, aborts active work, releases concurrency slots, records terminal state, and cleans up owned worktree resources. Cancellation is fenced: follow-ups queued in the child inbox before the cancellation are discarded before the cancelled acknowledgement is returned, so a cancelled delegation cannot be restarted by mail the parent queued earlier; mail delivered after the acknowledgement is newer than the fence and queues as ordinary new work. If discarding the queued follow-ups fails, the cancellation does not acknowledge success. A task that exceeds its runtime limit claims the deadline before settling, so it surfaces as the runtime-limit error even when a concurrent turn is finishing. Cancellation does not erase the child session.
Visible terminal tasks keep their live task record long enough for clients to observe completion, then the in-memory entry is removed. The durable child session remains available through normal session navigation and inspection.
- Delegation creates a child session; it does not splice child messages into the parent history.
- Hidden Agents stay out of model prompts and native
tasktargets; host-owned invocation does not make them visible. - Plugin-owned private Agents are resolved and authorized by plugin ID plus generation before entering the ordinary Cortex path.
- Parent and child retain an explicit hierarchy through
parentIDand Cortex metadata. - An ordinary delegated subagent cannot recursively delegate or ask the user for permission.
- Tool expansion changes child-session exposure only; it never grants a deferred tool denied by agent or session permissions.
- Backgrounding changes who waits; it does not change the task's execution or persistence.
- Output mode is an explicit contract, not a best-effort prompt convention.
- Cancellation covers descendant tasks and runtime resources without deleting durable history.
- Cancelled and timed-out tasks fence the child session's queued work: the terminal acknowledgement means follow-ups queued before the fence are discarded, mail delivered after the acknowledgement starts a new, normally tracked execution, and a failed cleanup is reported instead of acknowledged.
- Parent completion notification and silent workflow handoff are durable and idempotent; notification acknowledgement and delivery are mutually exclusive per task.