AgentStack
SKILL verified MIT Self-run

Thread

skill-fangxm233-cortex-agent-thread · by fangxm233

Use when working with the Cortex thread system — understanding thread architecture, writing or modifying thread config (the per-entity JSON files under config/thread-templates/ — agents, templates, shells; plus transitions and hooks fields within templates), debugging thread execution, or when the user asks about multi-agent pipelines, agent orchestration, or the !thread command. Also trigger whe…

No reviews yet
0 installs
4 views
0.0% view→install

Install

$ agentstack add skill-fangxm233-cortex-agent-thread

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Thread? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Thread System

You are Cortex, working with the Thread multi-agent orchestration system.

Arguments

$ARGUMENTS


What Is a Thread?

A Thread is a multi-agent orchestration work unit — a managed pipeline that coordinates one or more Claude Code agent instances. Each thread has:

  • A unique ThreadId (thr_XXXXX)
  • A file-system workspace at {REPO_ROOT}/tmp/threads/thr_XXXX/
  • A shared artifact.md file through which agents communicate
  • A lifecycle with states: runningcompleted | failed | cancelled | aborted
  • Cost tracking and step history

Threads can be template-based (following a predefined multi-agent pipeline) or ad-hoc (dynamically assembled by adding agents one at a time).

Architecture Layer Diagram

Slack message / Scheduler / Task Dispatch
         │
   message-router.ts / scheduled-runner.ts
         │
   thread-manager.ts  (orchestration: create, step, transition, prompt assembly)
         │
   thread-runner.ts   (execution loop: run agents, hooks, Slack output)
         │
   runAgent() → claude-bridge.ts → Claude Code CLI process
         │
   VirtualSlackMessage → Slack

Thread Lifecycle States

| State | Meaning | Terminal? | |-------|---------|-----------| | running | An agent step is in progress | No | | waiting | Paused, waiting for user input | No | | completed | All steps done | Yes | | failed | Unrecoverable error | Yes | | cancelled | User cancelled via !thread cancel | Yes | | aborted | Agent self-aborted via the thread_abort tool | Yes |


Control Plane (abort / split / wait) — DR-0015

Thread control is out-of-band: an agent signals its own thread by calling explicit MCP tools, NOT by writing markers into the artifact. The artifact is plain prose — text that merely mentions [ABORT] / [SPLIT] / [WAIT_CHILDREN] (e.g. "No [ABORT]", or a plan that says "if X then abort") triggers nothing. This eliminated the false-positive class behind the 2026-06-13 double-abort incident (DR-0015 problem 1).

The three control tools (self-control of the caller's own thread)

All three target CORTEX_THREAD_ID (the thread you are running inside) — you never pass a thread id. Calling outside a thread is an error. After calling, end your step; the runner consumes the signal at the next step boundary.

  • thread_abort({ kind, diagnosis }) — terminate the thread early (terminal state aborted, distinct from failed). kind ∈ {too-big, mis-scoped, blocked-external}; diagnosis is required and becomes thread.abortReason (and, for a dispatch task, the task block reason). too-big / mis-scoped escalate to a re-planning manager; blocked-external escalates to a human.
  • thread_split({ subtasks }) — propose a decomposition of a dispatch task instead of doing it: the task is decomposed keep-parent (your task becomes the join node), unclaimed, and the children flow through the dispatch queue. subtasks is a typed array (decomposeTask shape).
  • thread_wait({ on_tasks?, on_threads? }) — suspend the thread until its awaited children finish; you are re-entered with their results once ALL are terminal.

How Agents Know About the Control Plane

The protocol is auto-injected by the thread system — you do not need to add anything to an agent's directive. buildStepPrompt prepends a short THREAD_PROTOCOL_PREAMBLE block (defined in agent-server/src/domain/threads/prompt-builder.ts) to every step prompt of any thread that owns a workspace artifact. The preamble is skipped for auto-record / default / direct paths (no artifact) and on --resume of a persistent session (already delivered). Adding a new artifact-writing agent (a new file under config/thread-templates/agents/) automatically gets the control tools without editing its directive / promptTemplate / systemPrompt.

When to Abort

Call thread_abort when:

  • Upstream is missing or malformed — required inputs are absent or corrupt and cannot be produced by retrying.
  • Hardware / external resource unavailable (kind: blocked-external) — target machine offline, GPU exhausted, external API permanently unreachable.
  • Specification unresolvable (kind: mis-scoped) — the requirements contain a contradiction that cannot be decided autonomously.
  • Task is far bigger than described (kind: too-big) — multiple independent units crammed together (prefer thread_split if you can already name the children).

Do not abort for: normal retry situations (use the [REVISED] convergence convention and let the reviewer loop handle it), minor warnings, or disagreements with the plan that can be recorded as feedback in the artifact.

Execution Semantics

When the runner reads metadata.pendingControl after a step completes:

  1. abort → thread status aborted, abortReason recorded; for a dispatch thread the owning task is blocked BEFORE the onEnd hook runs (DR-0015 problem 2). No further steps. onEnd still fires; finalizeThread flushes the artifact.
  2. split → the runner breaks the loop leaving the signal in place; the dispatch path consumes pendingControl.subtasks and decomposes keep-parent.
  3. wait → if live children remain the thread enters waiting; otherwise it continues.

The signal is cleared after consumption so an intent fires exactly once; the webhook rejects a second concurrent control on the same thread.

Comparison with Other Terminators

| Mechanism | Who triggers it | Effect | |-----------|-----------------|--------| | [APPROVED] / [IMPL-APPROVED] | Reviewer agent (artifact text) | Convergence marker — reviewer approves, transition to next agent | | [REVISED] | Doer agent (artifact text, after retry) | Signals retry complete; output_not_contains transition terminates loop | | thread_abort tool | Any agent | Global abort — thread enters aborted terminal state, overrides all transitions | | !thread cancel | User (Slack command) | Thread enters cancelled terminal state |

([APPROVED] / [REVISED] remain in-band transition markers matched against agent output — they are evaluated by template transitions, a separate mechanism from the control plane.)


Configuration File: thread-templates/

Location: ~/.cortex/config/thread-templates/ (a directory, one JSON file per entity).

Config is directory-based (DR-0017 D6 Phase 2.5): entities live one-file-each under three subdirectories, and the filename without .json is the entity name:

~/.cortex/config/thread-templates/
├── agents/.json      # one agent definition per file
├── templates/.json   # one pipeline template (or shell binding) per file
└── shells/.json      # one shell definition (parameterized transition graph) per file
  • Loading priority: the directory is used if it exists; otherwise the loader falls back to the legacy single file config/thread-templates.json. Shell bindings only resolve in the directory form (shells live under shells/).
  • One-time migration: on startup a legacy single file (with no directory yet) is split into per-entity files and the original is renamed thread-templates.json.migrated-bak (preserved, never deleted; idempotent).
  • Defaults merge: shipped defaults are a directory too, merged per-file copy-if-missing (never overwrites your files) so new default agents/templates/shells reach existing installs.
  • Hot-reload: each entity subdirectory (agents/, templates/, shells/) and the prompts/ directory are watched via fs.watch (300ms debounce); reload is fail-soft (a malformed file is skipped, not the whole table).

The two core entity kinds are agents (independent agent definitions) and templates (multi-agent pipelines, either full templates or shell bindings); shells are parameterized transition graphs the templates reuse.

Agents Section

Each agent is an independent definition with its own identity, profile, and prompt configuration. Agents are referenced by templates but can also be used standalone via !thread .

{
  "agents": {
    "my-agent": {
      "name": "my-agent",
      "description": "Human-readable description",
      "profile": "execute",
      "persistSession": false,
      "directive": "You are a specialized agent for...",
      "promptTemplate": "{{input}}\n\nWrite your output to {{artifactPath}}.",
      "tools": "Agent,Bash,Edit,Glob,Grep,Read,Write"
    }
  }
}
Agent Fields Reference

| Field | Required | Type | Description | |-------|----------|------|-------------| | name | Yes | string | Agent ID, must match the key in the agents map | | description | No | string | Human-readable label shown in !thread agents | | profile | Yes | string | Profile from profiles.json, or "__active__" for current runtime profile | | persistSession | Yes | boolean | true: reuse Claude Code session across loop iterations. false: fresh session each time | | directive | No | string | Role/identity text prepended to the prompt. Supports file:filename.md to load from prompts/directives/ | | systemPrompt | No | string | Full system prompt override via --system-prompt flag. Supports file:filename.md to load from prompts/systemPrompts/ | | promptTemplate | Yes | string | Handlebars-style template with variables. Supports file:filename.md to load from prompts/promptTemplates/ | | claudeAgent | No | string | Claude Code agent name, loaded via --agent flag from .claude/agents/ | | outputStyle | No | string | Injected via Claude CLI --settings '{"outputStyle":""}' | | tools | No | string | Comma-separated tool list for --tools flag. Overrides the default tool set | | pluginDirs | No | string[] | Plugin directories passed via --plugin-dir flags |

persistSession Explained
  • true — The Claude Code process stays alive between loop iterations. The agent retains full conversation history. Use for agents that iterate on feedback (planner revising after review, writer revising after critique).
  • false — A fresh Claude Code session is spawned each time the agent runs. Use for stateless evaluators (reviewer, QA) where independence from prior context is important.
file: Reference Mechanism

Directive, systemPrompt, and promptTemplate values starting with file: are loaded from corresponding subdirectories under prompts/:

prompts/
├── directives/       ← for directive: "file:director.md"
├── systemPrompts/    ← for systemPrompt: "file:web.md"
└── promptTemplates/  ← for promptTemplate: "file:my-template.md"

Loaded files are processed through template-resolver.ts, which supports:

  • YAML frontmatter with extends: (template inheritance) and variable declarations
  • @fill(name) / @endfill blocks to fill template slots
  • @block(name) / @endblock for default content slots in base templates
  • ${var} / ${var:-default} variable interpolation
  • @if(var) / @if(!var) / @endif conditional sections
  • {{runtime_vars}} (e.g., {{currentDateTime}}) pass through untouched for runtime resolution

Templates Section

Templates define multi-agent pipelines by composing agents with transition rules.

{
  "templates": {
    "my-pipeline": {
      "name": "my-pipeline",
      "description": "Description of what this pipeline does",
      "agents": ["agent-a", {"ref": "agent-b", "promptTemplate": "override..."}],
      "transitions": [
        {"from": "agent-a", "to": "agent-b", "condition": {"type": "always"}}
      ],
      "entryAgent": "agent-a",
      "maxTotalSteps": 6,
      "maxTotalCostUsd": 5.0,
      "hooks": {
        "onEnd": {
          "command": "node ~/.cortex/hooks/post-task-hook.mjs",
          "args": ["agent-a"],
          "timeout": 10000
        }
      }
    }
  }
}
Template Fields Reference

| Field | Required | Type | Description | |-------|----------|------|-------------| | name | Yes | string | Template ID, must match the key | | description | Yes | string | Human-readable description | | agents | Yes | TemplateAgentRef[] | Ordered list of agent references (string name or override object) | | transitions | Yes | TransitionRule[] | Rules governing agent-to-agent transitions | | entryAgent | Yes | string | Which agent starts the pipeline | | maxTotalSteps | Yes | number | Hard cap on total agent steps | | maxTotalCostUsd | No | number | Cost limit in USD. Pipeline stops when exceeded | | hooks | No | ThreadHooks | Lifecycle hooks (onStart, onTransition, onEnd) |

Agent References in Templates

Templates reference agents in two ways:

Simple reference (string): Uses the agent definition as-is.

"agents": ["planner", "reviewer"]

Override reference (object): Overrides specific fields for this template only.

"agents": [
  {
    "ref": "planner",
    "promptTemplate": "Custom template for this pipeline...",
    "persistSession": true
  },
  "reviewer"
]

Override objects support: ref (required), promptTemplate, directive, systemPrompt, persistSession, claudeAgent, outputStyle, tools, pluginDirs.

Shell Templates (shells/ + shell bindings)

When several pipelines share the same transition graph and differ only in which agents fill the roles, define the graph once as a shell (pure JSON in shells/.json) and reference it from templates/ with a small shell binding. The shell declares params and uses placeholders in its graph:

  • {param} → the binding's value for that parameter (an agent name).
  • {param.entryStage} → that agent's entryStage, resolved from the agents map.

Shell definition (shells/worker-review.json — the shipped generic produce-then-audit loop):

{
  "params": ["worker", "reviewer"],
  "agents": ["{worker}", "{reviewer}"],
  "transitions": [
    { "from": "{worker}:{worker.entryStage}", "to": "{reviewer}", "condition": { "type": "always" } },
    { "from": "{reviewer}", "to": "{worker}:retry", "condition": { "type": "convergence", "marker": "[APPROVED]", "maxIterations": 1 } },
    { "from": "{worker}:retry", "to": "{reviewer}", "condition": { "type": "output_not_contains", "pattern": "\\[REVISED\\]" } }
  ],
  "entryAgent": "{worker}",
  "entryStage": "{worker.entryStage}",
  "maxTotalSteps": 4,
  "hooks": { "onEnd": { "command": "node ~/.cortex/hooks/post-task-hook.mjs", "args": ["{worker}"], "timeout": 10000 } }
}

Shell binding (templates/doc-review.json) — a template that just names the shell and its params:

{
  "shell": "worker-review",
  "worker": "doc-writer",
  "reviewer": "doc-reviewer",
  "description": "Generic produce-then-audit for documents"
}

At load, the loader interpolates the placeholders and validates the result into a full template (here: doc-writer at its entry stage → doc-reviewerdoc-writer:retry until [APPROVED]). A validation failure (missing param, unknown placeholder, agent not found, agent missing entryStage, or a transition endpoint naming a stage the agent lacks) fails that one template with a logged error; an unknown shell name is fail-soft skipped — the rest of the config still loads. A binding may also set maxTotalSteps to override the shell's default budget.


Prompt Template Variables

The promptTemplate field supports these variables, resolved at step execution time:

| Variable | Description | |----------|-------------| | {{input}} | The original user message that started the thread | | {{artifactPath}} | Absolute path to the workspace's artifact.md file | | {{previousOutput}} | The full output from the last completed agent step | | {{modifiedFiles}} | Bullet list of files edited/written by the previous agent (from session-activity logs) | | {{modifiedFilesWithDiff}} | Same files plus a fenced ``diff` block per file showing the previous agent's net change. Covers local Edit/Write **and** remote mcp__cortex__remote_edit/write`. Reconstructed without trusti

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.