{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreigaqdwapegrf3dldnfmysqemqc4offvjipynfgmih76atqlaq557y",
    "uri": "at://did:plc:pgryn3ephfd2xgft23qokfzt/app.bsky.feed.post/3mhpdts3pq3p2"
  },
  "path": "/t/how-to-make-2-ai-vtubers-talk-to-eachother-in-vtube-studio/174490#post_5",
  "publishedAt": "2026-03-22T23:38:03.000Z",
  "site": "https://discuss.huggingface.co",
  "tags": [
    "GitHub"
  ],
  "textContent": "Hmm… When multiple programs need to communicate with each other, I think it’s better to use either a separate server process or a separate port in general.\n\nOf course, there are cases where this isn’t feasible due to specific constraints—such as when opening an external port is difficult or when you want to conserve VRAM—but…\n\nSeparating them results in a cleaner implementation and makes it easier to coexist with other services.\n\n* * *\n\nYes. **Technically possible** , but only if both terminals are talking to the **same VTube Studio instance** on `8001`. In VTube Studio, port `8001` is one websocket server. If another VTS instance is already using that port, the next instance moves to `8002`, then higher. So `terminal 1 -> 8001` and `terminal 2 -> 8001` means both terminals are controlling the **same VTS window** , not two separate VTS windows. (GitHub)\n\nThat means your design\n\n  * terminal 1 → `8001` → `MouthOpen`\n  * terminal 2 → `8001` → `CustomMouthParam`\n\n\n\ncan work **only** as a **one-instance design**. VTS explicitly allows plugins to create custom parameters and to feed data into either default or custom parameters. Those parameters then become inputs for the loaded model and any loaded Live2D items in that same instance. (GitHub)\n\nSo the real answer is:\n\n  * **two VTS windows** : use `8001` and `8002`\n  * **one VTS window** : same port is possible, but the second terminal must control a **different parameter** , not the same one. (GitHub)\n\n\n\n## Will the two terminals interfere with each other on the same port?\n\nThey **can** , but not automatically. VTS allows multiple plugins to be connected at once. The API even reports `connectedPlugins` in `StatisticsResponse`, so multiple clients on one instance are a supported case. The interference problem is not “same port” by itself. The real rule is that only **one API plugin can write to one parameter at a time** in normal `\"set\"` mode. If plugin A controls `MouthOpen` and plugin B controls `CustomMouthParam`, that is fine. If both touch `MouthOpen`, VTS returns an error. (GitHub)\n\nSo for your one-port idea to work safely, you need all of these to be true:\n\n  * terminal 1 only sends `MouthOpen`\n  * terminal 2 only sends `YourCustomParam`\n  * neither terminal ever writes the other parameter\n  * your model or model-plus-item mapping is rigged so one character responds to one input and the other responds to the other input. (GitHub)\n\n\n\n## Would this cause more or less issues than what you have now?\n\nFor **your current situation** , I think it would usually cause **more issues** , not fewer. That is because you add another moving part: the custom parameter itself. Custom parameters must be created first, their names must be unique, and VTS will reject creation if a parameter with that name was already created by a different plugin. VTS also stores custom parameters in `custom_parameters.json`, and if a plugin’s authentication is revoked, the custom parameters created by that plugin are deleted. (GitHub)\n\nSo same-port plus custom mouth is valid, but it is **more fragile** than two cleanly separated instances. It adds:\n\n  * socket sharing\n  * plugin coordination\n  * custom parameter lifecycle\n  * rigging complexity. (GitHub)\n\n\n\n## About your token files\n\nUsing **two token files** is the correct direction. That reduces one major failure mode compared with a single shared token file. But it does **not** prove tokens are no longer part of the problem. The official API says you only need to obtain a token once and reuse it, and later authentication will fail unless `pluginName` and `pluginDeveloper` exactly match the values used when the token was created. Wrapper libraries reflect this by making persistent token storage a built-in feature. (GitHub)\n\nSo with two token files, the likely auth mistakes become:\n\n  * terminal 1 reads terminal 2’s file by mistake\n  * you changed `pluginName`\n  * you changed `pluginDeveloper`\n  * reconnect logic requests a fresh token instead of reusing the saved one\n  * one terminal created the custom parameter, but later a different plugin identity tries to manage it. (GitHub)\n\n\n\nThat means your current issue **could still be on your side** , even with two token files. Two files reduce risk. They do not eliminate bad identity matching or bad reconnect logic. (GitHub)\n\n## The mouth behavior problem is probably not only tokens\n\nThere is another rule that matters a lot here: if you want VTS to keep respecting your plugin’s parameter value, you have to resend that parameter **at least once every second**. If you stop, the parameter is considered lost and falls back to whatever controlled it before, or to default. That often looks like “it worked for a bit and then broke.” (GitHub)\n\nSo if your setup works briefly, the cause could be:\n\n  * auth mismatch on reconnect\n  * wrong token file used\n  * the terminal stops sending updates often enough\n  * both terminals touch the same parameter\n  * the custom parameter disappeared because the plugin permission was revoked. (GitHub)\n\n\n\n## My recommendation for your exact case\n\nIf you want the **lowest-risk design** , keep the two-instance layout:\n\n  * terminal 1 → `8001` → `MouthOpen`\n  * terminal 2 → `8002` → `MouthOpen`\n\n\n\nThat is simpler because each VTS instance already has its own `MouthOpen`, its own websocket port, and its own model context. (GitHub)\n\nIf you want the **same-port design** , do it only when you truly want **one VTS instance**. Then use:\n\n  * terminal 1 → `8001` → `MouthOpen`\n  * terminal 2 → `8001` → `HyoriMouth` or similar custom param\n\n\n\nand keep the plugin identities stable, the token files separate, and the parameter ownership strict. That can work, but it is the more delicate setup. (GitHub)\n\nMy blunt answer is:\n\n  * **Possible** : yes\n  * **Cleaner than two instances** : no\n  * **Likely to reduce your current problems** : probably no\n  * **Could the issue still be your implementation even with two token files** : yes. (GitHub)\n\n\n\nThe most likely stable path is still **two VTS instances, two ports, two token files, same standard`MouthOpen`**, with each terminal tied to one port and one plugin identity. (GitHub)\n\n* * *\n\n## Recommended default\n\nUse **two VTube Studio instances** and keep the mouth parameter simple:\n\n  * **terminal 1** → **VTS instance on 8001** → `MouthOpen`\n  * **terminal 2** → **VTS instance on 8002** → `MouthOpen`\n\n\n\nThat matches how VTube Studio handles plugins. The API starts on `8001` by default, and if that port is already taken by another VTS instance, the next instance uses `8002`, then higher. Each running VTS instance also has its own `instanceID`. (GitHub)\n\nMy reason for recommending this as the default is simple: it keeps **instance routing** , **auth** , and **mouth control** separated. Same-port designs are possible, but they add more moving parts. (GitHub)\n\n* * *\n\n## Hard rules\n\n### 1. Freeze plugin identity\n\nFor each terminal, pick one `pluginName` and one `pluginDeveloper` and do not change them casually. VTube Studio requires those values to match the ones used when the token was first requested, or authentication fails. (GitHub)\n\nGood example:\n\n  * terminal 1: `Goober Mouth Driver` / `YourName`\n  * terminal 2: `Hyori Mouth Driver` / `YourName`\n\n\n\nBad example:\n\n  * changing the plugin name every test run\n  * using the same token with a different plugin name later (GitHub)\n\n\n\n### 2. One terminal, one token file\n\nYou are already using two token files. Keep that. Store them separately and never let one terminal read or overwrite the other terminal’s file. Wrapper libraries are built around this exact idea: VTubeStudioJS exposes persistent token getter and setter hooks, and `pyvts` explicitly reads and writes a token file for reuse. (GitHub)\n\nGood example:\n\n\n    tokens/\n      goober_8001.token\n      hyori_8002.token\n\n\n### 3. Request the token once. Reuse it after that.\n\nVTube Studio says you only need to obtain the token once. Later sessions should authenticate with the saved token instead of requesting a new one every time. Re-request only when authentication fails because the token is invalid or permissions were revoked. (GitHub)\n\n### 4. Treat “same port” as “same VTS window”\n\nIf both terminals connect to `8001`, they are both controlling the **same** VTube Studio instance. That is not a two-instance layout. If you want true separation between two VTubers, use `8001` and `8002`. (GitHub)\n\n### 5. One parameter, one owner\n\nOnly one plugin can **set** a given parameter at a time in normal `\"set\"` mode. If two plugins try to control the same parameter, VTS returns an error. This applies to both default and custom parameters. (GitHub)\n\nThat means:\n\n  * safe: terminal 1 controls `MouthOpen`, terminal 2 controls `HyoriMouth`\n  * unsafe: both terminals control `MouthOpen` in `\"set\"` mode (GitHub)\n\n\n\n### 6. Keep the mouth alive\n\nIf you control a parameter through the API, VTS requires you to resend that parameter at least once every second. If you stop, the parameter is considered lost and control falls back to whatever was controlling it before, or to default. (GitHub)\n\nFor mouth animation, that means:\n\n  * send updates continuously while speaking\n  * stop only when you want control to fall back\n  * do not assume “I sent one mouth-open value, so it will stay there” (GitHub)\n\n\n\n### 7. Verify the loaded model before sending mouth values\n\nUse `CurrentModelRequest` after connecting. It tells you whether a model is loaded and returns `modelName` and `modelID`. You can also subscribe to `ModelLoadedEvent` instead of polling. (GitHub)\n\nSafe rule:\n\n  * connect\n  * authenticate\n  * confirm the expected model is loaded\n  * only then start sending mouth values (GitHub)\n\n\n\n### 8. Do not add custom mouth parameters until auth is stable\n\nCustom parameters are valid, but they are tied to plugin ownership. VTS stores them in `custom_parameters.json`, and if that plugin’s permission is revoked, the custom parameters created by that plugin are deleted. They remain referenced in models, but they turn red until recreated. (GitHub)\n\nSo custom parameters are fine when the system is stable. They are a bad first fix when auth and reconnect logic are still unreliable. (GitHub)\n\n### 9. Check rig mapping before blaming the API\n\nIf the parameter appears to move but the mouth does not behave correctly, check the VTS model config. Expressions, animation, and physics can override visible motion. The model settings docs are the place to verify this. (GitHub)\n\n### 10. If you use audio routing, isolate the input\n\nVTube Studio’s voice lipsync uses the selected microphone input, not magic global sound detection. Its lipsync docs recommend keeping the VTS cutoff low and putting a noise gate **before** the audio reaches VTS. That means separate virtual cables can be clean if each VTS instance listens to its own dedicated input. (GitHub)\n\n* * *\n\n## Safe layout for the two-instance design\n\nThis is the safest layout for you.\n\n\n    terminal 1\n      pluginName: Goober Mouth Driver\n      pluginDeveloper: YourName\n      token file: tokens/goober_8001.token\n      target port: 8001\n      target model: WAFFLE LOVING GOOBER\n      parameter: MouthOpen\n\n    terminal 2\n      pluginName: Hyori Mouth Driver\n      pluginDeveloper: YourName\n      token file: tokens/hyori_8002.token\n      target port: 8002\n      target model: hyori\n      parameter: MouthOpen\n\n\nStartup flow:\n\n  1. connect to assigned port\n  2. authenticate with that terminal’s saved token\n  3. if auth fails, request a new token and overwrite only that terminal’s token file\n  4. call `CurrentModelRequest`\n  5. confirm expected model\n  6. stream `MouthOpen` while speaking\n  7. on disconnect, reconnect to the same port and reuse the same token (GitHub)\n\n\n\n* * *\n\n## Safe layout for the same-port custom-parameter design\n\nOnly use this if you intentionally want **one VTS instance**.\n\n\n    terminal 1 -> 8001 -> MouthOpen\n    terminal 2 -> 8001 -> HyoriMouth\n\n\nRules for this design:\n\n  * terminal 1 never touches `HyoriMouth`\n  * terminal 2 never touches `MouthOpen`\n  * the custom parameter is created once and by a stable plugin identity\n  * the model mapping is rigged so each character responds only to its intended input\n  * both terminals still need their own token files and stable plugin identities (GitHub)\n\n\n\nThis works, but it is more fragile than the two-instance design because you are adding custom-parameter lifecycle and shared-instance coordination on top of the existing auth rules. (GitHub)\n\n* * *\n\n## What your two token files do and do not prove\n\nUsing two token files is good. It removes one big failure mode: accidental sharing of one token file across two clients. Wrapper libraries strongly suggest this kind of persistent, per-client token storage. (GitHub)\n\nBut two files do **not** prove the issue is no longer auth-related. You can still break authentication by:\n\n  * reading the wrong file\n  * changing `pluginName`\n  * changing `pluginDeveloper`\n  * requesting a new token when you should reuse the old one (GitHub)\n\n\n\nSo with two token files already in place, the likely causes become:\n\n  * identity mismatch\n  * wrong port\n  * wrong model loaded\n  * two clients touching the same parameter\n  * mouth updates not being resent often enough\n  * custom parameter ownership or deletion if you went that route (GitHub)\n\n\n\n* * *\n\n## Do / do not\n\n### Do\n\n  * use `8001` and `8002` if you want two truly separate VTS windows (GitHub)\n  * keep one stable plugin identity per terminal (GitHub)\n  * keep one token file per terminal (GitHub)\n  * verify the loaded model before starting mouth control (GitHub)\n  * resend mouth values continuously while speaking (GitHub)\n\n\n\n### Do not\n\n  * share a token file between terminals (GitHub)\n  * change `pluginName` or `pluginDeveloper` after the token exists (GitHub)\n  * let two terminals control the same parameter in `\"set\"` mode (GitHub)\n  * switch to custom parameters before auth and reconnect logic are stable (GitHub)\n\n\n\n* * *\n\n## My concrete recommendation\n\nFor your case, the safest practice is:\n\n  1. stay with **two VTS instances**\n  2. keep **standard`MouthOpen`** on each\n  3. keep **two token files**\n  4. freeze each terminal’s plugin identity\n  5. verify the model after connect\n  6. continuously resend mouth while that terminal’s TTS is speaking\n\n\n\nI would only move to **same-port plus custom mouth parameter** if you explicitly want **one VTS window** and are willing to manage the extra fragility that comes with plugin-owned custom parameters. (GitHub)",
  "title": "How to make 2 ai vtubers talk to eachother in vtube studio?"
}