One agent. Many specialists. One conversation.
SHIFT reads from your OpenClaw workspace:
~/.openclaw/workspace/MEMORY.md — excerpts attached to delegation context~/.openclaw/workspace/.shift/config.yaml — your persona and cost settings~/.openclaw/workspace/.shift/sessions/SHIFT writes to:
~/.openclaw/workspace/.shift/sessions/<runId>/ — per-delegation session files~/.openclaw/workspace/.shift/cost-tracking.json — delegation cost tracking~/.openclaw/workspace/.shift/personas/*.yaml — your persona overridesWhen you enable delegation, the following are sent to whatever model providers you configure for each persona:
contextBridge.historyTurns)You control which models are used. SHIFT does not ship with or store credentials — it uses the model providers configured in your OpenClaw agent config. Review your provider's data handling policy.
If your workspace files or MEMORY.md contain secrets (API keys, credentials, private data), and those files are referenced in a delegated task, that content will be transmitted to your configured model provider.
Mitigations:
contextBridge.historyTurns to a low number (e.g., 3)costManagement.trackOnly: true during evaluation to observe behavior without enforcementscripts/setup.sh creates local directories only. It does NOT:
Consultations spawn additional model calls. Costs are tracked in cost-tracking.json per delegation. Set costManagement.trackOnly: true to monitor before enforcing limits.
costManagement.trackOnly: true to observe costscontextBridge.historyTurns to a small number (5 or less)contextBridge.archiveAfterMinutes: 30 for faster cleanupTriggers on: any message that might benefit from specialized handling — coding tasks, research, analysis, quick lookups, or anything that warrants delegating to a sub-identity.
When SHIFT triggers, you read and follows the procedures below to: classify the task, delegate to the right sub-identity, handle consultation between sub-identities, and synthesize the final response in his own voice.
Follow these steps for every message that triggers SHIFT.
Before anything, check if this message is trivially handled by you directly.
Bypass delegation (return none) if message is ONLY:
hi, hey, hello, yo, supthanks, thank you, ty, ok, sure, got it, makes sense, cool, nicethat was fast, nice one, lol, hahaIf none of those match, proceed to STEP 2.
Read the SHIFT config at:
text
~/.openclaw/workspace/.shift/config.yaml
If the file doesn't exist, run the setup script first:
bash
bash ~/.openclaw/workspace/skills/shift/scripts/setup.sh
From config, extract:
displayMode — hidden or transparentfastPath — conservative or offcostManagement.enabled, costManagement.costBudgetPerHour, costManagement.alertThresholdcontextBridge.historyTurns, contextBridge.sessionFolderpersonas — enabled personas and their settingsIf costManagement.enabled: true:
Read ~/.openclaw/workspace/.shift/cost-tracking.json:
json
{
"hourStart": "2026-03-18T15:00:00Z",
"totalSpend": 0.87,
"delegations": [...]
}
If current UTC hour > hourStart (new hour):
→ Reset totalSpend: 0, update hourStart to current hour.
If totalSpend >= costBudgetPerHour:
→ You handle the task directly. Say: "Handling this one myself to stay within your cost budget."
Return budget_exceeded.
If totalSpend >= alertThreshold * costBudgetPerHour:
→ Send a quiet warning: "Approaching delegation budget limit for this hour."
Using the enabled personas from config, classify the user's message.
For each enabled persona, count keyword matches in the user's message (case-insensitive, whole-word). Raw score = matches / total keywords.
If raw score >= minConfidence: Add to candidates.
Runner special case: Runner has requireExplicit: true. It only triggers if the message is predominantly a Runner task, not just containing a keyword. If the message has strong code/research keywords, Runner is NOT a match even if it has Runner keywords.
If multiple personas score above threshold: → Higher score wins. If tied: codex > researcher > runner.
If no persona scores above threshold → You handle directly. Return none.
Generate a unique runId:
text
run-{YYYYMMDD}-{HHMM}-{persona}-{sequence}
Example: run-20260318-1551-codex-001
Create the session folder:
text
~/.openclaw/workspace/.shift/sessions/{runId}/
json
{
"runId": "<runId>",
"timestamp": "<ISO UTC>",
"persona": "<matched persona>",
"userMessage": "<exact user message>",
"masterConversationHistory": <last N turns from conversation>,
"activeFiles": <files mentioned>,
"masterSummary": "<brief summary of conversation so far>"
}
Pull from:
~/.openclaw/workspace/MEMORY.md (keyword-matched relevant sections)Format:
markdown
# Context for {persona}
## Project/Memory (relevant)
...
## Active Files
...
## Persona Context Needs
...
Construct the prompt for the sub-agent:
markdown
# You are {persona_name}
## Persona
{persona voice, tone, strengths, blind spots from config}
## Your Task
{user's exact message}
## Context
Before starting, read:
- /path/to/INBOUND.json — your input and conversation history
- /path/to/CONTEXT.md — relevant project context
## Your Protocol
1. Read INBOUND.json and CONTEXT.md
2. Execute your task
3. If you need to consult another sub-identity and your persona allows it:
- Write your question to the session folder as CONSULT-INBOUND.md
- Set a timeout: min(remaining_time * 0.5, your consultationTimeout)
- Spawn the target sub-identity using sessions_spawn
- Wait for them to write CONSULT-OUTBOUND.md
- Read their response and continue with that context
4. Write your final result to /path/to/OUTBOUND.md
5. Include a ConsultationLog if you consulted anyone
6. End your response with [DONE]
javascript
sessions_spawn({
model: personaConfig.model,
task: taskPrompt,
label: `shift-${persona}-${runId}`,
timeoutSeconds: personaConfig.timeout,
attachments: [
{ name: "INBOUND.json", content: <inboundJsonString> },
{ name: "CONTEXT.md", content: <contextMdString> }
]
})
Track elapsed time. If elapsed > timeout * 0.7:
→ Send status to user: "{persona} is still working on this..."
Wait for sessions_spawn to complete.
From the session folder:
text
~/.openclaw/workspace/.shift/sessions/{runId}/OUTBOUND.md
Parse:
Status: complete | error | escalationResult: the actual response contentConsultationLog: if sub-identity consulted anotherCost: token counts if availableIf Status: escalation:
→ Runner detected the task was too complex. You take over directly.
→ Read reason, summary, partialAnswer from ESCALATE.md.
→ Incorporate any partial work Runner did.
After successful delegation, update cost-tracking.json:
json
{
"hourStart": "<current hour UTC>",
"totalSpend": <previous + estimated cost>,
"delegations": [
{
"runId": "<runId>",
"persona": "<persona>",
"inputTokens": <from sub-agent result if available>,
"outputTokens": <from sub-agent result if available>,
"estimatedCost": <calculate from model cost config>,
"timestamp": "<now>"
}
]
}
Transform the sub-identity's output into your voice.
If ConsultationLog is present and non-empty:
→ Build consultation mention (ALWAYS shown):
text
{parent persona} consulted {target persona} on {topic}...
Sub-identity outputyour transformationCodeExplain what it does in plain English, highlight key parts, offer to add testsAnalysis/ResearchPull 2-3 key insights, bullet-point them, note caveatsQuick answer (Runner)Relay directly with warmth added
Hidden (default):
text
[Assistant]: Here's the {implementation/analysis/answer}...
Transparent:
text
[Assistant] → [{persona}] working on this...
[Assistant] ← [{persona}] done.
[Assistant]: Here's the...
Consultation mentions are ALWAYS shown in both modes.
ScenarioResponseSub-agent timeout_"Looks like this one is taking longer than expected — let me work through it directly."Sub-agent error"Hit a snag with the specialist — let me handle this myself."_sessions_spawn failsFall back to your handling directlyOUTBOUND.md missingAssume sub-agent failed, You handle directlyModel unavailableLog warning, You handle directly
Key insight: Sub-agent sessions cannot cleanly spawn child sessions and wait. The solution: You orchestrate the consultation from the master level.
text
Codex needs context
↓
Codex writes CONSULT-INBOUND.md + OUTBOUND.md (Status: needs_consultation)
↓
You detect needs_consultation flag in OUTBOUND.md
↓
You tell user (transparent mode): "Codex ↔ [Codex → Researcher]..."
↓
You spawn Researcher with CONSULT-INBOUND.md
↓
Researcher writes CONSULT-OUTBOUND.md
↓
You read it, appends to Codex's CONTEXT.md
↓
You re-spawn Codex with enriched context (brief task: "continue with this context")
↓
Codex writes final OUTBOUND.md with ConsultationLog
↓
You synthesize — mentions the consultation (always visible)
When a sub-agent needs to consult another sub-identity:
Check: is target in my consults list? Is target NOT in my consultsNever?
Write CONSULT-INBOUND.md to the session folder with the question + context
Write OUTBOUND.md with:
markdown
## Status: needs_consultation
## PartialResult
(what Codex has done/understood so far)
## ConsultationLog
4. End response with `[NEEDS_CONSULTATION]` instead of `[DONE]`
5. Stop — You will take over from here
## Master Side
When reading OUTBOUND.md after a sub-agent returns:
### STEP 8a — Detect Consultation Need
If `Status: needs_consultation` is present:
1. Read `ConsultationLog` to get target and question
2. Read `CONSULT-INBOUND.md` for full context
3. Send user update (transparent mode): `[Assistant] ↔ [{persona} → {target}] consulting on {topic}...`
4. Proceed to STEP 8b
### STEP 8b — Run Consultation
javascript
```javascript
// Spawn the target sub-identity as a one-shot consultation
sessions_spawn({
model: targetPersonaConfig.model,
task: `You are ${target}.
Read the consultation question from: <path to CONSULT-INBOUND.md>
Write your answer to: <path to CONSULT-OUTBOUND.md>
End with [DONE].`,
label: `shift-consult-${runId}`,
timeoutSeconds: targetPersonaConfig.consultationTimeout
})
After consultation completes:
Read CONSULT-OUTBOUND.md
Append consultation answer to CONTEXT.md:
markdown
## Consultation Answer from {target}
{answer from CONSULT-OUTBOUND.md}
Re-spawn the original sub-agent with enriched context: javascript
sessions_spawn({
model: originalPersonaConfig.model,
task: `${originalPersonaPrompt}
The ${target} provided the following guidance:
Continue your task using this context. Write your final result to OUTBOUND.md. End with [DONE]. , label: shift-${originalPersona}-consult-continue-${runId}`,
timeoutSeconds: originalPersonaConfig.timeout / 2 // Half remaining time
})
text
### STEP 8d — Final Result
When re-spawned sub-agent completes:
1. Read final `OUTBOUND.md`
2. `ConsultationLog.status` should now be `complete`
3. Proceed to STEP 10 (Synthesis)
Consultation is **always mentioned** to the user — not gated by display mode.
## Depth Limit
**Max consultation depth = 1.** A sub-agent that was consulted CANNOT consult anyone else. This is enforced by: the consultation re-spawn prompt does NOT include the consultation protocol section. Only the first-level sub-agent has it.
## Timeout Handling
- If consultation times out: You fall back to Codex's partial result + own knowledge
- You warn: *"{target} took too long — Codex will continue without that context."*
- Partial result (if any) is still used
---
# Commands
## /shift status
Show current SHIFT status:
- Which personas are enabled
- Current cost budget status
- Display mode
## /shift mode hidden|transparent
Toggle display mode. Updates config.yaml.
## /shift fastpath on|off
Toggle fast-path mode.
## /delegate \<persona\> \<task\>
Explicitly delegate a task to a specific persona, ignoring keyword routing.
---
# Config Reference
Full schema at: `~/.openclaw/workspace/skills/shift/config/SCHEMA.yaml`
Key settings:
```yaml
displayMode: hidden # hidden | transparent
fastPath: conservative # conservative | off
costManagement:
enabled: true
costBudgetPerHour: 2.00 # USD
alertThreshold: 0.75 # warn at 75%
personas:
codex:
model: <your coding model>
timeout: 60
consults: [researcher]
consultationTimeout: 45
researcher:
model: <your research model>
timeout: 90
consults: []
consultationTimeout: 45
runner:
model: <your fast model>
timeout: 30
consults: []
consultationTimeout: 20
All delegation session files live at:
text
~/.openclaw/workspace/.shift/sessions/{runId}/
Files:
INBOUND.json — delegation inputCONTEXT.md — project contextOUTBOUND.md — sub-identity resultESCALATE.md — Runner escalation (if any)CONSULT-INBOUND.md — consultation inputCONSULT-OUTBOUND.md — consultation resultAuto-archived after contextBridge.archiveAfterMinutes (default: 60min).
SHIFT — one brain, many specialists.