> 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.

# 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](https://github.com/open-spaced-repetition/awesome-fsrs/wiki), especially [The Algorithm](https://github.com/open-spaced-repetition/awesome-fsrs/wiki/The-Algorithm). The TypeScript APIs and framework boundaries describe ts-fsrs itself.

:::note 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 Prompt

Use the current ts-fsrs Model contract for this task: [describe the task].

1. Read https://open-spaced-repetition.github.io/ts-fsrs/guide/model/index.md and the page for the selected model version.
2. When useful, compare the formulas and descriptions for FSRS-6 (https://open-spaced-repetition.github.io/ts-fsrs/guide/model/fsrs-6.md), FSRS-5 (https://open-spaced-repetition.github.io/ts-fsrs/guide/model/fsrs-5.md), FSRS-4.5 (https://open-spaced-repetition.github.io/ts-fsrs/guide/model/fsrs-4dot5.md), FSRS-4 (https://open-spaced-repetition.github.io/ts-fsrs/guide/model/fsrs-4.md), and FSRS-3 (https://open-spaced-repetition.github.io/ts-fsrs/guide/model/fsrs-3.md). FSRS-7: https://open-spaced-repetition.github.io/ts-fsrs/guide/model/fsrs-7.md.
3. Define the config and memory-state schemas before implementing the model.
4. Keep the model limited to step, nextInterval, forgettingCurve, and forward behavior.
5. Pass desired retention explicitly to nextInterval.
6. Let the scheduler orchestrate reviews, chrono handle time, and middleware apply learning steps, fuzzing, maximum-interval limits, and queue-status policies.
7. Use bypass only after the config has already been migrated, clipped, and checked.
8. Use the current public API and add focused tests for every implemented equation.

## Shared symbols

| Symbol                           | Meaning                                                                                                  |
| -------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `math:D`                         | Difficulty from 1 (easiest) to 10 (hardest).                                                             |
| `math:S`                         | FSRS-3–6 stability in days; recall probability is 90% when `math:t = S`.                                 |
| `math:R`                         | Estimated probability of recall after `math:t` days.                                                     |
| `math:G`                         | Grade: 1 Again, 2 Hard, 3 Good, or 4 Easy.                                                               |
| `math:t`                         | Elapsed days since the previous review.                                                                  |
| `math:r`                         | Desired retention used by `nextInterval`.                                                                |
| `math:I(r, S)`                   | Base interval in days for stability `math:S` and desired retention `math:r`, before middleware policies. |
| `math:S_{\min}`, `math:S_{\max}` | Lower and upper stability bounds of the model.                                                           |
| `math:D_{\min}`, `math:D_{\max}` | Lower and upper difficulty bounds of the model.                                                          |
| `math:w_i`                       | A trained model weight.                                                                                  |

[FSRS-7](https://open-spaced-repetition.github.io/ts-fsrs/guide/model/fsrs-7.md) 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:

```ts twoslash file="../../../snippets/run-code/model.ts"
import { Rating } from 'ts-fsrs'
import { FSRS6_DEFAULT_WEIGHTS, FSRS6Model } from 'ts-fsrs/models/fsrs-6'

const model = FSRS6Model.create({
  config: {
    weights: FSRS6_DEFAULT_WEIGHTS,
    enableShortTerm: true,
    numRelearningSteps: 1, // Constrains weights; middleware runs the steps.
  },
})

const memoryState = model.step({
  memoryState: null,
  rating: Rating.Good,
  elapsedDays: 0,
})
const desiredRetention = 0.9
const interval = model.nextInterval(memoryState, desiredRetention)
const retrievabilityAtInterval = model.forgettingCurve(memoryState, interval)

console.log(
  JSON.stringify(
    {
      memoryState,
      desiredRetention,
      interval,
      retrievabilityAtInterval,
    },
    null,
    2
  )
)

```


▶ Run code

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](https://github.com/open-spaced-repetition/srs-benchmark/blob/main/models/sm2_trainable.py) to demonstrate how to define a model.

```ts twoslash fold height=420 file="../../../snippets/run-code/custom-model.ts"
import {
  clamp,
  defineModel,
  type ModelForwardInput,
  type ModelStepInput,
  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 MemoryState = z.output<typeof memoryStateSchema>

const SM2Model = defineModel({
  name: 'sm2',
  schema: { config: configSchema, memoryState: memoryStateSchema },
  defaultValue: {
    memoryState: ({ config }) => ({
      interval: 0,
      easeFactor: config.weights[2],
      repetitions: 0,
    }),
  },
  create({ config }) {
    const parsed = configSchema.parse(config)
    const w = parsed.weights
    const step = ({ memoryState, rating }: ModelStepInput<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 > Rating.Again ? previous.repetitions + 1 : 0
      const interval = clamp(
        repetitions <= 1
          ? w[0]
          : repetitions === 2
            ? w[1]
            : previous.interval * previous.easeFactor,
        0.01,
        36500
      )
      const easeFactor = clamp(
        previous.easeFactor - w[3] * (quality - w[4]) ** 2 + w[5],
        1.3,
        10
      )
      return { interval, easeFactor, repetitions }
    }
    const forward = ({
      history,
      initialState,
    }: ModelForwardInput<MemoryState>) => {
      const states: MemoryState[] = []
      let memoryState = initialState ?? null
      for (const review of history) {
        memoryState = step({
          memoryState,
          rating: review.rating,
          elapsedDays: review.deltaT,
        })
        states.push(memoryState)
      }
      return states
    }
    return {
      config: parsed,
      bounds: {
        iMin: 0.01,
        iMax: 36500,
        eMin: 1.3,
        eMax: 10,
        rMin: 0,
        rMax: Infinity,
      },
      algorithm: null,
      step,
      // Invert the benchmark curve to satisfy the Model interval contract.
      nextInterval: (state: Readonly<MemoryState>, desiredRetention: number) =>
        Math.max(
          1,
          Math.round(
            (state.interval * Math.log(desiredRetention)) / Math.log(0.9)
          )
        ),
      forgettingCurve: (state: Readonly<MemoryState>, elapsedDays: number) =>
        0.9 ** (elapsedDays / state.interval),
      forward,
    }
  },
})

const model = SM2Model.create({
  config: { weights: [1, 6, 2.5, 0.02, 7, 0.18] },
})
const states = model.forward({
  history: [
    { rating: Rating.Good, deltaT: 0 },
    { rating: Rating.Good, deltaT: 1 },
    { rating: Rating.Good, deltaT: 6 },
    { rating: Rating.Again, deltaT: 15 },
    { rating: Rating.Good, deltaT: 1 },
    { rating: Rating.Good, deltaT: 1 },
  ],
})
console.log(JSON.stringify(states, null, 2)) // Intervals: 1, 6, 15, 1, 1, 6 days.

```


▶ Run code

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.

:::warning 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](https://open-spaced-repetition.github.io/ts-fsrs/guide/model/fsrs-7.md) — dual-trace implementation reference; 34 weights and numerical intervals.
- [FSRS-6](https://open-spaced-repetition.github.io/ts-fsrs/guide/model/fsrs-6.md) — trainable decay and same-day updates.
- [FSRS-5](https://open-spaced-repetition.github.io/ts-fsrs/guide/model/fsrs-5.md) — fixed power forgetting curve with same-day updates.
- [FSRS-4.5](https://open-spaced-repetition.github.io/ts-fsrs/guide/model/fsrs-4dot5.md) — fixed power curve without a same-day formula.
- [FSRS-4](https://open-spaced-repetition.github.io/ts-fsrs/guide/model/fsrs-4.md) — the first power-curve version in this package.
- [FSRS-3](https://open-spaced-repetition.github.io/ts-fsrs/guide/model/fsrs-3.md) — the earlier exponential forgetting curve.
