@dsh-agentic-roleplay/state
Persistent SQLite session state, story memory and recall, repositories, and model-facing record tools for DSH roleplay plugins.
Install
dsh plugin --profile web add @dsh-agentic-roleplay/state
Sandbox test results
| Stage | Result |
|---|---|
| 1 install | ✓ |
| 2 list | ✓ |
| 3 mount | ✓ |
| 4 boot | — |
Four stages run in an isolated sandbox: install (network on) → list → mount → boot (last three with no network).
README
@dsh-agentic-roleplay/state
The persistent session-state and memory service for DSH Agentic Roleplay, maintained by Firefox2100.
Most users should install @dsh-agentic-roleplay/all. Install this package separately when another DSH plugin needs the roleplay database, repositories, recall engine, or model-facing record tools without the complete roleplay interface.
A DeepSeek Harness plugin that keeps the state of roleplay sessions: one SQLite
database per session, reached through repositories over canonical models. It
does no orchestration and has no UI, so it can be installed on its own to get a
roleplay database and state management without the rest of the roleplay bundle.
Other plugins use it through the roleplayState service. Model-facing tools
are included and can be enabled through its configuration.
Requires DSH 0.2 and Node 22.19 or later (it uses the built-in node:sqlite).
dsh plugin --profile <name> add @dsh-agentic-roleplay/state
Using it from another plugin
import type {} from '@dsh-agentic-roleplay/state' // types `ctx.roleplayState`
import type { Context } from '@deepseek-ai/cordis'
export const inject = ['roleplayState']
export function apply(ctx: Context) {
// Creates sessions/<uuid>/session.sqlite and opens it.
const session = ctx.roleplayState.createSession('/path/to/sessions', {
name: 'First night',
world: { id: 'blackwater-observatory', name: 'The Blackwater Observatory' },
})
session.transaction(() => {
const mayor = session.characters.add({ name: 'Eleanor Graves', /* … */ control: 'agent' })
session.characterStates.create(mayor.id, { publicState: '…', innerState: '…', activity: '…' })
session.intents.add(mayor.id, { type: 'agenda', name: '…', /* … */ })
})
session.characters.findByName('the mayor') // by name or alias, ignoring case
session.characterStates.update(1, { activity: 'looking for the key' }, { expectedVersion: 3 })
}
The service creates and opens sessions under a sessions folder you give it,
shares one connection per session, and closes them all when the plugin unloads:
Method · Does
createSession(sessionsDir, init) · Creates the session folder and database and returns its repositories. The id is generated (UUID v4) unless given. Nothing is left behind if it fails.
openSession(sessionsDir, id) · Returns the repositories of an existing session.
listSessions(sessionsDir) · The metadata of every session under the folder (name, world, creation time, last played time, turns). A session that cannot be read is reported in problems, not thrown.
closeSession(sessionsDir, id) · Releases the connection; opening again reconnects.
deleteSession(sessionsDir, id) · Closes the session and removes its folder.
Session ids become folder names, so only a UUID v4 is accepted. The package also
exports the same operations as plain functions (createSession, openSession,
createSessionStore, …) for code that does not run inside DSH, such as tests.
The records API and the model-facing tools
The package owns how a story is remembered, not only where it is stored:
session.edit(edit)changes the story's records (character states, events with who took each in, beliefs, threads) together or not at all. It checks the whole edit first and throwsInvalidEditErrornaming every problem, so a model's mistakes can be reported back to it.parseRecordsEdit(json)reads the wire format models write (snake_case) andrecordsEditSchema({ characters, changes })is its JSON schema, for a model's structured output or a tool's parameters.session.records(scope)reads the records back in the same wire format.session.recall({ characterId, situation, turn, policy })is what a character is reminded of: events it took in and things it believes, by who the situation mentions, shared words and tags, importance and story-time recency; withpolicy.flakythe older and less important are told as faded or vague, or are gone. Age is elapsed story time fromsession.clock, never turn count; an unimportant event has a six-story-day half-life. It counts what it brings back as recalled, which makes it last longer.session.recallStored(query, { queryVector? })adds the session's search index to the cues: texts that match the situation by words and, with the vector of the situation, by meaning.session.searchEvents({ text, vector?, fromTurn, toTurn, minImportance, limit })finds what happened by the same two searches, merged by reciprocal rank.session.recallRanked(query, signal, { queryVector? })is the call for anything that reminds a character of something:recall, ranked by meaning when a plugin has registered a ranker withroleplayState.rankMemories(ranker)(an embedding model, a reranker; seedocs/capabilities.md).session.recallCandidates(query)gives what could be recalled, with the text to match, for a ranker. Withexplain: truein the query each recalled thing carrieswhy: the cues of the moment (people named, words shared, place), what each search scored it, how much it matters, how long ago it was, how much of it is left in mind and what it was ranked by.session.knowledgeis the closed-world awareness graph for locations, items and directed exits (learn,knows,get,list,forget). Directional relationships are the corresponding character-to-character edges. Awareness grants identity only, not current state or whereabouts.session.planKnownMoveplans through only a character's known places and exits;planMoveremains the authoritative world planner.session.factions,factionStates,factionMemberships,factionRelationshipsandfactionIntentsstore persistent organisations separately from characters that act in scenes. Membership is many-to-many; relationships involving factions are directional and double as closed-world awareness e
…(truncated)
Rendered from the README published on npm. Formatted only, not edited.
Versions
Published on npm::28,最后更新 2026-11-05