For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/llms-full.txt, and this page is available as Markdown at https://open-spaced-repetition.github.io/ts-fsrs/guide/model/index.md.

Model overview

An FSRS model turns a rating and elapsed time into a new memory state, estimates recall probability with a forgetting curve, and converts memory state plus desired retention into a base interval.

The FSRS-3 through FSRS-6 terminology and equations in this section are reviewed against the awesome-fsrs Wiki, especially The Algorithm. The TypeScript APIs and framework boundaries describe ts-fsrs itself.

Model boundary

A model owns memory-state updates, forgetting-curve evaluation, and base-interval calculation. The scheduler orchestrates reviews, chrono handles dates and elapsed time, and middleware applies learning steps, fuzzing, maximum-interval limits, and queue-status policies.

For your Agent
Implement or explain an SRS model

Copy this prompt to keep an AI agent inside the Model contract instead of mixing in scheduling policy.

Shared symbols

SymbolMeaning
DDDifficulty from 1 (easiest) to 10 (hardest).
SSFSRS-3–6 stability in days; recall probability is 90% when t=St = S.
RREstimated probability of recall after tt days.
GGGrade: 1 Again, 2 Hard, 3 Good, or 4 Easy.
ttElapsed days since the previous review.
rrDesired retention used by nextInterval.
I(r,S)I(r, S)Base interval in days for stability SS and desired retention rr, before middleware policies.
Smin⁡S_{\min}, Smax⁡S_{\max}Lower and upper stability bounds of the model.
Dmin⁡D_{\min}, Dmax⁡D_{\max}Lower and upper difficulty bounds of the model.
wiw_iA trained model weight.

FSRS-7 is documented separately from its implementation snapshot: it adds a fast stability, uses a mixed curve, and returns fractional-day intervals. The 90% definition of stability and I(r, S) notation above apply to FSRS-3–6.

Use a versioned model

DefaultScheduler defaults to FSRS-7 and can select any supported FSRS version through its version option. Every versioned model can also be created and called directly; the following example uses FSRS-6:

import { 
type Rating = 0 | 1 | 2 | 3 | 4
const Rating: Readonly<{
    readonly Manual: 0;
    readonly Again: 1;
    readonly Hard: 2;
    readonly Good: 3;
    readonly Easy: 4;
}>
Rating
} from 'ts-fsrs'
import {
const FSRS6_DEFAULT_WEIGHTS: number[]
FSRS6_DEFAULT_WEIGHTS
,
const FSRS6Model: Model<{
    readonly name: "fsrs-6";
    readonly config: SRSSchema<{
        input: {
            readonly weights: number[];
            readonly enableShortTerm: boolean;
            readonly numRelearningSteps: number;
        };
        output: {
            readonly weights: number[];
            readonly enableShortTerm: boolean;
            readonly numRelearningSteps: number;
        };
    }>;
    readonly memoryState: SRSSchema<{
        input: {
            stability: number;
            difficulty: number;
        };
        output: {
            stability: number;
            difficulty: number;
        };
    }>;
    readonly algorithm: FSRS6Algorithm;
}>
FSRS6Model
} from 'ts-fsrs/models/fsrs-6'
const
const model: ModelCore<{
    readonly config: {
        readonly weights: number[];
        readonly enableShortTerm: boolean;
        readonly numRelearningSteps: number;
    };
    readonly memoryState: {
        stability: number;
        difficulty: number;
    };
    readonly algorithm: FSRS6Algorithm;
}>
model
=
const FSRS6Model: Model<{
    readonly name: "fsrs-6";
    readonly config: SRSSchema<{
        input: {
            readonly weights: number[];
            readonly enableShortTerm: boolean;
            readonly numRelearningSteps: number;
        };
        output: {
            readonly weights: number[];
            readonly enableShortTerm: boolean;
            readonly numRelearningSteps: number;
        };
    }>;
    readonly memoryState: SRSSchema<{
        input: {
            stability: number;
            difficulty: number;
        };
        output: {
            stability: number;
            difficulty: number;
        };
    }>;
    readonly algorithm: FSRS6Algorithm;
}>
FSRS6Model
.
Model<{ readonly name: "fsrs-6"; readonly config: SRSSchema<{ input: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; output: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; }>; readonly memoryState: SRSSchema<{ input: { stability: number; difficulty: number; }; output: { stability: number; difficulty: number; }; }>; readonly algorithm: FSRS6Algorithm; }>.create: (ctx: {
    readonly config: {
        readonly weights: number[];
        readonly enableShortTerm: boolean;
        readonly numRelearningSteps: number;
    } | {
        readonly weights: number[];
        readonly enableShortTerm: boolean;
        readonly numRelearningSteps: number;
    };
    readonly migrate?: boolean;
    readonly clip?: boolean;
    readonly check?: boolean;
    readonly bypass?: boolean;
}) => ModelCore<{
    readonly config: {
        readonly weights: number[];
        readonly enableShortTerm: boolean;
        readonly numRelearningSteps: number;
    };
    readonly memoryState: {
        stability: number;
        difficulty: number;
    };
    readonly algorithm: FSRS6Algorithm;
}>
create
({
config: { weights:
const FSRS6_DEFAULT_WEIGHTS: number[]
FSRS6_DEFAULT_WEIGHTS
,
enableShortTerm: true, numRelearningSteps: 1, // Constrains weights; middleware runs the steps. }, }) const memoryState =
const model: ModelCore<{
    readonly config: {
        readonly weights: number[];
        readonly enableShortTerm: boolean;
        readonly numRelearningSteps: number;
    };
    readonly memoryState: {
        stability: number;
        difficulty: number;
    };
    readonly algorithm: FSRS6Algorithm;
}>
model
.
ModelCore<{ readonly config: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; readonly memoryState: { stability: number; difficulty: number; }; readonly algorithm: FSRS6Algorithm; }>.step: (input: ModelStepInput<{
    stability: number;
    difficulty: number;
}>) => {
    stability: number;
    difficulty: number;
}
step
({
ModelStepInput<{ stability: number; difficulty: number; }>.memoryState: Readonly<{
    stability: number;
    difficulty: number;
}> | null
memoryState
: null,
ModelStepInput<{ stability: number; difficulty: number; }>.rating: 1 | 2 | 3 | 4
rating
:
const Rating: Readonly<{
    readonly Manual: 0;
    readonly Again: 1;
    readonly Hard: 2;
    readonly Good: 3;
    readonly Easy: 4;
}>
Rating
.Good,
ModelStepInput<{ stability: number; difficulty: number; }>.elapsedDays: number
elapsedDays
: 0,
}) const desiredRetention = 0.9 const interval =
const model: ModelCore<{
    readonly config: {
        readonly weights: number[];
        readonly enableShortTerm: boolean;
        readonly numRelearningSteps: number;
    };
    readonly memoryState: {
        stability: number;
        difficulty: number;
    };
    readonly algorithm: FSRS6Algorithm;
}>
model
.
ModelCore<{ readonly config: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; readonly memoryState: { stability: number; difficulty: number; }; readonly algorithm: FSRS6Algorithm; }>.nextInterval: (memoryState: Readonly<{
    stability: number;
    difficulty: number;
}>, desiredRetention: number) => number
nextInterval
(memoryState, desiredRetention)
const retrievabilityAtInterval =
const model: ModelCore<{
    readonly config: {
        readonly weights: number[];
        readonly enableShortTerm: boolean;
        readonly numRelearningSteps: number;
    };
    readonly memoryState: {
        stability: number;
        difficulty: number;
    };
    readonly algorithm: FSRS6Algorithm;
}>
model
.
ModelCore<{ readonly config: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; readonly memoryState: { stability: number; difficulty: number; }; readonly algorithm: FSRS6Algorithm; }>.forgettingCurve: (memoryState: Readonly<{
    stability: number;
    difficulty: number;
}>, elapsedDays: number) => number
forgettingCurve
(memoryState, interval)
console.log( JSON.stringify( { memoryState, desiredRetention, interval, retrievabilityAtInterval, }, null, 2 ) )

The example first calculates an interval for 90% desired retention, then uses forgettingCurve to estimate the recall probability at that interval. The result may differ slightly from 90% because the FSRS-6 nextInterval returns whole days.

In the FSRS-6 config, enableShortTerm controls the model's same-day stability update. numRelearningSteps only informs weight clipping and checking; relearning-step scheduling remains middleware behavior.

Define a model

defineModel preserves the concrete config, memory-state, and algorithm types while packaging schemas, defaults, and a model-core factory into one definition. It does not parse config by itself:

The following example implements an SM-2 variant to demonstrate how to define a model.

import {
  
function clamp(value: number, min: number, max: number): number
clamp
,
function defineModel<const Name extends string | symbol, const ConfigSchema extends AnySchema, const MemoryStateSchema extends AnyObjectSchema, const Algorithm>(definition: {
    readonly name: Name;
    readonly schema: ModelSchema<{
        readonly config: ConfigSchema;
        readonly memoryState: MemoryStateSchema;
    }>;
    readonly defaultValue: {
        readonly memoryState: (ctx: {
            readonly config: SchemaOutput<ConfigSchema>;
        }) => SchemaInput<MemoryStateSchema>;
    };
    readonly create: ModelCreate<{
        readonly config: ConfigSchema;
        readonly memoryState: MemoryStateSchema;
    }, Algorithm>;
}): Model<{
    readonly name: Name;
    readonly config: ConfigSchema;
    readonly memoryState: MemoryStateSchema;
    readonly algorithm: Algorithm;
}>
defineModel
,
type
interface ModelForwardInput<MemoryState extends object>
ModelForwardInput
,
type
interface ModelStepInput<MemoryState extends object>
ModelStepInput
,
type Rating = 0 | 1 | 2 | 3 | 4
const Rating: Readonly<{
    readonly Manual: 0;
    readonly Again: 1;
    readonly Hard: 2;
    readonly Good: 3;
    readonly Easy: 4;
}>
Rating
,
} from 'ts-fsrs' import { z } from 'zod' const configSchema = z.object({ weights: z.tuple([ z.number().min(0.01).max(100), z.number().min(0.01).max(100), z.number().min(1.3).max(10), z.number().nonnegative(), z.number().min(5), z.number(), ]), }) const memoryStateSchema = z.object({ interval: z.number().min(0).max(36500), easeFactor: z.number().min(1.3).max(10), repetitions: z.number().int().nonnegative(), }) type
type MemoryState = {
    interval: number;
    easeFactor: number;
    repetitions: number;
}
MemoryState
= z.output<typeof memoryStateSchema>
const
const SM2Model: Model<{
    readonly name: "sm2";
    readonly config: z.ZodObject<{
        weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
    }, z.core.$strip>;
    readonly memoryState: z.ZodObject<{
        interval: z.ZodNumber;
        easeFactor: z.ZodNumber;
        repetitions: z.ZodNumber;
    }, z.core.$strip>;
    readonly algorithm: null;
}>
SM2Model
=
defineModel<"sm2", z.ZodObject<{
    weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
}, z.core.$strip>, z.ZodObject<{
    interval: z.ZodNumber;
    easeFactor: z.ZodNumber;
    repetitions: z.ZodNumber;
}, z.core.$strip>, null>(definition: {
    readonly name: "sm2";
    readonly schema: ModelSchema<{
        readonly config: z.ZodObject<{
            weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
        }, z.core.$strip>;
        readonly memoryState: z.ZodObject<{
            ...;
        }, z.core.$strip>;
    }>;
    readonly defaultValue: {
        ...;
    };
    readonly create: ModelCreate<...>;
}): Model<...>
defineModel
({
name: 'sm2',
schema: ModelSchema<{
    readonly config: z.ZodObject<{
        weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
    }, z.core.$strip>;
    readonly memoryState: z.ZodObject<{
        interval: z.ZodNumber;
        easeFactor: z.ZodNumber;
        repetitions: z.ZodNumber;
    }, z.core.$strip>;
}>
schema
: {
ModelSchema<{ readonly config: ZodObject<{ weights: ZodTuple<[ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber], null>; }, $strip>; readonly memoryState: ZodObject<...>; }>.config: z.ZodObject<{
    weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
}, z.core.$strip>
config
: configSchema,
ModelSchema<{ readonly config: ZodObject<{ weights: ZodTuple<[ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber], null>; }, $strip>; readonly memoryState: ZodObject<...>; }>.memoryState: z.ZodObject<{
    interval: z.ZodNumber;
    easeFactor: z.ZodNumber;
    repetitions: z.ZodNumber;
}, z.core.$strip>
memoryState
: memoryStateSchema },
defaultValue: { memoryState: ({ config }) => ({ interval: 0, easeFactor: config.weights[2], repetitions: 0, }), },
create: ModelCreate<{
    readonly config: z.ZodObject<{
        weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
    }, z.core.$strip>;
    readonly memoryState: z.ZodObject<{
        interval: z.ZodNumber;
        easeFactor: z.ZodNumber;
        repetitions: z.ZodNumber;
    }, z.core.$strip>;
}, null>
create
({ config }) {
const parsed = configSchema.
ZodType<any, any, $ZodObjectInternals<{ weights: ZodTuple<[ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber], null>; }, $strip>>.parse(data: unknown, params?: z.core.ParseContext<z.core.$ZodIssue>): {
    weights: [number, number, number, number, number, number];
}
parse
(config)
const w = parsed.weights const
const step: ({ memoryState, rating }: ModelStepInput<MemoryState>) => {
    interval: number;
    easeFactor: number;
    repetitions: number;
}
step
= ({ memoryState, rating }:
interface ModelStepInput<MemoryState extends object>
ModelStepInput
<
type MemoryState = {
    interval: number;
    easeFactor: number;
    repetitions: number;
}
MemoryState
>) => {
const previous = memoryState ?? { interval: 0, easeFactor: parsed.weights[2], repetitions: 0, } // Again / Hard / Good / Easy -> SM-2 quality 2 / 3 / 4 / 5. const quality = rating + 1 const repetitions = rating >
const Rating: Readonly<{
    readonly Manual: 0;
    readonly Again: 1;
    readonly Hard: 2;
    readonly Good: 3;
    readonly Easy: 4;
}>
Rating
.Again ? previous.repetitions + 1 : 0
const interval =
function clamp(value: number, min: number, max: number): number
clamp
(
repetitions <= 1 ? w[0] : repetitions === 2 ? w[1] : previous.interval * previous.easeFactor, 0.01, 36500 ) const easeFactor =
function clamp(value: number, min: number, max: number): number
clamp
(
previous.easeFactor - w[3] * (quality - w[4]) ** 2 + w[5], 1.3, 10 ) return { interval, easeFactor, repetitions } } const
const forward: ({ history, initialState, }: ModelForwardInput<MemoryState>) => {
    interval: number;
    easeFactor: number;
    repetitions: number;
}[]
forward
= ({
history: readonly ModelReview[]
history
,
initialState, }:
interface ModelForwardInput<MemoryState extends object>
ModelForwardInput
<
type MemoryState = {
    interval: number;
    easeFactor: number;
    repetitions: number;
}
MemoryState
>) => {
const
const states: {
    interval: number;
    easeFactor: number;
    repetitions: number;
}[]
states
:
type MemoryState = {
    interval: number;
    easeFactor: number;
    repetitions: number;
}
MemoryState
[] = []
let memoryState = initialState ?? null for (const
const review: ModelReview
review
of
history: readonly ModelReview[]
history
) {
memoryState =
const step: ({ memoryState, rating }: ModelStepInput<MemoryState>) => {
    interval: number;
    easeFactor: number;
    repetitions: number;
}
step
({
ModelStepInput<{ interval: number; easeFactor: number; repetitions: number; }>.memoryState: Readonly<{
    interval: number;
    easeFactor: number;
    repetitions: number;
}> | null
memoryState
,
ModelStepInput<{ interval: number; easeFactor: number; repetitions: number; }>.rating: 1 | 2 | 3 | 4
rating
:
const review: ModelReview
review
.
ModelReview.rating: 1 | 2 | 3 | 4
rating
,
ModelStepInput<{ interval: number; easeFactor: number; repetitions: number; }>.elapsedDays: number
elapsedDays
:
const review: ModelReview
review
.
ModelReview.deltaT: number
deltaT
,
})
const states: {
    interval: number;
    easeFactor: number;
    repetitions: number;
}[]
states
.push(memoryState)
} return
const states: {
    interval: number;
    easeFactor: number;
    repetitions: number;
}[]
states
} return {
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.config: {
    weights: [number, number, number, number, number, number];
}
config
: parsed,
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.bounds: ModelBoundsShape<{
    interval: number;
    easeFactor: number;
    repetitions: number;
}>
bounds
: {
iMin: 0.01, iMax: 36500, eMin: 1.3, eMax: 10, rMin: 0, rMax: Infinity, },
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.algorithm: null
algorithm
: null,
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.step: (input: ModelStepInput<{
    interval: number;
    easeFactor: number;
    repetitions: number;
}>) => {
    interval: number;
    easeFactor: number;
    repetitions: number;
}
step
,
// Invert the benchmark curve to satisfy the Model interval contract.
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.nextInterval: (memoryState: Readonly<{
    interval: number;
    easeFactor: number;
    repetitions: number;
}>, desiredRetention: number) => number
nextInterval
: (state: Readonly<
type MemoryState = {
    interval: number;
    easeFactor: number;
    repetitions: number;
}
MemoryState
>, desiredRetention: number) =>
Math.max( 1, Math.round( (state.interval * Math.log(desiredRetention)) / Math.log(0.9) ) ),
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.forgettingCurve: (memoryState: Readonly<{
    interval: number;
    easeFactor: number;
    repetitions: number;
}>, elapsedDays: number) => number
forgettingCurve
: (state: Readonly<
type MemoryState = {
    interval: number;
    easeFactor: number;
    repetitions: number;
}
MemoryState
>, elapsedDays: number) =>
0.9 ** (elapsedDays / state.interval),
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.forward: (input: ModelForwardInput<{
    interval: number;
    easeFactor: number;
    repetitions: number;
}>) => readonly {
    interval: number;
    easeFactor: number;
    repetitions: number;
}[]
forward
,
} }, }) const
const model: ModelCore<{
    readonly config: {
        weights: [number, number, number, number, number, number];
    };
    readonly memoryState: {
        interval: number;
        easeFactor: number;
        repetitions: number;
    };
    readonly algorithm: null;
}>
model
=
const SM2Model: Model<{
    readonly name: "sm2";
    readonly config: z.ZodObject<{
        weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
    }, z.core.$strip>;
    readonly memoryState: z.ZodObject<{
        interval: z.ZodNumber;
        easeFactor: z.ZodNumber;
        repetitions: z.ZodNumber;
    }, z.core.$strip>;
    readonly algorithm: null;
}>
SM2Model
.
Model<{ readonly name: "sm2"; readonly config: ZodObject<{ weights: ZodTuple<[ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber], null>; }, $strip>; readonly memoryState: ZodObject<...>; readonly algorithm: null; }>.create: (ctx: {
    readonly config: {
        weights: [number, number, number, number, number, number];
    } | {
        weights: [number, number, number, number, number, number];
    };
    readonly migrate?: boolean;
    readonly clip?: boolean;
    readonly check?: boolean;
    readonly bypass?: boolean;
}) => ModelCore<{
    readonly config: {
        weights: [number, number, number, number, number, number];
    };
    readonly memoryState: {
        interval: number;
        easeFactor: number;
        repetitions: number;
    };
    readonly algorithm: null;
}>
create
({
config: { weights: [1, 6, 2.5, 0.02, 7, 0.18] }, }) const
const states: readonly {
    interval: number;
    easeFactor: number;
    repetitions: number;
}[]
states
=
const model: ModelCore<{
    readonly config: {
        weights: [number, number, number, number, number, number];
    };
    readonly memoryState: {
        interval: number;
        easeFactor: number;
        repetitions: number;
    };
    readonly algorithm: null;
}>
model
.
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.forward: (input: ModelForwardInput<{
    interval: number;
    easeFactor: number;
    repetitions: number;
}>) => readonly {
    interval: number;
    easeFactor: number;
    repetitions: number;
}[]
forward
({
ModelForwardInput<{ interval: number; easeFactor: number; repetitions: number; }>.history: readonly ModelReview[]
history
: [
{
ModelReview.rating: 1 | 2 | 3 | 4
rating
:
const Rating: Readonly<{
    readonly Manual: 0;
    readonly Again: 1;
    readonly Hard: 2;
    readonly Good: 3;
    readonly Easy: 4;
}>
Rating
.Good,
ModelReview.deltaT: number
deltaT
: 0 },
{
ModelReview.rating: 1 | 2 | 3 | 4
rating
:
const Rating: Readonly<{
    readonly Manual: 0;
    readonly Again: 1;
    readonly Hard: 2;
    readonly Good: 3;
    readonly Easy: 4;
}>
Rating
.Good,
ModelReview.deltaT: number
deltaT
: 1 },
{
ModelReview.rating: 1 | 2 | 3 | 4
rating
:
const Rating: Readonly<{
    readonly Manual: 0;
    readonly Again: 1;
    readonly Hard: 2;
    readonly Good: 3;
    readonly Easy: 4;
}>
Rating
.Good,
ModelReview.deltaT: number
deltaT
: 6 },
{
ModelReview.rating: 1 | 2 | 3 | 4
rating
:
const Rating: Readonly<{
    readonly Manual: 0;
    readonly Again: 1;
    readonly Hard: 2;
    readonly Good: 3;
    readonly Easy: 4;
}>
Rating
.Again,
ModelReview.deltaT: number
deltaT
: 15 },
{
ModelReview.rating: 1 | 2 | 3 | 4
rating
:
const Rating: Readonly<{
    readonly Manual: 0;
    readonly Again: 1;
    readonly Hard: 2;
    readonly Good: 3;
    readonly Easy: 4;
}>
Rating
.Good,
ModelReview.deltaT: number
deltaT
: 1 },
{
ModelReview.rating: 1 | 2 | 3 | 4
rating
:
const Rating: Readonly<{
    readonly Manual: 0;
    readonly Again: 1;
    readonly Hard: 2;
    readonly Good: 3;
    readonly Easy: 4;
}>
Rating
.Good,
ModelReview.deltaT: number
deltaT
: 1 },
], }) console.log(JSON.stringify(
const states: readonly {
    interval: number;
    easeFactor: number;
    repetitions: number;
}[]
states
, null, 2)) // Intervals: 1, 6, 15, 1, 1, 6 days.

The schemas describe the public boundary and are consumed when a scheduler creates its schema. A direct create implementation must perform any required parsing and normalization before returning config, bounds, algorithm, step, nextInterval, forgettingCurve, and forward. Keep that core deterministic and free of scheduler policy.

Use bypass only with prepared config

Only use FSRS6Model.create({ config, bypass: true }) when the caller has explicitly completed that model's migration, clipping, schema validation, and range checks. It skips those create-time steps and freezing. Never use bypass for raw user, storage, or network input.

Model versions

  • FSRS-7 — dual-trace implementation reference; 34 weights and numerical intervals.
  • FSRS-6 — trainable decay and same-day updates.
  • FSRS-5 — fixed power forgetting curve with same-day updates.
  • FSRS-4.5 — fixed power curve without a same-day formula.
  • FSRS-4 — the first power-curve version in this package.
  • FSRS-3 — the earlier exponential forgetting curve.