Author: demensdeum

  • DeepSeek Harness: installation, capabilities and first launch

    DeepSeek Harness, or dsh for short, is an open source AI agent harness from DeepSeek AI. If the model answers questions, then the harness gives her hands: reads and edits files, runs commands, holds sessions, shows the execution trajectory and allows itself to be expanded with plugins. The project is distributed under the MIT license and is in public preview.

    The main idea of ​​DeepSeek Harness is formulated as “everything is a plugin” – literally “everything is a plugin”. The entire product is built on this: model adapters, a tool registry, a session log, an interface, and even the agent cycle itself. It is based on the Cordis framework, so a developer can not just add a new command, but replace the entire behavior of the agent without forking the kernel.

    You can work with dsh in several ways: through the local web interface, through a desktop application, from the command line in headless mode, or from the Python SDK. Below is installation, first launch and an overview of the capabilities.

    What you need

    The web interface and CLI require Node.js. The project has strict requirements:

    • Node.js 22.19.0 or later in the 22.x line.
    • Node.js 24.0.0 or later in the 24.x line.
    • Node.js 23.x is not supported in any patch: this line is not LTS and has already been discontinued.

    In addition to Node.js, you will need the DeepSeek API key. It is created in your personal account on platform.deepseek.com. The desktop application does not need Node.js – the environment is already packaged there.

    You can check the version with the command:

    node -v
    

    Installation via npx

    The fastest way to get to know the project is to not install anything globally at all. Just run:

    npx @deepseek-ai/dsh web
    

    The command will download the @deepseek-ai/dsh package from npm, set up a local web server and open the interface in the browser. By default it listens to the address:

    If you don’t need to open the browser, for example on a remote machine, add the –no-open flag:

    npx @deepseek-ai/dsh web --no-open
    

    When launched via SSH, dsh prints the address but does not attempt to open the browser: the local address is owned by the SSH client or editor.

    Global installation

    If you need a tool constantly, it is more convenient to install it globally. The package provides the dsh binary:

    npm install -g @deepseek-ai/dsh
    dsh web
    

    After this, the dsh command is available from any directory. The update is performed in the usual way:

    npm install -g @deepseek-ai/dsh@latest
    

    Desktop application

    For those who don’t want to hold a terminal and Node.js, DeepSeek releases desktop builds. They are available on the official download page:

    • macOS – Apple silicon, macOS 13 or later, DMG format.
    • Windows – 64-bit assembly, EXE format.

    The application works with the same local files, can perform tasks in the background and stores sessions in the same way as the web version. This is a convenient option for everyday work, when you don’t want to raise the server manually every time.

    Building from sources

    If you need full control over the code, your own patches or plugin development, the project is collected from the repository. You will need pnpm, and the repository fixes a specific version, so you should first enable Corepack:

    corepack enable
    git clone https://github.com/deepseek-ai/deepseek-harness.git
    cd deepseek-harness
    pnpm install
    pnpm run build
    pnpm dsh web
    

    The pnpm run build command prepares package and frontend artifacts, and pnpm dsh web runs the already built version without rebuilding. For development, there is a mode pnpm run dev:web: it builds, starts the server and rebuilds client bundles when the sources change.

    First launch

    After starting the web interface, the procedure is as follows.

    First, open Settings → Models and enter the DeepSeek API key. The key is saved in the file $DSH_HOME/.credentials.yaml, and only a link to it remains in the settings. The settings page receives an already masked description of the secret, so it is impossible to read the key back from the interface. Model changes are applied from the next request; there is no need to restart the server.

    Then select your work folder via Choose workspace. While the workspace is not selected, the task input field remains grayed out. This is done intentionally: the agent must understand the boundaries within which it is allowed to read and change files.

    After this, you can submit the first task. For example:

    Summarize this repository and identify its main packages.
    

    The agent will read the project, draw up a plan, call the necessary tools and show the result. For operations that require confirmation based on the current access policy, dsh will ask before executing.

    Models and providers

    DeepSeek connects in one step: the provider card in the settings contains only a field for the API key. Additionally, you can add third-party services. The built-in directory knows identifiers like anthropic, openai, moonshotai for Kimi and zai for GLM.

    For your own gateway, corporate proxy or local server, there is a Custom model API mode. It asks:

    • permanent provider identifier;
    • display name;
    • API base address;
    • protocol: OpenAI Chat Completions, OpenAI Responses or Anthropic Messages;
    • key and list of models.

    The protocol is important: one provider speaks one protocol. If a gateway supports both OpenAI and Anthropic, it will have to be described by two providers. For models, you can specify the size of the context window, the maximum number of output tokens, and supported input data types, including images.

    DeepSeek models have off, low, high and max reasoning levels. The level is selected in the Effort menu next to the model. For routine tasks in the repository, a fast model is sufficient, but for complex architectural tasks it makes sense to increase the effort or take a higher-class model.

    Plugins and Creator mode

    Plugins are the main feature of DeepSeek Harness. Through them, tools, skills, interface elements and entire applications are added. The interface has a plugin manager with official and experimental sets: agent commands, auto-confirmation, scheduled tasks, voice input, terminal, subagents.

    The Creator mode stands out separately. In this mode, you can ask the agent to write a plugin directly in the chat. It will download the Cordis plugin development skill, create a package structure, write code, install the plugin and check its status. There is an example on the official project page where a floating Pomodoro timer is assembled in this way.

    Plugins can also be installed from the command line. For a web profile, the command looks like this:

    dsh plugin --profile web add some-dsh-plugin
    

    Npm packages, links like github:owner/repo, local directories and tgz archives are supported. After installation, it is useful to check that the plugin layer is actually included in the configuration:

    dsh --profile web --dump-config
    

    The command prints the assembled plugin tree without starting the server. If there is no new layer, the bundle most likely did not declare a dsh.bundle manifest and installed as a regular library rather than as an included plugin.

    The experimental settings include Custom plugin markets. This is an index JSON file with a list of plugins, their versions, icons and sources. By enabling the set, you get a Discover tab on the plugins page and the ability to connect third-party markets. Like any third-party source, such a market should be added only if you trust its authors.

    Trajectory and audit

    For each session, dsh writes an append-only event log – Trajectory. It stores the system prompt, reasoning, tool calls and their results, subagent scheduling, and context insertions. Session resumption, forks, search and replay are built on this log.

    The practical benefit is that the conversation ceases to be a black box. In the interface, you can open the trajectory and see which tools were called, how long each step took, which files were changed, and how many tokens were spent on the request. If an agent behaved strangely, the reason is usually visible here, and not in a short answer in the chat.

    Sessions are located in ~/.dsh/sessions in the form of JSONL logs. You can read them, search for them and build your own analytics. This also makes it possible to accurately calculate expenses and cache hits that are not visible in a regular API bill.

    Background tasks and schedules

    DeepSeek Harness can work not only in the “ask and receive an answer” mode. Tasks can be launched in the background and scheduled. For example, ask the agent to collect a weekly report from the project notes every Friday at 17:00. In the interface, such tasks are visible separately: you can check the status, view the history and open execution details.

    There is a headless profile for automation. It starts one session, prints the final response, and exits:

    dsh --profile headless "run the tests"
    

    This mode is convenient for CI, cron and scripts where a web interface is not needed. In addition to headless, the sdk and acp profiles are available: the first serves SDK clients via JSON-RPC, the second serves automation clients via ACP stdio.

    Subagents, MCPs and skills

    A complex task can be decomposed among subagents. The main agent plans the work, and individual subagents work on their areas. This helps keep the context compact and not dragging the entire history into each request.

    External tools and data sources are connected via MCP. The documentation includes a separate guide to memory MCP, as well as integrations like GitHub review sessions. Skills describe repeatable procedures that the agent loads as needed, rather than keeping all the instructions in a system prompt all the time.

    Profiles, bundles and patches

    The dsh configuration is assembled from three layers. Bundle is a set of configuration lines and plugin code, that is, a ready-made piece of functionality. Profile is a named composition of bundles, which is stored in $DSH_HOME/profiles/. Patch is a YAML file that changes already loaded lines by identifier, disables them, or adds new ones.

    The order of application is as follows: first the bundles in profile order, then the profile patch, then the general patch $DSH_HOME/cordis.patch.yml and at the end the overlays transmitted via –patch. This means that almost any setting can be overridden without touching the bundle sources. You can view the resulting tree with the command:

    dsh --profile web --dump-config
    

    A standard web profile is called web, a serverless profile is called headless. Their settings files live in $DSH_HOME/profiles/web/cordis.patch.yml and $DSH_HOME/profiles/headless/cordis.patch.yml. This is where dsh writes changes made through the settings interface.

    Python SDK

    There is an official SDK for Python projects. It is installed in a virtual environment and brings with it a native runtime, so the system Node.js is not needed for normal work:

    python -m venv .venv
    .venv\Scripts\Activate.ps1
    python -m pip install deepseek-harness-sdk
    

    A minimal startup example looks like this:

    from pathlib import Path
    
    from deepseek_harness import DeepSeekHarness
    
    workspace = Path("C:/work/disposable-workspace").resolve()
    dsh_home = Path("C:/work/example-dsh-home").resolve()
    
    with DeepSeekHarness(
        provider="deepseek-official",
        model="deepseek-v4-flash",
        max_tokens=49_152,
        cwd=str(workspace),
        dsh_home=str(dsh_home),
        profile="sdk-minimal",
    ) as harness:
        result = harness.run(
            "Inspect the repository and fix the failing tests.",
            session_id="example-001",
        )
    
    print(result.final_response)
    

    The SDK starts the bundled dsh process with the sdk-minimal profile and reuses it before exiting the context manager. The minimal profile is intentionally limited: it has bash or pwsh, image reading, and a working directory, but no web tools, subagents, or some of the file tools. For one-time tasks, this is a plus: there is less surface to control.

    What to pay attention to

    DeepSeek Harness is a developer preview version 0.1.0-rc.x. The README explicitly warns that compatibility will break. For experiments, personal projects and automation, this is fine, but for a process that makes money right now, it’s worth weighing the risk and keeping a backup plan.

    The second point is Node.js. If you have 22.18 or 23.x, the installation will crash or behave unpredictably. Check the version in advance: the project uses node:sqlite and TypeScript’s built-in erasure of types that need a fresh branch.

    The third point is the sandbox and rights. By default, the agent operates in a policy close to workspace-write: it can change files in the selected folder, and potentially dangerous operations require confirmation. The danger-full-access mode gives more freedom, but the risk becomes higher. Before experimenting with a real repository, it is useful to make a commit or backup.

    The fourth point is cost. DSH itself is free, but for inference you pay the provider in tokens. For long sessions, prefix caching is important: dsh composes requests so that the history and tool schemas remain unchanged for as long as possible. If the score turns out to be higher than expected, look not only at the price per token, but also at the share of cash hits in the trajectory.

    Is it worth trying

    DeepSeek Harness is of interest primarily to those who want not just “another chat with a model,” but a controlled environment for agents. There is a transparent trajectory, normal work with files and commands, background tasks, subagents and truly deep expansion through plugins. The ability to replace an agent loop with your own is rare and potentially very useful for research and custom orchestration.

    If you need a stable tool for daily paid work, it is now wiser to treat dsh as a second agent next to the main one: try it on a personal project, compare the cost and quality of your tasks, and only then transfer it to the workflow.

    Links

    https://www.deepseek.com/harness/
    https://www.deepseek.com/download/
    https://github.com/deepseek-ai/deepseek-harness
    https://deepseek-harness.github.io/deepseek-harness/en/guide/quickstart
    https://deepseek-harness.github.io/deepseek-harness/en/guide/providers
    https://deepseek-harness.github.io/deepseek-harness/en/guide/python-sdk
    https://platform.deepseek.com/

    Sources

    https://github.com/deepseek-ai/deepseek-harness/blob/master/README.md
    https://github.com/deepseek-ai/deepseek-harness/blob/master/apps/cli/README.md
    https://deepseek-harness.github.io/deepseek-harness/en/guide/quickstart
    https://deepseek-harness.github.io/deepseek-harness/en/guide/providers
    https://deepseek-harness.github.io/deepseek-harness/en/guide/python-sdk
    https://deepseek-harness.github.io/deepseek-harness/en/guide/plugin-market
    https://dshdocs.com/getting-started/installation/
    https://dshdocs.com/concepts/profiles-bundles-patches/
    https://dshdocs.com/guides/dsh-vs-claude-code/

  • Jev solution models in Ollama: practical use /v1/systemone

    Starting from version 0.35, Ollama supports decision models built on the Jev API from TypeSafe. These are not chat models in the usual sense: they do not write text, but answer a set of typed questions about the transferred state. For classification, routing, moderation and triage, this turns out to be faster and more reliable than asking a regular LLM to return JSON and then parsing its response.

    In this post, I’ll look at what decision models are, how they work at the HTTP level, what models are already available, and how to build them into existing code so that regular generative models continue to work.

    How do decision models differ from regular ones

    A regular LLM receives a prompt and generates text token by token. Even if you ask her to answer in one word, generation still occurs inside, and the format of the answer has to be controlled by a diagram, instructions and subsequent verification. The Decision model works differently: you pass state and questions with types, and the model returns probabilities and selected options. No free text, no parsing JSON from the response.

    The new endpoint is called /v1/systemone. In one request, you can ask several questions at once, and the model will answer all of them. This is convenient for tasks where you need to make several related decisions: for example, determining the team, the need for a return, and the urgency of the ticket.

    What models are available

    Now you can run three decision models through Ollama:

    • nimble is an open model with 9 billion parameters from Bespoke Labs. It is the largest and most accurate available.
    • tev1 is an experimental model with 4 billion parameters from Together AI.
    • tev1:0.8b is an experimental model with 0.8 billion parameters from Together AI. The fastest and easiest.

    They all work locally, do not require payment and do not send data to an external service. Ollama 0.35 and later are supported.

    Installation

    First check the Ollama version:

    ollama --version
    

    Then download the desired model. For the easiest one:

    ollama pull tev1:0.8b
    

    For the full version of tev1:

    ollama pull tev1
    

    For nimble:

    ollama pull nimble
    

    The nimble model takes up about 9.5 GB of disk space, so it’s a good idea to clear some space before downloading. tev1:0.8b takes up less than a gigabyte.

    First request via curl

    The simplest example is to determine whether a text is a job advertisement:

    curl http://localhost:11434/v1/systemone -d '{
      "model": "tev1:0.8b",
      "state": {
        "message": "Senior Swift developer, Berlin, remote friendly"
      },
      "questions": {
        "isJob": {
          "type": "noul",
          "instructions": "Does the message advertise a job posting that someone can apply to?"
        }
      }
    }'
    

    The answer looks like this:

    {
      "model": "tev1:0.8b",
      "answers": {
        "isJob": {
          "type": "noul",
          "noul": 0.921
        }
      },
      "usage": {
        "input_tokens": 42,
        "output_tokens": 0
      }
    }
    

    The noul field is a probability from 0 to 1. The decision is made by comparison with a threshold, usually 0.5. Unlike the generative model, there is no need to ask for a response in JSON format and then check that the model did not break it.

    Types of questions

    Decision models support three types of questions.

    noul is a binary question. Returns the probability that a statement is true:

    {
      "questions": {
        "refund": {
          "type": "noul",
          "instructions": "Does the customer explicitly ask for a refund?"
        }
      }
    }
    

    choice – selecting one option from a given set. Returns the selected option, the probabilities of all options and confidence:

    {
      "questions": {
        "team": {
          "type": "choice",
          "instructions": "Which team should handle this ticket?",
          "criteria": {
            "billing": "Payments and refunds",
            "technical": "Bugs and integrations",
            "other": "None of the above"
          }
        }
      }
    }
    

    score – rating on a scale. Returns the number, legend, and probabilities for each scale point:

    {
      "questions": {
        "urgency": {
          "type": "score",
          "instructions": "How urgent is this ticket?",
          "criteria": ["Routine", "Soon", "Urgent"]
        }
      }
    }
    

    One request can contain several questions of different types at once. This is what makes decision models convenient for triage: one operation, one state, several independent decisions.

    Python without additional libraries

    For requests, the standard urllib is sufficient; a separate SDK is not required:

    import json
    from urllib.request import Request, urlopen
    
    OLLAMA_DECISION_URL = "http://localhost:11434/v1/systemone"
    
    def ask_decision(state, questions, model="tev1:0.8b"):
        payload = {
            "model": model,
            "state": state,
            "questions": questions,
        }
        request = Request(
            OLLAMA_DECISION_URL,
            data=json.dumps(payload).encode(),
            headers={"Content-Type": "application/json"},
            method="POST",
        )
        with urlopen(request, timeout=3600) as response:
            return json.loads(response.read().decode())["answers"]
    

    Usage:

    answers = ask_decision(
        {"message": "Senior Swift developer, Berlin, remote friendly"},
        {
            "isJob": {
                "type": "noul",
                "instructions": "Does the message advertise a job posting that someone can apply to?"
            }
        },
    )
    
    probability = answers["isJob"]["noul"]
    is_job = probability >= 0.5
    

    If you need an official Python SDK, it is called typesafe-sdk. For localhost, it can be directed to Ollama with the environment variables TYPESAFE_BASE_URL, TYPESAFE_API_KEY and TYPESAFE_DEFAULT_MODEL. But for simple tasks, a direct HTTP request is sufficient.

    How to understand that a model supports decision

    Decision models cannot respond via /api/generate or /api/chat. If you send such a request, Ollama will return a 400 error with text like:

    "tev1:0.8b" does not support generate
    

    This is important if you already have code that works with regular models via /api/generate: if you change the model to a decision model, it will stop working. It’s better to find out the model’s capabilities in advance via /api/show and choose the appropriate calling method:

    import json
    from urllib.request import Request, urlopen
    
    OLLAMA_SHOW_URL = "http://localhost:11434/api/show"
    
    def supports_decision(model):
        request = Request(
            OLLAMA_SHOW_URL,
            data=json.dumps({"model": model}).encode(),
            headers={"Content-Type": "application/json"},
            method="POST",
        )
        try:
            with urlopen(request, timeout=30) as response:
                capabilities = json.loads(response.read().decode()).get("capabilities", [])
        except (OSError, TypeError, ValueError):
            return False
        return "decision" in capabilities
    

    Decision models will have only decision in their capabilities. Regular models will have completion, tools or other features there. If Ollama is not available, it is reasonable to consider the model normal and use the generative path.

    How to embed into existing code

    The simplest way is not to rewrite the classifier, but to divide it into two strategies. The generative strategy works as before: it collects the prompt, sends it to /api/generate and parses the JSON. The decision strategy sends the state and typed question to /v1/systemone and compares the probability with a threshold.

    The choice of strategy occurs in one place, when creating a classifier:

    def create_classifier(model):
        if supports_decision(model):
            return DecisionClassifier(model)
        return GenerativeClassifier(model)
    

    This approach has several advantages. Generative models continue to run unchanged. The decision model can be enabled by changing one environment variable. And the entire difference between the two methods is concentrated in two small classes, and is not spread out throughout the application.

    For a binary classification, the decision strategy looks something like this:

    async def classify(text):
        answers = await ask_decision(
            {"message": text},
            {
                "isJob": {
                    "type": "noul",
                    "instructions": instructions,
                }
            },
            model,
        )
        probability = answers["isJob"]["noul"]
        return probability >= 0.5, probability
    

    Please note that the decision model does not return an explanation. If your result format has a reason field, you can construct it from the probability: for example, store the probability itself or a string like “the model rated the message at 0.935 as a vacancy.” This is more honest than inventing a reason that the model did not give.

    Instructions should be a question

    This is the most important practical conclusion. Decision models do not understand long prompts in the style of “you classify messages, answer true if…, false otherwise.” They want a short, direct question.

    On the same message about a vacancy, I received the following probabilities:

    • Direct question “Is this message a job posting?” — 0.921.
    • Long instructions explaining what is considered a vacancy – 0.026.
    • Long instructions with a question at the end – 0.391.

    The difference is almost thirty times. The model is not mistaken in classification, it simply does not understand what they want from it when the question is hidden inside a paragraph. Therefore, for decision models it is worth keeping separate short instructions, and leaving long prompts for generative models.

    It is convenient to create a separate environment variable:

    OLLAMA_JOB_CLASSIFIER_DECISION_INSTRUCTIONS=Does the message advertise a job posting that someone can apply to?
    

    And use a regular prompt only for the generative path.

    Threshold and probabilities

    The threshold of 0.5 is suitable as a default value, but it is useful to include it in the configuration. For different tasks, the cost of a false positive and false negative response is different. For moderation, it is safer to raise the threshold; for searching for vacancies, it is safer to lower it, so as not to miss useful messages.

    It is also useful to log the probability itself, and not just the resulting boolean. When the classifier makes an error, the log shows whether it was a confident answer or a decision on the border. For choice questions, it is worth saving probabilities and confidence: they show how much the model fluctuates between options.

    Performance and memory

    The speed greatly depends on the size of the model and the hardware. On the CPU in my tests I got the following numbers:

    • tev1:0.8b – split seconds to solve, easily fits into memory.
    • tev1 – 4B, noticeably slower, but still suitable for interactive tasks.
    • nimble – 9B in Q8_0, about 10 GB in memory. The first request, including loading the model, took about 137 seconds, subsequent solutions took from 12 to 32 seconds on the CPU.

    Nimble requires a lot of RAM. On a machine with 16 GB and Docker/WSL with a limit of about 8 GB, the model does not fit: the system begins to swap and the containers stop responding. Increasing the WSL limit to 12 GB via the .wslconfig file helped:

    [wsl2]
    memory=12GB
    swap=8GB
    

    After the change, you need wsl –shutdown and restart Docker Desktop. But even with 12 GB, the reserve remains small: the model takes up about 10 GB, and there is not much left for the system.

    By default, Ollama unloads the model after five minutes of inactivity, freeing up memory. This is convenient: nimble does not keep 10 GB busy all the time, but each new launch after a pause starts loading the model again.

    When to use decision models

    Decision models are well suited for tasks where you need a quick and cheap answer to a clearly formulated question:

    • Classification of messages, tickets and requests.
    • Routing by commands or executors.
    • Content moderation and security checks.
    • Fast binary and multivariate solutions in gaming or interactive applications.

    They are poorly suited for tasks where detailed text is needed: explanation, field extraction, summarization, answer generation. For this purpose, the usual generative models remain. The best result is obtained when combined: a decision model makes a decision, and a generative model explains it or extracts data.

    Total

    Decision models in Ollama are a separate, narrower tool than regular LLMs. They don’t replace generative models, but they take away the routine part of the work: typed responses, probabilities instead of text parsing, and a predictable format. If you already have a classification pipeline, the transition comes down to checking the capabilities of the model, a separate calling strategy, and short instructional questions.

    Sources
    https://ollama.com/blog/ollama-now-supports-jev-style-decision-models
    https://ollama.com/library/nimble
    https://ollama.com/library/tev1
    https://typesafe.ai

  • Free models for OpenCode via FreeLLMAPI

    OpenCode is an open programming agent that works in the terminal: reads project files, edits code, runs commands and tests, searches the repository and works with git. It is not tied to one model provider, so almost any OpenAI-compatible service can be connected to it.

    FreeLLMAPI is an open self-hosted router that collects free plans from dozens of AI providers under one OpenAI-compatible /v1 address. Instead of thirty keys, thirty limits and thirty places where a request can fail, you have one local gateway and one unified key. The router itself selects an available model, switches to the next one when error 429 or 5xx occurs, and makes sure that requests do not exceed the quotas of free tariffs.

    In this post I will describe installing FreeLLMAPI, adding providers and connecting the gateway to OpenCode.

    What you need

    To work you need four things:

    • OpenCode installed. If the agent is not yet installed, I discussed the installation procedure in a separate note about OpenCode and DeepSeek.
    • FreeLLMAPI running. The easiest way is to install a desktop application or Docker container.
    • Node.js 20.18 or later. This is needed for the CLI generator that configures OpenCode. OpenCode itself can be installed without Node.
    • Keys of at least one provider. Free plans of Google AI Studio, Groq, Cerebras, Mistral, OpenRouter, NVIDIA, HuggingFace and other services from the catalog are suitable.

    Installing FreeLLMAPI

    The project has several installation paths. Choose the one that is more convenient for your system.

    Desktop application

    The easiest option for Windows and macOS is the ready-made installer from the releases page. On Windows it is .exe, on macOS it is .dmg for Apple Silicon or Intel. The application lives in the tray and raises the router along with the control panel locally.

    A password is not needed for the desktop version: the panel logs in on its own through a hidden local account. The only credentials you will need is the unified key from the tray pop-up window or from the Keys page header.

    The application data is located in one directory:

    • Windows — %APPDATA%\FreeLLMAPI\
    • macOS — ~/Library/Application Support/FreeLLMAPI/
    • Linux — ~/.config/FreeLLMAPI/

    Docker

    If Docker is already installed, the server is raised with one command. The script creates the ~/freellmapi directory, generates an encryption key, downloads the image and starts the container:

    curl -fsSL https://freellmapi.co/install.sh | bash
    

    Running the script again is safe: the .env file and encryption key are saved, and the container is updated to the latest version. On Windows, this method works in WSL or any bash environment.

    After launch, the panel opens at:

    http://localhost:3001
    

    For server installation, you need to create an account with an email and password. If the password is forgotten, the reset code is printed in the container log:

    docker compose logs -f freellmapi
    

    Docker Compose

    If you want to keep the configuration in git and manage the launch manually, regular Compose will do. On macOS and Linux:

    git clone https://github.com/tashfeenahmed/freellmapi.git
    cd freellmapi
    ENCRYPTION_KEY="$(openssl rand -hex 32)"
    printf "ENCRYPTION_KEY=%s\nPORT=3001\n" "$ENCRYPTION_KEY" > .env
    docker compose up -d
    

    In Windows PowerShell:

    git clone https://github.com/tashfeenahmed/freellmapi.git
    cd freellmapi
    $Bytes = New-Object Byte[] 32
    [Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($Bytes)
    $ENCRYPTION_KEY = -join ($Bytes | ForEach-Object { "{0:x2}" -f $_ })
    "ENCRYPTION_KEY=$ENCRYPTION_KEY`nPORT=3001" | Out-File -Encoding utf8 .env
    docker compose up -d
    

    The ENCRYPTION_KEY key is required: it is used to encrypt provider keys in the SQLite database. You cannot change it on the fly – old secrets will no longer be decrypted. For rotation, the project has a separate command rotate-encryption-key.

    Running from source

    For development or debugging, you can run the project through Node.js:

    git clone https://github.com/tashfeenahmed/freellmapi.git
    cd freellmapi
    npm install
    ENCRYPTION_KEY="$(node -e 'console.log(require("crypto").randomBytes(32).toString("hex"))')"
    printf "ENCRYPTION_KEY=%s\nPORT=3001\n" "$ENCRYPTION_KEY" > .env
    npm run dev
    

    In this mode, the API and panel are raised on port 3001, and the Vite interface is raised on 5173.

    First launch and provider keys

    Open the panel, go to the Keys page and add the keys of the services we use. For each provider, the interface has a link to registration and a hint where to get the key. It’s enough to start with one or two: for example, Google AI Studio and Groq.

    Once added, the key undergoes a health check. If the provider responded with an error, the key will remain in the list, but will be marked as unhealthy – it can be checked again or deleted.

    Next, you should go to the Models page and see what models are available. There you can also configure the routing strategy and the order of the Fallback Chain – the chain along which the router goes in the event of errors and limits. The auto model means that the choice is made by the router itself. In addition to it, options like auto:fast, auto:smart and named profiles for specific tasks are available.

    It’s also worth checking that there are models in the chain that support tools. OpenCode actively uses tool calling, and a model without this feature will not be able to read files or run commands normally.

    The Unified key is taken there, on the Keys page, or in the tray of the desktop application. It looks like a string with a prefix:

    freellmapi-...
    

    It is this key, and not the keys of individual providers, that is indicated in OpenCode.

    Connect to OpenCode

    FreeLLMAPI has a configuration generator that itself reads the model directory from the running gateway, makes a backup copy of the existing config and carefully adds the provider to it.

    First, let’s set the unified key to the environment variable. The generator reads it instead of a separate flag, so the secret will not end up in the command history.

    On macOS and Linux:

    export FREELLMAPI_API_KEY=freellmapi-YOUR_KEY
    

    In Windows PowerShell:

    $env:FREELLMAPI_API_KEY="freellmapi-YOUR_KEY"
    

    Now let’s see what exactly will be recorded:

    npx freellmapi setup-opencode --url http://localhost:3001 --dry-run
    

    If diff is satisfactory, repeat the command without the flag:

    npx freellmapi setup-opencode --url http://localhost:3001
    

    The generator writes the global OpenCode config:

    ~/.config/opencode/opencode.json
    

    The freellmapi provider appears in it with the @ai-sdk/openai-compatible package, the base address http://localhost:3001/v1 and a map of models from the live catalog. The default model becomes freellmapi/auto.

    The key itself does not end up in the file: the config contains the substitution {env:FREELLMAPI_API_KEY}. Therefore, the variable must remain set when the agent is started:

    opencode
    

    This method is also convenient because the secret does not end up in the config and will not end up in git. For CI and one-time runs, this is more correct than storing the key in a file.

    Manual setting

    If you don’t want to run the generator, you can manually describe the provider in opencode.json. The minimum option looks like this:

    {
      "$schema": "https://opencode.ai/config.json",
      "model": "freellmapi/auto",
      "provider": {
        "freellmapi": {
          "npm": "@ai-sdk/openai-compatible",
          "name": "FreeLLMAPI",
          "options": {
            "baseURL": "http://localhost:3001/v1",
            "apiKey": "{env:FREELLMAPI_API_KEY}"
          },
          "models": {
            "auto": {
              "name": "FreeLLMAPI Auto Router",
              "limit": { "context": 1048576, "output": 65536 }
            }
          }
        }
      }
    }
    

    The model field at the top level is important: without it, OpenCode may continue to use the previous model or not select anything on a fresh installation. It is better to take the context and output limits from the gateway response, rather than invent them:

    curl http://localhost:3001/v1/models
    

    You can add specific identifiers to the models list if you need to switch to them manually. But for everyday work, auto is enough: the router itself will select a live model and switch when the limit is reached.

    How to use it

    After launching opencode, the list of models opens with a familiar command:

    /models
    

    It will have a FreeLLMAPI provider and an auto model. Then the work proceeds as usual: we describe the task, specify files via @, switch the planning mode with the Tab key, roll back the edits with the /undo command. For a new project, it is useful to run /init to have the agent create AGENTS.md.

    A couple of practical tips:

    • Routing. For routine, leave it at auto. If you need speed, try auto:fast, if the quality is auto:smart or a specific strong model from the catalog.
    • Diagnostics Each gateway response contains an X-Routed-Via header with the name of the provider and model that actually served the request. It shows where the router sent the task.
    • Analytics. On the Analytics page, the panel shows the volume of requests, success, p50/p95 delays and token consumption by provider.
    • Continuity of dialogue. When switching models in the middle of a conversation, the gateway can add a short note about the transfer of context. Enabled by the variable FREELLMAPI_CONTEXT_HANDOFF=on_model_switch.

    If OpenCode is running in a container and FreeLLMAPI is running on a host, localhost will point to the container itself. In this case, you need to use host.docker.internal or another address accessible from the container in the baseURL.

    What to pay attention to

    FreeLLMAPI is not a replacement for paid models and not a service with an SLA. It’s a way to bundle free plans into one neat gateway. Hence there are several restrictions:

    • Quality fluctuates. Free tariffs end, and late in the evening, closer to the exhaustion of daily quotas, the router may switch to weaker models. Quotas usually reset at midnight UTC.
    • Latencies are variable. Different providers respond differently, and when switching to a backup model, the request may take noticeably longer.
    • There are no flagship models. Good, but not top-end, models are available in free plans. For complex architectures this is worth considering.
    • The gateway must be running. If the application or container is stopped, OpenCode will not receive a response. Autostarting the application in the tray or restarting the container policy solves this problem.
    • The project is designed for one user. Provider keys are stored locally and encrypted, but a shared panel for several people is not the scenario for this tool. The Unified key and .env should be protected in the same way as a regular secret.

    And one more nuance: the model catalog updates itself, but the free installation receives a snapshot of the catalog with a delay of about thirty days. The Premium paid plan speeds up the update, but it is not needed to get acquainted with the project – the router itself remains free and open.

    Links

    https://github.com/tashfeenahmed/freellmapi
    https://freellmapi.co/
    https://freellmapi.co/models
    https://github.com/tashfeenahmed/freellmapi/releases/latest
    https://github.com/tashfeenahmed/freellmapi/blob/main/docs/en/install/01-install.md
    https://github.com/tashfeenahmed/freellmapi/blob/main/docs/en/clients/01-agent-clients.md
    https://github.com/tashfeenahmed/freellmapi/blob/main/cli/README.md
    https://opencode.ai/
    https://opencode.ai/docs/
    https://opencode.ai/docs/providers/

    Sources

    https://github.com/tashfeenahmed/freellmapi
    https://github.com/tashfeenahmed/freellmapi/blob/main/README.ru.md
    https://github.com/tashfeenahmed/freellmapi/blob/main/docs/en/install/01-install.md
    https://github.com/tashfeenahmed/freellmapi/blob/main/docs/en/clients/01-agent-clients.md
    https://github.com/tashfeenahmed/freellmapi/blob/main/docs/en/clients/02-supported-agents.md
    https://github.com/tashfeenahmed/freellmapi/blob/main/cli/README.md
    https://opencode.ai/docs/providers/

  • Russian AI providers for OpenCode

    OpenCode is an open programming agent that works directly in the terminal: reads project files, edits code, runs commands and tests, searches the repository and works with git. Its strength is that it is not tied to one model provider and can connect dozens of providers.

    GigaChat is a large language model from Sber. It is not in the ready list of OpenCode providers, but this is not a problem: OpenCode connects arbitrary providers through Vercel AI SDK packages. For GigaChat there is a package gigachat-ai-sdk-provider, which takes care of OAuth authorization and updating the access token. A separate nuance is the Ministry of Digital Development certificates: without them, Node does not trust GigaChat servers, and requests fail with a certificate verification error.

    In this post I will describe how to get a key, install certificates and connect GigaChat to OpenCode.

    What you need

    To work you need three things:

    • OpenCode installed. If the agent is not yet installed, I discussed the installation procedure in a separate note about OpenCode and DeepSeek.
    • GigaChat account. Registration in GigaChat Studio and authorization key – you will need Sber ID to log in.
    • Modern terminal. WezTerm, Alacritty, Ghostty, Kitty or any other will do.

    GigaChat authorization key

    The key is created in your GigaChat Studio personal account. Go to developers.sber.ru/studio, log in and create a project in the GigaChat API section. In the project settings there is a block with authorization data: the line that is copied from there is a ready-made authorization key in base64. It is this that is substituted into the environment variable, and not the client-secret pair separately.

    Together with the key, scope is specified – the access area:

    • GIGACHAT_API_PERS – for individuals.
    • GIGACHAT_API_B2B – for individual entrepreneurs and legal entities.
    • GIGACHAT_API_CORP – corporate access.

    The displayed key value should be saved immediately: it will not be shown again later, and if necessary, a new one will have to be issued.

    It is important to understand what this line is. The authorization key is not a separate secret, but a pair of client_id:client_secret already encoded in base64. The gigachat-js library substitutes this header into the OAuth request:

    Authorization: Basic ваш_ключ_авторизации
    

    In response, a JWE access token comes with a lifetime of about half an hour, and then the library updates it automatically. The package itself checks that the value is similar to base64 and warns if the variable accidentally contains a “raw” client secret. Therefore, it is necessary to transfer the ready-made base64 string from the account to the config and environment variable, and not the client_id and client_secret pair separately.

    Certificates of the Ministry of Digital Development

    GigaChat API uses certificates from the NCA Ministry of Digital Development. The standard trusted roots store does not have them, so when trying to get an access token, the request fails with an error like:

    self-signed certificate in certificate chain
    

    Certificates can be installed at the operating system level – then they will be trusted by the browser and system utilities. But OpenCode runs on Node and Bun, so it’s safer and easier to specify the certificate file directly in the NODE_EXTRA_CA_CERTS variable. Download the root and issuer certificates and put them into one PEM file:

    curl -s https://gu-st.ru/content/lending/russian_trusted_root_ca_pem.crt -o russian_trusted_root_ca_pem.crt
    curl -s https://gu-st.ru/content/lending/russian_trusted_sub_ca_pem.crt -o russian_trusted_sub_ca_pem.crt
    cat russian_trusted_root_ca_pem.crt russian_trusted_sub_ca_pem.crt > russian_trusted_ca_bundle.pem
    

    The file can be placed in a convenient location and its full path can be specified when starting the agent.

    There’s a catch here. Downloaded files use CRLF line breaks, and the root certificate does not have a trailing line feed. Therefore, a regular cat concatenates the end of the first certificate and the beginning of the second into one line:

    -----END CERTIFICATE----------BEGIN CERTIFICATE-----
    

    LibreSSL – and the system openssl on macOS, let me remind you, is exactly LibreSSL – does not accept such a file and responds with an error:

    PEM routines:CRYPTO_internal:bad end line
    

    This means that cat alone is not enough. Each certificate must first be run through openssl: it will recode it into canonical PEM with LF line feeds and a final line feed, and only then merged:

    openssl x509 -in russian_trusted_root_ca_pem.crt -out russian_trusted_root_ca.pem
    openssl x509 -in russian_trusted_sub_ca_pem.crt -out russian_trusted_sub_ca.pem
    cat russian_trusted_root_ca.pem russian_trusted_sub_ca.pem > russian_trusted_ca_bundle.pem
    

    You can check that the file is being read like this:

    openssl x509 -in russian_trusted_ca_bundle.pem -noout -subject
    

    If the certificates are taken from another source and come in DER or PKCS#7 format, they can also be recoded into PEM:

    openssl x509 -in cert.crt -inform DER -outform PEM -out cert.pem
    openssl pkcs7 -print_certs -in bundle.p7b -out cert.pem
    

    Connect to OpenCode

    The provider is described in the opencode.jsonc configuration file. OpenCode itself will download and connect the npm package specified in the npm field, find the createGigaChat factory in it and pass the options to it. It is enough to list the models by their identifiers from the API.

    The file can be placed in two places:

    • Global – ~/.config/opencode/opencode.jsonc. The settings apply to all user projects.
    • In the project – opencode.jsonc in the project root. This config has higher priority and is safe to commit to git.

    Both files use the same scheme and are combined: the project file overlaps the global one only with matching keys, the remaining settings are saved. The .jsonc extension is also supported. If GigaChat is needed only for some repositories, it is more convenient to keep the provider in the project config rather than in the global one.

    {
      "$schema": "https://opencode.ai/config.json",
      "provider": {
        "gigachat": {
          "npm": "gigachat-ai-sdk-provider",
          "name": "GigaChat",
          "models": {
            "GigaChat-2-Max": { "name": "GigaChat 2 Max" },
            "GigaChat-2-Pro": { "name": "GigaChat 2 Pro" },
            "GigaChat-2": { "name": "GigaChat 2 Lite" },
            "GigaChat": { "name": "GigaChat" }
          }
        }
      }
    }
    

    Models available with a personal key are listed here. If you have a project for an individual entrepreneur or a legal entity with access to GigaChat 3, add the necessary identifiers from the models method to the models block.

    It is more convenient not to store the key itself in a file, but to pass it to an environment variable: the gigachat-js package reads it automatically. The scope value can also be set to a variable. All that remains is to set the path to the certificates and launch the agent:

    export GIGACHAT_CREDENTIALS=ваш_ключ_авторизации
    export GIGACHAT_SCOPE=GIGACHAT_API_PERS
    export NODE_EXTRA_CA_CERTS=/путь/к/russian_trusted_ca_bundle.pem
    opencode
    

    This option is also good because the secret does not end up in the local auth.json storage: it lives only in the environment of the process. This is more convenient for CI and one-time launches.

    Models

    The set of available models depends on the scope key. The personal key (GIGACHAT_API_PERS) is available to the GigaChat and GigaChat 2 family:

    • GigaChat – basic model.
    • GigaChat-2 – a fast and lightweight model for everyday tasks, displayed as Lite in the interface.
    • GigaChat-2-Pro – an improved model for resource-intensive tasks.
    • GigaChat-2-Max is the most powerful available using a personal key.

    The GigaChat 3 family (GigaChat-3-Ultra, GigaChat-3-Pro, open models like GigaChat3.5-432B-A28B-Reasoning) is visible in the console catalog, but it is not available to the personal key: it requires an individual entrepreneur project or a legal entity with scope GIGACHAT_API_B2B or GIGACHAT_API_CORP and a suitable tariff. On a personal key, such a model responds with an error:

    {"status":404,"message":"No such model"}
    

    You can check what your key actually outputs by querying the list of models:

    curl -H "Authorization: Bearer <токен_доступа>" https://gigachat.devices.sberbank.ru/api/v1/models
    

    Two more points. Model IDs in the API do not match the displayed names – the light model is called as GigaChat-2, not GigaChat-2-Lite. And the catalog in the console does not show the same thing as what is available to your key, so you should focus on the response of the models method, and not on the list with prices.

    In the interface, the list of models is opened with the command:

    /models
    

    For routine work in the repository – navigating through files, minor edits, running tests – GigaChat-2 is quite enough. For complex tasks and design, it makes sense to switch to Pro or Max.

    First launch in the project

    Go to the project directory and launch the agent:

    cd ваш_проект
    opencode
    

    The first step is to initialize the agent:

    /init
    

    OpenCode will analyze the project structure and create a file AGENTS.md – instructions for the agent. This file is worth committing to git: it helps the agent understand the conventions and patterns adopted in the project.

    Alternative: local proxy

    If for some reason you don’t want to use the npm provider, a local proxy gives the same result. The GigaChat team has an official gpt2giga – FastAPI service that translates requests in OpenAI, Anthropic and Gemini format into the GigaChat API and updates the access token itself. It is raised locally on port 8090, after which a regular OpenAI-compatible provider is configured in OpenCode via the @ai-sdk/openai-compatible package with the base address http://localhost:8090/v1. The downside of this path is that you also need to keep a running Python service next to OpenCode.

    {
      "$schema": "https://opencode.ai/config.json",
      "provider": {
        "gigachat": {
          "npm": "@ai-sdk/openai-compatible",
          "name": "GigaChat (proxy)",
          "options": {
            "baseURL": "http://localhost:8090/v1"
          },
          "models": {
            "GigaChat-2-Max": { "name": "GigaChat 2 Max" }
          }
        }
      }
    }
    

    DeepSeek and other models via Cloud.ru

    The GigaChat API itself does not have DeepSeek models: there are only the GigaChat family and models for embeddings. If you need DeepSeek in OpenCode, but through a Russian cloud gateway, the Foundation Models service from Cloud.ru is suitable. It delivers models using the OpenAI-compatible protocol, so it is connected using the standard @ai-sdk/openai-compatible package.

    In the Cloud.ru Foundation Models catalog there are, for example, the following models:

    deepseek-ai/DeepSeek-V4.1-Flash
    deepseek-ai/DeepSeek-V4-Flash
    deepseek-ai/DeepSeek-V4-Pro
    

    The key is issued in the Cloud.ru console: section Users, tab Service accounts. We create a project-level service account, then in its credentials we create an API key with the Foundation Models service. The key secret is shown once and we save it.

    The provider configuration in opencode.json looks like this:

    {
      "$schema": "https://opencode.ai/config.json",
      "provider": {
        "cloudru": {
          "npm": "@ai-sdk/openai-compatible",
          "name": "Cloud.ru Foundation Models",
          "options": {
            "baseURL": "https://foundation-models.api.cloud.ru/v1",
            "apiKey": "{env:CLOUDRU_API_KEY}"
          },
          "models": {
            "deepseek-ai/DeepSeek-V4.1-Flash": { "name": "DeepSeek V4.1 Flash", "limit": { "context": 1048576, "output": 1048576 } },
            "deepseek-ai/DeepSeek-V4-Flash": { "name": "DeepSeek V4 Flash", "limit": { "context": 1048576, "output": 1048576 } },
            "deepseek-ai/DeepSeek-V4-Pro": { "name": "DeepSeek V4 Pro", "limit": { "context": 1048576, "output": 1048576 } }
          }
        }
      }
    }
    

    We pass the key to the environment variable:

    export CLOUDRU_API_KEY=ваш_ключ_cloudru
    opencode
    

    Ministry of Digital Development certificates are not needed here: the api.cloud.ru domain has regular TLS, unlike GigaChat. But it is worth monitoring the balance of the project. If the account is zero, authorization passes, and requests are dropped with the billing response:

    {"message":"Not enough money"}
    

    This is not a config error, but a signal that you need to update the project in the Cloud.ru console.

    What to pay attention to

    A couple of practical points that most often get in the way when setting up for the first time:

    • Certificate verification error. The message about self-signed certificate in the chain means that the NODE_EXTRA_CA_CERTS variable was not picked up. Check the path to the file, that the agent is running in the same environment, and the file itself: if, when merging two certificates using cat, their ends end up on the same line, LibreSSL will return a bad end line, and the bundle needs to be rebuilt via openssl, as in the section about certificates.
    • Error 401. As a rule, this is an incorrect authorization key or a mismatched scope – for an individual you need GIGACHAT_API_PERS.
    • Error 404 with the text No such model. This is not a connection failure, but a lack of access to a specific model. Remove it from the config or replace it with an available one. In a message like GigaChat 404: Unknown error, the same GigaChat response is to blame – the provider simply could not parse the error body.
    • Cost. OpenCode does not require a subscription: you pay GigaChat directly when you spend tokens, so long sessions are predictable in price.
    • Model quality. For complex architectural tasks, models of different classes differ, so for design it makes sense to take a stronger model and give the routine to a faster one.

    Links

    https://opencode.ai/
    https://opencode.ai/docs/providers/
    https://developers.sber.ru/studio/
    https://developers.sber.ru/docs/ru/gigachat/guides/main
    https://github.com/nyddle/gigachat-ai-sdk-provider
    https://github.com/ai-forever/gpt2giga
    https://cloud.ru/docs/foundation-models/ug/topics/quickstart

    Sources

    https://opencode.ai/docs/providers/#custom-provider
    https://developers.sber.ru/docs/ru/gigachat/certificates
    https://developers.sber.ru/docs/ru/gigachat/models/main
    https://developers.sber.ru/docs/ru/gigachat/guides/selecting-a-model
    https://github.com/nyddle/gigachat-ai-sdk-provider
    https://github.com/ai-forever/gpt2giga
    https://cloud.ru/docs/foundation-models/ug/topics/quickstart
    https://cloud.ru/docs/foundation-models/ug/topics/overview__available__models

  • AirLLM: large language models on a weak graphics card

    Typically, running a large language model with 70 billion parameters requires several tens of gigabytes of video memory. AirLLM approaches this problem more calmly and offers a different way: such a model can be run on a video card with only 4 GB of memory, and without quantization, distillation and trimming. In this note, I will try to tell you without haste how AirLLM works, how to use it, and I will share my small project AirLLM-experiments, which makes working with this library a little more convenient.

    What is AirLLM

    AirLLM is an open source library for large language model inference written by Gavin Li. Its idea is simple and yet elegant: instead of storing all the model weights in video memory at once, the library loads only one layer at a time onto the GPU. The weights are stored on disk in the form of shards cut into layers, and during generation, each layer is loaded, processed, and unloaded in turn.

    It follows that the amount of video memory required does not depend on the overall size of the model, but on the size of one of its layers. That’s why the 70B model fits in 4 GB, the DeepSeek-V3 on 671B fits in about 12 GB, and the Qwen3-235B fits in about 3 GB. But the size of the model itself is limited primarily by disk space, and not by the capabilities of the video card.

    These memory savings come at the cost of speed: each layer is read from disk when each token is generated, so output is slow, about seconds per token. So AirLLM is less about a quick interactive chat and more about the ability to even launch a model that otherwise wouldn’t fit into the hardware.

    Installation

    The library is installed from PyPI using the usual command:

    pip install airllm
    

    When you first launch, the model will be downloaded from Hugging Face and will be decomposed into layers. This process is slow and takes up a lot of disk space, so you should prepare free space in advance. If desired, you can enable 4bit or 8bit compression – then block quantization will be applied to the weights, which speeds up loading layers from disk by about three times, and the accuracy is lost quite a bit.

    Basic example

    You can work with AirLLM in almost the same way as with a regular model from transformers. It is enough to enter the model ID once on Hugging Face, and then everything will be familiar:

    from airllm import AutoModel
    
    MAX_LENGTH = 128
    model = AutoModel.from_pretrained("Qwen/Qwen3-32B")
    
    input_text = ['What is the capital of United States?']
    
    input_tokens = model.tokenizer(
        input_text,
        return_tensors="pt",
        return_attention_mask=False,
        truncation=True,
        max_length=MAX_LENGTH,
        padding=False)
    
    generation_output = model.generate(
        input_tokens['input_ids'].cuda(),
        max_new_tokens=20,
        use_cache=True,
        return_dict_in_generate=True)
    
    print(model.tokenizer.decode(generation_output.sequences[0]))
    

    The AutoModel class itself determines the model type, so the same line can be used for Llama, Qwen, and DeepSeek. And if you take a model like DeepSeek-V3 with 671B parameters, it will also fit in about 12 GB of video memory – this is largely what makes the project attractive.

    Launch on macOS

    AirLLM runs not only on CUDA, but also on Apple Silicon. On macOS it uses the MLX backend, so will need mlx and torch installed, and only supports Apple chips. There is also a small limitation: the MLX implementation only supports Llama-like architectures, so Qwen and similar models cannot be launched on a MacBook in this way.

    Use example

    AirLLM-experiments are two small interactive console utilities for running large language models locally. They eliminate the need to write the same code every time to load the model and organize the chat.

    The first tool, airllm-lib-usage.py, runs the AirLLM library directly in the current Python process. It shows a numbered list of models from the general catalog, if necessary, it installs missing dependencies and opens a chat session. MLX runtime is used on macOS, torch is used on other platforms.

    The second tool, airllm-openai-server-client.py, helps you work with the airllm-openai-server server, which runs in Docker and provides an OpenAI-compatible API. When you launch it for the first time, the utility asks for settings, collects the image, picks up the container and opens a streaming chat to the running server. When launched repeatedly, it finds an existing container and simply reuses it, and stores the model cache in a separate Docker volume so as not to download the model again.

    The list of available models is included in the common module model_catalog.py, so both tools work with the same set. Chat commands are also common: /help, /clear, /exit. The source code and instructions are in the repository:

    https://github.com/zefir1990/AirLLM-experiments

    What to pay attention to

    The first response can take several minutes to prepare: first, AirLLM downloads the model and cuts it into layers, and only then starts generating. Further, the speed, however, also remains low – this is a deliberate compromise for the sake of saving memory. For gated models like meta-llama, you need to accept the terms on Hugging Face and pass an access token. It’s best not to delete the cache of downloaded models and shards, otherwise everything will have to be downloaded again.

    Links

    https://github.com/lyogavin/airllm
    https://github.com/mkamranr/airllm-openai-server
    https://github.com/zefir1990/AirLLM-experiments
    https://pypi.org/project/airllm/
    https://huggingface.co/

    Sources

    https://github.com/lyogavin/airllm
    https://github.com/zefir1990/AirLLM-experiments

  • Installing OpenCode and connecting DeepSeek

    OpenCode is an open programming agent that lives right in the terminal. It can read project files, edit code, run commands and tests, search the repository and work with git – about the same thing that Claude Code and other console agents do. The main difference is that OpenCode is not tied to one model provider: dozens of providers can be connected through it, including DeepSeek. In this post I will describe installing the agent and connecting DeepSeek to it.

    What you need

    You only need two things to work:

    • Modern terminal. WezTerm, Alacritty, Ghostty, Kitty or any other will do.
    • API key of the provider. In our case, DeepSeek.

    Node.js is not required if you install the agent with a standard installation script – the binary arrives ready-made.

    Installation

    The easiest way is the official installation script:

    curl -fsSL https://opencode.ai/install | bash
    

    It will detect the platform itself and put the executable file in PATH. After installation, you should check that everything is in place:

    opencode --version
    

    If the command displays the version, the installation was successful.

    Installation using other methods

    If you prefer package managers, there are several options. Via Node.js:

    npm install -g opencode-ai
    

    Bun, pnpm and Yarn can do the same:

    bun install -g opencode-ai
    pnpm install -g opencode-ai
    yarn global add opencode-ai
    

    macOS and Linux have Homebrew. Please note: it is tap from the developers that is recommended, and not the official formula – it is updated less frequently:

    brew install anomalyco/tap/opencode
    

    On Arch Linux:

    sudo pacman -S opencode
    paru -S opencode-bin
    

    The first command installs the stable version from the repository, the second – the latest one from the AUR.

    Installation on Windows

    In Windows, it is recommended to work via WSL: this way the performance is higher and the compatibility with the agent’s capabilities is complete. But there are also native methods. Via Chocolatey, Scoop or npm:

    choco install opencode
    scoop install opencode
    npm install -g opencode-ai
    

    There is also an option through Mise, as well as a Docker container:

    mise use -g github:anomalyco/opencode
    docker run -it --rm ghcr.io/anomalyco/opencode
    

    Finally, the finished binary can always be taken from the releases page on GitHub.

    DeepSeek key

    The key is created in your DeepSeek personal account. Go to platform.deepseek.com, open the section with API keys and click create a new key. It is better to save the resulting string immediately: it will be shown only once.

    DeepSeek connection

    Then everything is done inside the agent itself. Launch OpenCode in the terminal:

    opencode
    

    In the interface we execute the connection command:

    /connect
    

    In the list of providers that opens, look for DeepSeek, select it and insert the API key. Keys added in this way are saved to a file:

    ~/.local/share/opencode/auth.json
    

    After connecting, all that remains is to select the model with the command:

    /models
    

    DeepSeek models will be available in the list, including deepseek-v4-pro, deepseek-flash and deepseek-v4-flash. For routine work in the repository, the flash option is sufficient; For complex tasks, it makes sense to switch to pro.

    Configuration via config

    If you don’t want to store the key through a master or need to set your own API address, the provider is described directly in the opencode.json configuration file:

    {
      "$schema": "https://opencode.ai/config.json",
      "provider": {
        "deepseek": {
          "options": {
            "baseURL": "https://api.deepseek.com"
          }
        }
      }
    }
    

    The baseURL field is useful if you go through a proxy or your own gateway. In this case, it is convenient to transfer the key itself to an environment variable:

    export DEEPSEEK_API_KEY=ваш_ключ_deepseek
    opencode
    

    This option is good for CI and one-time launches: the key does not end up in storage, but lives only in the environment of the process.

    First launch in the project

    Go to the project directory and launch the agent:

    cd ваш_проект
    opencode
    

    The first step is to initialize it:

    /init
    

    OpenCode will analyze the project structure and create a file AGENTS.md – an analogue of instructions for the agent. This file is worth committing to git: it helps the agent understand the conventions and patterns adopted in the project.

    How to use it

    The work is structured in much the same way as with other agents. A couple of tricks you should know right away:

    • Planning mode. The Tab key switches the agent between planning and assembly modes. In the first, he only suggests how to solve the problem and does not change anything – it is convenient to check the idea before making changes.
    • Links to files. The @ key opens a fuzzy search through project files to transfer a specific file to the agent in the context.
    • Rolling back changes. The /undo command undoes the last edits and returns the original request, /redo returns them back. You can roll back several steps in a row.
    • Sharing a conversation. The /share command creates a link to the current conversation and copies it to the clipboard. By default, conversations are not published anywhere.

    For simple changes, you don’t have to switch to planning mode, but immediately describe what needs to be done and specify the files via @.

    What to pay attention to

    A couple of practical points. OpenCode does not require a subscription: you pay the provider directly when you spend tokens, so long sessions with DeepSeek are predictably cheaper than top models. The key is stored locally in auth.json – this is worth considering on a shared machine, but for other people’s environments an environment variable is more reliable.

    And one more thing: the quality of answers to complex architectural problems differs among models of different classes, so for design it makes sense to take a stronger model, and give the routine away to a quick and cheap one.

    Links

    https://opencode.ai/
    https://opencode.ai/docs/
    https://opencode.ai/docs/providers/
    https://github.com/anomalyco/opencode
    https://platform.deepseek.com/api_keys
    https://api-docs.deepseek.com

    Sources

    https://opencode.ai/docs/
    https://opencode.ai/docs/providers/#deepseek
    https://github.com/anomalyco/opencode/releases
    https://models.dev/

  • Installing Asahi Linux on a MacBook with an M2 processor

    On a Mac with an Apple Silicon processor, you cannot install Linux in the usual way: booting from a flash drive is impossible here, and the bootloader is signed by Apple. The Asahi Linux project undertook to get around this – its participants used reverse engineering to make the M1 and M2 hardware work under regular Linux. Now the flagship distribution of the project is Fedora Asahi Remix, and it works quite reliably on the M2. In this post I will describe the installation step by step.

    What is Asahi Linux

    The project started in 2020, when Apple switched to its own chips and with them came a closed boot scheme: the device runs only Apple-signed code. Asahi Linux participants wrote their m1n1 bootloader, on top of which they wrote U-Boot and a UEFI environment. Next, the most common ARM64 system is loaded, but with a linux-asahi kernel, to which drivers for non-standard Apple hardware are added: GPU, video, sound, camera units.

    In practice, it looks like this: you run the installer from macOS, it partitions the disk, installs the boot chain and the system. macOS remains in place, you get dual boot and choose your system when you turn it on.

    Requirements

    • Mac on the M2 chip – MacBook Air, MacBook Pro 13″, as well as 14″ and 16″ with M2 Pro and M2 Max. M1 cars are also supported.
    • Current macOS. The installer requires the latest version of the system, so update before starting.
    • Free space. Minimum about 30 GB, comfortable – 100 GB or more. The space is cut off from the APFS container and will not come back on its own.
    • macOS administrator password. You will need it both during partitioning and when booting into recovery mode for the first time.
    • Backup. Disk partitioning is an operation that is best started with a fresh backup.

    Step 1. Backup and update

    Make a copy via Time Machine or any other method. Update macOS to the latest version available and reboot. If FileVault is enabled, the password for it will still be needed at the boot stage in recovery mode – the installer needs to unlock the drive.

    Step 2. Launch the installer

    Open Terminal on macOS and run one command:

    curl https://alx.sh | sh
    

    This is the official installer of the project. It will download itself, check your Mac model and offer to choose a distribution. There is also an option directly for Fedora:

    curl https://fedora-asahi-remix.org/install | sh
    

    The first option is preferable: it shows which systems are currently available, and it also offers a choice of desktop.

    Step 3. Disk partition

    The installer will ask for the administrator password and show the current layout. Then the dialogue looks like this:

    • Press r to resize the macOS partition.
    • Enter the size of the new Linux partition. It can be in gigabytes, it can be as a percentage of the disk, or you can write min – then the smallest possible piece will be cut off.
    • Confirm the marking with the letter y. This step is the longest: the system physically moves the data in the APFS container; on a large disk this takes several minutes.
    • Press f to point the installer to the newly created free partition.

    It is important to understand what is happening: Linux is not installed inside APFS, but on a separate partition next to it. This is why macOS remains untouched, and the rollback is reduced to deleting this section.

    Step 4. Selecting distribution and desktop

    Next, the installer will offer the system and desktop environment. Fedora Asahi Remix comes with KDE Plasma by default – it is the flagship option that receives updates first. An alternative is GNOME, which is closer in feel to macOS. There is also a minimal image if you don’t need a graphical environment.

    Step 5. First boot in recovery mode

    After partitioning, the installer will prompt you to continue, and the Mac will shut down. Here comes the most unusual part:

    • Wait at least 25 seconds – this is Apple’s requirement to enter recovery mode.
    • Hold and hold the power button until boot options appear.
    • Select the installation volume and enter your macOS password.

    At this step, the boot chain is installed in the service partition: m1n1, U-Boot and UEFI environment. Then the machine will reboot again – hold down the power button again and select a new volume, this time to get into the system. There will be several such reboots, each requiring manual selection: by default, the Mac continues to load macOS.

    Step 6. Installing the system

    Next, the initial setup wizard starts: system name, user, password, time zone, keyboard layout. After this, the installation itself begins, and it is already coming from the Internet – you will need a stable connection, and in time it takes from fifteen minutes to half an hour.

    When the installation is complete, reboot and select Linux from the boot menu. When you log in for the first time, the system will prompt you to complete the setup and update.

    What works and what doesn’t

    Before installation, you should soberly assess what you will lose. On M2 laptops the situation is as follows:

    Works:

    • Wi-Fi and Bluetooth – without restrictions.
    • Integrated screen and graphics – hardware accelerated, including OpenGL and Vulkan.
    • Webcam, microphone, and speakers on MacBook Air and MacBook Pro.
    • Sleep mode – works normally.
    • Hardware video decoding – players do not load the processor.
    • USB ports, including USB 2 and USB 3 via Thunderbolt connectors.

    Doesn’t work or works partially:

    • Touch ID. Fingerprint is not available in Linux – log in with password.
    • Thunderbolt (USB4). Status – in development. Unfortunately, devices that specifically require Thunderbolt will not work.
    • External monitor via USB-C. DisplayPort Alt Mode is also in operation. M2 laptops don’t have an HDMI port, so you can’t connect an external screen. Models with M2 Pro and M2 Max have HDMI, and it works. A workaround for USB-C is in the fairydust core section below.
    • Hardware video encoding is in the plans, decoding is already in place.

    Simply put, it’s a great system for working with code, terminal, browser and local models, but not for an external monitor dock.

    External monitor via fairydust core

    USB-C image output is available in the experimental fairydust kernel branch from the Asahi Linux team. It takes a long time to assemble it manually, so it’s easier to use the wrapper script from the asahi-fairydust-display project:

    git clone https://github.com/bharambetejas/asahi-fairydust-display
    cd asahi-fairydust-display
    chmod +x asahi-fairydust-build.sh
    ./asahi-fairydust-build.sh
    

    The script clones the fairydust branch, configures and assembles the kernel, places it next to the standard one and edits GRUB. You need 15 GB of free space, a USB-C → HDMI or DisplayPort adapter and 60–90 minutes to assemble. After the reboot, select the kernel labeled -fairydust in GRUB and insert the adapter into the frontmost USB-C port.

    Check after download:

    uname -r
    glxinfo | grep "OpenGL renderer"
    xrandr
    

    The first command should show the version with the suffix -fairydust, the second should show the Apple M2, not llvmpipe, the third should show the connected DP-1 output.

    Reservations. The branch is experimental and not officially supported. The output only works through one USB-C port and does not always survive hot plugging – it’s safer to reboot with the adapter already inserted. The script does not automatically configure the display: the corresponding step in it is disabled because it caused a login loop on Wayland. Updating the stock kernel via dnf rearranges the /boot/dtb symlink, which is why the output may silently stop working – then the build must be repeated.

    The standard kernel is not touched: to return, just select it in the GRUB menu.

    External drive instead of upgrade

    Here’s the good side. The MacBook cannot be upgraded: the memory and storage are soldered onto the board, and their capacity is selected once upon purchase. In macOS, the external drive remains external – the system and applications cannot really be transferred to it.

    In Linux, this limitation is removed: an external SSD via USB is a regular block device, and any mount point can be transferred to it via /etc/fstab. If you want to transfer your home directory, Steam library, virtual machine images or local models, please do so. It turns out exactly the upgrade that this car does not have.

    It looks like this. First, look at the UUID of the desired partition:

    lsblk -f
    sudo blkid
    

    Then add the line to /etc/fstab:

    UUID=ваш-uuid  /home  ext4  defaults,nofail  0  2
    

    The transfer itself is done by copying: the data is moved to an external drive, after which it is mounted at the desired point. More often than not, not the entire /home is taken away this way, but individual heavy directories – this way there is less risk.

    A couple of caveats. The nofail parameter is worth setting in the options: without it, the system will not boot if the disk is not connected. The speed of a USB drive is still lower than the built-in NVMe, so it’s better to leave space on the internal disk for frequent I/O operations. And there is no need to pull out the disk while moving with directories mounted there – first unmount it.

    How to switch between systems

    When you turn it on, hold down the power button – the boot menu will appear, it will contain macOS and Linux. You can select the default system in macOS: “System Preferences” → “General” → “System Disk”. Linux also has the corresponding settings, so you can switch in both directions without a terminal.

    Links

    https://asahilinux.org/fedora/
    https://asahilinux.org/docs/
    https://asahilinux.org/docs/platform/feature-support/m2/
    https://fedoraproject.org/asahi-remix
    https://github.com/bharambetejas/asahi-fairydust-display
    https://github.com/AsahiLinux/linux/tree/fairydust

    Sources

    https://asahilinux.org/docs/platform/feature-support/m2/
    https://discussion.fedoraproject.org/t/fedora-asahi-remix-installation-guide/
    https://github.com/AsahiLinux/docs
    https://github.com/bharambetejas/asahi-fairydust-display/blob/main/README.md
    https://github.com/bharambetejas/asahi-fairydust-display/blob/main/asahi-fairydust-build.sh

  • Installing VirtualBox with Guest Additions on an ARM Mac

    Instructions for installing Ubuntu Server in VirtualBox on a Mac with an ARM64 processor and connecting Guest Additions.

    Installing VirtualBox

    Download the ARM64 build of VirtualBox, mount the image and transfer VirtualBox.app to Applications.

    Configuring a virtual machine

    We create a new machine, specify the Linux type and the Ubuntu version (64-bit ARM). In the settings we set:

    • TPM – disable
    • UEFI – disable
    • RAM – 4096 MB.
    • Graphics controller – VMSVGA (for VBoxLinuxAdditions).
    • Processor – 4 cores.

    Installing Ubuntu Server

    Mount the Ubuntu Server ISO image for ARM64 into the CD-ROM drive. In the download section, put the optical drive first in the list. We start the machine and go through the installation of Ubuntu Server.

    Installing xubuntu-desktop

    Install the XFCE graphical environment:

    sudo apt update
    sudo apt install -y xubuntu-desktop
    

    In the login manager selection dialog, select lightdm.

    Reboot the guest system:

    sudo reboot
    

    Guest Additions

    After the first boot, install packages for building kernel modules:

    sudo apt update
    sudo apt install -y build-essential linux-headers-$(uname -r)
    

    We mount the disk with Guest Additions through the VirtualBox menu: Devices → Mount Guest Additions disk image. The disk will appear inside the guest in the /media/$USER directory.

    Open a terminal in Xubuntu, go to the directory of the mounted disk and run the installer:

    cd /media/$USER/*
    sudo ./VBoxLinuxAdditions-arm64.run
    

    Reboot the guest system:

    sudo reboot
    

    What should work after installation

    We check that the kernel modules are loaded:

    lsmod | grep vbox
    

    The output should include vboxguest and vboxsf.

    Add a shared folder in the machine properties and mount it inside the guest:

    sudo mkdir -p /media/sf_shared
    sudo mount -t vboxsf shared /media/sf_shared
    

    Working features:

    • Shared folders – the host directory is visible inside the guest via vboxsf.
    • Shared clipboard – copy text between host and guest.
    • Mouse integration – the cursor freely extends outside the machine window.
    • Automatic resolution change – the guest screen adjusts to the window size.
    • Time synchronization – the guest’s time is adjusted to the host’s time.

    Sources

    https://www.virtualbox.org/manual/topics/guestadditions.html
    https://forums.virtualbox.org/viewtopic.php?t=112886

  • How to connect DeepSeek to Claude Code

    Claude Code is a console agent from Anthropic that can read project files, edit code, run commands and tests, search the repository, and work with git. The main inconvenience when using it is the cost: long sessions with a lot of context quickly eat up the subscription budget.

    There is a solution. DeepSeek provides an endpoint compatible with the Anthropic API. It is enough to change the base address and token, and Claude Code will start working on DeepSeek models, without requiring any reinstallation or patches. In this post I will describe how to set this up on macOS, Linux and Windows.

    Why is this needed

    The reasons may be different:
    – Price. DeepSeek models are noticeably cheaper than Claude, and for routine agent tasks – navigating a project, minor edits, running tests – top quality is not always needed.
    – Availability. If you do not have a subscription or payment with an Anthropic card is not available for some reason, DeepSeek becomes a working option.
    – Experiments. It’s interesting to compare how different models handle the same codebase in the same environment.

    What you need

    You need Claude Code installed and the DeepSeek API key. If Claude Code is not already installed, the order is as follows:
    1. Install Node.js 18 or later. On Windows, you will additionally need Git for Windows.
    2. Install Claude Code itself using the command below.
    3. Check the installation.

    npm install -g @anthropic-ai/claude-code
    claude --version
    

    If the version is displayed, then the installation was successful. The API key is created in your DeepSeek personal account on the keys page.

    Configuration via environment variables

    All integration comes down to environment variables. For macOS and Linux it looks like this:

    export ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic
    export ANTHROPIC_AUTH_TOKEN=ваш_ключ_deepseek
    
    export ANTHROPIC_MODEL=deepseek-flash[1m]
    export ANTHROPIC_DEFAULT_OPUS_MODEL=deepseek-flash[1m]
    export ANTHROPIC_DEFAULT_SONNET_MODEL=deepseek-flash[1m]
    export ANTHROPIC_DEFAULT_HAIKU_MODEL=deepseek-flash
    export CLAUDE_CODE_SUBAGENT_MODEL=deepseek-flash
    
    export CLAUDE_CODE_EFFORT_LEVEL=max
    export CLAUDE_CODE_AUTO_COMPACT_WINDOW=786432
    

    For Windows in PowerShell the syntax is different, but the variable names are the same:

    $env:ANTHROPIC_BASE_URL="https://api.deepseek.com/anthropic"
    $env:ANTHROPIC_AUTH_TOKEN="ваш_ключ_deepseek"
    $env:ANTHROPIC_MODEL="deepseek-flash[1m]"
    

    Let’s figure out what’s what here. The `ANTHROPIC_BASE_URL` variable redirects requests from Anthropic servers to DeepSeek. `ANTHROPIC_AUTH_TOKEN` substitutes your key. The remaining variables determine which model is used in which role. There are models with the `[1m]` suffix – this is an option with a context window of about a million tokens, which allows the agent to hold significantly more project files at a time. Accordingly, `CLAUDE_CODE_AUTO_COMPACT_WINDOW` is set to the size of this window so that automatic history compression does not work too early.

    It is convenient not to export variables manually each time, but to register them in the Claude Code configuration. Then the settings will be picked up automatically upon startup.

    {
      "env": {
        "ANTHROPIC_BASE_URL": "https://api.deepseek.com/anthropic",
        "ANTHROPIC_AUTH_TOKEN": "ваш_ключ_deepseek",
        "ANTHROPIC_MODEL": "deepseek-flash[1m]",
        "ANTHROPIC_DEFAULT_HAIKU_MODEL": "deepseek-flash",
        "CLAUDE_CODE_SUBAGENT_MODEL": "deepseek-flash"
      }
    }
    

    After that, go to the project directory and run the agent:

    cd ваш_проект
    claude
    

    Claude Code in VS Code

    Claude Code is available not only in the terminal, but also as an extension for VS Code. It is installed in the normal way: open the extensions panel using Cmd+Shift+X, find “Claude Code” and click Install. The expansion is produced by Anthropic itself.

    The extension contains its own copy of the CLI, and it reads the same ~/.claude/settings.json file as the terminal version. The settings from the previous section also apply here – but with one caveat, which most often causes confusion.

    Input verification occurs before launch

    Before starting, the extension checks the credentials from its own `claudeCode.environmentVariables` setting, and not from settings.json. The values ​​from settings.json reach the running process – that is, the API address and selected models are picked up correctly – but they do not pass the extension’s own login check. If you see the login screen even though everything is already working in the terminal, this is the reason.

    This can be cured with a few lines in the VS Code settings: duplicate the variables in `claudeCode.environmentVariables` and disable the login request.

    {
      "claudeCode.environmentVariables": [
        { "name": "ANTHROPIC_BASE_URL", "value": "https://api.deepseek.com/anthropic" },
        { "name": "ANTHROPIC_AUTH_TOKEN", "value": "ваш_ключ_deepseek" },
        { "name": "ANTHROPIC_MODEL", "value": "deepseek-flash[1m]" }
      ],
      "claudeCode.disableLoginPrompt": true
    }
    

    macOS nuance

    If you launch VS Code from the Dock or Finder, it does not inherit environment variables from ~/.zshrc. This is standard macOS behavior and is specifically discussed in the Claude Code documentation. That is, `export ANTHROPIC_BASE_URL=…` in your shell for the extension simply will not work – this variable will not be in its environment.

    There are two conclusions from this. Firstly, the settings in `claudeCode.environmentVariables` are more reliable than shell variables. Secondly, if you still prefer a shell, launch the editor from the terminal, where the variables are already exported:

    code .
    

    What won’t work

    On a third-party provider, some extension features are not available because they require a claude.ai account:
    – there will be no plan usage bar, voice input and Web tab for cloud sessions;
    – logout commands are not shown in the menu;
    – `/usage` will show the consumption and number of tokens of the current session instead of plan limits;
    – Remote Control does not work if the base address is not Anthropic.

    This is expected behavior and not a sign that the setup failed.

    Separately, I would like to add that JetBrains has its own plugin, but it is designed differently: it does not contain a built-in CLI, but launches one already installed in the integrated terminal. There are no settings for environment variables, so you should launch the IDE from the terminal with already exported variables.

    How DeepSeek understands Claude model names

    Claude Code internally refers to models by names like claude-opus, claude-sonnet and claude-haiku. DeepSeek intercepts these names and replaces them with its own:
    – everything that starts with claude-opus goes to deepseek-v4-pro;
    – everything that starts with claude-sonnet or claude-haiku goes to deepseek-flash;
    – the unknown model name is also reduced to deepseek-flash.

    This means that even without explicitly specifying the models, the integration will work – but it is better to control explicitly which models and at what tariff will serve the request. That is why in the settings above all roles are written manually.

    What to pay attention to

    Compatibility is incomplete, and you should know this in advance. DeepSeek simply ignores some of the capabilities of the Anthropic API:

    – Caching prompts. The `cache_control` field is not supported. Claude Code actively uses the cache so as not to overpay for sending the same context repeatedly. This will not happen here, so long sessions are more expensive than you would expect from the price per token. Periodically start a new dialogue instead of endlessly continuing the old one.
    – Thinking Budget. The `thinking` parameter is supported, but the `budget_tokens` inside it are ignored.
    – Miscellaneous. The `top_k`, `service_tier`, `container` fields and the connection of MCP servers from the API are also ignored.
    – MCP. The built-in MCP server mechanism on the DeepSeek side does not work, although the agent’s usual tools – reading files, editing, running commands, searching – function normally.

    Separately, it is worth mentioning proxies. There are projects in the community like `ds-cc-proxy`, which are placed between Claude Code and DeepSeek and take on the task of smoothing out minor incompatibilities, as well as separating the main session and subagents into different models. If the standard setup behaves unstable, such an intermediate layer can help.

    Claude Code Router

    The method described above puts one model on all tasks. But the agent does very different things: he understands the structure of the project, corrects little things, and sometimes solves a truly complex problem. It is logical that different models should be used for different types of work. This is exactly what the claude-code-router (CCR) can do – a local gateway that stands between Claude Code and model providers.

    What does it give

    – Routing by rules. You can set which model serves the main session, which one serves background tasks, which one serves the scheduling mode. It makes sense to use an expensive model only for complex reasoning, and to give away the routine to a cheap one.
    – Backup options. If the provider returns an error, the request goes to the next model in the chain, rather than dropping the session.
    – Observability. The interface shows the request log, delays, token consumption and cost – something that you can only guess about with a direct connection.
    – One address for several agents. Providers, keys and rules live in one place, and clients connect to one local address.

    First about versions – this is important

    It’s worth warning here because it will save you time. CCR has been heavily redesigned over its history, and almost all the articles you find in a search describe the outdated version.

    In older versions, the configuration was in the file ~/.claude-code-router/config.json with the Providers and Router blocks, and everything was launched with the `ccr code` command. Now it doesn’t work that way anymore. The current version (3.x) stores settings in the SQLite database and is managed via a web interface. The old config.json is read exactly once as a source for migration if there is no database yet – after that, edits to it do not affect anything.

    That is, if you found instructions on the Internet with editing config.json and the `ccr code` command and nothing worked for you, you are editing a file that no one reads anymore. It’s not your fault.

    Installation

    You will need Node.js 22 or later.

    npm install -g @musistudio/claude-code-router
    ccr ui
    

    The `ccr ui` command brings up the service in the background and opens the management interface in the browser. It is useful to know the other commands: `ccr start` starts a service, `ccr stop` stops it, `ccr serve` works in the foreground and is convenient when you need to see logs.

    By default, the management interface lives on port 3458, and the gateway itself for models lives on port 3456.

    Settings

    Everything is done in the web interface, without manually editing files:
    1. In the Providers section, add the DeepSeek provider and specify its key. Please note: the full address up to the chat/completions point is needed here, not just the domain.
    2. In the Models section, describe what this model is – the description helps routing.
    3. In the Agent Config section, set the default model.
    4. In the Routing section, configure the rules: which model responds to which requests.
    5. On the API Keys page, create a CCR client key – this is what Claude Code will use, not the DeepSeek key.

    After this, all that remains is to send Claude Code to the gateway: specify the gateway address shown in the interface as the base address, and the CCR client key as the token.

    A nice thing: routing rules can be written not only with fields, but also with a JavaScript script, if the logic is more complex than comparing one field.

    Is it worth using it

    Connecting DeepSeek to Claude Code is primarily a way to reduce costs without giving up a convenient agent environment. You get the same interface, the same tools, and the same workflow, but on a different model. The setup takes a few minutes and is completely reversible: just remove the environment variables to return to Anthropic models.

    The limitations are also clear: there is no prompt cache, incomplete compatibility with respect to API fields, and the quality of the model may differ for complex architectural tasks. For routine work in the repository – navigation, refactoring, running tests – this is more than enough.

    Example: Oni-Extended

    A living example of such a combination is my project Oni-Extended, a fork of the port of the game Oni (Bungie, 2001) for Apple Silicon. The game sources are written in C and have been around for more than twenty years; there have never been any tests in them, and the file sizes are measured in tens of thousands of lines. However, new features have been added to the project via Claude Code from DeepSeek: fast saving and loading via F5/F9, a key hold block, and a -nodamage launch flag that disables damage.

    This is a good illustration of what was said above. All tasks relate to routine work in a large repository – the agent examines other people’s code, finds connections between modules and tests hypotheses, rather than designing an architecture from scratch. The cost of such sessions on DeepSeek turns out to be noticeably lower than on Anthropic models, despite the fact that the result is verified by the project’s own infrastructure: assembly, level run stand and offline tests.

    Links

    https://platform.deepseek.com/api_keys
    https://api-docs.deepseek.com
    https://docs.anthropic.com/en/docs/claude-code
    https://github.com/anthropics/claude-code
    https://github.com/musistudio/claude-code-router
    https://ccrdesk.top/en/guides/cli/
    https://github.com/zefir1990/Oni-Extended

    Sources

    https://api-docs.deepseek.com/guides/anthropic_api
    https://api-docs.deepseek.com/quick_start/agent_integrations/claude_code
    https://ccrdesk.top/en/routing/
    https://code.claude.com/docs/en/vs-code

  • maimat: Bulk image compression and resizing

    maimat (Mass Images Manipulation Tool) is an open source tool designed for batch image processing: resizing and compression. The application is written in Python 3.10+ and uses ImageMagick as the processing engine.

    Links

    https://github.com/zefir1990/maimat

  • Donki Hills has officially been on Steam for over a year!

    Donki Hills has been on Steam for over a year now!

    Exactly a year ago the project appeared on the Steam page, and since then the game has come a long way. During this time, there were many experiments, changes and searches for the very direction in which Donki Hills should develop.

    Work on the gameplay continues, and now the project is gradually acquiring its true face. Donki Hills is starting to turn into what it was intended to be – Comedy First Person Roguelike Parody Survival Horror.

    A strange adventure where horror meets humor, exploration and unexpected situations. You have to go to the city, look for objects, solve riddles and gradually uncover the secret that will lead you to Maria.

    Below are fresh screenshots from the next playtest of the prototype. This is not the final look of the game yet, but you can already feel the atmosphere of the city and the direction in which the project is moving.

    Very interested to hear your reaction! 👀
    How do you like the new direction? What are your impressions of the atmosphere and ideas of the game?

    Thanks to everyone who added Donki Hills to their wishlist, follows the development and supports the project ❤️

    There are still many experiments, new mechanics and strange adventures ahead.

    https://store.steampowered.com/app/3476390/Donki_Hills/

  • Bypassing throttling on gaming laptop processors

    Manufacturers of modern gaming laptops often configure systems so that the processor warms up to 90 degrees and even reaches 100 degrees Celsius under load. This problem is especially acute in the summer when running demanding games or heavy work tasks.

    Due to extreme heating, throttling is activated (resetting the processor frequencies to prevent physical damage), which leads to a sharp loss of performance, freezes and a drop in FPS. There are many ways to combat overheating (from using cooling pads to undervolting), but in this article I will describe the simplest and fastest method that is relevant for Windows 11 and earlier versions of the operating system.

    The method is to limit the maximum processor state through power supply settings:

    1. Open Power Options via Windows Search or Control Panel.
    2. In the active power plan, go to the section for changing additional parameters.
    3. Find the Processor power management branch.
    4. Expand the Maximum processor state option.
    5. For Plugged In, change the value from 100% to 80% or less.

    After applying these settings, the processor will no longer automatically overclock to extreme frequencies that generate excess heat. Although the maximum peak performance will decrease by approximately 20%, the processor will no longer overheat. Stable operation at a reduced frequency without throttling is MUCH better and more comfortable for the system than constant sharp jumps in performance due to overheating at temperatures under 100 degrees.

    Each laptop is unique, so I recommend that users experiment and find the optimal balance (percentage) that will provide the desired performance without overheating.

  • Aleka is a cross-platform drawing app built on Flutter

    Aleka is a lightweight, fast drawing app written in Flutter. It works everywhere: in the browser, on the desktop (Windows, macOS, Linux) and on mobile devices (iOS, Android).

    Under the hood is pure Dart, Material 3 and attention to detail.

    Opportunities

    • Freehand drawing: smooth strokes with quadratic Bezier interpolation.
    • Palette of 15 colors: quick selection with visual indication.
    • Brush size: adjustable with a slider from 1 to 30 px.
    • Undo: step back through your strokes.
    • Clear the canvas: with one click.
    • Flood Fill: BFS with tolerance for smooth edges.
    • Saving: format .aleka (JSON).
    • Loading: open previously saved drawings and continue.
    • PNG export: Capture canvas at 3x resolution.
    • 🎬 Animation mode: Frame-by-frame animation with timeline:
      • Adding and removing frames, each with its own design.
      • Frame duration from 50 ms to 5 s.
      • Change FPS: 6, 8, 12, 15, 24, 30 or 60.
      • Export to MP4 (via ffmpeg on desktop, ffmpeg.wasm in browser).
    • Dark and light theme: Material 3, follows the system.

    Why Flutter?

    Flutter allows you to write one application for six platforms without duplicating code. Aleka uses only the native capabilities of Dart and Flutter SDK and a minimum of third-party dependencies: file_picker, image for working with pixels when filling, ffmpeg_wasm for exporting video in the browser.

    Architecture

    The project adheres to the principles of clean architecture and SOLID:

    • PaintCanvasController – controls the state of strokes and fills through the Listener/Observer pattern.
    • PaintToolbar – UI component of the palette, slider and buttons.
    • AlekaFile – serialization/deserialization in JSON, PNG capture.
    • MovieController – model of frames and animation state.
    • VideoExport – encapsulates MP4 encoding logic (desktop/web).

    I/O functions (saveStrokes, loadStrokes, capturePng, etc.) are made injectable – they can be replaced with mocks during testing. The project has 79 tests: checking serialization, cancellation, loading/saving, PNG and video export, frame model and timeline.

    File format .aleka

    The .aleka file is readable JSON that can be opened in any text editor:

      "aleka": "1.0",
      "strokes": [
        {
          "color": 4278190080,
          "strokeWidth": 3.0,
          "points": [[100.0, 200.0], [150.0, 250.0]]
        }
      ]
    }
    

    Each stroke stores a color (ARGB32), a thickness, and an array of points. The format is versioned by the aleka field.

    Animation mode

    One of the key features is the built-in frame-by-frame animation editor. You draw a frame, add a new one, draw the next one, and so on. The timeline shows thumbnails and allows you to change the order and duration of frames. The finished animation can be exported to MP4.

    The export algorithm iterates through all frames, captures them via RepaintBoundary, collects PNG bytes and transfers them to the encoder. On the desktop, the system ffmpeg is called, in the browser, ffmpeg.wasm is used – a full-fledged port of ffmpeg to WebAssembly, working directly in the browser sandbox.

    Flood Fill

    The fill implementation uses Breadth First Search (BFS) with a color tolerance (_kFillTolerance = 1000). This allows you to correctly handle smooth edges of strokes – the fill does not flow beyond the outline, but captures translucent pixels on the border. For an empty canvas, a solid fill is created without unnecessary calculations.

    Try

    Aleka is available on the web and as a native application:

    demensdeum.com/software/aleka/

    The source code is open under the MIT license:

    github.com/demensdeum/Aleka

  • Mama Calendar – cross-platform reminder app

    Mama Calendar is a cross-platform events and reminders application written in React Native (Expo) with two backend implementations: Node.js + MongoDB and PHP + MariaDB.

    The application allows you to create events with emoji categorization, select the type of repetition (once, annually, monthly, weekly, daily) and view important events for the next 3 days in a separate “Important Events” tab.

    Technology stack

    The frontend is written in React Native 0.86 via Expo SDK 57 with TypeScript, Expo Router for navigation and react-native-reanimated for animations. Expo-symbols are used for icons (SF Symbols on iOS, Material icons on Android/Web).

    The backend is implemented in two versions:

    • Node.js: Express 4, MongoDB 8 via native driver, custom authentication with scrypt + salt, manual token management. Docker Compose brings up the client (nginx), server and MongoDB.
    • PHP: PHP 8.2 without frameworks, PDO + MariaDB 11, bcrypt for password hashing, your own router and PSR-4 autoloading via Composer.

    Application functions

    • Registration and authorization: simple system with username/password and Bearer tokens
    • CRUD events: create, view, edit and delete events with date, emoji and repeat type
    • Smart filtering: The Important Events tab shows events for the next 3 days, taking into account repetition rules. For example, annual events show the nearest annual occurrence, monthly – by day of the month, weekly – by day of the week
    • Emoji picker: 20 preset emoji for quick categorization of events
    • Highlighting today’s events: Today’s events are highlighted with a red background (#B71C1C)
    • Dark theme: Automatic detection of system color scheme with separate palettes for light and dark theme
    • Internationalization: Russian and English languages ​​via React Context
    • Анимации: анимированный сплэш-скрин с Keyframe API и упругой интерполяцией, анимированная иконка приложения с вращающимся свечением
    • Admin endpoints: managing users, tokens and clearing the database through master tokens

    Why two backend implementations?

    The Node.js version is the main one, but the PHP version exists as an alternative for more classic hosting. The API is completely identical, which allows you to switch between them without changing the client code.

    Infrastructure

    The entire stack is raised through Docker Compose: the client is assembled through a multistage build (Expo export → nginx alpine), a server on Node.js, MongoDB 8. For the PHP version there is a separate compose file with MariaDB 11 and phpunit for tests.

    The assembly of the web version is automated by a PowerShell script that patches the API_URL before expo export and restores the originals after.

    Output

    Mama Calendar is a full-featured cross-platform application that covers the full cycle: from mobile and web frontend on React Native to two server-side options. The project is interesting for its architecture, use of modern Expo SDK capabilities and dual backend implementation.

    Links

    https://github.com/zefir1990/Mama-Calendar
    https://demensdeum.com/software/mama-calendar/events

  • Porting Surreal Engine C++ to WebAssembly

    In this post I will describe how I ported the Surreal Engine game engine to WebAssembly.
    https://demensdeum.com/demos/SurrealEngine/”
    Surreal Engine – a game engine that implements most of the functionality of the Unreal Engine 1, famous games on this engine – Unreal Tournament 99, Unreal, Deus Ex, Undying. It refers to classic engines that worked primarily in a single-threaded execution environment.
    I originally had the idea of ​​taking on a project that I couldn’t complete in any reasonable time frame, thus showing my Twitch followers that there are projects that even I can’t do. During my first stream, I suddenly realized that the task of porting Surreal Engine C++ to WebAssembly using Emscripten was feasible.

    A month later, I can demonstrate my fork and engine assembly on WebAssembly:
    https://demensdeum.com/demos/SurrealEngine/
    Control, as in the original, is carried out using the keyboard arrows. Next, I plan to adapt it for mobile control (tachi), adding correct lighting and other graphic features of the Unreal Tournament 99 render.

    Where to start?

    The first thing I want to say is that any project can be ported from C++ to WebAssembly using Emscripten, the only question is how complete the functionality will be. Choose a project whose library ports are already available for Emscripten; in the case of Surreal Engine, you are very lucky, because the engine uses the SDL 2, OpenAL – libraries. they are both ported to Emscripten. However, Vulkan is used as a graphics API, which is currently not available for HTML5, work is underway to implement WebGPU, but it is also in the draft stage, and it is also unknown how simple the further port from Vulkan to WebGPU will be, after it is fully standardized. Therefore, I had to write my own basic OpenGL-ES / WebGL renderer for Surreal Engine.

    Building the project

    Build system in Surreal Engine – CMake, which also simplifies porting, because Emscripten provides its native builders – emcmake, emmake.
    The Surreal Engine port was based on the code of my latest game in WebGL/OpenGL ES and C++ called Death-Mask, because of this the development was much simpler, I had all the necessary build flags with me and code examples.
    One of the most important points in CMakeLists.txt is the build flags for Emscripten, below is an example from the project file:

    set(CMAKE_CXX_FLAGS "-s MIN_WEBGL_VERSION=2 
    -s MAX_WEBGL_VERSION=2 
    -s EXCEPTION_DEBUG 
    -fexceptions 
    --preload-file UnrealTournament/ 
    --preload-file SurrealEngine.pk3 
    --bind 
    --use-preload-plugins 
    -Wall 
    -Wextra 
    -Werror=return-type 
    -s USE_SDL=2 
    -s ASSERTIONS=1 
    -w 
    -g4 
    -s DISABLE_EXCEPTION_CATCHING=0 
    -O3 
    --no-heap-copy 
    -s ALLOW_MEMORY_GROWTH=1 
    -s EXIT_RUNTIME=1")
    

    The build script itself:

    clear
    emmake make -j 16
    cp SurrealEngine.data /srv/http/SurrealEngine/SurrealEngine.data
    cp SurrealEngine.js /srv/http/SurrealEngine/SurrealEngine.js
    cp SurrealEngine.wasm /srv/http/SurrealEngine/SurrealEngine.wasm
    cp ../buildScripts/Emscripten/index.html /srv/http/SurrealEngine/index.html
    

    Next, let’s prepare index.html, which includes the project file system preloader. To upload to the web, I used Unreal Tournament Demo version 338. As you can see from the CMake file, the unpacked game folder was added to the build directory and linked as a preload-file for Emscripten.

    Main code changes

    Then we had to change the game loop of the game, you can’t run an endless loop, this leads to the browser freezing, instead you need to use emscripten_set_main_loop, I wrote about this feature in my 2017 note “Porting SDL C++ games to HTML5 (Emscripten)”
    We change the code for exiting the while loop to if, then we display the main class of the game engine, which contains the game loop, in the global scope, and write a global function that will call the game loop step from the global object:

    #if __EMSCRIPTEN__
    #include <emscripten.h>
    Engine *EMSCRIPTEN_GLOBAL_GAME_ENGINE = nullptr;
    void emscripten_game_loop_step() {
    	EMSCRIPTEN_GLOBAL_GAME_ENGINE->Run();
    }
    #endif
    

    After this, you need to make sure that there are no background threads in the application; if there are, then get ready to rewrite them for single-threaded execution, or use the phtread library in Emscripten.
    The background thread in Surreal Engine is used to play music, data comes from the main engine thread about the current track, the need to play music, or its absence, then the background thread receives a new state via a mutex and starts playing new music, or pauses. The background thread is also used to buffer music during playback.
    My attempts to build Surreal Engine for Emscripten with pthread were unsuccessful, because the SDL2 and OpenAL ports were built without pthread support, and I didn’t want to rebuild them for the sake of music. Therefore, I transferred the functionality of the background music stream to single-threaded execution using a loop. By removing pthread calls from the C++ code, I moved the buffering and music playback to the main thread, so that there would be no delays, I increased the buffer by a few seconds.
    Next, I will describe specific implementations of graphics and sound.

    Vulkan is not supported!

    Yes, Vulkan is not supported in HTML5, although all the marketing brochures present cross-platform and broad platform support as the main advantage of Vulkan. For this reason, I had to write my own basic graphics renderer for a simplified OpenGL type – ES, it is used on mobile devices, sometimes it does not contain the fashionable features of modern OpenGL, but it ports very well to WebGL, which is exactly what Emscripten implements. Writing basic tile rendering, bsp rendering, for the simplest GUI display, and rendering models + maps was completed in two weeks. This was perhaps the most difficult part of the project. There is still a lot of work ahead to implement the full functionality of Surreal Engine rendering, so any help from readers is welcome in the form of code and pull requests.

    OpenAL supported!

    The big luck is that Surreal Engine uses OpenAL for audio output. Having written a simple hello world in OpenAL and assembled it in WebAssembly using Emscripten, it became clear to me how simple everything was, and I set off to port the sound.
    After several hours of debugging, it became obvious that the OpenAL implementation of Emscripten has several bugs, for example, when initializing reading the number of mono channels, the method returned an infinite number, and after trying to initialize a vector of infinite size, C++ crashes with the exception vector::length_error.
    We managed to get around this by hardcoding the number of mono channels to 2048:

    		alcGetIntegerv(alDevice, ALC_MONO_SOURCES, 1, &monoSources);
    		alcGetIntegerv(alDevice, ALC_STEREO_SOURCES, 1, &stereoSources);
    
    #if __EMSCRIPTEN__
    		monoSources = 2048; // for some reason Emscripten's OpenAL gives infinite monoSources count, bug?
    #endif
    
    

    Is there a network?

    Surreal Engine does not currently support online play, play with bots is supported, but we need someone to write AI for these bots. Theoretically, you can implement a network game on WebAssembly/Emscripten using Websockets.

    Conclusion

    In conclusion, I would like to say that the porting of Surreal Engine turned out to be quite smooth due to the use of libraries for which there are Emscripten ports, as well as my past experience in implementing a game in C++ for WebAssembly on Emscripten. Below are links to sources of knowledge and repositories on the topic.
    M-M-M-MONSTER KILL!
    Also, if you want to help the project, preferably with WebGL/OpenGL ES rendering code, then write to me in Telegram:
    https://t.me/demenscave

    Links

    https://demensdeum.com/demos/SurrealEngine/

    https://github.com/demensdeum/SurrealEngine-Emscripten

    https://github.com/dpjudas/SurrealEngine

  • Port forwarding between clients via Chisel: a stripped-down tunnel without L3

    When two devices are behind NAT or strict firewalls and cannot “see” each other directly, a VPN seems to be the standard solution. But a full-fledged L3 tunnel (like WireGuard or OpenVPN) is often redundant: it requires root rights, setting up virtual interfaces, and can conflict with existing routes.

    In such cases, it is convenient to use Chisel – a TCP/UDP tunnel that runs on top of HTTP and uses WebSockets for data transfer. In this note I will show how to “forward” a port from one client to another through an intermediate server.

    How does it work?

    Imagine the situation: you have Client A (for example, your home server), Client B (your work laptop) and a VPS with a public IP address. Clients A and B can access the VPS, but not each other.

    The forwarding scheme will look like this:
    1. Client A connects to the VPS and opens a “reverse” port on the server. Now everything that comes to port X of the server goes to port Y of Client A.
    2. Client B connects to the VPS and forwards port Z from its local machine to port X of the server.
    3. As a result, Client B accesses localhost:Z and ends up on Client A:Y.

    This approach is one of the solutions in cases where communication with a VPS does not implement an L3 layer or there is no ability to configure routing between clients. We work exclusively at the application and port level.

    Step 1: Start the server

    On your VPS, just run Chisel in server mode. The --reverse flag is required to allow clients to open ports on the server side.

    chisel server --port 8080 --reverse
    

    Step 2: Connecting Client A (Source)

    Let’s say Client A wants to open access to his local web server on port 3000. He connects to the VPS and says: “reserve port 2000 on the server and forward it to me to 3000.”

    chisel client vps-ip:8080 R:2000:127.0.0.1:3000
    

    Now port 2000 on the VPS (on the loopback interface) leads to Client A.

    Step 3: Connecting Client B (Consumer)

    Now Client B wants to access this resource. It connects to the same VPS and forwards its local port 8080 to port 2000 of the server.

    chisel client vps-ip:8080 8080:127.0.0.1:2000
    

    Ready! Now, when you open http://localhost:8080 on Client B, you will see the service running on Client A.

    Safety and nuances

    Chisel supports authentication via the --auth flag, which is highly recommended when working through public servers. You can also use TLS certificates to encrypt traffic.

    The main advantage of this approach is that there is no need for TUN/TAP devices and complex routing tables. This is a stripped-down tunnel that does exactly one thing: binds ports via a WebSocket connection. This even works through corporate proxies if you configure Chisel to work through port 443.

    Output

    Chisel is a utility for specific network tasks. When you need to forward ports between isolated nodes without setting up a full-fledged VPN, a combination of forward and reverse tunnels through a relay server turns out to be a completely viable solution.

    Links

    https://github.com/jpillora/chisel

  • Why can’t I fix the bug?

    You spend hours working on the code, going through hypotheses, adjusting the conditions, but the bug is still reproduced. Sound familiar? This state of frustration is often called “ghost hunting.” The program seems to live its own life, ignoring your corrections.

    One of the most common – and most annoying – reasons for this situation is looking for an error in completely the wrong place in the application.

    The trap of “false symptoms”

    When we see an error, our attention is drawn to the place where it “shot”. But in complex systems, where a bug occurs (crash or incorrect value) is only the end of a long chain of events. When you try to fix the ending, you are fighting the symptoms, not the disease.

    This is where the flowchart concept comes in.

    How it works in reality

    Of course, it is not necessary to directly draw (draw) a flowchart on paper every time, but it is important to have it in your head or at hand as an architectural guide. A flowchart allows you to visualize the operation of an application as a tree of outcomes.

    Without understanding this structure, the developer is often groping in the dark. Imagine the situation: you edit the logic in one condition branch, while the application (due to a certain set of parameters) goes to a completely different branch that you didn’t even think about.

    Result: You spend hours on a “perfect” code fix in one part of the algorithm, which, of course, does nothing to fix the problem in another part of the algorithm where it actually fails.


    Algorithm for defeating a bug

    To stop beating on a closed door, you need to change your approach to diagnosis:

    • Find the state in the outcome tree:Before writing code, you need to determine exactly the path that the application has taken. At what point did logic take a wrong turn? What specific state (State) led to the problem?
    • Reproduction is 80% of success: This is usually done by testers and automated tests. If the bug is “floating”, development is involved in the process to jointly search for conditions.
    • Use as much information as possible: Logs, OS version, device parameters, connection type (Wi-Fi/5G) and even a specific telecom operator are important for localization.

    “Photograph” of the moment of error

    Ideally, to fix it, you need to get the full state of the application at the time the bug was reproduced. Interaction logs are also critically important: they show not only the final point, but also the entire user path (what actions preceded the failure). This helps to understand how to recreate a similar state again.

    Future tip: If you encounter a complex case, add extended debug logging information to this section of code in case the situation happens again.


    The problem of “elusive” states in the era of AI

    In modern systems using LLM (Large Language Models), classical determinism (“one input, one output”) is often violated. You can pass exactly the same input data, but get a different result.

    This happens due to the non-determinism of modern production systems:

    • GPU Parallelism: GPU floating point operations are not always associative. Due to parallel execution of threads, the order in which numbers are added may change slightly, which may affect the result.
    • GPU temperature and throttling: Execution speed and load distribution may depend on the physical state of the hardware. In huge models, these microscopic differences accumulate and can lead to the selection of a different token at the output.
    • Dynamic batching: In the cloud, your request is combined with others. Different batch sizes change the mathematics of calculations in the kernels.

    Under such conditions, it becomes almost impossible to reproduce “that same state”. Only a statistical approach to testing can save you here.


    When logic fails: Memory problems

    If you are working with “unsafe” languages ​​(C or C++), the bug may occur due to Memory Corruption.

    These are the most severe cases: an error in one module can “overwrite” data in another. This leads to completely inexplicable and isolated failures that cannot be traced using normal application logic.

    How to protect yourself at the architectural level?

    To avoid such “mystical” bugs, you should use modern approaches:

    • Multithreaded programming patterns:Clear synchronization eliminates race conditions.
    • Thread-safe languages: Tools that guarantee memory safety at compile time:
      • Rust: Ownership system eliminates memory errors.
      • Swift 6 Concurrency:Strong data isolation checks.
      • Erlang: Complete process isolation through the actor model.

    Summary

    Fixing a bug is not about writing new code, but about understanding how the old one works. Remember: you could be wasting time editing a branch that management doesn’t even touch. Record the state of the system, take into account the factor of AI non-determinism and choose safe tools.

  • Why documentation is your best friend

    (and how to create solutions that continue to work after updates)

    “Apps may only use public APIs and must run on the currently shipping OS.” Apple App Review Guidelines

    If you’ve ever started working with a new framework and found yourself thinking: “Now I’ll understand everything myself, reading the documentation is too long,” you’re definitely not alone. Many of us have a natural investigative instinct: try first, and only then look at the instructions. And that’s completely normal.

    However, at this stage it can be easy to get carried away and end up in a situation where the code works great, but perhaps relies on non-obvious features of the system.

    Why is it sometimes not enough to simply “figure it out on your own”?

    Frameworks, especially closed ones, are complex and multi-layered systems. They often hide internal logic and optimizations that:

    * are not described in public documentation;
    * do not guarantee that behavior will be maintained in the future;
    * may change with the release of new versions;
    * may contain features known to the developers that have not yet been fixed.

    When we act intuitively, there is a risk of building architecture on random observations rather than on documented rules. This can make the code more sensitive to updates.

    Documentation is not a limitation, but a reliable support

    Framework developers create manuals to help us. Acting within the documentation, we get:

    * stability;
    * support;
    * predictable system behavior.

    By going beyond these limits, we take on additional risks, and maintaining such code becomes more difficult.

    Experiments? Certainly. But with an understanding of boundaries.
    Curiosity is a great trait to have in a developer. Exploring and trying new things is absolutely essential. But here is a small wish:

    The most comfortable way to experiment is to rely on best practices.

    The documentation is a map that shows which paths are the most secure and supported by the creators.

    An outside perspective: expert advice

    We often learn from experienced colleagues:

    * they conduct useful courses,
    * speak at conferences,
    * write wonderful books and blogs,
    * share their unique vision.

    Many of them share truly valuable experiences. But it is worth remembering: if the author’s approaches contradict the official documentation, they may turn out to be fragile.

    Such “empirical patterns” sometimes:

    * work only on a specific version of the framework;
    * sensitive to updates;
    * may behave unpredictably in unusual situations.

    Learning from the community is great and rewarding. But any advice, even the most authoritative, should always be carefully checked with official manuals.

    A little about SOLID

    Three ideas from the SOLID principles perfectly complement this approach:

    * Open/Closed Principle: Try to extend behavior through public APIs and, if possible, not depend on hidden implementation.
    * Liskov Substitution Principle: Rely on the contract, not the specific implementation. Otherwise, changes under the hood can lead to unexpected difficulties.
    * Dependency Inversion: build dependencies on abstractions, not details.

    In practice, this means that being tied to internal, undocumented details of the framework makes the system brittle.
    Based on public interfaces and contracts, we get:

    * better isolation of code from changes in the framework;
    * ease of testing;
    * predictability and reliability of the architecture.

    What if there is a bug?

    It also happens that everything is done according to the rules, but the result does not meet expectations. Frameworks evolve and are not always perfect. In such cases:

    * Build a minimal example that reproduces the problem.
    * Ensure that only documented APIs are used.
    * Send a bug report – the development team will certainly appreciate your work and try to help.

    If the example relies on workarounds, it will be much more difficult for developers to provide support.

    How to get the most out of the framework

    *Refer to documentation.
    * Follow the guides and recommendations of the authors.
    * Experiment within the described functionality.
    * Check advice from the Internet with official sources.
    * Localize bugs while respecting framework contracts.

    Conclusion

    Frameworks are powerful tools with their own rules of the game. By forgetting about them, we risk making our code overly vulnerable. But we all want the created products to live for a long time and not require urgent corrections after each minor update.

    Manuals and documentation are excellent support that helps create truly reliable solutions.

    Sources

    https://developer.apple.com/app-store/review/guidelines/
    https://en.wikipedia.org/wiki/SOLID
    https://en.wikipedia.org/wiki/API
    https://en.wikipedia.org/wiki/RTFM

  • Building a C++ SDL application for iOS on Linux

    In this note, I will describe the procedure for building a C++ SDL application for iOS on Linux, signing an ipa archive without a paid Apple Developer subscription, and installing it on a clean device (iPad) using macOS without Jailbreak.

    First, let’s install the build toolchain for Linux:
    https://github.com/tpoechtrager/cctools-port

    The toolchain needs to be downloaded from the repository, then follow the instructions on the Godot Engine website to complete the installation:
    https://docs.godotengine.org/ru/latest/development/compiling/cross-compiling_for_ios_on_linux.html

    At the moment, you need to download Xcode dmg and copy the sdk from there to build cctools-port. This stage is easier to complete on macOS; just copy the necessary sdk files from the installed Xcode. After successful assembly, the terminal will contain the path to the cross-compiler toolchain.
    Next, you can start building the SDL application for iOS. Let’s open cmake and add the necessary changes to build the C++ code:

    SET(CMAKE_SYSTEM_NAME Darwin)
    SET(CMAKE_C_COMPILER arm-apple-darwin11-clang)
    SET(CMAKE_CXX_COMPILER arm-apple-darwin11-clang++)
    SET(CMAKE_LINKER arm-apple-darwin11-ld)
    
    

    Now you can compile using cmake and make, but do not forget to add $PATH to the cross-compiler toolchain:

    
    PATH=$PATH:~/Sources/cctools-port/usage_examples/ios_toolchain/target/bin
    
    

    For correct linking with frameworks and SDL, we write them in cmake, dependencies of the game Space Jaguar for example:

    
    target_link_libraries(
    ${FSEGT_PROJECT_NAME}
    ${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/libclang_rt.ios.a
    ${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/libSDL2.a
    ${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/libSDL2_mixer.a
    ${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/libSDL2_image.a
    "${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/CoreServices.framework"
    "${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/ImageIO.framework"
    "${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/Metal.framework"
    "${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/AVFoundation.framework"
    "${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/GameController.framework"
    "${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/CoreMotion.framework"
    "${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/CoreGraphics.framework"
    "${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/AudioToolbox.framework"
    "${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/CoreAudio.framework"
    "${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/QuartzCore.framework"
    "${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/OpenGLES.framework"
    "${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/UIKit.framework"
    "${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/Foundation.framework"
    )
    
    

    In my case, the SDL, SDL_Image, SDL_mixer libraries are compiled in Xcode on macOS in advance for static linking; Frameworks copied from Xcode. Also added is the libclang_rt.ios.a library, which includes iOS-specific runtime calls, for example isOSVersionAtLeast. Included a macro for working with OpenGL ES, disabling unsupported functions in the mobile version, similar to Android.
    After solving all the build problems, you should get the assembled binary for arm. Next, let’s consider running the assembled binary on a device without Jailbreak.
    On macOS, install Xcode, register on the Apple portal, without paying for the developer program. Add an account in Xcode -> Preferences -> Accounts, create a blank application and build on a real device. During assembly, the device will be added to your free developer account. After assembly and launch, you need to build the archive; to do this, select Generic iOS Device and Product -> Archive. Once the archive is built, extract the embedded.mobileprovision and PkgInfo files from it. From the build log to the device, find the codesign line with the correct signature key, the path to the entitlements file with the extension app.xcent, copy it.
    Copy the .app folder from the archive, replace the binary in the archive with one compiled by a cross-compiler in Linux (for example, SpaceJaguar.app/SpaceJaguar), then add the necessary resources to the .app, check the integrity of the PkgInfo and embedded.mobileprovision files in the .app from the archive, copy again if necessary. We re-sign the .app using the codesign command – codesign requires an input key for sign, the path to the entitlements file (can be renamed with a .plist extension)
    After re-signing, create a Payload folder, move the folder with the .app extension there, create a zip archive with Payload in the root, rename the archive with the .ipa extension. After that, in Xcode, open the list of devices and Drag’n’Drop the new ipa to the device’s list of applications; Installation via Apple Configurator 2 does not work for this method. If the re-signing is done correctly, then the application with the new binary will be installed on an iOS device (for example, iPad) with a 7-day certificate, this is enough for the testing period.

    Sources

    https://github.com/tpoechtrager/cctools-port

    https://docs.godotengine.org/ru/latest/development/compiling/cross-compiling_for_ios_on_linux.html

    https://jonnyzzz.com/blog/2018/06/13/link-error-3/

    https://stackoverflow.com/questions/6896029/re-sign-ipa-iphone

    https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/Procedures/Procedures.html

  • Build for Windows under Ubuntu MinGW CMake

    In this post I will describe the process of building libraries and applications for Windows using the MinGW32 toolchain on Ubuntu.
    Install wine, mingw:

    sudo apt-get install wine mingw-w64
    

    After this, you can already build C/C++ applications for Windows:

    # C
    i686-w64-mingw32-gcc helloWorld.c -o helloWorld32.exe      # 32-bit
    x86_64-w64-mingw32-gcc helloWorld.c -o helloWorld64.exe    # 64-bit
     
    # C++
    i686-w64-mingw32-g++ helloWorld.cc -o helloWorld32.exe     # 32-bit
    x86_64-w64-mingw32-g++ helloWorld.cc -o helloWorld64.exe   # 64-bit
    

    The collected exe can be checked using wine.
    Next, let’s look at the changes to the CMake build, the CMakeLists.txt file, adding MinGW specific things to the build file:

    if (MINGW32)
    set(CMAKE_SYSTEM_NAME Windows)
    SET(CMAKE_C_COMPILER i686-w64-mingw32-gcc)
    SET(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)
    SET(CMAKE_RC_COMPILER i686-w64-mingw32-windres)
    set(CMAKE_RANLIB i686-w64-mingw32-ranlib)
    endif()
    
    // для сборки shared dll
    elseif (MINGW32)
    add_library(FlameSteelEngineGameToolkit.dll SHARED ${SOURCE_FILES})
    else()
    
    // обязательно линкуем со всеми зависимостями
    if (MINGW32)
    target_link_libraries(
                            FlameSteelEngineGameToolkit.dll 
                            -static-libgcc
                            -static-libstdc++
                            SDL2 
                            SDL2_mixer 
                            /home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelCore/FlameSteelCore.dll
                            /home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelBattleHorn/FlameSteelBattleHorn.dll
                            /home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelCommonTraits/FlameSteelCommonTraits.dll)
    
    set_target_properties(FlameSteelEngineGameToolkit.dll PROPERTIES
            PREFIX ""
            SUFFIX ""
            LINK_FLAGS "-Wl,--add-stdcall-alias"
            POSITION_INDEPENDENT_CODE 0 # this is to avoid MinGW warning; 
            # MinGW generates position-independent-code for DLL by default
    )
    else()
    

    We collect:

    cmake -DMINGW32=1 .
    make
    

    The output will be a dll or exe, depending on what you are collecting. For a working example, you can look at the repository of the new Cube-Art-Project and its libraries:
    https://gitlab.com/demensdeum/cube-art-project

    https://gitlab.com/demensdeum/FlameSteelEngineGameToolkitFSGL

    https://gitlab.com/demensdeum/cube-art-project-bootstrap

    Sources
    https://arrayfire.com/cross-compile-to-windows-from-linux/

  • Building macOS applications for Ubuntu OSXCross CMake

    In this post, I will describe building cross-platform C++ applications for macOS on an Ubuntu build machine using CMake and osxcross.
    First, install the osxcross toolchain:
    https://github.com/tpoechtrager/osxcross
    Installation occurs in 3 stages, downloading dependencies:

    cd tools
    ./get_dependencies.sh
    

    Download XCode.xip from the official Apple website, then download the SDK from XCode:

    ./gen_sdk_package_pbzx.sh /media/demensdeum/2CE62A79E62A4404/LinuxSupportStorage/xcode111.xip
    

    I hope you read the XCode license agreement in the last step? Next, build the toolchain with the required prefix:

    INSTALLPREFIX=/home/demensdeum/Apps/osxcross ./build.sh 
    

    Now you can use osxcross from the prefix directory of the previous step. Let’s add a new build macro for CMake and write everything necessary:

    if (OSXCROSS)
    SET(CMAKE_SYSTEM_NAME Darwin)
    SET(CMAKE_C_COMPILER o64-clang)
    SET(CMAKE_CXX_COMPILER o64-clang++)
    SET(CMAKE_C_COMPILER_AR x86_64-apple-darwin19-ar)
    SET(CMAKE_CXX_COMPILER_AR x86_64-apple-darwin19-ar)
    SET(CMAKE_LINKER x86_64-apple-darwin19-ld)
    SET(ENV{OSXCROSS_MP_INC} 1)
    endif()
    

    Dynamic linking was not successful for me, so we export the libraries statically:

    if (OSXCROSS)
    add_library(FlameSteelCore STATIC ${SOURCE_FILES})
    else()
    

    Next, you may be faced with the fact that you do not have the necessary libraries for osxcross, I encountered this when using SDL2. osxcross supports ready-made library packages – macports. For example, installing SDL2-mixer:

    osxcross-macports -v install libsdl2_mixer
    

    After this, you can start building libraries/applications as usual in the cmake-make link, do not forget to specify static linking of libraries if necessary.

    Manual assembly of libraries

    Currently, I have encountered the problem of incorrect archiving of libraries during static linking; when building the final application I receive the error:

    file was built for archive which is not the architecture being linked (x86_64)
    

    Very similar to this ticket, we managed to implement a workaround resulting in the build completing correctly. Let’s unzip the static library and build it anew using the osxcross archiver:

    ar x ../libFlameSteelCore.a
    rm ../libFlameSteelCore.a
    x86_64-apple-darwin19-ar rcs ../libFlameSteelCore.a *.o
    

    Also, one of the problems I personally consider is the lack of ability to run macOS applications directly on Ubuntu (at least with part of the functionality). Of course, there is a project darling, but the support still leaves much to be desired.

    Sources

    https://github.com/tpoechtrager/osxcross

  • Local image generation: ComfyUI and FLUX model

    Nowadays, you don’t have to rely on cloud services: you can generate high-quality images entirely on your own hardware. In this post, I will describe how to run the modern FLUX model locally on your computer using ComfyUI.

    ComfyUI uses node-based architecture. This allows you to:
    – Totally control every stage of generation.
    – Easily share ready-made “workflows”

    FLUX is a large model, so the hardware requirements are higher than SD 1.5 or SDXL:
    – Video card (GPU): Nvidia RTX with 12 GB VRAM or higher (for comfortable work). If you have 8 GB or less, you will have to use the quantized versions (GGUF or NF4).
    – Random access memory (RAM): minimum 16 GB (preferably 32 GB and above).
    – Disk Space: Approximately 20–50 GB for models and components.

    The easiest way to start FLUX is to use a ready-made template. Just search for flux text to image in the workflows window and install.

    Write a prompt in English in the `Text to Image (Flux.1 Dev)` node, select the resolution (FLUX works well with 1024×1024 and even higher) and press RUN.

    The first generation may take time as the models will be loaded into the video card memory.

    https://github.com/comfyanonymous/ComfyUI

  • Local Vibe coding: LM Studio, VS Code and Continue

    If you had a desire to use neural networks to help write code (so-called Vibe coding), and you have a fairly powerful computer, for example with an Nvidia RTX video card, then you can deploy the entire environment absolutely free on your machine. This solves problems with paid subscriptions and allows you to safely work with projects under NDA, since your code is not sent anywhere. In this post I will describe how to assemble a local bundle of LM Studio, VS Code and the Continue extension.

    Tools for local Vibe coding

    For comfortable work we need three main components:
    – LM Studio: a convenient application for downloading and running local LLMs. It takes on all the complexity of working with GGUF models and puts up a local server compatible with the OpenAI API.
    – VS Code: a popular and familiar code editor.
    – Continue: extension for VS Code that integrates neural networks directly into the work environment. Allows you to chat, highlight code for refactoring, and supports autocomplete.

    Hardware requirements

    Local language models are memory intensive:
    – Video card (GPU): Nvidia with 8 GB VRAM or higher (for comfortable work with models with 7-8 billion parameters). Heavier models will require 16 GB of VRAM.
    – Disk space: about 500 GB for storing various downloaded models.

    Configuring the link

    The setup process is quite simple and does not require complex manipulations in the terminal:
    1. Download and install LM Studio. Use the built-in search to find a lightweight model like Qwen Coder or gemma3:12b.
    2. In LM Studio, go to the Local Server tab and click Start Server. By default it will start on `http://localhost:1234/v1`.
    3. Open VS Code and install the Continue extension from the plugin store.
    4. Open the Continue configuration file and add a new model, specifying the `openai` provider and the address of your local server from LM Studio.

    You can then communicate with your local LLM directly in the Continue sidebar, ask questions about your code, and generate new components.

    Why does this work?

    As I wrote earlier, LLMs do better with flat structure and WET (Write Everything Twice) code. Local parameter models may be inferior to giants like GPT-4 when it comes to designing complex architectures, but they are more than capable of generating boilerplate code, refactoring simple functions, and rapid prototyping.

    Additionally, with local Vibe coding, your code never leaves the machine. This makes this combination ideal for corporate development and working with sensitive data.

    Output

    Local neural networks are not capable of fully replacing a programmer or designing a complex system. However, the combination of LM Studio + VS Code + Continue provides independence from cloud services and maintains privacy. This is a completely working auxiliary tool for routine tasks, if you are willing to put up with the limitations of small models and independently control the project architecture.

    Links

    https://code.visualstudio.com/
    https://lmstudio.ai/
    https://continue.dev/

    Sources

    https://youtu.be/IqqCwhG46jY
    https://www.youtube.com/watch?v=7AImkA96mE8

  • Local video generation: ComfyUI and LTX-2.3

    Previously, creating videos using neural networks was the prerogative of cloud services like Runway or Luma. Today, if you have a modern Nvidia graphics card, you can generate high-quality videos right on your computer. In this post, I will tell you how to set up local video generation using ComfyUI and the effective LTX-2.3 model.

    Tools for video generation

    For work we will need:
    – ComfyUI: a powerful interface with a node-based architecture that allows you to flexibly customize the generation process.
    – LTX-2.3: A modern model from Lightricks, optimized for creating smooth and detailed videos with relatively moderate video memory requirements.

    Hardware requirements

    Generating video is a much more resource-intensive process than working with images:
    – Video card (GPU): Nvidia RTX with 8 GB VRAM is the minimum required for a resolution of 768×512. For comfortable operation and higher resolutions, it is highly desirable to have 16–24 GB of VRAM.
    – Random access memory (RAM): minimum 32 GB. Video models and VAEs take up a lot of space when downloading.
    – Disk space: about 500 GB for the model itself and related components.

    Setup and launch

    The process of launching LTX-2.3 in ComfyUI is as follows:
    1. Update ComfyUI: The model is relatively new, so make sure you have the latest version of the interface installed.
    2. Install Workflow: The easiest way is to find a ready-made JSON template for LTX Video. The model requires specific nodes to work with video latent space.
    3. Prompt and parameters: Enter a description of the scene in English. Note that the LTX-2.3 understands motion well (eg “camera orbits around”, “fast movement”).

    Why choose LTX-2.3?

    LTX-2.3 is notable because it delivers results comparable to proprietary cloud services, but runs locally. This gives you:
    – Complete privacy: your prompts and generated videos do not go to other people’s servers.
    – Control: you can experiment with frame rate (FPS), resolution and prompt strength without having to pay for each attempt.

    Local video generation is still in active development, and LTX-2.3 is a great entry into the world of “home Hollywood.”

    Links

    https://github.com/comfyanonymous/ComfyUI
    https://huggingface.co/Lightricks/LTX-Video

  • Local music generation: ComfyUI and ACE-Step-1.5 model

    Nowadays, you don’t have to rely on cloud services to create content: you can generate high-quality music entirely on your own hardware. In this post, I will describe how to run the modern ACE-Step-1.5 model locally on your computer using ComfyUI.

    ComfyUI uses node-based architecture. This allows you to:
    – Totally control every stage of audio generation.
    – Easily share ready-made “workflows”.

    ACE-Step-1.5 is an advanced model for music generation that requires significant computational resources. The hardware requirements are higher than those of many simple synthesizers:
    – Video card (GPU): Nvidia RTX with 8 GB VRAM or higher (12 GB+ recommended) for comfortable work at high quality.
    – Random access memory (RAM): minimum 16 GB (preferably 32 GB and above).
    – Processor (CPU): Modern multi-core processor with good support for AVX/CUDA computing.
    – Disk Space: Approximately 20–50 GB for models and components.

    The easiest way to run ACE-Step-1.5 is to use a ready-made audio generation template. Just search for music text to audio in the workflows window and install.

    Write a prompt describing the genre and mood (for example, “uplifting synthwave track with heavy bass”) in the `Prompt Input` node. Specify the desired duration and press RUN.
    The first generation may take time, as the models will be loaded into the video card memory and process complex acoustic patterns.

    https://github.com/comfyanonymous/ComfyUI
    https://www.youtube.com/watch?v=UAlLD5fS7-c

  • Local neural networks using ollama

    If you had a desire to launch something like ChatGPT and you have a fairly powerful computer, for example with an Nvidia RTX video card, then you can run the ollama project, which will allow you to use one of the ready-made LLM models on your local machine, absolutely free. ollama provides the ability to communicate with LLM models, in the manner of ChatGPT; also in the latest version, the ability to read images and format the output data in json format has been announced.

    I also ran the project itself on a MacBook with an Apple M2 processor, and I know that the latest models of video cards from AMD are supported.

    To install on macOS, go to the ollama website:
    https://ollama.com/download/mac

    Click “Download for macOS”, you will download an archive of the form ollama-darwin.zip, inside the archive there will be Ollama.app which needs to be copied to “Applications”. After this, launch Ollama.app, most likely the installation process will occur on the first launch. After that, in the tray you saw the ollama icon, the tray is on the top right next to the clock.

    After that, launch a regular macOS terminal, and type the command to download, install and run any ollama model. A list of available models, descriptions, and their characteristics can be seen on the ollama website:
    https://ollama.com/search

    Choose the model with the fewest parameters if it does not fit into your video card at launch.

    For example, the command to launch the llama3.1:latest model:

    ollama run llama3.1:latest
    

    Installation for Windows and Linux is generally similar, in one case there will be an ollama installer and further work with it via Powershell.
    For Linux, installation is done using a script, but I recommend using the version of your specific package manager. On Linux, ollama can also be launched via a regular bash terminal.

    Sources
    https://www.youtube.com/watch?v=Wjrdr0NU4Sk
    https://ollama.com

  • Video stabilization using ffmpeg

    If you want to stabilize videos and remove camera shake, the `ffmpeg` tool offers a powerful solution. Thanks to the built-in filters `vidstabdetect` and `vidstabtransform`, you can achieve professional results without using complex video editors.

    Preparing for work

    Before you start, make sure your `ffmpeg` supports the `vidstab` library. On Linux you can check this with the command:

    bash  
    ffmpeg -filters | grep vidstab  
    

    If the library is not installed, you can add it:

    sudo apt install ffmpeg libvidstab-dev  
    

    Installation for macOS via brew:

    brew install libvidstab
    brew install ffmpeg
    

    Now let’s move on to the process.

    Step 1: Motion Analysis

    First you need to analyze the motion of the video and create a file with stabilization parameters.

    ffmpeg -i input.mp4 -vf vidstabdetect=shakiness=10:accuracy=15 transfile=transforms.trf -f null -  
    

    Parameters:

    shakiness: Video shake level (default 5, can be increased to 10 for more complex cases).
    accuracy: Analysis accuracy (default 15).
    transfile: File name to save the motion parameters.

    Step 2: Apply Stabilization

    Now you can apply stabilization using the transformation file:

    ffmpeg -i input.mp4 -vf vidstabtransform=input=transforms.trf:zoom=5 output.mp4
    

    Parameters:

    input: Points to the file with transformation parameters (created in the first step).
    zoom: Zoom factor to remove black edges (e.g. 5 – auto zoom until artifacts are removed).

  • Turing computing machines

    I present to your attention a translation of the first pages of Alan Turing’s article “ON COMPUTABLE NUMBERS WITH AN APPLICATION TO THE PROBLEM OF RESOLUTION” from 1936. The first chapters contain a description of computers, which later became the basis for modern computing.

    The full translation of the article and explanation can be read in the book by American popularizer Charles Petzold, entitled “Reading Turing: A Journey through Turing’s Historical Article on Computability and Turing Machines” (ISBN 978-5-97060-231-7, 978-0-470-22905-7)

    Original article:
    https://www.astro.puc.cl/~rparra/tools/PAPERS/turing_1936.pdf

    ON COMPUTABLE NUMBERS WITH APPLICATION TO THE RESOLUTION PROBLEM

    A. M. TURING

    [Received May 28, 1936 – Read November 12, 1936]

    “Computable” numbers can be briefly described as real numbers whose expressions as decimal fractions are calculable in a finite number of ways. Although at first glance this article treats numbers as computable, it is almost as easy to define and explore computable functions of an integer variable, a real variable, a computable variable, computable predicates, and the like. However, the fundamental problems associated with these computable objects are the same in each case. For a detailed consideration, I chose computable numbers as a computable object because the method of considering them is the least cumbersome. I hope to soon describe the relationship of computable numbers with computable functions and so on. At the same time, research will be carried out in the field of the theory of functions of a real variable expressed in terms of computable numbers. By my definition, a real number is computable if its decimal representation can be written by a machine.

    In paragraphs 9 and 10 I give some arguments to show that computable numbers include all numbers that are naturally thought to be computable. In particular, I show that some large classes of numbers are computable. They include, for example, the real parts of all algebraic numbers, the real parts of the zeros of Bessel functions, the numbers π, e and others. However, computable numbers do not include all definable numbers, as evidenced by the following example of a definable number that is not computable.

    Although the class of computable numbers is very large and in many respects similar to the class of real numbers, it is still enumerable. In §8 I consider certain arguments that would seem to argue to the contrary. When one of these arguments is correctly applied, conclusions are drawn that, at first glance, are similar to those of Gödel*. These results have extremely important applications. In particular, as shown below (§11), the resolution problem cannot have a solution.

    In a recent article, Alonzo Church introduced the idea of ​​“effective calculability,” which is equivalent to my idea of ​​“computability” but has a completely different definition. Church also comes to similar conclusions regarding the problem of resolution. The proof of the equivalence of “computability” and “effectively calculable” is presented in the appendix to this article.

    1. Computers

    We have already said that computable numbers are those numbers whose decimal places are countable by finite means. A clearer definition is needed here. This article will make no real attempt to justify the definitions given here until we get to §9. For now, I will just note that the (logical) rationale (for this) is that human memory is, by necessity, limited.

    Let us compare a person in the process of calculating a real number with a machine that is capable of fulfilling only a finite number of conditions q1, q2, …, qR; Let’s call these conditions “m-configurations”. This (that is, so defined) machine is equipped with a “tape” (analogous to paper). This belt passing through the machine is divided into sections. Let’s call them “squares”. Each such square can contain some kind of “symbol”. At any moment, there is only one such square, say the rth one, containing the symbol that is “in this machine.” Let’s call such a square a “scanned symbol”. A “scanned character” is the only character that the machine is, so to speak, “directly aware” of. However, by changing its m-configuration, the machine can effectively remember some of the characters it has “seen” (scanned) previously. The possible behavior of the machine at any moment is determined by the m-configuration qn and the scanned symbol***. Let’s call this pair of symbols qn, “configuration”. The configuration thus designated determines the possible behavior of a given machine. In some of these configurations in which the scanned square is blank (ie, does not contain a character), the machine writes a new character on the scanned square, and in other of these configurations it erases the scanned character. This machine is also capable of moving to scan another square, but in this way it can only move to the adjacent square to the right or left. In addition to any of these operations, the m-configuration of the machine can be changed. In this case, some of the written characters will form a sequence of digits, which is the decimal part of the real number being calculated. The rest of them will be nothing more than inaccurate marks in order to “help memory”. In this case, only the above-mentioned inaccurate marks can be erased.

    I claim that the operations considered here include all those operations that are used in calculation. The rationale for this statement is easier to understand for the reader who has an understanding of machine theory. Therefore, in the next section I will continue to develop the theory in question, based on an understanding of the meaning of the terms “machine”, “tape”, “scanned”, etc.

    *Gödel “On the Formally Undecidable Sentences of the Principia Mathematics (published by Whitehead and Russell in 1910, 1912 and 1913) and Related Systems, Part I,” Journal of Mathematics. Physics, monthly bulletin in German No. 38 (for 1931, pp. 173-198.
    ** Alonzo Church, “An Undecidable Problem in Elementary Number Theory,” American J. of Math., No. 58 (1936), pp. 345-363.
    *** Alonzo Church, “A Note on the Problem of Resolution,” J. of Symbolic Logic, No. 1 (1936), pp. 40-41

  • Donki Hills

    Donki Hills is a first-person horror-comedy adventure that takes players through a mysterious story that combines suspense with unexpected humor.
    The main character James goes in search of his online friend Maria, whose connection with her suddenly broke off. The only evidence is a random photograph pointing to the remote village of Donki Hills in the Novosibirsk region. Driven by the desire to get to the bottom of the truth, James begins his own investigation to find out all the circumstances of Maria’s disappearance.

    The game is available on Steam:
    https://store.steampowered.com/app/3476390/Donki_Hills/

  • Masonry AR

    Masonry AR is a location-based multiplayer augmented reality (AR) game that immerses you in the world of secret societies. Explore the streets of your city, collect Masonic books of knowledge, found your own lodges and compete for influence on the real world map.

    Key features

    • Geolocation-based gameplay:move around the real world using your device’s GPS to find secret books and resources.
    • Autowalk Mode:If GPS access is limited, you can start Autowalk mode by selecting one of the world’s capitals as a starting point.
    • Lodge Creation: Establish Masonic lodges on a real map and expand the influence of your order.
    • In-game economy: Earn in-game currency (MOS), develop your territories and share referral links with friends.
    • Cross-platform: The game runs directly in web browsers on mobile devices and PCs thanks to the Flame Steel Engine 2 game engine with rendering based on Three.js.

    Play:
    https://demensdeum.com/games/masonry-ar/client/

    GitHub:
    https://github.com/zefir1990/Masonry-AR

  • Teflecher


    Teflecher is a fast, interactive, cross-platform quiz application built on top of Kotlin Multiplatform (KMP) and Compose Multiplatform. It allows users to intuitively load quizzes from local JSON files or remote URLs, answer multiple-choice questions, see instant feedback on correct answers, and track their results.

    Web:
    https://demensdeum.com/software/teflecher/

    GitHub:
    https://github.com/zefir1990/teflecher

    Also a quiz editor in the Teflecher Editor format based on Ionic + Capacitor technologies

    Web:
    https://demensdeum.com/software/teflecher-editor/

    GitHub:
    https://github.com/zefir1990/teflecher-editor

  • Ghost Contacts


    Ghost Contacts is a simple web application designed to hide your contacts from standard system APIs and phone books. All information is stored exclusively locally in your browser and is not transferred to external servers.

    Main features

    • Hiding from system APIs:Your contacts are not synced with the standard system contacts APIs and device phone book.
    • Panic Password: A special password, when entered, the entire contact database is instantly and irrevocably deleted.
    • Import and export: Convenient transfer of contact database via CSV files for creating backup copies.

    Online application:
    https://demensdeum.com/software/ghost-contacts/

    GitHub:
    https://github.com/demensdeum/GhostContacts

  • Cube Art Project 2

    Welcome to Cube Art Project 2 – a meditative 3D canvas where you can let your imagination run wild. Create voxel paintings, experiment with colors, and bring unique 3D models to life right in your browser.

    What is Cube Art Project 2?

    This is a creative game that turns your screen into an interactive canvas for 3D painting. Before you is a clean three-dimensional space, ready to be filled with your ideas, shapes and bright colors.

    Key features

    • 3D Drawing: A relaxing creative process focused on creating voxel art and 3D geometry.
    • Color picker: fine-tune the shades of each block using convenient RGB sliders.
    • Freedom of movement: Explore your paintings from any angle with convenient camera controls.
    • Canvas Saving: Save your work to files and return to drawing at any time, or share your work with others.

    Management

    • WASD – camera movement
    • Mouse – camera rotation and inspection
    • Interface (GUI) – color selection and adjustment

    Play:
    https://demensdeum.com/software/cube-art-project-2/

    GitHub:
    https://github.com/zefir1990/cube-art-project-2

  • Raiden Video Ripper

    Raiden Video Ripper is an open source project designed for video editing and format conversion. It is created using Qt 6 (Qt Creator) and allows you to edit and convert videos to MP4, GIF and WebM formats. You can also extract audio from videos and convert it to MP3 format.

    Microsoft Store:
    https://apps.microsoft.com/detail/9nvzjs98smgc

    GitHub:
    https://github.com/demensdeum/RaidenVideoRipper/releases

  • Mars Miners

    In the harsh conditions of the red planet, every second and every sector counts. We’re excited to introduce Mars Miners, a turn-based strategy game where you fight for the survival of a colony and control of valuable Martian resources.

    What are Mars Miners?

    In Mars Miners you control a mining corporation on Mars. Your task is to build bases, capture resource sectors and coordinate the actions of autonomous mining units in the face of fierce competition with artificial intelligence or other players.

    Key features

    • Tactical strategy: plan the development of your base and the seizure of territory, calculating your opponents’ moves in advance.
    • Autonomous units: Coordinate the actions of mining robots and customize their logic for maximum efficiency.
    • Various modes: Hone your skills in single-player training grounds or compete with other colonists in multiplayer mode.
    • Advanced AI: fight for resources against smart automata that do not forgive tactical mistakes.

    Play Mars Miners

  • Flame Steel: Death Mask 2

    In the endless corridors of the industrial megastructure, flooded with cold light, a new challenge awaits you. We’re excited to introduce Flame Steel: Death Mask 2, a 3D dungeon crawler that combines classic aesthetics with modern real-time gameplay.

    What is Flame Steel: Death Mask 2?

    Imagine waking up in a procedurally generated maze where a hostile entity called “Filter” could be lurking around every turn. In Flame Steel: Death Mask 2 you take on the role of a Seeker, exploring a world built on the Flame Steel Engine 2 (with Three.js graphics rendering).

    Key features

    • Procedural Dungeons: Each attempt is unique. The server creates new maps full of secrets and dangers.
    • Terminal: For those who prefer full control, the built-in command line interface allows you to interact directly with the system: perform advanced actions, debug or send commands.
    • Combat and Survival: Fight Filters to earn Bits, then use them to open chests and improve your stats. Watch your health – survival is not guaranteed.
    • Industrial aesthetic: high-contrast visualization and sterile atmosphere of a giant megastructure.

    Technology behind the mask

    The game is created for the browser and uses:

    • Frontend: Pure JavaScript and Flame Steel Engine 2 (with Three.js graphics renderer) for smooth 3D rendering.
    • Backend: Node.js for running the server side.
    • Infrastructure: MongoDB for persistent data storage and Redis for real-time spatial indexing

    Play Flame Steel: Death Mask 2

  • Ushki Radio

    Ushki-Radio is a cross-platform radio player for online radio, made with a focus on simplicity and listening pleasure. No unnecessary functions, no overloaded interfaces – just turn it on and listen.


    https://demensdeum.com/software/ushki-radio

    The project uses the open source Radio Browser, making thousands of radio stations from all over the world available in the application. You can search for them by name, genre or popularity, add them to your favorites and quickly return to your favorite stations.

    Ushki-Radio is perfect for the role of a background radio player: it remembers the last station, allows you to control the volume and does not require complex settings. The interface is concise and understandable – everything is done so that nothing distracts from music, conversations and broadcasting.

    Technically, the project is built on React Native and Expo, so it works both in the browser and as a native application. Under the hood, expo-av is used to play audio, and user settings are stored locally. There is support for several languages, including Russian and English.

    Ushki-Radio is a good example of what a modern Internet radio player can be: open, lightweight, expandable and focused primarily on the listener. The project is distributed under the MIT license and is perfect for both personal use and as a basis for your own experiments with audio applications.

    GitHub:
    https://github.com/demensdeum/Ushki-Radio

    Google Play:
    https://play.google.com/store/apps/details?id=com.demensdeum.ushkiradio

  • Glazki TV: Modern player for Internet television

    Glazki TV is a modern, high-performance player for Internet television (IPTV), built on the basis of React Native and Expo. The project is focused on ease of use and speed, providing a convenient interface for viewing IPTV channels both on mobile devices and in the browser.

    Main features

    • Channel Browsing: Browse thousands of channels, categorized for easy navigation.
    • Search: Quickly find the channels you need by name.
    • Favorites: Save your favorite channels for quick access (data saved locally).
    • Deep Linking: Share direct links to channels that open automatically.
    • Theme support: The interface automatically adapts to the system dark or light theme.
    • Web support: The player is fully functional in the browser with URL synchronization.

    Technology stack

    The project is based on modern development tools:

    • Framework: React Native + Expo
    • Video Player: expo-video (замена устаревшему expo-av)
    • UI Toolkit: react-native-paper
    • Playlist Parser: iptv-playlist-parser

    Web version:
    https://demensdeum.com/software/glazki-tv/

    Google Play version:
    https://play.google.com/store/apps/details?id=com.demensdeum.glazkitv

  • Zefir1990


    Zefir1990 – Ilia Prokhorov, I am a developer with more than 15 years of experience in commercial development. Founder of the Demens Deum studio, which develops games and applications for mobile, web, and desktop systems. The first 3D OpenGL ES 2 game Mad Racer on Android 2 was released in 2010, he was a programmer and game designer on the project, he also involved composer Anton Dmitriev (coolspotdreamer) in the development, after the successful launch of the game he was engaged in full-time custom development and project development under contracts. Clients include Decathlon, Playboy, Fitbit.

    I am also the author of articles on software development, games, design patterns, algorithms, AI, and I plan to write a book about software entropy.

    Preferred programming languages: ASM, C, C++, ObjC, Python, Kotlin, Swift, Java, Rust, Go, TypeScript, JavaScript, C#, Dart, PHP.

    LinkedIn: linkedin.com/in/zefir1990
    GitHub: github.com/zefir1990
    Telegram: t.me/zefir1990
    Email: ceo@demensdeum.com
    Twitch: twitch.tv/zefir1990
    Youtube: youtube.com/zefir1990

DemensDeum
Privacy Overview

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.