Skip to content

Recommender (experimental) ​

Experimental

The recommender is at version 0.0.1. Its algorithms, settings and resource needs can still change between updates.

The recommender is an optional service, published as TypeType Signals. It lets each user build their home page with an algorithm they pick and tune. See Choosing your algorithm for what users get.

A stack without it behaves exactly as before and shows no option anywhere. With it, three switches stay in different hands:

  1. you install the service;
  2. the administrator switches it on for the instance;
  3. each user turns it on for themselves.

Before you start ​

ResourceAmountHow to check
MemoryAbout 1 GB at rest, capped at 2 GB by default (RECOMMENDER_MEMORY_LIMIT)free -h, column available
DiskAbout 550 MB for the language model and about 330 MB for the imagedf -h /var/lib/docker
NetworkInternet access on first start, to download the model from Hugging Face. The service never talks to YouTube.
TypeTypeA stack installed with the installer or the supported Compose filedocker compose ps in the stack directory

What changes on your stack when you add it:

  • a new container, typetype-recommender;
  • a new volume, recommender_models, for the model;
  • a new database, typetype_recommender, in the PostgreSQL you already have;
  • the postgres service moves from postgres:17 to pgvector/pgvector:pg17, the same PostgreSQL 17 with the pgvector extension, on the same data volume. PostgreSQL restarts once, so the instance is unavailable for a few seconds;
  • Server is recreated with the address of the service.

Your existing data is not migrated or rewritten. Take a database backup first all the same, as before any change to the stack: see Maintenance.

The commands of this page use $STACK_DIR for the directory your stack lives in. Set it once in your terminal. ~/typetype-stack is only the installer's default: use the directory you chose, whichever it is. See Choose the directory.

sh
STACK_DIR=~/typetype-stack

Pick one of the three ways below.

A. New instance, with the installer ​

  1. Run the installer with the recommender:

    sh
    curl -fsSL https://raw.githubusercontent.com/TypeType-Video/TypeType/main/scripts/install-stack.sh | bash -s -- --recommender

    Without --recommender, the installer asks instead, and answers no if you just press Enter:

    text
    [install] Optional: the recommender (experimental) lets each user pick the algorithm of their home page.
    [install] It needs about 2 GB of memory and 1 GB of disk, and downloads a language model on first start.
    Add the experimental recommender? [y/N]:
  2. The installer confirms the choice, then asks before starting anything:

    text
    [install] Recommender (experimental) added to the stack.
    [install] Validating the resolved Compose stack...
    Proceed with Docker pull + startup in <your stack directory>? [y/N]:

    Answer y.

  3. Wait. The first start downloads the image and then the model. The installer waits up to 15 minutes for every service to be healthy, instead of the usual 3. It ends with:

    text
    Done. Open http://localhost:8082
  4. Go to Check that it runs.

The installer has set RECOMMENDER_ENABLED=true in .env, so the option is already offered on the instance.

B. Existing instance, with the installer ​

Run the installer again with the directory of your stack. It backs up the managed files, keeps your .env and your docker-compose.override.yml, and adds the recommender.

sh
curl -fsSL https://raw.githubusercontent.com/TypeType-Video/TypeType/main/scripts/install-stack.sh | bash -s -- --recommender --dir "$STACK_DIR"

Expect the instance to be unavailable for a few seconds while PostgreSQL and Server restart, then go to Check that it runs.

C. Existing instance, by hand ​

Use this if you manage the stack yourself, for example with Dockge.

  1. Go to the stack directory and make sure the overlay is there:

    sh
    cd "$STACK_DIR"
    ls docker-compose.recommender.yml

    If it is missing, your stack files are older than the recommender. Fetch it:

    sh
    curl -fsSLO https://raw.githubusercontent.com/TypeType-Video/TypeType/main/docker-compose.recommender.yml
  2. Tell the stack to offer it. In .env, set:

    dotenv
    RECOMMENDER_ENABLED=true

    This line is also what makes the installer keep the recommender on a later update.

  3. Switch PostgreSQL to the pgvector image first. This keeps the existing data volume:

    sh
    docker compose -f docker-compose.yml -f docker-compose.recommender.yml up -d --wait postgres

    On ARM64, include -f docker-compose.arm64.yml between the Compose files.

    Then create the database and shared secret:

    sh
    COMPOSE_RECOMMENDER_FILE=docker-compose.recommender.yml ./scripts/run-stack-init.sh

    The output ends with:

    text
    CREATE DATABASE
    CREATE EXTENSION

    On a second run, CREATE DATABASE is absent and the extension line says it already exists. Both are fine.

    If you manage the shared secret yourself, you can create the database directly with docker exec instead of running the init helper:

    sh
    RECOMMENDER_DATABASE=typetype_recommender
    DB_CONTAINER="$(docker compose -f docker-compose.yml -f docker-compose.recommender.yml ps -q postgres)"
    test -n "$DB_CONTAINER"
    docker exec -i -e "RECOMMENDER_DATABASE=$RECOMMENDER_DATABASE" "$DB_CONTAINER" sh -eu <<'SH'
    export PGPASSWORD="$POSTGRES_PASSWORD"
    psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v recommender_database="$RECOMMENDER_DATABASE" <<'SQL'
    SELECT format('CREATE DATABASE %I', :'recommender_database')
    WHERE NOT EXISTS (
        SELECT 1 FROM pg_database WHERE datname = :'recommender_database'
    )
    \gexec
    SQL
    psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$RECOMMENDER_DATABASE" -c 'CREATE EXTENSION IF NOT EXISTS vector'
    SH

    Set RECOMMENDER_DATABASE to the same value as in .env. This command only creates the database and extension; it does not create /run/typetype-secrets/recommender_internal_token. Use the init helper above unless you have already provisioned that shared secret. If its PostgreSQL container is not part of this Compose project, set DB_CONTAINER to its container name or ID. On ARM64, include -f docker-compose.arm64.yml in the Compose commands above.

  4. Start the service and recreate Server:

    sh
    docker compose -f docker-compose.yml -f docker-compose.recommender.yml up -d --wait --wait-timeout 900

    On an ARM64 host, name the ARM64 file too, as you already do: -f docker-compose.yml -f docker-compose.arm64.yml -f docker-compose.recommender.yml.

  5. Go to Check that it runs.

Name both files from now on

Every docker compose command for this stack must now name docker-compose.recommender.yml as well. A command that names only docker-compose.yml with --remove-orphans removes the recommender.

Thanks to @Toastienergy, whose feedback from a manually managed Compose stack helped make these setup steps clearer.

Rootless Docker with a bind-mounted model directory ​

TypeType Signals writes its model cache to /models and runs as UID/GID 10001:10001 in the container. The named recommender_models volume needs no host-side ownership change. With a host bind mount under rootless Docker, the container IDs map to different host IDs. Do not copy ownership values from another machine.

With the recommender container running, find the host IDs that correspond to the container's 10001:10001:

sh
RECOMMENDER_CONTAINER="$(docker compose -f docker-compose.yml -f docker-compose.recommender.yml ps -q typetype-recommender)"
PID="$(docker inspect -f '{{.State.Pid}}' "$RECOMMENDER_CONTAINER")"
HOST_UID="$(awk -v id=10001 '$1 <= id && id < $1 + $3 { print $2 + id - $1; exit }' "/proc/$PID/uid_map")"
HOST_GID="$(awk -v id=10001 '$1 <= id && id < $1 + $3 { print $2 + id - $1; exit }' "/proc/$PID/gid_map")"
printf 'Mapped host owner: %s:%s\n' "$HOST_UID" "$HOST_GID"

Set MODEL_PATH to the dedicated host directory mounted at /models, then apply that mapping and verify that the service can write there:

sh
sudo chown -R "$HOST_UID:$HOST_GID" "$MODEL_PATH"
docker exec "$RECOMMENDER_CONTAINER" test -w /models

Only change ownership of the dedicated model directory, not a shared parent path.

Check that it runs ​

Run these from the stack directory. Every command names both Compose files, whichever way you installed: docker compose alone only knows the services of docker-compose.yml. On an ARM64 host, add -f docker-compose.arm64.yml between the two.

sh
cd "$STACK_DIR"
  1. The container is healthy:

    sh
    docker compose -f docker-compose.yml -f docker-compose.recommender.yml ps typetype-recommender

    The STATUS column must read Up ... (healthy). (health: starting) means the model is still being downloaded or loaded.

  2. Its log shows a complete start:

    sh
    docker compose -f docker-compose.yml -f docker-compose.recommender.yml logs typetype-recommender
    text
    INFO:     Started server process [1]
    INFO:     Waiting for application startup.
    Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.
    INFO:     Application startup complete.
    INFO:     Uvicorn running on http://0.0.0.0:8190 (Press CTRL+C to quit)

    The warning about the HF Hub is expected: the model is public and needs no token. The pause before Application startup complete is the download.

  3. Server reaches it:

    sh
    docker compose -f docker-compose.yml -f docker-compose.recommender.yml exec -T typetype-server wget -qO- http://127.0.0.1:8080/version/recommender
    json
    {"service":"recommender","version":"0.0.1","revision":"...","shortRevision":"...","buildTime":"..."}

    An empty answer or an error means Server does not see the service: see Troubleshooting.

  4. The instance announces it:

    sh
    docker compose -f docker-compose.yml -f docker-compose.recommender.yml exec -T typetype-server wget -qO- http://127.0.0.1:8080/instance

    The answer contains:

    json
    "recommender":{"installed":true,"enabled":true,"experimental":true}

    installed is true as soon as Server knows the service. enabled is the instance-wide switch described next.

  5. In the web app, Settings, then About, shows a line Algorithm with the version 0.0.1.

Switch it on ​

Installing the service shows nothing to users until the instance offers it.

  1. Sign in as an administrator and open the admin console.
  2. Open the Recommender section.
  3. Switch on Offer the experimental home page. It is already on if .env holds RECOMMENDER_ENABLED=true.
  4. Reload the web app: Algorithm appears in the menu.

Then try it with your own account:

  1. Open Algorithm.
  2. Switch on Build my home page with an algorithm I choose.
  3. Go back to the home page. On an empty account you get a welcome card; import some data or watch a few videos first.
  4. Open Algorithm again, then How the last home page was built: it must show the sources that were asked and the videos found.

Each user has to do the same for themselves, unless you turn on On for everyone by default.

The Recommender section of the admin console holds everything an administrator decides:

ControlEffect
Offer the experimental home pageOff, everyone keeps the standard home page and the Algorithm page disappears. On, each user chooses to turn it on.
On for everyone by defaultOff by default. On, every user gets the home page by algorithm at once and can turn it off.
Algorithms offeredWhich algorithms users may pick. Nothing selected means all of them.
Preselected algorithmThe one a user gets before choosing. The service's own choice follows RECOMMENDER_DEFAULT_ALGORITHM.
Answer time allowed (ms)How long one call to the service may take. 0 keeps the value set at installation.
Video requests to YouTube per minuteSee What it asks of YouTube. 0 keeps the value set at installation.

Below, State of the service shows whether it answers and in how many milliseconds, the average time to build a page (split between the algorithm and the YouTube sources), pages served and how many came from the cache, how often a visitor was given the standard home page instead, and the video requests sent and held back this minute. It also shows how many users turned the option on, per algorithm, and the opinions they chose to send. No opinion names its author. The counters start again when Server restarts.

What it stores ​

Your users' data stays in Server's database. The recommender has no account system and does not know who a user is.

WhatWhereFor how long
Vectors of public video titlesIts own PostgreSQL database, table title_vectorsUntil deleted. No user is named.
Public video details sent by ServerIts own PostgreSQL database, table catalogue_videosUntil the row is removed; newer details replace it. Not linked to an account.
A user's history, sent by Server under a keyMemory, and the shared cacheMemory: expires after 30 minutes idle and is pruned on the next history or recommendation request; shared cache: 30 minutes from upload
The plan of a page being builtMemory, and the shared cache2 minutes
What was concluded from a historyMemory only10 minutes, while the history is unchanged

The video catalogue stores the video ID, title, channel URL, category, up to 12 tags and up to 300 characters of description. It has no automatic expiry.

Server also keeps each built home page in Dragonfly for 30 minutes, and each user's choice of algorithm, corrections and opinions in its own database. A user's export carries their choices and opinions.

The service can be restarted at any time. Server then sends the history again and the next page is built normally.

What it asks of YouTube ​

The recommender itself sends nothing to YouTube. It tells Server which sources a page needs (a channel's videos, a search, the videos next to one), and Server fetches them through its usual extraction, behind its shared cache.

Server limits what the recommender may make it open:

VariableDefaultPurpose
RECOMMENDER_STREAM_REQUESTS_PER_MINUTE15Videos the recommender may make Server open at the provider in a minute, for the whole instance. People pressing play are never limited by it.

When the provider refuses a request, the recommender asks for no video at all for a while, so that it never makes a refusal last longer for the people watching. A page built during that time simply has fewer sources.

Settings ​

These stack-level values are read from .env:

VariableDefaultPurpose
RECOMMENDER_ENABLEDfalseOffers the recommender on the instance. The admin console can change it afterwards.
RECOMMENDER_MEMORY_LIMIT2gMemory cap of the container
RECOMMENDER_DEFAULT_ALGORITHMsignalsThe algorithm preselected for someone who has not chosen
RECOMMENDER_DATABASEtypetype_recommenderName of the service's own database
RECOMMENDER_CACHE_URLredis://dragonfly:6379Shared cache of the service
RECOMMENDER_THREADSup to 8CPU threads of the language model
TYPETYPE_RECOMMENDER_IMAGEstable imageImage override, for a pinned deployment or a rollback
TYPETYPE_POSTGRES_VECTOR_IMAGEpgvector/pgvector:pg17PostgreSQL image that ships pgvector
TYPETYPE_RECOMMENDER_PORT8190Port the service listens on, inside the Compose network

Server's side of the integration can be tuned too:

VariableDefaultPurpose
RECOMMENDER_REQUEST_TIMEOUT_MS2500How long one call to the recommender may take
RECOMMENDER_PAGE_DEADLINE_MS4000How long a visitor waits for a page before the usual home page is shown; the page keeps being built for the next visit
RECOMMENDER_PAGE_TTL_SECONDS1800How long a built page is kept
RECOMMENDER_PAGE_VARIANTS3Pages kept per user, so that a refresh shows another one at once
RECOMMENDER_RELATED_TTL_SECONDS86400How long the videos next to one are kept
RECOMMENDER_SEARCH_TTL_SECONDS21600How long a search result is kept
RECOMMENDER_CHANNEL_TTL_SECONDS1800How long a channel's videos are kept
RECOMMENDER_SOURCE_PARALLELISM6Sources of one page fetched at once

The overlay passes only the values above. The service reads more of its own, which you set with a Compose override on typetype-recommender:

VariableDefaultPurpose
RECOMMENDER_HISTORY_TTL_SECONDS1800Memory entries are pruned on a request after the idle timeout; shared-cache expiry starts at upload
RECOMMENDER_HISTORY_ITEMS_KEPT400000Watches and likes of all the histories held in memory at once, about 0.5 MB per 1,000
RECOMMENDER_PLAN_TTL_SECONDS120How long the plan of a page is kept
RECOMMENDER_PROFILE_TTL_SECONDS600How long what was concluded from an unchanged history is kept; 0 turns it off
RECOMMENDER_EMBED_BUDGET100New titles read by the model at most inside one request
RECOMMENDER_MAX_TITLES2000Titles a profile reads at most
RECOMMENDER_EMBEDDING_MODEL, RECOMMENDER_EMBEDDING_FILEmultilingual mpnet, fp16The language model and its build
RECOMMENDER_DATABASE_POOL4Connections kept to the database
RECOMMENDER_CACHE_TIMEOUT_MS2000How long a cache call may take

A variable left empty counts as not set.

yaml
services:
  typetype-recommender:
    environment:
      RECOMMENDER_PARALLEL_BATCHES: "3"

Save it as docker-compose.override.yml in the stack directory; the installer keeps that file and applies it last.

Memory against speed ​

With its defaults the service stays under 2 GB. On a host with 4 GB to give it, two settings of the service make it faster when many new people arrive at once, at the cost of memory:

VariableDefaultEffect
RECOMMENDER_PARALLEL_BATCHES1Batches of titles going through the model at once. 3 serves a crowd of new people about a third faster, for about 0.8 GB more at the peak.
RECOMMENDER_ONNX_ARENA01 lets the model keep the memory of its largest batches: about 10 % faster under a crowd, about 1.3 GB more.

Raise RECOMMENDER_MEMORY_LIMIT with them.

Update it ​

With the installer, update as usual. It keeps the recommender because .env holds RECOMMENDER_ENABLED=true:

sh
curl -fsSL https://raw.githubusercontent.com/TypeType-Video/TypeType/main/scripts/install-stack.sh | bash -s -- --yes --dir "$STACK_DIR"

By hand, pull and recreate with both files:

sh
cd "$STACK_DIR"
docker compose -f docker-compose.yml -f docker-compose.recommender.yml pull
docker compose -f docker-compose.yml -f docker-compose.recommender.yml up -d --wait --wait-timeout 900

The model stays in its volume, so an update does not download it again. Then run the checks of Check that it runs: the version line tells you which version answers.

Remove it ​

  1. In .env, set RECOMMENDER_ENABLED=false, so that the installer does not add it again on the next update.

  2. Recreate the stack without the overlay:

    sh
    cd "$STACK_DIR"
    docker compose -f docker-compose.yml up -d --remove-orphans --wait

    The recommender container is removed, PostgreSQL goes back to postgres:17 and Server is recreated without the address of the service. The home page is the standard one for everyone and the Algorithm page disappears.

  3. Check:

    sh
    docker compose exec -T typetype-server wget -qO- http://127.0.0.1:8080/instance

    The answer now contains "recommender":{"installed":false,.

Users' choices, corrections and opinions stay in Server's database, in case you add the recommender again. Nothing else of theirs was ever stored by the service.

To reclaim the disk space, optionally:

sh
docker volume ls | grep recommender_models
docker volume rm <name printed above>
docker compose exec -T postgres sh -c 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "DROP DATABASE typetype_recommender"'

Troubleshooting ​

Read the log of the service first:

sh
cd "$STACK_DIR"
docker compose -f docker-compose.yml -f docker-compose.recommender.yml logs --tail 100 typetype-recommender
What you seeCauseWhat to do
STATUS stays (health: starting) for minutes on first startThe model, about 550 MB, is being downloadedWait. The log shows Application startup complete when it is done.
The log shows a network error before Application startup completeThe model could not be downloaded from Hugging FaceGive the container internet access for the first start. Afterwards the model is read from the volume.
no such service: typetype-recommenderThe command names only docker-compose.ymlAdd -f docker-compose.recommender.yml.
The container is killed and restarts, OOMKilled in docker inspectIt reached RECOMMENDER_MEMORY_LIMITRaise the limit, or keep the defaults of Memory against speed.
/version/recommender answers nothingServer was not recreated with the overlay, or the shared secret is missingRun the two commands of C. By hand, steps 3 and 4, again.
Algorithm is not in the menuThe instance does not offer itAdmin console, Recommender, Offer the experimental home page; then reload the page.
The home page stays the standard oneThe page was not ready in time, or the account has no dataReload after a few seconds. In the admin console, Returns to the standard home page counts how often it happens.
State of the service says Not answeringThe service is down or still startingCheck its status and log as above. Home pages are the standard ones meanwhile.

Source references ​