---
name: stashlog-quests
description: Turns a learning goal, trip, or scenario into a complete StashLog learning package (Words, Lines, a Story, a Shunkan course, and/or a Talk role-play scene) built through StashLog's MCP tools and bundled into one Quest the user can revisit at their /quests page. Use this whenever StashLog's MCP tools (add_word, add_line, create_story, add_course_units, create_talk_scenario, add_to_quest, list_quests, etc.) are connected and the user describes something they want to learn, prepare for, or practice in English or Taiwan Mandarin — e.g. "I have a business trip to Taipei next week and need airport phrases," "出張前に空港の入国審査で使う表現をまとめて", "help me get ready for a job interview in Mandarin", "この会話で出た表現をまとめて保存して". Also use it when the user asks to save several related items at once — even if they only name one content type, like "add ten words for ordering coffee" or "write me some interview lines" — asks what's in their Quests, or wants existing StashLog content organized into one. Don't wait for the word "quest" — most users never say it, and naming one format doesn't mean they've ruled out the rest of the package.
---

# StashLog Quests

A Quest is StashLog's name for a themed bundle of learning content — Words,
Lines, a Story, a Shunkan course, and/or a Talk scene — that the user can
revisit as one unit at `/quests`. You (the connected agent) are the only way
Quests get created: StashLog has no in-app authoring UI for any of this.
That also means you're the only quality control between "what the user
asked for" and what StashLog ends up storing — none of the content tools
rewrite or improve what you send them, except where noted below.

## Notice the moment

Reach for this whenever the user describes something to learn, prepare for,
or practice — a trip, a meeting, a hobby, a scene from a show, a topic
they're stuck on — rather than a single word or sentence to file away. They
will rarely say "quest." "I've got a work trip to Taipei and my Mandarin is
rusty," "help me get ready to order food without pointing at the menu,"
"make something out of the phrases in this chat" are all this moment. A
single ad-hoc save ("remember this word") isn't — just save it, no quest
needed.

A **list** of words or phrases handed to you at once ("save these: gate,
boarding pass, customs declaration...") is also this moment — even though
each item alone would be an ad-hoc save. So is a request that names only
**one** content type but is clearly in service of something bigger — a
goal, an event, a topic — even though the user only asked for that one
type ("add ten words for ordering coffee," "write me some interview
lines," "make a Story about my trip to Taipei"). Naming the type isn't the
same as ruling out the rest of the package; it's just the part the user
thought to ask for. Don't silently register what was asked and stop
there. Confirm once, briefly, and lead with a shape suited to the purpose
rather than asking blind:

> User: "Add ten words for ordering coffee."
>
> You: "Got it — adding those ten words. Since it's for ordering coffee, I
> could also put together a couple of Lines you'd actually say at the
> counter, or a short Talk scene to rehearse the exchange out loud. Want
> either of those, or just the words?"

Only stop at the named type if they say so explicitly — either up front
("just the words, nothing else") or in answer to that question. A
genuinely single, contextless item ("remember this word: 曖昧") stays
ad-hoc — there's no purpose to design around, so no quest is needed.

## Propose before you build

Building costs the user real quota (each save is metered) and their
attention, so don't generate a full package speculatively. You already
know more than the user does about what each shape is good for, so lead
with a recommendation instead of a neutral menu — weigh their stated goal,
level, and time available, name the option that fits best, and offer the
rest as adjustments to pick from:

> User: "I have a job interview in Mandarin next week and I'm nervous."
>
> You: "With a week to go, I'd build a Shunkan course so you can practice
> forming answers on the fly, plus a handful of Q&A lines as a script to
> fall back on. If you also want to rehearse out loud, I can add a Talk
> role-play on top — or if time's tighter than a week, I can just do the
> Talk scenario alone. Which sounds right?"

Time-pressured → a Talk scenario alone. Wants fundamentals → Words plus a
Shunkan course. Wants context and motivation → add a Story. Designing this
composition — not just asking "which formats?" — is the actual job here;
a Quest is only as good as the judgment behind what went into it. This is
deliberately not a fixed template — the whole point of a Quest is that it
fits the request, not the other way around. If you don't yet know their
goal, level, or timeline well enough to recommend anything, ask — a short
question about purpose is worth more than a guess.

Size it to what they'll actually study, too. The daily quota is modest — a
50-word list often won't fit in one day — so offer a focused core set first
and grow the quest later rather than front-loading everything.

Put the cost in the proposal, so the user agrees to a number rather than a
vague "more stuff." Roughly: a word or a line is 1 point each, a Story 1
point, a Shunkan drill 0.1 point (10 units × 3 drills = 3 points), and a
Talk scene and the quest itself are free. Check `find_words` first and quote
only what's actually new — "40 of your 47 words are new (40 points); 10
Lines using them +10; a 10-unit Shunkan course +3; one Story +1." The server
instructions carry the current prices; prefer those if they differ.

If the user already said "just the words" — here or earlier in the
conversation — skip the confirmation above. Don't ask twice for the same
package.

## Look before you write

Nearly every tool in this surface exists in a create/list or create/find
pair for the same reason: an agent that can't see what the user already has
re-creates it, and StashLog fills up with near-duplicates. Call the read
side first, every time:

| Creating | Check first |
| --- | --- |
| a word | `find_words` for a list, `find_word` for one (exact headword) — or `search_words` (keyword — also surfaces existing saves worth reusing) |
| a line/phrase | `find_line_by_text` (exact) or `search_lines` (keyword) |
| a Shunkan course | `list_courses` (append with `course_id`, or confirm none fits) |
| a Talk scenario | `list_talk_scenarios` |
| a quest (bundling) | `list_quests` (append with `quest_id`, or confirm none fits) |
| a Story | `list_stories`, then `get_story` for the saved paragraphs |

For a word list, one `find_words` call answers every item — don't loop
`find_word`. It matches the way `add_word` saves: case, spacing and
trailing punctuation don't matter, and an inflected form is checked under
its dictionary headword (`sobering` → `sober`, reported as `saved_as`).
Clean up notation that isn't part of the word before checking or saving —
`stand by (～)` → `stand by`, `keep ~ at bay` → `keep someone at bay` — and
tell the user what you changed.

The find and search tools return each match's id together with its
content (headword and meaning, or text and translation). When something
the package needs is already saved, don't save it again — reuse that id
when you bundle the quest. And read what's there before writing a new
drill or Story, so you're not asking the user to repeat themselves.

## What StashLog writes for you, and what you must write yourself

| Tool | You supply | StashLog generates |
| --- | --- | --- |
| `add_word` | the word/phrase only (+ optional `meaning_hint` to disambiguate a sense) | meaning, example, pronunciation |
| `add_line` / `add_lines_batch` | the sentence/phrase itself | translation, explanation, pronunciation |
| `create_story` | every paragraph, its Japanese translation, and each learnable expression with meaning | image links only (never supply an image URL) |
| `queue_story` | the same authored Story payload for a longer Story | a durable `job_id`; check `get_story_status` until saved |
| `add_course_units` | every drill sentence and its Japanese, per unit | nothing — write it in full |
| `create_talk_scenario` | setting, both roles, objective, the AI's opening line, in character | nothing — write it in full |

The ones you fully author — `create_story`, `add_course_units`,
`create_talk_scenario` — are where quality actually depends on you: level-
appropriate phrasing, natural rather than textbook-stiff sentences,
Japanese that matches the register. `add_word` / `add_line` are
lighter-touch by design: send the raw item and let StashLog enrich it.
Writing your own definition or translation for those just gets
overwritten, so don't bother.

For a word list, use `add_words_batch` in groups of at most five after
`find_words`. Inspect each returned item: successful entries have ids and
failed items can be retried with the identical batch key and payload.
Before a large course write, call `validate_course_units` with the same
content. It checks every drill, estimates points, shows the current balance,
and reports targets already covered by the chosen course. Resolve warnings
before calling `add_course_units`.
For a Story with several paragraphs, prefer `queue_story`; it returns promptly
with a job id while StashLog links images and saves the Story. Use
`get_story_status` with the same key to obtain the final Story id before
adding it to a Quest.

Keep a Story's tone coherent. Informal or insulting terms such as *bitchy*,
*dumbass*, and *bum* can be useful learning targets without appearing in a
neutral B2 Story. Put them in a short drill or Line when the Story has no
natural speaker and context for that register; include them in a Story only
when the user's purpose calls for it and the translation carries the tone.

`create_talk_scenario` takes `target_expressions` — pass the words/lines
you just saved for the same theme, and the in-conversation AI will work
them into the role-play as hints. Use this to tie a scenario back to the
vocabulary you built alongside it.

## Bundle it into one quest

The composition you settled on in "Propose before you build" — whichever
mix of Words, Lines, a Story, a course, and/or a Talk scene it turned out
to be — is what becomes the quest. Group everything that came out of that
one conversation together, under one title; don't split it into a
separate quest per content type just because each type is a different
tool call.

Once the content exists, group it: call `list_quests`, append to a
matching one with `quest_id`, or create with a short, thematic `title`
(e.g. "Airport immigration," not "Taipei trip stuff — Tuesday").
`add_to_quest` only references ids the other tools already returned — the
save calls for what you just created, find/search for what already existed
— so keep those ids as you go. It never creates or edits content, so this
is always the last step, never the first. It also costs no quota.

It caps at 50 members per call. A package that size is unusual, but if you
hit it, keep calling `add_to_quest` with the same `quest_id` for the rest
(each call its own idempotency key) rather than splitting into a second
quest.

Finish by telling the user the quest's title and `quest_id` and what went
into it, per type — including anything already saved that you reused, and
anything `add_word` saved under a different headword than they wrote. If
they keep their own notes or source files for this material, that id is what
lets them find the quest again, so give it to them rather than just a link.

## When a call fails

Every write takes an `idempotency_key`: one key per item you intend to
save, reused for every retry of that item. A retry with the same key never
saves or charges twice, so it is always the safe move. Any string works, so
don't mint and track random UUIDs — derive the key from the package and the
item, like `2026-09-25 airport/word/boarding pass` or
`2026-09-25 airport/story`. A retry then reuses the right key without any
bookkeeping, and two items can't end up sharing one. Keep the date or task
name in it: a key reused for a later save replays the earlier result.

- **No response / timeout.** Re-send the same call with the same key. You
  get the saved result if it went through, or `IDEMPOTENCY_IN_PROGRESS` if
  it is still running. For `create_story`, call `get_story_status` with that
  key first; `committed` includes the Story summary. A word StashLog has to
  generate can take a minute or two. Do not look it up with `find_word`
  to decide whether the original write finished.
- **`retryable: true`** (still in progress, rate-limited, a write that
  failed). Wait `retry_after_seconds` when the error gives one, then re-send
  the identical call with the same key. A failed write was refunded, so the
  retry really runs it again.
- **Validation error** (`INVALID_INPUT`, `INPUT_LIMIT_EXCEEDED`).
  `details.path` names the offending field — fix that one thing and re-send
  the **whole call with the same key**; these tools validate
  all-or-nothing, so a partial fix still needs the full payload.
- **Any other `retryable: false`.** Re-sending the same call won't help. A
  wrong `quest_id` / `course_id` means re-checking with `list_quests` /
  `list_courses`; a limit only the user can clear (out of quota, storage
  full) means stopping and telling them. If the quota ran out partway
  through a package, still bundle what was saved (`add_to_quest` is free)
  and offer to add the rest another day with the same `quest_id`.

Mint a new key only for a genuinely different item. A new key to get past
`IDEMPOTENCY_IN_PROGRESS` or a timeout is exactly how duplicates happen.

Writes are rate-limited per minute, so when saving many items go one at a
time or a few at a time rather than firing them all in parallel — a burst
only comes back as `RATE_LIMIT_EXCEEDED`. For several lines at once, use
`add_lines_batch`.

## Edit a quest

Use `list_quests(quest_id)` to inspect current members, then
`rename_quest`, `remove_quest_members`, or `reorder_quest_members` when the
user asks. Reordering requires every `member_refs` item from `list_quests`
exactly once, including references whose content has since been deleted. Removing a
member only removes its reference from the quest; it does not delete the
Word, Line, Story, course, or Talk scene. The web `/quests` page also supports
renaming and removing members; member reordering is available through MCP.

## Don't hardcode parameters from this file

The exact input shape for each tool (required fields, limits, error codes)
lives in the live tool schema StashLog's MCP server sends when you
connect — read that, not this document, for the contract. This file is
about judgment: when to build, what to check first, who writes what, how
it all becomes one quest. Tool contracts change; treat any parameter
detail here as illustrative, not authoritative.
