ワンクリックで
st-arc-save
Bake a completed RP arc into a SillyTavern lorebook as persistent memory. Run after each arc concludes.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Bake a completed RP arc into a SillyTavern lorebook as persistent memory. Run after each arc concludes.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Search, download, and mine prompts from Civitai for Forge
Audit current SillyTavern config — explain settings, surface non-defaults, recommend changes for a goal. Read-only.
Build ST image gen prompt — booru tags from chat scene
Convert a SillyTavern character into a user persona — or create a new persona from scratch with --new. Migrates/builds visuals, lorebook link, avatar.
Onboard a SillyTavern character — set SD visual baseline + audit. Optional: redistribute card fields, generate expressions, build lorebook.
Drain the home-server local-Forge image-ticket queue with deep Forge expertise (NoobAI, ADetailer, ControlNet, img2img, per-seed loops). USE on 'fulfill the image queue', 'process gen tickets', 'gen-fulfill', 'drain the forge queue', 'resolve image requests', or after gen-art queued a Forge ticket. The ONLY place the hub's Forge expertise lives.
| name | st-arc-save |
| model | sonnet |
| description | Bake a completed RP arc into a SillyTavern lorebook as persistent memory. Run after each arc concludes. |
| argument-hint | [<arc-title>] [--char-bound] [--char <CharName>] [--no-brain] |
| allowed-tools | Bash, Read, Edit, Write, AskUserQuestion, mcp__st__st_get_settings, mcp__st__st_save_settings_path, mcp__st__st_get_worldinfo, mcp__st__st_save_worldinfo |
Hai's flow: RP an arc with {{char}} → arc concludes → use Memory extension to summarize → run this skill to bake the summary into searchable lore. Future chats with the same persona/char auto-load this context.
Two complementary entry types per persona:
| Entry type | Mode | Position | When |
|---|---|---|---|
| {Persona} — Established State | constant=true | After Char Defs (pos=1) | UPDATED each new arc — cumulative facts about persona's current state |
| {Persona} Arc N — {Title} | selective=true (~30 keys) | At Depth 4 (pos=4) | APPENDED per arc — full event narrative, triggers on backstory keywords |
Default target = persona-bound lorebook (worlds/{PersonaName}.json). Use --char-bound for arcs that are genuinely char-specific.
Universal {{char}} mechanics (parasite biology, etc.) belong in char's primary lorebook with {{user}} macros — NOT this skill's domain.
/st-arc-save # prompt for arc title interactively
/st-arc-save "Subway Encounter" # specify arc title
/st-arc-save "Arc 2 — Daughter Awakens" # quoted multi-word title
/st-arc-save "Arc 1" --char-bound # save to char primary book instead
/st-arc-save --char Parasite # explicit char (skip auto-detect)
/st-arc-save "Arc 2" --no-brain # skip brain_save followup
ST_DATA = /home/haint/Projects/home-server/sillytavern/data/default-user
ST_SCRIPTS = /home/haint/Projects/home-server/scripts
SETTINGS = $ST_DATA/settings.json
WORLDS = $ST_DATA/worlds
Extract from $ARGUMENTS:
arc_title = first quoted string OR first non-flag token (optional)char_bound = --char-bound flag presentexplicit_char = value of --char <CharName> if givenno_brain = --no-brain flag presentDetect active persona:
import json
with open("/home/haint/Projects/home-server/sillytavern/data/default-user/settings.json") as f:
s = json.load(f)
pu = s['power_user']
active_avatar = pu.get('user_avatar', '')
persona_name = pu.get('personas', {}).get(active_avatar, '')
print(f"Active persona: {persona_name!r} (avatar: {active_avatar!r})")
If persona_name is empty:
power_user.personas.values() + "no persona (skip persona binding)"--char-bound mode automaticallyDetect char (only matters for --char-bound):
explicit_char given → use itcharacters/*.png (excluding folders), AskUserQuestion to pickIf arc_title not provided:
Most reliable path: ask user to paste from ST's Memory panel (Current summary box).
Use AskUserQuestion with body:
Paste the 5-section summary from ST's Memory panel:
(Setting / Plot Events / Character State / World Facts / Open Threads)
Skill will parse into Established State (cumulative facts) + Arc N (event log).
Validation: if pasted text < 200 chars → warn "summary looks too short, did Memory extension generate properly? Continue anyway?"
Optional advanced path (skip if too brittle): try to read summary from chat file metadata. ST stores Memory output in chats/{CharName}/{chat}.jsonl either as a system message or in chat_metadata. Pattern varies by ST version. For v1 of this skill, just rely on user paste.
import json
if char_bound:
target_name = explicit_char or active_char # from Phase 0
else:
# persona-bound (default)
target_name = persona_name
# Read world_names directly (small subtree)
world_names = json.loads(mcp__st__st_get_settings(path="world_names")) or []
# (world_names lives at the top level of settings.json's *response wrapper*, not inside settings.
# st_get_settings hits the wrapper-aware /api/settings/get and exposes top-level keys too.)
target_exists = target_name in world_names
print(f"Target lorebook: {target_name} ({'existing' if target_exists else 'NEW'})")
If lorebook doesn't exist, init skeleton (will be saved via st_save_worldinfo later):
target_lb = {"entries": {}, "name": f"{target_name} Lore"}
Given the pasted summary text, produce 2 distinct outputs.
Distill the summary into present-tense facts about persona's CURRENT state. Cover:
Tone: factual, third-person, present-tense for ongoing state, past-tense for completed events.
Example output structure:
**{Persona}'s Current State (post-Arc {N}, established as ongoing context):**
- {Persona} ({age}, {key descriptors}) is {{char}}'s {relationship}. {1-line bond description}.
- {Family/social context line}
- {Home base / current location}
- {Persistent condition 1}
- {Cumulative effect from prior arcs}
If lorebook already has Established State entry: this CONTENT REPLACES the old (cumulative update, not append). The LLM should integrate prior facts + new arc events.
Convert the Plot Events + Character State + Open Threads sections into chronological numbered list with bold section headers.
Structure:
**{Persona}'s {Arc N Title} — Detailed Events (chronological):**
1. **{Event 1 heading}**: {1-3 sentences, who/where/what/outcome}.
2. **{Event 2 heading}**: {1-3 sentences}.
...
8. **{Final event}**: {how arc concluded; what state persona ended in}.
Extract distinctive content terms from summary:
Avoid:
ST hot-reloads on mcp__st__st_save_worldinfo and mcp__st__st_save_settings — no need to stop the container.
import json
# Load (or init) target lorebook via MCP
if target_exists:
wi_resp = mcp__st__st_get_worldinfo(name=target_name)
lb = json.loads(wi_resp) if isinstance(wi_resp, str) else wi_resp
else:
lb = {"entries": {}, "name": f"{target_name} Lore"}
entries = lb['entries']
# Auto-detect existing arc count
existing_arcs = [e for e in entries.values() if 'Arc ' in e.get('comment', '')]
next_arc_num = len(existing_arcs) + 1
arc_label = f"Arc {next_arc_num} — {arc_title}"
# Find existing Established State (update target)
established_uid = None
for uid, e in entries.items():
if 'Established State' in e.get('comment', '') and target_name.split()[0] in e.get('comment', ''):
established_uid = uid
break
# Determine fresh uid for new entries
existing_uids = [e['uid'] for e in entries.values()]
next_uid = max(existing_uids) + 1 if existing_uids else 0
# Standard schema function
def make_entry(uid, comment, content, keys, constant, position, depth):
return {
"uid": uid, "key": keys, "keysecondary": [],
"comment": comment, "content": content,
"constant": constant, "vectorized": False,
"selective": not constant, "selectiveLogic": 0,
"addMemo": False, "order": 100, "position": position,
"disable": False, "ignoreBudget": False,
"excludeRecursion": False, "preventRecursion": False,
"matchPersonaDescription": False, "matchCharacterDescription": False,
"matchCharacterPersonality": False, "matchCharacterDepthPrompt": False,
"matchScenario": False, "matchCreatorNotes": False,
"delayUntilRecursion": 0, "probability": 100, "useProbability": True,
"depth": depth, "outletName": "", "group": "",
"groupOverride": False, "groupWeight": 100, "scanDepth": None,
"caseSensitive": None, "matchWholeWords": None, "useGroupScoring": None,
"automationId": "", "role": 0, "sticky": None, "cooldown": None,
"delay": None, "triggers": []
}
# Established State — UPDATE existing or CREATE new
established_comment = f"{target_name} — Established State (cumulative, always-on)"
if established_uid is not None:
# Update content; keep uid + key=[] (constant doesn't use keys)
entries[established_uid]['content'] = established_state_content
entries[established_uid]['comment'] = established_comment
print(f"Updated Established State entry [uid={established_uid}]")
else:
new_uid = next_uid
next_uid += 1
entries[str(new_uid)] = make_entry(
uid=new_uid, comment=established_comment,
content=established_state_content, keys=[],
constant=True, position=1, depth=4
)
print(f"Created Established State entry [uid={new_uid}]")
# Arc N — APPEND new
arc_comment = f"{target_name} {arc_label} (event log, selective)"
arc_uid = next_uid
entries[str(arc_uid)] = make_entry(
uid=arc_uid, comment=arc_comment,
content=arc_event_log_content, keys=trigger_keywords,
constant=False, position=4, depth=4
)
print(f"Appended {arc_label} entry [uid={arc_uid}, {len(trigger_keywords)} keys]")
# Write back via MCP — ST persists + reloads automatically
mcp__st__st_save_worldinfo(name=target_name, data=lb)
active_avatar ends in .png — the st-mcp path parser would split on the dot and corrupt the tree on a naked dotted path. Use bracket-escape syntax ["..."] for the leaf key. (Background gotcha: see project CLAUDE.md "ST MCP server" entry, "Dotted-key gotcha + fix 2026-05-24".)
if not char_bound and not target_exists:
# Surgical writes — no full-tree round-trip needed.
avatar_key = f'["{active_avatar}"]' # bracket-escape leaf with '.png'
mcp__st__st_save_settings_path(
path=f"power_user.persona_descriptions.{avatar_key}.lorebook",
value=target_name,
)
mcp__st__st_save_settings_path(
path="power_user.persona_description_lorebook",
value=target_name,
)
print(f"Bound persona '{persona_name}' → lorebook '{target_name}'")
=== Arc Saved: {target_name} → Arc {N} — {arc_title} ===
✓ Established State entry: {UPDATED | CREATED} (constant=true, ~{N} words)
✓ Arc {N} entry: APPENDED (selective=true, {M} keys, ~{P} words)
✓ Lorebook: worlds/{target_name}.json ({total} entries total)
[✓ Persona binding: {persona} → {target_name}]
Next:
- ST hot-reloaded automatically — no restart needed
- Verify in World Info panel → 2 new/updated entries visible
- Start new chat with {{char}} → Established State always injects; Arc {N} triggers on backstory keywords
If --no-brain not set:
# Save to brain for cross-session reference
brain_save(
content=f"{target_name} Arc {N}: {arc_title} — {1-line summary}",
type="entity",
tags=["roleplay", "st-arc", target_name.lower()],
project="home-server",
metadata={"source": "st-arc-save", "lorebook": target_name, "arc_num": N}
)
This makes arc summaries searchable via brain_recall from any future Claude session.
| Case | Handling |
|---|---|
No active persona AND no --char-bound | AskUserQuestion → pick from list, OR auto-fallback to char-bound |
| Persona-bound lorebook doesn't exist | Create new lorebook + bind in settings.json |
| Established State exists but for different name | Search uses comment substring match; if mismatch, create new (don't conflate) |
| Arc number collision (Arc 3 exists, user titles new "Arc 3") | Auto-increment to next free number; warn user |
| Summary is empty/too short | AskUserQuestion: continue anyway / abort / paste again |
| User pastes summary in non-5-section format | Best-effort parse; warn that structure may be incomplete |
/st-setup <CharName> → run BEFORE first arc to establish char's primary lorebook + char_prompts/st-persona <CharName> → convert char to persona before saving arcs to persona-bound book/st-setup Parasite --all # baseline
/st-persona Mother # convert to Naoko persona
[RP arc 1 with Parasite, ~50-100 messages]
Memory panel → Summarize now → copy 5-section summary
/st-arc-save "Subway Encounter" # bake into Naoko.json
[start new chat → Established State auto-loads]