

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Tang Moying</title>
    <description>A developer-turned DevOps engineer writing about DevOps, software craft and clear thinking.</description>
    <link>https://tangmoying.com/en/</link>
    <atom:link href="https://tangmoying.com/en/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Tue, 13 Oct 2026 03:56:09 +0000</pubDate>
    <lastBuildDate>Tue, 13 Oct 2026 03:56:09 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>GitLab Predefined Variables in Practice (Part 2): The Pipeline &quot;Orchestration Signals&quot; — Trigger Source, Parent-Child Pipelines, Runner and Environment</title>
        <description>&lt;p&gt;The previous post talked about the “identification” variables — how a pipeline knows “who it is, where it is, and which commit it came from”. This post is about the “orchestration” variables, which answer a different kind of question: &lt;strong&gt;who triggered me, which larger pipeline do I belong to, which machine am I running on, and which environment did I reach.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the identification variables brought configuration to life (one set of configuration adapting to many branches and many projects), then the orchestration variables make pipelines truly able to cooperate — multi-project chaining, parent-child splitting, and executing differently by trigger method all depend on them.&lt;/p&gt;

&lt;!--以上为摘要内容--&gt;

&lt;h2 id=&quot;1-who-triggered-the-pipeline-ci_pipeline_source&quot;&gt;1. Who Triggered the Pipeline: CI_PIPELINE_SOURCE&lt;/h2&gt;

&lt;p&gt;This is the one to figure out first among the orchestration variables. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_PIPELINE_SOURCE&lt;/code&gt; tells you how this pipeline came about; its possible values include:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Value&lt;/th&gt;
      &lt;th&gt;Meaning&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;push&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Triggered by a code push&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;merge_request_event&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Triggered by an MR event (see the previous post)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;schedule&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Triggered by a scheduled job&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Manually clicking “Run pipeline” in the GitLab web UI&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;api&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Triggered through the API&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;trigger&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Triggered through a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;trigger&lt;/code&gt; token (common in multi-project pipelines)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pipeline&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Triggered by another pipeline (parent-child / multi-project pipelines)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chat&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Triggered through ChatOps (for example the Slack &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/gitlab&lt;/code&gt; command)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;external&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Triggered by an external CI integration&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Its value lies in this: &lt;strong&gt;the same configuration behaves completely differently under different trigger methods&lt;/strong&gt;. The classic pattern is “run the cheap ones constantly, the expensive ones on demand”:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;quick_lint&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./lint.sh&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$CI_PIPELINE_SOURCE == &quot;push&quot;&lt;/span&gt;          &lt;span class=&quot;c1&quot;&gt;# run the lightweight check on every push&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$CI_PIPELINE_SOURCE == &quot;merge_request_event&quot;&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;heavy_e2e&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./e2e.sh&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$CI_PIPELINE_SOURCE == &quot;schedule&quot;&lt;/span&gt;      &lt;span class=&quot;c1&quot;&gt;# only the scheduled job runs the heavy e2e&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$CI_PIPELINE_SOURCE == &quot;web&quot;&lt;/span&gt;           &lt;span class=&quot;c1&quot;&gt;# and it can also run when you click to run it manually&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This way day-to-day pushes return results in seconds, and the expensive end-to-end tests only run on a nightly schedule, so Runner resources aren’t wasted.&lt;/p&gt;

&lt;h2 id=&quot;2-scheduled-jobs-need-names-too-ci_pipeline_schedule_description&quot;&gt;2. Scheduled Jobs Need Names Too: CI_PIPELINE_SCHEDULE_DESCRIPTION&lt;/h2&gt;

&lt;p&gt;Knowing that it was triggered by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;schedule&lt;/code&gt; isn’t enough. You may have configured several scheduled jobs: one that “runs the full regression every day at dawn” and one that “runs smoke tests every hour”. With the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitlab-ci.yml&lt;/code&gt;, how do you tell which scheduled job this run is?&lt;/p&gt;

&lt;p&gt;The answer is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_PIPELINE_SCHEDULE_DESCRIPTION&lt;/code&gt; — it takes the Description you filled in for that scheduled job under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CI/CD → Schedules&lt;/code&gt;. Use it to branch:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;nightly_regression&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./regression.sh&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$CI_PIPELINE_SOURCE == &quot;schedule&quot; &amp;amp;&amp;amp; $CI_PIPELINE_SCHEDULE_DESCRIPTION == &quot;nightly-full&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note that it compares the &lt;strong&gt;description text&lt;/strong&gt;, so scheduled jobs need to be named consistently and stably. Don’t call it &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nightly&lt;/code&gt; today and change it to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;每晚全量&lt;/code&gt; tomorrow, or the rule will stop matching.&lt;/p&gt;

&lt;h2 id=&quot;3-passing-context-between-parent-and-child-pipelines-ci_parent_pipeline_id-and-ci_upstream_&quot;&gt;3. Passing Context Between Parent and Child Pipelines: CI_PARENT_PIPELINE_ID and CI_UPSTREAM_*&lt;/h2&gt;

&lt;p&gt;As &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitlab-ci.yml&lt;/code&gt; grows longer, you’ll want to split build, test, and deploy into “child pipelines”, pulled in with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;trigger: include&lt;/code&gt;. Or project A finishes building and triggers a deployment in project B (a multi-project pipeline). At that point, how do parent and child know who each other are?&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Variable&lt;/th&gt;
      &lt;th&gt;Meaning&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_PIPELINE_ID&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The unique ID of the current pipeline&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_PARENT_PIPELINE_ID&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The ID of the parent pipeline that triggered me (has a value in child pipelines)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_UPSTREAM_PIPELINE_ID&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The upstream pipeline ID (in multi-project scenarios, pointing at the triggering side)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_UPSTREAM_PROJECT_PATH&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The upstream project path&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_UPSTREAM_NAMESPACE&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The upstream namespace&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_UPSTREAM_REF&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The upstream ref at the time of triggering&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;A practical scenario: a child pipeline finishes its tests and wants to write the status back or notify the parent project. With &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_UPSTREAM_PROJECT_PATH&lt;/code&gt; you can pin down “who set me running”:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# in the child pipeline&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;notify_parent&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./notify.sh &quot;子流水线 $CI_PIPELINE_ID 完成，父流水线为 $CI_PARENT_PIPELINE_ID&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Another gotcha: in a child pipeline, “identification” variables such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_BRANCH&lt;/code&gt; are &lt;strong&gt;inherited from the parent pipeline&lt;/strong&gt;, not recomputed. So the branch-based branching rules from the previous post work just as correctly in parent-child pipelines, without needing to pass parameters again.&lt;/p&gt;

&lt;h2 id=&quot;4-which-machine-am-i-running-on-ci_runner_-and-gitlab_&quot;&gt;4. Which Machine Am I Running On: CI_RUNNER_* and GITLAB_*&lt;/h2&gt;

&lt;p&gt;GitLab Runner is multi-platform: Linux, Windows, macOS, even specific GPU machines. Your scripts often need to sense which kind of Runner they’re running on.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Variable&lt;/th&gt;
      &lt;th&gt;Meaning&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_RUNNER_ID&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The Runner’s numeric ID&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_RUNNER_DESCRIPTION&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The Runner’s description (what you filled in when registering it)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_RUNNER_TAGS&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The Runner’s tag list, comma-separated, e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker,linux&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_SERVER_HOST&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The GitLab instance’s hostname&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_SERVER_VERSION&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The GitLab version number&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$GITLAB_USER_NAME&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$GITLAB_USER_EMAIL&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$GITLAB_USER_LOGIN&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$GITLAB_USER_ID&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The identity of the user who triggered this pipeline&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The most classic use case is &lt;strong&gt;platform-specific builds&lt;/strong&gt;. For example, an iOS package can only be built on a macOS Runner:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;build_ios&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./build-ios.sh&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;macos&lt;/span&gt;          &lt;span class=&quot;c1&quot;&gt;# only pick Runners tagged macos&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Inside a script, if you want to branch on Runner capabilities, you can parse &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_RUNNER_TAGS&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$CI_RUNNER_TAGS&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-q&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;gpu&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
  &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USE_GPU&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1
&lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$GITLAB_USER_*&lt;/code&gt;, by contrast, is often used for audit and notification — when something goes wrong, it tells you clearly who clicked to release and who triggered this pipeline, instead of leaving you staring at an anonymous CI bot.&lt;/p&gt;

&lt;h2 id=&quot;5-which-environment-did-it-reach-ci_environment_-and-deploy-freezes&quot;&gt;5. Which Environment Did It Reach: CI_ENVIRONMENT_* and Deploy Freezes&lt;/h2&gt;

&lt;p&gt;Jobs that carry an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;environment&lt;/code&gt; get extra environment-related variables injected by GitLab:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Variable&lt;/th&gt;
      &lt;th&gt;Meaning&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_ENVIRONMENT_NAME&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The current environment name (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;production&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;review/my-feature&lt;/code&gt;)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_ENVIRONMENT_SLUG&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The slug form of the environment name (DNS-safe)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_ENVIRONMENT_URL&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The address you declared in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;environment:url&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Together with the review environment pattern from the first post, this set of variables makes “dynamic environments” genuinely usable: every MR automatically gets its own review environment, cleaned up automatically when the MR is closed.&lt;/p&gt;

&lt;p&gt;Another practical one is the &lt;strong&gt;deploy freeze&lt;/strong&gt;. GitLab lets you mark out “no deploy” windows on the calendar (during a major sale, say). During a freeze, related variables are set:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_DEPLOY_FREEZE_STARTED&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_DEPLOY_FREEZE_*&lt;/code&gt; and the like, injected by instance/project configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can use it to “block” production deployments and avoid mistakes inside a freeze window:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;deploy_prod&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./deploy.sh prod&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;production&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Although the deploy freeze itself is intercepted by GitLab at the scheduling layer, knowing these variables lets you write finer-grained, custom safeguards inside your scripts.&lt;/p&gt;

&lt;h2 id=&quot;6-parallel-splitting-and-job-metadata-ci_job_-and-ci_node_&quot;&gt;6. Parallel Splitting and Job Metadata: CI_JOB_* and CI_NODE_*&lt;/h2&gt;

&lt;p&gt;The last group is job-level and mainly useful in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parallel&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parallel:matrix&lt;/code&gt; scenarios:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Variable&lt;/th&gt;
      &lt;th&gt;Meaning&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_JOB_ID&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The unique ID of the current job&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_JOB_NAME&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The job name&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_JOB_STAGE&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The stage it belongs to&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_JOB_URL&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The job’s web page URL&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_NODE_INDEX&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The index within the parallel split (starting from 0)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_NODE_TOTAL&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The total number of parallel slices&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;A typical scenario: split a large test suite into 4 slices running in parallel, each running only its own portion:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;test_split&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;parallel&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;4&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./run-tests.sh --slice $CI_NODE_INDEX/$CI_NODE_TOTAL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Most test frameworks (pytest, jest, and so on) support slicing by “total + index”; just feed them &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_NODE_INDEX&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_NODE_TOTAL&lt;/code&gt; and you don’t need to compute anything yourself.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;The core problem the orchestration variables solve is “how does a pipeline know what collaborative relationship it’s part of”:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Trigger method: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_PIPELINE_SOURCE&lt;/code&gt; — branch behavior by push / MR / schedule / web&lt;/li&gt;
  &lt;li&gt;Scheduled identity: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_PIPELINE_SCHEDULE_DESCRIPTION&lt;/code&gt; — tell the intent of several scheduled jobs apart&lt;/li&gt;
  &lt;li&gt;Parent-child cooperation: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_PARENT_PIPELINE_ID&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_UPSTREAM_*&lt;/code&gt; — pass context between pipelines and write status back&lt;/li&gt;
  &lt;li&gt;Execution machine: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_RUNNER_*&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$GITLAB_USER_*&lt;/code&gt; — platform adaptation and audit tracing&lt;/li&gt;
  &lt;li&gt;Environment and deployment: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_ENVIRONMENT_*&lt;/code&gt; — dynamic environments, deploy freezes&lt;/li&gt;
  &lt;li&gt;Parallel splitting: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_NODE_INDEX&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_NODE_TOTAL&lt;/code&gt; — test sharding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together, the two posts complete the two main threads of GitLab predefined variables: identification and orchestration. Remember the principle that runs through it all: &lt;strong&gt;if a predefined variable can give you a value, don’t hard-code it&lt;/strong&gt; — hard-coding will come back to bite you in the second repository, on the Nth trigger, on the Mth Runner.&lt;/p&gt;

&lt;p&gt;One last reminder of an easily overlooked point: predefined variables &lt;strong&gt;expand at different times in different places&lt;/strong&gt;. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rules:&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;variables:&lt;/code&gt; expand during YAML parsing, whereas variables inside &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;script:&lt;/code&gt; expand during shell execution. So comparing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_BRANCH&lt;/code&gt; inside &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rules&lt;/code&gt; is safe, but if you want to build a variable name dynamically inside &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;script&lt;/code&gt; and then reference it, you have to mind the shell’s expansion order — that set of pitfalls deserves a post of its own, and we’ll get into it another time.&lt;/p&gt;

&lt;p&gt;A small step forward every day is a new height!&lt;/p&gt;
</description>
        <pubDate>Sun, 21 Jun 2026 00:00:00 +0000</pubDate>
        <link>https://tangmoying.com/en/post/gitlab-predefined-variables-orchestration</link>
        <guid isPermaLink="true">https://tangmoying.com/en/post/gitlab-predefined-variables-orchestration</guid>
        
        <category>GitLab CI</category>
        
        <category>Predefined Variables</category>
        
        <category>CI_PIPELINE_SOURCE</category>
        
        <category>Parent-Child Pipelines</category>
        
        <category>Environment Deployment</category>
        
        
        <category>devops</category>
        
      </item>
    
      <item>
        <title>GitLab Predefined Variables in Practice (Part 1): The Pipeline&apos;s &quot;Identity&quot; — Branch, Tag, Commit, and MR</title>
        <description>&lt;p&gt;Every time a pipeline runs, GitLab automatically injects a pile of “built-in variables” (officially called Predefined Variables). You don’t need to configure them; they work out of the box, and they’re the only window through which a pipeline can perceive “what context it’s in right now.”&lt;/p&gt;

&lt;p&gt;Many people writing a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitlab-ci.yml&lt;/code&gt; have only ever used one &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_BRANCH&lt;/code&gt;, and then rely on a pile of hard-coded &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt;s to tell environments apart. But this set of predefined variables can actually support some very intricate scenarios: one and the same configuration can automatically adapt to the branch, automatically tag a version, automatically recognize whether it’s an MR, and automatically know which project is running.&lt;/p&gt;

&lt;p&gt;If you’re not yet familiar with GitLab’s variable hierarchy (how Project / Group / predefined variables relate to one another), I’d suggest reading that 2023 article, “Gitlab CI Variables and Environments,” first. This article won’t restate the hierarchy; it focuses solely on &lt;strong&gt;how to use predefined variables to solve real scenarios&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This article focuses on the “identity” variables — the ones that answer “who am I, where am I, where did I come from, and which commit did I come from.” The next article will cover the “orchestration” variables.&lt;/p&gt;

&lt;!--以上为摘要内容--&gt;

&lt;h2 id=&quot;1-deciding-the-deployment-target-by-branch-ci_commit_branch-and-its-friends&quot;&gt;1. Deciding the Deployment Target by Branch: CI_COMMIT_BRANCH and Its Friends&lt;/h2&gt;

&lt;p&gt;The most classic scenario: the develop branch deploys to the test environment, the main branch deploys to production.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;deploy_staging&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;deploy&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./deploy.sh staging&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$CI_COMMIT_BRANCH == &quot;develop&quot;&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;deploy_prod&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;deploy&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./deploy.sh prod&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There are two easily confused variables here; let’s get them straight first:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_BRANCH&lt;/code&gt;&lt;/strong&gt;: the current branch name. Note that &lt;strong&gt;it is empty when a pipeline is triggered by a Tag&lt;/strong&gt; (because a Tag doesn’t belong to any branch).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_REF_NAME&lt;/code&gt;&lt;/strong&gt;: the name of the current ref — a branch name when it’s a branch, a Tag name when it’s a Tag. It’s a superset of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_BRANCH&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_REF_SLUG&lt;/code&gt;&lt;/strong&gt;: converts the ref name into a “DNS-safe” form — for example &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;feature/login&lt;/code&gt; becomes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;feature-login&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;release/v1.2&lt;/code&gt; becomes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;release-v1-2&lt;/code&gt;. When you want to use a ref name to build a host name, a namespace, or a directory name, use this rather than the ref name itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A common mistake: using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_BRANCH&lt;/code&gt; to build a Kubernetes namespace in a review environment:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;review&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;review/$CI_COMMIT_BRANCH&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# the branch name feature/login contains a /, which K8s doesn&apos;t allow&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The correct form uses the slug:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;review&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;review/$CI_COMMIT_REF_SLUG&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# feature-login, safe&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;https://$CI_COMMIT_REF_SLUG.review.example.com&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;2-tagging-artifacts-as-immutable-ci_commit_sha-and-ci_commit_tag&quot;&gt;2. Tagging Artifacts as “Immutable”: CI_COMMIT_SHA and CI_COMMIT_TAG&lt;/h2&gt;

&lt;p&gt;For images and artifact repositories, the thing to dread most is “overwriting under the same name.” The ideal practice is to tag every build with an &lt;strong&gt;immutable&lt;/strong&gt; tag — the commit SHA is a naturally immutable identifier.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;build&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_SHA&lt;/code&gt;&lt;/strong&gt;: the full 40-character commit SHA.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_SHORT_SHA&lt;/code&gt;&lt;/strong&gt;: the first 8 characters as a short SHA, good enough and easier to read — just use it for everyday scenarios.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you come to a “release” scenario, you want the tag to be a semantic version number rather than a string of SHA. Distinguishing “an ordinary commit” from “a pushed Tag” comes down to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_TAG&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;release&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$CI_COMMIT_TAG&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# only runs when a Tag was pushed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The key point: &lt;strong&gt;when a Tag is pushed, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_BRANCH&lt;/code&gt; is empty, but &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_TAG&lt;/code&gt; has a value&lt;/strong&gt;. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_REF_NAME&lt;/code&gt; has a value in both scenarios (the branch name and the Tag name respectively). So if your logic needs to cover both “a branch push” and “a pushed Tag,” prefer &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_REF_NAME&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;3-distinguishing-an-ordinary-push-from-a-merge-request-ci_pipeline_source-and-the-mr-variable-family&quot;&gt;3. Distinguishing “an Ordinary Push” from “a Merge Request”: CI_PIPELINE_SOURCE and the MR Variable Family&lt;/h2&gt;

&lt;p&gt;This is where people trip up most easily. By default, pushing to a branch and opening an MR against that same branch &lt;strong&gt;may each run a pipeline&lt;/strong&gt; (depending on whether you have “Merge Request Pipelines” enabled). Your script needs to be able to tell “am I running inside an MR right now, or inside an ordinary push.”&lt;/p&gt;

&lt;p&gt;The key to telling them apart is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_PIPELINE_SOURCE&lt;/code&gt;. When the pipeline comes from an MR, its value is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;merge_request_event&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;run_tests&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./run-tests.sh&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$CI_PIPELINE_SOURCE == &quot;merge_request_event&quot;&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# only run the heavy tests inside an MR&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The MR scenario also has a whole set of dedicated variables, whose value is that they let you get information about the target branch “before the merge” and validate ahead of time:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Variable&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_MERGE_REQUEST_IID&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The MR’s number within the repository (not a global ID)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_MERGE_REQUEST_TITLE&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The MR’s title&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The source branch (the one you’re merging in)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_MERGE_REQUEST_TARGET_BRANCH_NAME&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The target branch (the one you’re merging into)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_MERGE_REQUEST_APPROVED&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Whether it has been approved (requires a license)&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;A practical scenario: only allow merges into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt;, and the source branch must have passed the checks.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;guard&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;echo &quot;检查目标分支&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$CI_PIPELINE_SOURCE == &quot;merge_request_event&quot; &amp;amp;&amp;amp; $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == &quot;main&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Another common need: &lt;strong&gt;on an ordinary push run only a lightweight lint, and run the full tests only inside an MR&lt;/strong&gt;, so you don’t spend half an hour on every push and waste Runner resources. This is the most typical value of using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_PIPELINE_SOURCE&lt;/code&gt; to route the work.&lt;/p&gt;

&lt;h2 id=&quot;4-digging-information-out-of-commit-messages-the-ci_commit_message-family&quot;&gt;4. Digging Information Out of Commit Messages: the CI_COMMIT_MESSAGE Family&lt;/h2&gt;

&lt;p&gt;Commit messages aren’t just for people to read; a pipeline can read them too. GitLab splits them up in fine detail:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Variable&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_MESSAGE&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The full commit message (including the body)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_TITLE&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The first line (the subject)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_DESCRIPTION&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The body excluding the first line&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_AUTHOR_NAME&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The author’s name&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_AUTHOR_EMAIL&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The author’s email&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_TIMESTAMP&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The commit timestamp (ISO 8601)&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Practical scenario 1: &lt;strong&gt;automatically skipping CI&lt;/strong&gt;. Although these days it’s preferable to use GitLab’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[skip ci]&lt;/code&gt; convention (write &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[skip ci]&lt;/code&gt; directly in the commit message and GitLab automatically won’t run the pipeline), you can also check for it manually in a script if you want:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;if echo &quot;$CI_COMMIT_MESSAGE&quot; | grep -q &quot;\[skip ci\]&quot;; then echo &quot;跳过&quot;; exit 0; fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Practical scenario 2: &lt;strong&gt;using a commit prefix to trigger different behavior&lt;/strong&gt;. For example, agree that commits starting with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chore(release):&lt;/code&gt; automatically cut a release:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;auto_release&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./release.sh&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH &amp;amp;&amp;amp; $CI_COMMIT_TITLE =~ /^chore\(release\)/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Practical scenario 3: &lt;strong&gt;automatically generating a changelog&lt;/strong&gt;. Take &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_TITLE&lt;/code&gt;, string it into the release notes, and combine it with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_AUTHOR_NAME&lt;/code&gt; to note who committed it — far less hassle than writing it by hand.&lt;/p&gt;

&lt;h2 id=&quot;5-which-project-am-i-running-in-the-ci_project_-family&quot;&gt;5. Which Project Am I Running In: the CI_PROJECT_* Family&lt;/h2&gt;

&lt;p&gt;When you use a single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitlab-ci.yml&lt;/code&gt; as a template and reuse it across a dozen repositories under a Group, a hard-coded project name falls apart immediately. That’s when you rely on the project-level predefined variables:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Variable&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_PROJECT_PATH&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The full path, e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;group/subgroup/myproject&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_PROJECT_NAME&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The project name (the last segment of the path)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_PROJECT_NAMESPACE&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The namespace it belongs to (a group or a user)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_PROJECT_DIR&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The absolute path where the code is cloned on the Runner&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_PROJECT_URL&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The project’s web URL on GitLab&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_PROJECT_ID&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;The project’s numeric ID&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The most useful thing is to include the project context in &lt;strong&gt;notification&lt;/strong&gt; scripts; otherwise the group chat receives a pile of “build failed” and nobody knows which project it was:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;notify&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./notify.sh &quot;项目 $CI_PROJECT_PATH 的流水线 $CI_PIPELINE_ID 失败了：$CI_PROJECT_URL/-/pipelines/$CI_PIPELINE_ID&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_PROJECT_DIR&lt;/code&gt; — once you’ve &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cd&lt;/code&gt;-ed back and forth in a script and want to get back to the code root, using the absolute path &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_PROJECT_DIR&lt;/code&gt; is far more reliable than a relative path, especially inside &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;after_script&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;This group of variables solves the core problem of “how does a pipeline know what context it’s in”:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Branch / ref / slug: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_BRANCH&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_REF_NAME&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_REF_SLUG&lt;/code&gt; — deciding “where to deploy and what it’s called”&lt;/li&gt;
  &lt;li&gt;Commit / Tag: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_SHA&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_SHORT_SHA&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_TAG&lt;/code&gt; — giving artifacts an immutable, readable tag&lt;/li&gt;
  &lt;li&gt;MR context: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_PIPELINE_SOURCE == &quot;merge_request_event&quot;&lt;/code&gt; plus &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_MERGE_REQUEST_*&lt;/code&gt; — telling MR apart from push, and validating ahead of the merge&lt;/li&gt;
  &lt;li&gt;Commit message: the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_COMMIT_MESSAGE&lt;/code&gt; family — driving behavior from conventions and generating notes automatically&lt;/li&gt;
  &lt;li&gt;Project identity: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$CI_PROJECT_*&lt;/code&gt; — letting one configuration be reused across many repositories&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remember one principle: &lt;strong&gt;whatever a predefined variable can give you, don’t hard-code.&lt;/strong&gt; Hard-coded project names, branch names, and paths will all turn on you the moment you copy the configuration into a second repository.&lt;/p&gt;

&lt;p&gt;The next article covers the “orchestration” variables: who triggered my pipeline, how context is passed between parent and child pipelines, and those Runner and environment variables.&lt;/p&gt;

&lt;p&gt;A small step forward every day is a new height!&lt;/p&gt;
</description>
        <pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate>
        <link>https://tangmoying.com/en/post/gitlab-predefined-variables-identity</link>
        <guid isPermaLink="true">https://tangmoying.com/en/post/gitlab-predefined-variables-identity</guid>
        
        <category>GitLab CI</category>
        
        <category>Predefined Variables</category>
        
        <category>CI_COMMIT_BRANCH</category>
        
        <category>Pipelines</category>
        
        
        <category>devops</category>
        
      </item>
    
      <item>
        <title>MCP vs. CLI: The Two Paths for Agents to Call Tools, and Who Is Eroding Whom?</title>
        <description>&lt;p&gt;When an Agent calls an external tool, there are two broad paths: MCP and the CLI. MCP (Model Context Protocol) is a standardized protocol Anthropic introduced at the end of 2024, with the goal of giving Agents one unified way to connect to every kind of third-party service. The CLI is just the command line — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubectl get pods&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git diff&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker ps&lt;/code&gt; — where the Agent runs the command directly and takes the output. Many people instinctively feel that MCP is the more “advanced” approach: standardized, type-safe, dynamically discovered. It sounds so sophisticated that it ought to gradually replace crude CLI calls. But the reality is exactly the opposite — the CLI is eroding MCP’s share in reverse. This article looks at why.&lt;/p&gt;

&lt;!--以上为摘要内容--&gt;

&lt;h2 id=&quot;1-mcps-ideal-one-standard-to-connect-everything&quot;&gt;1. MCP’s Ideal: One Standard to Connect Everything&lt;/h2&gt;

&lt;p&gt;First, let’s understand what problem MCP set out to solve.&lt;/p&gt;

&lt;p&gt;Before MCP existed, connecting an Agent to a new service meant doing four things: write a Tool definition (description + parameter schema), write the execution logic, write the error handling, and register the Tool with the Agent. You did all of it again for every service. Ten services meant ten pieces of glue code.&lt;/p&gt;

&lt;p&gt;MCP’s idea was this: &lt;strong&gt;define a standard protocol; the service provider writes an MCP Server, the Agent framework writes an MCP Client, and the two communicate over the standard protocol.&lt;/strong&gt; As a developer you no longer write glue code — the Agent dynamically discovers the capabilities a Server offers through the MCP protocol and calls them directly.&lt;/p&gt;

&lt;p&gt;For example: GitHub has an MCP Server that exposes tools like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;search_repositories&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;create_issue&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;get_pr&lt;/code&gt;. Once an Agent is connected to that MCP Server, it immediately “knows” how to work with GitHub — without you writing any code at all.&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Example&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;MCP&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;tool&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;definition&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;—&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;every&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;tool&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;carries&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;complete&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;schema&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;create_issue&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Create a new GitHub issue in a repository&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;inputSchema&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;object&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;owner&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Repository owner&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;repo&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Repository name&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Issue title&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;body&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Issue description body&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;required&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;owner&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;repo&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Standardized, type-safe, dynamically discovered — those three keywords look unassailable. Backed by Anthropic and a number of other AI companies, MCP quickly became the “correct choice” for Agent tool calling.&lt;/p&gt;

&lt;h2 id=&quot;2-mcps-reality-the-token-bill-is-much-steeper-than-you-think&quot;&gt;2. MCP’s Reality: The Token Bill Is Much Steeper Than You Think&lt;/h2&gt;

&lt;p&gt;MCP’s standardization comes at a price. Every Tool has to sit in the System Prompt with its complete description and parameter schema. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;create_issue&lt;/code&gt; example above is not very long — around 400 Tokens. But a complete MCP Server usually has anywhere from a dozen to several dozen Tools.&lt;/p&gt;

&lt;p&gt;A GitHub MCP Server exposes 20 tools at an average of 400 Tokens each; that is 8,000 Tokens — &lt;strong&gt;before any work has begun, 8,000 Tokens of the System Prompt are already gone.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What is more, those Tokens are consumed on every turn of the conversation. It is not “load whichever one you use” — every Tool definition lives in the System Prompt and is carried along every single time. For an MCP Server that supports 50 tools, the Tool definitions alone can easily take up more than 20,000 Tokens in the System Prompt.&lt;/p&gt;

&lt;p&gt;More troublesome still: &lt;strong&gt;those definitions may have nothing at all to do with your actual task.&lt;/strong&gt; You ask the Agent to “change a config file”, and it has no need whatsoever to know the definitions of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;create_issue&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;list_milestones&lt;/code&gt;, yet those definitions still show up in the System Prompt, crowding out the space that could have gone to file context and conversation history.&lt;/p&gt;

&lt;p&gt;And that is without counting the operational cost of the MCP Server itself. Running an MCP Server means an extra process, extra network overhead and extra authentication management. A simple &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git diff&lt;/code&gt; is one command through the CLI; through MCP it is “Agent → MCP Client → MCP Server → local Git command → return result” — a chain several times as long.&lt;/p&gt;

&lt;h2 id=&quot;3-the-return-of-the-cli-why-models-already-know-the-command-line&quot;&gt;3. The Return of the CLI: Why Models “Already Know” the Command Line&lt;/h2&gt;

&lt;p&gt;The Token-consumption problem with MCP made everyone take another look at the CLI, that “old approach”. What they found was that the CLI has one fundamental advantage over MCP in Agent tool calling: &lt;strong&gt;models have already learned these commands in bulk during training.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GPT-4, Claude, Qwen, DeepSeek — every mainstream model’s training data contains an enormous volume of shell scripts, command-line tutorials and operations manuals. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubectl&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;curl&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grep&lt;/code&gt; — the syntax, the parameters and the typical usage of these commands have already been internalized by the model.&lt;/p&gt;

&lt;p&gt;That means: &lt;strong&gt;you do not need to tell the model what &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubectl get pods&lt;/code&gt; means or how to use it. The model already knows.&lt;/strong&gt; All you have to do is give the Agent an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;execute_shell&lt;/code&gt; Tool, and it will construct the right command for itself when it needs one.&lt;/p&gt;

&lt;p&gt;Compare the Token consumption:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Call method&lt;/th&gt;
      &lt;th&gt;System Prompt footprint&lt;/th&gt;
      &lt;th&gt;Extra Tokens per call&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;MCP (full toolset)&lt;/td&gt;
      &lt;td&gt;15,000~25,000 Tokens (all Tool definitions)&lt;/td&gt;
      &lt;td&gt;Small (results returned structured)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CLI (one Shell Tool)&lt;/td&gt;
      &lt;td&gt;~200 Tokens (one Tool definition)&lt;/td&gt;
      &lt;td&gt;Small (command output text)&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;That is two orders of magnitude apart. And MCP’s Token consumption is &lt;strong&gt;fixed&lt;/strong&gt; — whether or not this conversation uses those Tools, the definitions sit in the System Prompt. The CLI’s Token consumption is on demand — it costs you only when you actually call a command.&lt;/p&gt;

&lt;p&gt;That is why the CLI is eroding MCP’s share in reverse. Not because the CLI is better, but because it is cheaper. And in an era when Tokens are money, “cheaper” is itself productivity.&lt;/p&gt;

&lt;h2 id=&quot;4-but-the-cli-is-not-perfect-either&quot;&gt;4. But the CLI Is Not Perfect Either&lt;/h2&gt;

&lt;p&gt;The CLI saves Tokens, but it has problems of its own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Uncontrolled output.&lt;/strong&gt; When the Agent runs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubectl get pods -o json&lt;/code&gt;, the output may be 500 lines of JSON — including a pile of metadata, status details, managedFields and so on that the Agent does not need. Shoving all of that into the context is another Token expense. MCP’s responses are usually filtered on the Server side and return only the key fields after structuring — on the “information density” of the return value, MCP wins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Crude error handling.&lt;/strong&gt; The CLI’s output is just stdout + stderr + exit code. The Agent has to work out for itself why a command failed. An MCP Server can hand back structured error information the moment a failure occurs, making it easier for the Agent to understand where the problem lies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blurry security boundaries.&lt;/strong&gt; Giving the Agent a general-purpose &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;execute_shell&lt;/code&gt; Tool is giving it access to the entire file system and every executable command. There is no way to say “the Agent may run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git&lt;/code&gt; but may not run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rm -rf&lt;/code&gt;” — a Shell Tool does not make that distinction. MCP can achieve fine-grained permission control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parameter combinations depend on the Agent “guessing”.&lt;/strong&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubectl&lt;/code&gt; has countless parameters, and although the model “knows” them, complex parameter combinations (fetching a specific field of a specific resource in a specific namespace in a specific format, say) can go wrong. MCP’s type-safety constraints can prevent that class of error.&lt;/p&gt;

&lt;p&gt;So the return of the CLI does not mean the death of MCP. It just means the two suit different kinds of scenarios.&lt;/p&gt;

&lt;h2 id=&quot;5-how-to-choose-a-decision-table&quot;&gt;5. How to Choose: A Decision Table&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Scenario&lt;/th&gt;
      &lt;th&gt;Recommendation&lt;/th&gt;
      &lt;th&gt;Reason&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Everyday Git operations (diff, log, commit)&lt;/td&gt;
      &lt;td&gt;CLI&lt;/td&gt;
      &lt;td&gt;Already internalized by the model; extremely Token-cheap&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Common kubectl / docker operations&lt;/td&gt;
      &lt;td&gt;CLI&lt;/td&gt;
      &lt;td&gt;The model saw command-line usage in bulk during training&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Complex GitHub APIs (Issue management, PR review)&lt;/td&gt;
      &lt;td&gt;MCP&lt;/td&gt;
      &lt;td&gt;Many parameters, many steps, type safety needed&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Database queries&lt;/td&gt;
      &lt;td&gt;MCP&lt;/td&gt;
      &lt;td&gt;Needs connection management, structured results, permission control&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Monitoring and alerting (Prometheus / Grafana)&lt;/td&gt;
      &lt;td&gt;It depends&lt;/td&gt;
      &lt;td&gt;Simple queries via CLI (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;curl&lt;/code&gt;), complex configuration via MCP&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;In-house internal services&lt;/td&gt;
      &lt;td&gt;MCP&lt;/td&gt;
      &lt;td&gt;No ready-made CLI; MCP is the standardized way in&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;One-off scripts and text processing&lt;/td&gt;
      &lt;td&gt;CLI&lt;/td&gt;
      &lt;td&gt;The Agent writes its own Shell script and runs it — flexible and efficient&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;One practical principle: &lt;strong&gt;try the CLI first, and only reach for MCP when the CLI cannot get the job done.&lt;/strong&gt; Do not start from the assumption that “MCP is better”. MCP’s Token overhead is there the moment you go live; the CLI’s Token overhead is on demand — start with the cheap option and add more only when it is not enough. That is the most pragmatic path in engineering.&lt;/p&gt;

&lt;h2 id=&quot;6-summary&quot;&gt;6. Summary&lt;/h2&gt;

&lt;p&gt;The relationship between MCP and the CLI is exactly the reverse of what many people’s intuition suggests. It is not “standardized MCP replacing the crude CLI” but “lightweight CLI proving, in practice, that it is already good enough in most scenarios, and squeezing MCP back into the few areas where it is genuinely necessary”.&lt;/p&gt;

&lt;p&gt;That is not a bad thing. This is simply how technology evolves — not every “advanced” approach survives; what survives is the approach with the best cost-performance ratio. MCP has irreplaceable value in the standardized integration of complex third-party services, but across a large number of everyday operations and development scenarios, the CLI — riding on two advantages, “already internalized by the model” plus “zero extra Token cost” — has proved to be the most pragmatic way to call a Tool.&lt;/p&gt;

&lt;p&gt;Across three articles — Tool, Skill, and MCP vs. CLI — the same point is really being made: &lt;strong&gt;Agent engineering is a process of subtraction.&lt;/strong&gt; More Tools is not better, more Skills is not better, and a more “advanced” way of calling a Tool is not better either. Restraint, pragmatism, and understanding the trade-offs at each layer — that is the right way to do Agent development.&lt;/p&gt;

&lt;p&gt;A small step forward every day is a new height!&lt;/p&gt;
</description>
        <pubDate>Sat, 13 Jun 2026 00:00:00 +0000</pubDate>
        <link>https://tangmoying.com/en/post/ai-agent-mcp-vs-cli</link>
        <guid isPermaLink="true">https://tangmoying.com/en/post/ai-agent-mcp-vs-cli</guid>
        
        <category>AI</category>
        
        <category>Agent</category>
        
        <category>MCP</category>
        
        <category>CLI</category>
        
        <category>Tool</category>
        
        <category>Function Calling</category>
        
        <category>Token Optimization</category>
        
        <category>DevOps</category>
        
        
        <category>ai</category>
        
      </item>
    
      <item>
        <title>An Agent&apos;s &quot;Code of Conduct&quot; — the Essence of Skill and the Common Misconceptions</title>
        <description>&lt;p&gt;Last time we talked about Tools — the “hands and feet” an Agent uses to operate on the outside world. Once it has hands and feet, the next question follows: how should the Agent use them? When should it do what, how far is far enough to count as “done”, and how should it judge a situation that is ambiguous? That is the problem Skill solves. It is not what tells the Agent “how you use kubectl” — that is the Tool’s business. What Skill tells the Agent is “the right process and principles for doing a thing”. But many people have a deep-rooted misconception about Skill, and this article is about that.&lt;/p&gt;

&lt;!--以上为摘要内容--&gt;

&lt;h2 id=&quot;1-what-a-skill-is-custom-processes-and-principles&quot;&gt;1. What a Skill Is: Custom Processes and Principles&lt;/h2&gt;

&lt;p&gt;Let’s start from a concrete scenario. You have written a Skill in Hermes called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;code-review&lt;/code&gt;. It goes roughly like this:&lt;/p&gt;

&lt;div class=&quot;language-markdown highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gu&quot;&gt;## Code Review Skill&lt;/span&gt;

在进行代码审查时，遵循以下流程和原则：

&lt;span class=&quot;gu&quot;&gt;### 审查流程&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;1.&lt;/span&gt; 先理解改动的业务背景（从 PR 描述和关联 Issue 中获取）
&lt;span class=&quot;p&quot;&gt;2.&lt;/span&gt; 检查安全性问题（SQL 注入、XSS、敏感信息泄露）
&lt;span class=&quot;p&quot;&gt;3.&lt;/span&gt; 检查性能瓶颈（N+1 查询、不必要的循环、大对象创建）
&lt;span class=&quot;p&quot;&gt;4.&lt;/span&gt; 检查代码风格和可读性
&lt;span class=&quot;p&quot;&gt;5.&lt;/span&gt; 检查测试覆盖（改动是否包含测试，测试是否覆盖了边界情况）

&lt;span class=&quot;gu&quot;&gt;### 判断原则&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; 安全问题是 blocker，必须修复
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; 性能问题视影响范围决定是否 blocking
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; 代码风格问题建议修改但不强制
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; 不确定的地方标记为&quot;建议确认&quot;，不要直接判断

&lt;span class=&quot;gu&quot;&gt;### 输出格式&lt;/span&gt;
每个问题标注严重程度：[Blocker] / [Major] / [Suggestion]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Notice that this Skill contains no technical detail at all — it does not tell you how &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grep&lt;/code&gt; is used or how &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git diff&lt;/code&gt; is invoked. It is all about &lt;strong&gt;process&lt;/strong&gt; (what order to check things in), &lt;strong&gt;principles&lt;/strong&gt; (what counts as a blocker, what counts as a suggestion) and &lt;strong&gt;output conventions&lt;/strong&gt; (how to label severity).&lt;/p&gt;

&lt;p&gt;That is the essence of a Skill: &lt;strong&gt;turning experience into an Agent’s code of conduct.&lt;/strong&gt; The judgements that run naturally through a senior engineer’s head during a code review — “check security first, then performance, style last; zero tolerance on security issues, stop at a hint on style issues” — become rules an Agent can follow, by way of a Skill.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Skill is not a manual for a Tool.&lt;/strong&gt; A Tool answers “what can be done”; a Skill answers “how to do it”. The two are not on the same dimension and do not stand in a relationship of replacement or competition.&lt;/p&gt;

&lt;h2 id=&quot;2-the-more-skills-the-better--the-most-common-misconception&quot;&gt;2. “The More Skills the Better” — The Most Common Misconception&lt;/h2&gt;

&lt;p&gt;This is also the most deeply held of all the misconceptions about Skill.&lt;/p&gt;

&lt;p&gt;Many people using Hermes or similar Agent tools share an intuition: the more Skills you install, the more powerful the Agent becomes. You find a “Python best practices” Skill and install it; you find a “K8s operations standards” Skill and install it; a “database optimization” Skill, install that too. In the end the Agent’s Skill list is enormously long, and it feels reassuring — “my Agent can do everything”.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That intuition is wrong.&lt;/strong&gt; Adding Skills does not increase an Agent’s capability linearly. The more likely result is that the Agent actually gets dumber.&lt;/p&gt;

&lt;p&gt;There are three reasons.&lt;/p&gt;

&lt;h3 id=&quot;21-the-context-blows-up&quot;&gt;2.1 The Context Blows Up&lt;/h3&gt;

&lt;p&gt;An Agent’s context window has limits. On every conversation, the content of all the Skills has to be injected into the System Prompt — it is not “load the one you use” but “inject all of them”. Install 20 Skills at 500 words each and your System Prompt has 10,000 extra words. Add the conversation history, the Tool definitions and file memory, and the context is soon filled up.&lt;/p&gt;

&lt;p&gt;What gets squeezed out is not Skills but &lt;strong&gt;the information the Agent genuinely needs&lt;/strong&gt; — the complete content of the file you have open right now, or the complete context of your conversation. The Agent is forced to trade off between “remembering the Skills” and “understanding your problem”, and that trade-off itself lowers the quality of its answers.&lt;/p&gt;

&lt;h3 id=&quot;22-skills-may-contradict-each-other&quot;&gt;2.2 Skills May Contradict Each Other&lt;/h3&gt;

&lt;p&gt;One Skill says “a function should not exceed 50 lines”; another says “do not split for splitting’s sake; logical completeness comes first”. When it comes time to act, the Agent does not know whose rule to follow when the two conflict, because Skill definitions usually contain no priority. The Agent will most likely pick one at random, or take both into account — and the result is a function that gets split and then merged again, the Agent fighting itself.&lt;/p&gt;

&lt;p&gt;When you maintain 20 Skills yourself, the further along you get, the harder it is to spot which of them contradict each other. Skills usually come from different sources and are installed at different times — reviewing them all as a whole to check for conflicts is very hard.&lt;/p&gt;

&lt;h3 id=&quot;23-the-agents-difficulty-choosing&quot;&gt;2.3 The Agent’s “Difficulty Choosing”&lt;/h3&gt;

&lt;p&gt;A Skill is not only a “passive rule”. Many Skills also shape the Agent’s behaviour pattern — a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;code-review&lt;/code&gt; Skill, for instance, steers the Agent into “review mode”: more cautious in what it says, asking more confirming questions, preferring to flag a TODO rather than change the code directly. A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;debugging&lt;/code&gt; Skill steers it into “investigation mode”: running diagnostic commands on its own initiative, analysing logs, offering several possibilities.&lt;/p&gt;

&lt;p&gt;When the Agent has a pile of Skills loaded at the same time, it becomes hesitant about “which mode to enter”. Its behaviour grows unstable — the same problem, and sometimes it enters review mode (only offering suggestions), sometimes investigation mode (executing commands on its own). This is not the Agent “getting smarter”; it is behaviour drift caused by too many Skills.&lt;/p&gt;

&lt;h2 id=&quot;3-how-to-write-a-good-skill&quot;&gt;3. How to Write a Good Skill&lt;/h2&gt;

&lt;p&gt;The “few but fine” principle for Skills also points, in reverse, to how a Skill should be designed. A good Skill has a few characteristics:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. A clear scenario.&lt;/strong&gt; Do not write a catch-all Skill like “backend development best practices” — the scenario is too broad, and covering everything amounts to covering nothing. It should be a Skill with a clear scenario such as “Code Review”, “Pre-deployment checks” or “Database query optimization”. Then, when the Agent is handling the matching scenario, it can recognize at once that “this Skill is the one to use now”.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Priorities and decision criteria.&lt;/strong&gt; Do not list a pile of rules and let the Agent choose for itself; tell it explicitly what counts as a Blocker, what counts as a Suggestion, and which rule wins when two of them conflict. Write the “judgement” that lives in human experience out in a structured form.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Good examples and counter-examples.&lt;/strong&gt; A bare description of a rule is not as good as an example. For a performance check, for instance, do not just write “avoid N+1 queries” — attach a before-and-after code comparison. An Agent (like a human) learns more solidly from examples than from rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. No overlap with existing Skills.&lt;/strong&gt; Before installing a new Skill, go through the list of Skills you already have and confirm that the new Skill’s function is not a subset of an existing Skill and that there are no obviously conflicting rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Light on context.&lt;/strong&gt; If a rule can be stated clearly, do not write more. A process that can be told in 500 words should not be padded into a 3,000-word encyclopaedia to look “professional”. A Skill’s goal is to help the Agent make decisions, not to help a person look things up in documentation.&lt;/p&gt;

&lt;h2 id=&quot;4-summary&quot;&gt;4. Summary&lt;/h2&gt;

&lt;p&gt;If this article conveys one thing, it is this: &lt;strong&gt;a Skill is a code of conduct, not a feature list; quality matters far more than quantity.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A Tool lets an Agent “be able to”; a Skill lets an Agent “know how to” — two entirely different questions. However good its hands and feet, without a set of principles and processes for getting things done, the Agent is just flailing. But the reverse holds too: too many principles, fighting each other, and the Agent is worse off than with none — it does not know whose rule to follow, which mode to enter, and it cannot even remember your question in full.&lt;/p&gt;

&lt;p&gt;Designing Skills is a process of “subtraction”. What matters is not how many rules you found but how many genuinely useful ones you kept in the end. Three clear enough Skills beat twenty contradictory ones a hundred times over.&lt;/p&gt;

&lt;p&gt;Next time, MCP vs. CLI — the two paths for an Agent to call a Tool, and why the situation is the exact opposite of what our intuition suggests.&lt;/p&gt;

&lt;p&gt;A small step forward every day is a new height!&lt;/p&gt;
</description>
        <pubDate>Sun, 07 Jun 2026 00:00:00 +0000</pubDate>
        <link>https://tangmoying.com/en/post/ai-agent-skill-principles</link>
        <guid isPermaLink="true">https://tangmoying.com/en/post/ai-agent-skill-principles</guid>
        
        <category>AI</category>
        
        <category>Agent</category>
        
        <category>Skill</category>
        
        <category>Hermes</category>
        
        <category>Process Design</category>
        
        <category>Custom Rules</category>
        
        <category>Context Management</category>
        
        
        <category>ai</category>
        
      </item>
    
      <item>
        <title>The Hands and Feet of an Agent: Tool Capability Boundaries and Their Evolution</title>
        <description>&lt;p&gt;However clever a large model is, if it cannot interact with the outside world it is just a talking head. Chatting, writing a poem, fixing a function for you — all of that is still within the scope of “outputting text”. But the moment you want an Agent to do something practical — deploy a service, look something up in a database, open a browser and fill in a form for you — it has to have “hands”. In Agent terminology, those “hands” are called Tools. From the earliest file reads and writes to today’s Computer Use and Browser Use, the capability boundary of Tools has kept expanding outward. This article walks through the evolution of Tools, and, along the way, the things that never changed.&lt;/p&gt;

&lt;!--以上为摘要内容--&gt;

&lt;h2 id=&quot;1-what-a-tool-is-function-calling-isnt-calling-its-translating&quot;&gt;1. What a Tool Is: Function Calling Isn’t “Calling”, It’s “Translating”&lt;/h2&gt;

&lt;p&gt;Before we talk about Tools, we need to get one thing straight: &lt;strong&gt;a large model doesn’t “call” anything.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You tell an Agent to “check the weather” and the Agent does not really open a browser to check. What it does is actually quite simple — it outputs a piece of JSON:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;get_weather&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;arguments&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;city&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Beijing&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then a host program (Hermes, ChatGPT’s backend, your Python script) goes and really executes that function, carries the result back to the model, and the model gives you a natural-language reply based on the result.&lt;/p&gt;

&lt;p&gt;This mechanism is called &lt;strong&gt;Function Calling&lt;/strong&gt;. The name is misleading — what it really amounts to is “the model outputs a structured expression of intent, and the host program does the executing”. The model is the brain, the Tool is the hands and feet, and Function Calling is the nerve signal.&lt;/p&gt;

&lt;p&gt;Understanding this matters, because it explains the core design principle of Tools: &lt;strong&gt;a Tool’s quality does not depend on how powerful its function is, but on how clear its “expression of intent” is.&lt;/strong&gt; A Tool’s description, its parameter definitions, its return-value format — these determine whether an Agent can “use” it correctly. A badly written Tool (vague description, arbitrary parameter names, a tangle of JSON for a return value) is no different from a hand with a severed nerve — however strong it is, it cannot move.&lt;/p&gt;

&lt;h2 id=&quot;2-the-evolution-of-tools-from-reading-and-writing-files-to-manipulating-the-world&quot;&gt;2. The Evolution of Tools: From Reading and Writing Files to Manipulating the World&lt;/h2&gt;

&lt;p&gt;The form of Tools has gone through several phases. It is not “a new version replacing an old version” but &lt;strong&gt;a capability boundary that keeps expanding outward&lt;/strong&gt; — just as human tools went from the stone axe to the power drill, not as a replacement but as more and more things they can handle.&lt;/p&gt;

&lt;h3 id=&quot;21-the-first-generation-reading-and-writing-files&quot;&gt;2.1 The First Generation: Reading and Writing Files&lt;/h3&gt;

&lt;p&gt;The earliest Tools, provided by default in almost every Agent framework: read a file, write a file, execute a Shell command.&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;The&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;earliest&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Calling&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;—&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;simple&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;direct&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;write_file&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;arguments&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/app/config.yaml&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;...&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The hallmark of this phase: &lt;strong&gt;about all a Tool can do is “write text down”&lt;/strong&gt;. Changing code, generating a config file, writing a script — at bottom they are all a combination of text generation plus a file write.&lt;/p&gt;

&lt;p&gt;That is already enough to solve many problems. In your day-to-day use of Hermes, when you ask it to fix a bug, all it does is: read the file (understand the context), output a revision plan, and write it back. But for scenarios that “need to interact with an external system”, file reads and writes are not enough.&lt;/p&gt;

&lt;h3 id=&quot;22-the-second-generation-web-requests-and-api-calls&quot;&gt;2.2 The Second Generation: Web Requests and API Calls&lt;/h3&gt;

&lt;p&gt;The Tools of the second phase began to break through the boundary of “local files” — &lt;strong&gt;an Agent could now call external APIs directly.&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;call_api&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;arguments&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;method&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;GET&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://api.example.com/v1/services&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;headers&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Authorization&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Bearer xxx&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Behind this sit a series of “connecting to external systems” Tools such as web_fetch and Shell command execution. The Agent is no longer just “writing files locally” — it can query APIs, run database queries, trigger CI/CD pipelines; anything a Shell can do, the Agent can do.&lt;/p&gt;

&lt;p&gt;The Tools of this phase are already enough to support the core scenarios of “making DevOps AI-driven”: an Agent can run the entire CI/CD process, from code repository to build server to deployment cluster.&lt;/p&gt;

&lt;h3 id=&quot;23-standardizing-tools-how-mcp-took-tools-from-built-by-the-vendor-to-built-together-by-the-ecosystem&quot;&gt;2.3 Standardizing Tools: How MCP Took Tools From “Built by the Vendor” to “Built Together by the Ecosystem”&lt;/h3&gt;

&lt;p&gt;The two generations of Tools we discussed above — file reads and writes, web requests, Shell execution — are almost all built in by the Agent vendor itself. The vendor’s engineering team defines the Tool’s schema, writes the execution logic, and packages it into the product. Early on this was no problem, because there were few kinds of Tools and the scenarios were concentrated.&lt;/p&gt;

&lt;p&gt;But the problem arrived before long: &lt;strong&gt;there are far too many external platforms to integrate.&lt;/strong&gt; Databases include MySQL, PostgreSQL, MongoDB, Redis… cloud platforms include AWS, Azure, GCP, Alibaba Cloud… monitoring includes Grafana, Prometheus, Datadog… collaboration tools include Slack, Jira, Confluence… Every platform has its own API, its own authentication method, its own data format. No vendor could build all of that in — not for lack of wanting to, but because it is impossible.&lt;/p&gt;

&lt;p&gt;Before there was a standardized protocol, if you wanted to add a “query Datadog monitoring” capability to an Agent, you either waited for the vendor to support it (which most likely never happened), or wrote your own glue code to wrap the Datadog API into a Tool the Agent could understand. And every vendor defined Tools differently — a Tool you had written for Claude Code would not work in Hermes, and the reverse was just as true. Tools and Agent platforms were &lt;strong&gt;tightly coupled&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is the problem &lt;strong&gt;MCP (Model Context Protocol)&lt;/strong&gt; exists to solve. MCP is a standardized Tool description protocol — it defines a “common language” for Tools: every Tool has a name, a description, a parameter schema and a return format. Whichever Agent platform it is, as long as it supports MCP it can understand and call any Tool Server that implements MCP. Tool developers and Agent platform developers can from then on &lt;strong&gt;work independently of each other&lt;/strong&gt; — connecting through the standard protocol is all it takes.&lt;/p&gt;

&lt;p&gt;The significance of this change: &lt;strong&gt;the Tool ecosystem went from “built in by the vendor” to “built together by the community”.&lt;/strong&gt; It used to be that you waited for Anthropic to add a Kubernetes Tool to Claude Code; now a community developer writes an MCP Server and every MCP-capable Agent can use it. Datadog, Grafana, Slack, Jira — as long as somebody writes an MCP Server, the Agent can connect. Vendors do not need to build those Tools in; they only need to implement the MCP protocol properly — that is the decoupling standardization brings.&lt;/p&gt;

&lt;h3 id=&quot;24-the-third-generation-computer-use--letting-an-agent-drive-the-desktop&quot;&gt;2.4 The Third Generation: Computer Use — Letting an Agent Drive the Desktop&lt;/h3&gt;

&lt;p&gt;If the Tools of the first two generations operate on “systems that have an API”, then Computer Use steps into “systems that have a user interface” — &lt;strong&gt;an Agent can use desktop software the way a person does.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Anthropic introduced Computer Use at the end of 2024. The principle: the Agent can see a screenshot of the screen, then output operation instructions — mouse clicks, keyboard input, scrolling — and after the host program executes those operations, it hands the Agent a fresh screenshot.&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;left_click&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;coordinate&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;450&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;320&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What is the essential difference from the Tools above? The earlier Tools operate on “interfaces designed to be programmable” — the API has documentation, the parameters have types, the return value has a format. Computer Use operates on “interfaces made for people” — no documentation, no schema, and the output is just an image. The Agent has to &lt;strong&gt;look&lt;/strong&gt;, &lt;strong&gt;understand&lt;/strong&gt;, &lt;strong&gt;decide&lt;/strong&gt; and &lt;strong&gt;execute&lt;/strong&gt; — every step is a combination of visual understanding and reasoning.&lt;/p&gt;

&lt;p&gt;What this capability means for DevOps: &lt;strong&gt;not every system has an API.&lt;/strong&gt; Your company may have a server from 15 years ago whose management interface is just a web console, with no API at all. Before, you could only operate it by hand; now an Agent can do it for you — inelegant, but already usable.&lt;/p&gt;

&lt;p&gt;Computer Use is not stable enough yet (the latency of each step, the accuracy of visual recognition and the reliability on complex interfaces are all still being improved), but the direction is clear: &lt;strong&gt;the Agent’s capability boundary is expanding from “the programmable world” to “the visible world”.&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;25-the-fourth-generation-browser-use--a-new-shape-for-web-automation&quot;&gt;2.5 The Fourth Generation: Browser Use — A New Shape for Web Automation&lt;/h3&gt;

&lt;p&gt;Browser Use can be seen as a specialized version of Computer Use for the web. It lets an Agent open a browser, browse web pages, click buttons, fill in forms and download files — completing a full web interaction flow.&lt;/p&gt;

&lt;p&gt;What is the difference from traditional Web Scraping? Scraping follows preset rules (“find the .price class and extract the text”), while Browser Use has the Agent deciding for itself (“I need to find the price, let me see which element on the page looks like a price”). Hard-coded rules break the moment the page is redesigned; an Agent that searches on the basis of semantic understanding is far more fault-tolerant and generalizes much better.&lt;/p&gt;

&lt;p&gt;An example: you are troubleshooting a production issue and need to log into Grafana to look at the CPU trend of some service, and then post the screenshot into the group chat. That used to be three separate things — log into Grafana, take the screenshot, paste the image into the chat tool — and each one needed a person. With Browser Use, the Agent can do it all in one go: open Grafana → navigate to the corresponding Dashboard → set the time range → take a screenshot → send it to the messaging channel.&lt;/p&gt;

&lt;p&gt;Behind Browser Use, Playwright or Puppeteer usually drives the browser, but the key difference is &lt;strong&gt;who writes the control logic&lt;/strong&gt; — no longer a developer hard-coding locators and a sequence of operations, but the Agent “reading” the page in real time from a natural-language instruction and deciding what to do next.&lt;/p&gt;

&lt;h2 id=&quot;3-how-the-four-phases-relate-not-replacement-but-expansion-of-capability&quot;&gt;3. How the Four Phases Relate: Not Replacement, but Expansion of Capability&lt;/h2&gt;

&lt;p&gt;Put these four generations of Tools side by side:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Phase&lt;/th&gt;
      &lt;th&gt;What it operates on&lt;/th&gt;
      &lt;th&gt;Interaction mode&lt;/th&gt;
      &lt;th&gt;Typical scenario&lt;/th&gt;
      &lt;th&gt;Stability&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;File reads and writes&lt;/td&gt;
      &lt;td&gt;Local file system&lt;/td&gt;
      &lt;td&gt;Structured IO&lt;/td&gt;
      &lt;td&gt;Changing code, writing config, generating documents&lt;/td&gt;
      &lt;td&gt;★★★★★&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Web/API calls&lt;/td&gt;
      &lt;td&gt;The APIs of external services&lt;/td&gt;
      &lt;td&gt;Structured request/response&lt;/td&gt;
      &lt;td&gt;Calling REST APIs, executing Shell scripts, CI/CD automation&lt;/td&gt;
      &lt;td&gt;★★★★☆&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Computer Use&lt;/td&gt;
      &lt;td&gt;Desktop application interfaces&lt;/td&gt;
      &lt;td&gt;Screenshot + mouse/keyboard&lt;/td&gt;
      &lt;td&gt;Driving legacy systems that have no API&lt;/td&gt;
      &lt;td&gt;★★★☆☆&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Browser Use&lt;/td&gt;
      &lt;td&gt;Web page interfaces&lt;/td&gt;
      &lt;td&gt;DOM + vision + clicks&lt;/td&gt;
      &lt;td&gt;Web automation, data collection, monitoring screenshots&lt;/td&gt;
      &lt;td&gt;★★★☆☆&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;One key pattern: &lt;strong&gt;the further to the right, the more “human-like” a Tool’s operation is; the further to the left, the more “program-like”.&lt;/strong&gt; “Human-like” means more general but less stable; “program-like” means more specialized but more reliable.&lt;/p&gt;

&lt;p&gt;So in real engineering, the principle for choosing a Tool is: &lt;strong&gt;if file reads and writes can solve it, don’t call an API; if an API call can do it, don’t reach for Computer Use.&lt;/strong&gt; Higher-level is not better — Computer Use sounds cool, but the latency and failure rate of each step are far higher than those of a stable API call. Choose the most “program-like” option, and until that option is no longer enough, take one step in the “human-like” direction.&lt;/p&gt;

&lt;h2 id=&quot;4-more-tools-is-not-better&quot;&gt;4. More Tools Is Not Better&lt;/h2&gt;

&lt;p&gt;Having written out the history of their evolution, I have to say one counterintuitive thing: &lt;strong&gt;more Tools is not better.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The intuition of many Agent developers is that installing more Tools makes an Agent more capable. It is like adding functions to a Swiss Army knife — one more small blade, one more bottle opener, one more pair of tweezers — it always feels good.&lt;/p&gt;

&lt;p&gt;But there is one fundamental difference between an Agent’s Tools and a Swiss Army knife: &lt;strong&gt;the Swiss Army knife’s tools are physically separate, while an Agent’s Tools are all crammed into the context.&lt;/strong&gt; Every extra Tool means one more description and one more set of parameter definitions in the System Prompt. Once the number of Tools goes up, the Agent has to “choose” among them — and that process itself consumes Tokens, consumes reasoning time, and increases the chance of choosing wrong.&lt;/p&gt;

&lt;p&gt;Worse still: &lt;strong&gt;Tools can interfere with each other.&lt;/strong&gt; Your JSON-validation Tool is called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;validate_json&lt;/code&gt;, and your weather Tool is called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;get_weather&lt;/code&gt;; they look like they keep to themselves. But when the Agent handles a “user request”, it has to judge “which Tool’s remit this request falls under”. If the descriptions are not written precisely enough, the Agent may route “check this config file for me” to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;get_weather&lt;/code&gt; — because the config file happens to mention “deployed on the cloud”, and the Agent’s chain of reasoning links “cloud” to “weather”.&lt;/p&gt;

&lt;p&gt;So the design principle for Tools is not “the more the better” but &lt;strong&gt;“just enough”&lt;/strong&gt;. The Tools you give an Agent should cover exactly what you need it to do; one more is interference.&lt;/p&gt;

&lt;h2 id=&quot;5-summary&quot;&gt;5. Summary&lt;/h2&gt;

&lt;p&gt;From file reads and writes to Browser Use, the changing form of Tools reflects the steady expansion of the Agent’s capability boundary. But however the form of Tools changes, a few principles have never changed:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;The essence of a Tool is “intent translation”&lt;/strong&gt; — the Agent does not really execute anything; it only outputs structured instructions. The clearer the description, the more reliably the Agent can use it.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;The more “program-like” the Tool you choose, the more reliable it is&lt;/strong&gt; — in scenarios with high stability requirements (DevOps automation, for example), prefer an API/CLI and keep Computer Use as a last resort.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;More Tools is not better&lt;/strong&gt; — every Tool is an occupation of Context and an interference with reasoning. Trimming down to just enough is the optimal answer.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Next time, we will talk about a concept that is often misunderstood — Skill. A Skill is not a sibling of Tool; it is something on another layer.&lt;/p&gt;

&lt;p&gt;A small step forward every day is a new height!&lt;/p&gt;
</description>
        <pubDate>Sat, 06 Jun 2026 00:00:00 +0000</pubDate>
        <link>https://tangmoying.com/en/post/ai-agent-tool-evolution</link>
        <guid isPermaLink="true">https://tangmoying.com/en/post/ai-agent-tool-evolution</guid>
        
        <category>AI</category>
        
        <category>Agent</category>
        
        <category>Tool</category>
        
        <category>Function Calling</category>
        
        <category>Computer Use</category>
        
        <category>Browser Use</category>
        
        <category>MCP</category>
        
        <category>CLI</category>
        
        
        <category>ai</category>
        
      </item>
    
      <item>
        <title>Hermes Memory Provider and Mem0: An Overlooked Memory Abstraction Layer That Lets Agents Share Knowledge</title>
        <description>&lt;p&gt;If you’re using Hermes Agent, chances are you’ve only ever used its file memory — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MEMORY.md&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;USER.md&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SKILL.md&lt;/code&gt;. That alone is already quite useful: tell it your preferences and it remembers them; work with it enough and it understands you better and better. But what many people don’t know is that Hermes hides a badly underrated capability: the &lt;strong&gt;Memory Provider mechanism&lt;/strong&gt;. It turns “memory storage” into a pluggable abstraction layer, so that your Agent doesn’t just remember you — it can share knowledge across different Agents. For enterprise scenarios that need multiple Agents dividing the work and collaborating, this is the key step from “fighting alone” to “teamwork”.&lt;/p&gt;

&lt;!--以上为摘要内容--&gt;

&lt;h2 id=&quot;1-wait-hermes-has-this-feature-too&quot;&gt;1. “Wait, Hermes Has This Feature Too?”&lt;/h2&gt;

&lt;p&gt;Let me start with a real scenario. You set up a “code review Agent” in Hermes and tell it your coding standards: functions no longer than 50 lines, variables named with full words, tests must be run before any change. Two weeks in, this Agent has your preferences down.&lt;/p&gt;

&lt;p&gt;Then you spin up a new “deployment Agent” and ask it to help you write deployment scripts. You may well be puzzled — this new Agent knows nothing about those earlier standards. You have to teach it all over again from scratch.&lt;/p&gt;

&lt;p&gt;Worse, if you have three Agents — code review, deployment, operations — each of them has its own memory file under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.hermes/&lt;/code&gt;, isolated from the others. This is the &lt;strong&gt;memory silo&lt;/strong&gt; problem: the Agent isn’t insufficiently smart, it’s locked inside its own “memory cell”.&lt;/p&gt;

&lt;p&gt;Many people’s reaction at this point is: “Nothing to be done, that’s just how Agents are.” But Hermes actually thought about this problem long ago.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hermes doesn’t only record things in files. It has a complete Memory Provider mechanism — one that abstracts “where memory lives” into a replaceable interface.&lt;/strong&gt; The built-in file storage (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MEMORY.md&lt;/code&gt; and friends) is merely the default implementation. You can absolutely plug in an external memory service and have multiple Agents share one and the same memory.&lt;/p&gt;

&lt;p&gt;I don’t know how many Hermes users are aware this feature exists. My guess: not many. Because “file memory” is already good enough, and the vast majority of tutorials stop at “edit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.hermes/config.yaml&lt;/code&gt; to configure the model” and never go deeper into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;memory.provider&lt;/code&gt; configuration key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This example is itself a reminder: to use a tool well, you can’t stop at the “getting started tutorial” level.&lt;/strong&gt; Tools evolve far faster than tutorials. The Hello World you see today may be missing a core feature that was added three months ago. Hermes’s Memory Provider has been around since an early 2026 release, yet you can barely find a complete introduction to it in Chinese search results. It isn’t that the feature isn’t good enough — it’s that information spreads with a lag. &lt;strong&gt;Learn the fundamentals well and follow updates to the thing itself — these two matter more than chasing every “I built XX system with XX tool” share video.&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;2-inside-the-memory-provider-mechanism&quot;&gt;2. Inside the Memory Provider Mechanism&lt;/h2&gt;

&lt;h3 id=&quot;21-design-philosophy-built-in-memory-and-external-providers-coexist&quot;&gt;2.1 Design Philosophy: Built-in Memory and External Providers Coexist&lt;/h3&gt;

&lt;p&gt;Hermes’s memory architecture has a clever design: &lt;strong&gt;built-in file memory is always in effect, and an external Provider is an additive supplement.&lt;/strong&gt; It’s not “pick one of the two”, it’s “take both”.&lt;/p&gt;

&lt;p&gt;Concretely, when a Memory Provider is active, Hermes does the following six things on every conversation:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Injects the Provider’s context into the system prompt&lt;/li&gt;
  &lt;li&gt;Before each turn, asynchronously prefetches relevant memories from the Provider&lt;/li&gt;
  &lt;li&gt;Syncs the conversation history to the Provider&lt;/li&gt;
  &lt;li&gt;Automatically extracts memories at the end of a session (if the Provider supports it)&lt;/li&gt;
  &lt;li&gt;Mirrors writes to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MEMORY.md&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;USER.md&lt;/code&gt; over to the Provider&lt;/li&gt;
  &lt;li&gt;Exposes Provider-specific tools to the Agent (such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mem0_search&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mem0_profile&lt;/code&gt;, and so on)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You don’t need to change your business code, don’t need to change your prompts, don’t need to change your habits. You simply have one more “memory backend” and one more “knowledge sharing layer”.&lt;/p&gt;

&lt;h3 id=&quot;22-the-nine-providers-at-a-glance&quot;&gt;2.2 The Nine Providers at a Glance&lt;/h3&gt;

&lt;p&gt;As of June 2026, Hermes ships with 9 external Memory Providers. I’ll sort them by deployment model and use case:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Provider&lt;/th&gt;
      &lt;th&gt;Deployment&lt;/th&gt;
      &lt;th&gt;Core capabilities&lt;/th&gt;
      &lt;th&gt;Who it’s for&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Mem0&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Cloud / self-hosted&lt;/td&gt;
      &lt;td&gt;Automatic fact extraction, semantic search, multi-tenant isolation&lt;/td&gt;
      &lt;td&gt;Enterprise scenarios (the focus of this article)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Honcho&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Cloud / self-hosted&lt;/td&gt;
      &lt;td&gt;User modeling, dialectical reasoning, session summaries&lt;/td&gt;
      &lt;td&gt;Cross-session understanding of users&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;OpenViking&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Self-hosted&lt;/td&gt;
      &lt;td&gt;Filesystem-style knowledge hierarchy, automatic extraction of 6 memory types&lt;/td&gt;
      &lt;td&gt;Open-source self-hosting&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Holographic&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Local SQLite&lt;/td&gt;
      &lt;td&gt;FTS5 full-text search, trust scoring&lt;/td&gt;
      &lt;td&gt;Fully local, lightweight&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;ByteRover&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Local / optional cloud sync&lt;/td&gt;
      &lt;td&gt;Hierarchical knowledge tree, pre-compression extraction, CLI management&lt;/td&gt;
      &lt;td&gt;Local-first with sync&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Hindsight&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Cloud / local pg&lt;/td&gt;
      &lt;td&gt;Knowledge graph, entity relations, reflective synthesis across memories&lt;/td&gt;
      &lt;td&gt;You need graph reasoning&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;RetainDB&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Cloud&lt;/td&gt;
      &lt;td&gt;Hybrid search (BM25 + vectors), 7 memory types, incremental compression&lt;/td&gt;
      &lt;td&gt;You need advanced search&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Supermemory&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Cloud&lt;/td&gt;
      &lt;td&gt;Context isolation, session graph, multiple containers, user profile injection&lt;/td&gt;
      &lt;td&gt;Preventing memory contamination&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Memori&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Cloud&lt;/td&gt;
      &lt;td&gt;Structured memory, attribution by project/session, background ingestion&lt;/td&gt;
      &lt;td&gt;Structured scenarios&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;That’s more than most people would expect. And every Provider has a clear design philosophy — not “big and all-encompassing”, but “each one solves a particular class of memory problem”. Need knowledge-graph reasoning, go with Hindsight; need to be fully local, go with Holographic; need cloud-native multi-tenancy, go with Mem0 or RetainDB.&lt;/p&gt;

&lt;h3 id=&quot;23-the-limitation-only-one-provider-can-be-enabled-at-a-time&quot;&gt;2.3 The Limitation: Only One Provider Can Be Enabled at a Time&lt;/h3&gt;

&lt;p&gt;One limitation of Hermes: &lt;strong&gt;only one external Memory Provider can be active at a time&lt;/strong&gt;. Built-in file memory and an external Provider can coexist, but you can’t use two external Providers at the same time (Mem0 + Honcho, for example, can’t both be on).&lt;/p&gt;

&lt;p&gt;For now that limitation looks reasonable — several Providers injecting context at once would blow up the System Prompt. But if you happen to be hesitating between two Providers, choose by “fit” rather than by “number of features”: pick the one that best matches your pain point.&lt;/p&gt;

&lt;h2 id=&quot;3-mem0-in-depth-memory-infrastructure-for-enterprise-autonomous-agents&quot;&gt;3. Mem0 in Depth: Memory Infrastructure for Enterprise Autonomous Agents&lt;/h2&gt;

&lt;p&gt;Why focus on Mem0 out of the nine Providers? Because it best fits the needs of &lt;strong&gt;enterprise autonomous AI Agent workflows&lt;/strong&gt;. When you deploy Agents in a team to do real work — not just a personal assistant, but multiple Agents dividing the work and collaborating — you run into these very real problems:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Memory must be shared across Agents, yet isolated along the Agent dimension&lt;/li&gt;
  &lt;li&gt;Memory must be persistent, not lost on restart&lt;/li&gt;
  &lt;li&gt;Memory must support semantic retrieval — finding “user preferences” by keyword search is wishful thinking&lt;/li&gt;
  &lt;li&gt;Memory must have an audit log (who recorded and changed what, and when)&lt;/li&gt;
  &lt;li&gt;The whole memory system must be self-hosted, with no data leaving the intranet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mem0 is designed for every one of those needs. It’s currently the most active Agent-specific memory layer project on GitHub, recommended by Anthropic, LangChain and several other teams, with an API simple enough that three lines of code wire it into any Agent framework.&lt;/p&gt;

&lt;h3 id=&quot;31-how-mem0-processes-memory&quot;&gt;3.1 How Mem0 Processes Memory&lt;/h3&gt;

&lt;p&gt;When you call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;memory.add(&quot;用户喜欢简洁的命名风格&quot;)&lt;/code&gt;, Mem0 does five things behind the scenes:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Fact extraction&lt;/strong&gt;: calls an LLM to automatically extract structured facts from the text (entity: user, relation: preference, object: concise naming)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Deduplication and merging&lt;/strong&gt;: if a similar memory already exists, it merges rather than writing a duplicate. This avoids “the same preference being recorded 20 times”.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Vectorization&lt;/strong&gt;: converts the memory into a vector through an Embedding model&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Storage&lt;/strong&gt;: vectors go into Qdrant (or another vector store), metadata goes into SQLite&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;History&lt;/strong&gt;: writes this operation to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;history&lt;/code&gt; table — inserts, deletes and updates are all traceable&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Retrieval uses a &lt;strong&gt;three-layer hybrid strategy&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Vector semantic search&lt;/strong&gt; — finds semantically similar memories (“naming convention” can match “variable naming preference”)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;BM25 keyword search&lt;/strong&gt; — captures exact keyword matches, compensating for vector search’s “approximately equal” bias&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Entity filtering&lt;/strong&gt; — filters along the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;user_id&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;agent_id&lt;/code&gt; dimensions to achieve multi-tenant isolation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The three result sets are finally fed into a &lt;strong&gt;Reranker&lt;/strong&gt; for re-ranking, which outputs the most relevant Top-N memories. That means the memories injected into the Agent’s context are both relevant and concise — no wasted Tokens.&lt;/p&gt;

&lt;h3 id=&quot;32-multi-tenant-isolation-a-native-capability-for-enterprise-scenarios&quot;&gt;3.2 Multi-Tenant Isolation: A Native Capability for Enterprise Scenarios&lt;/h3&gt;

&lt;p&gt;The two-dimensional isolation of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;user_id&lt;/code&gt; + &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;agent_id&lt;/code&gt; is Mem0’s most powerful answer to the multi-Agent scenario:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Code review Agent
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;函数不超过 50 行&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;team-backend&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;agent_id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;code-reviewer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Deployment Agent
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;部署前必须跑冒烟测试&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;team-backend&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;agent_id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;deployer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Operations Agent searches memory
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;results&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;代码规范&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;team-backend&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;agent_id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;maintainer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Search results retain the memories from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;user_id=team-backend&lt;/code&gt; dimension, but cross-Agent sharing can also be configured. The flexibility is high enough: fully isolated, shared within a team, or shared globally.&lt;/p&gt;

&lt;h3 id=&quot;33-self-hosting-no-data-leaves-the-intranet&quot;&gt;3.3 Self-Hosting: No Data Leaves the Intranet&lt;/h3&gt;

&lt;p&gt;Full-pipeline self-hosting is supported:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;vector_store&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;provider&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;qdrant&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;config&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;host&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;qdrant-internal&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;port&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;6333&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;llm&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;provider&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;openai&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;config&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;model&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;qwen2.5-7b-instruct&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;api_base&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;http://vllm-internal:8000/v1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;api_key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;not-needed&quot;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;embedder&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;provider&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;huggingface&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;config&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;model&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;BAAI/bge-m3&quot;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;All three key backends can be localized:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Vector store&lt;/strong&gt;: Qdrant / Milvus / ChromaDB / Weaviate&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Extraction LLM&lt;/strong&gt;: a local vLLM or Ollama (you don’t need GPT-4; a small model is enough for good fact extraction)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Embedding&lt;/strong&gt;: BGE-M3 or a local Embedding model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The entire system runs on the intranet, and not a single line of data leaves your servers.&lt;/p&gt;

&lt;h2 id=&quot;4-hands-on-wiring-mem0-into-hermes&quot;&gt;4. Hands On: Wiring Mem0 into Hermes&lt;/h2&gt;

&lt;h3 id=&quot;41-deploying-mem0&quot;&gt;4.1 Deploying Mem0&lt;/h3&gt;

&lt;p&gt;First start Qdrant (the vector database):&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; qdrant &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; 6333:6333 qdrant/qdrant
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Install Mem0:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pip &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;mem0ai
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;42-configuring-hermes-to-use-mem0&quot;&gt;4.2 Configuring Hermes to Use Mem0&lt;/h3&gt;

&lt;p&gt;Interactive configuration is the simplest:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# One step and done&lt;/span&gt;
hermes memory setup
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The list of options shows every supported Provider; type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mem0&lt;/code&gt; to select it. Then follow the prompts to fill in your API Key (if you’re using the cloud version) or your self-hosted endpoint information.&lt;/p&gt;

&lt;p&gt;Or change the configuration manually:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Set the Provider&lt;/span&gt;
hermes config &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;memory.provider mem0

&lt;span class=&quot;c&quot;&gt;# Configure the keys (written to ~/.hermes/.env)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;MEM0_API_KEY=your-key&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.hermes/.env
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;MEM0_API_URL=http://your-mem0-server:8000&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.hermes/.env

&lt;span class=&quot;c&quot;&gt;# Configure the user identity (optional; specify user_id and agent_id in ~/.hermes/mem0.json)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;43-verifying-check-whether-memory-is-syncing&quot;&gt;4.3 Verifying: Check Whether Memory Is Syncing&lt;/h3&gt;

&lt;p&gt;After configuring, chat a few lines with Hermes about anything:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;hermes chat &lt;span class=&quot;s2&quot;&gt;&quot;Our team&apos;s backend services all run Go 1.22+ and are deployed on K8s&quot;&lt;/span&gt;
hermes chat &lt;span class=&quot;s2&quot;&gt;&quot;Write me a K8s deployment template&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After the second conversation, check the memory:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;hermes chat &lt;span class=&quot;s2&quot;&gt;&quot;mem0_search: Go version requirement&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If the answer is correct, then the memory made it into Mem0 and can be retrieved.&lt;/p&gt;

&lt;h3 id=&quot;44-multi-agent-verification&quot;&gt;4.4 Multi-Agent Verification&lt;/h3&gt;

&lt;p&gt;Using Hermes’s Profile mechanism (see the earlier Profile article), create two separate Agent personas, then point them at the same Mem0 instance — with different &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;agent_id&lt;/code&gt;s but the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;user_id&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Profile A (code review Agent)&lt;/span&gt;
hermes &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; code-reviewer config &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;memory.provider mem0

&lt;span class=&quot;c&quot;&gt;# Profile B (deployment Agent)&lt;/span&gt;
hermes &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; deployer config &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;memory.provider mem0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now a preference recorded in Agent A can also be retrieved by Agent B. Memory is no longer an island.&lt;/p&gt;

&lt;h2 id=&quot;5-why-enterprises-should-care&quot;&gt;5. Why Enterprises Should Care&lt;/h2&gt;

&lt;p&gt;At this point you might be thinking, “it’s just one more backend, what’s the big deal”. But shift your perspective from “individual user” to “enterprise DevOps team” and you’ll see that this step is a qualitative leap.&lt;/p&gt;

&lt;h3 id=&quot;51-the-watershed-from-single-agent-to-multi-agent&quot;&gt;5.1 The Watershed from Single-Agent to Multi-Agent&lt;/h3&gt;

&lt;p&gt;Personal use case: one Agent, one project, one person. File memory is enough.&lt;/p&gt;

&lt;p&gt;Team use case: three Agents (review, deployment, operations) sharing one project context, and memory has to flow between them. File memory is no longer enough.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Memory Provider is the bridge over that watershed.&lt;/strong&gt; Without changing tools or processes, just by configuring one more backend, it switches you from “single-Agent mode” to “multi-Agent collaboration mode”.&lt;/p&gt;

&lt;h3 id=&quot;52-auditing-and-compliance&quot;&gt;5.2 Auditing and Compliance&lt;/h3&gt;

&lt;p&gt;With file memory, a change is simply a change — there’s no version history. When the team is troubleshooting a production incident and needs to trace back “when did the Agent remember that bad configuration?”, file memory can’t answer.&lt;/p&gt;

&lt;p&gt;Mem0’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;history&lt;/code&gt; table logs every memory operation — what time, which Agent, what change was made. In compliance scenarios that’s a hard requirement.&lt;/p&gt;

&lt;h3 id=&quot;53-switch-freely-without-lock-in&quot;&gt;5.3 Switch Freely, Without Lock-In&lt;/h3&gt;

&lt;p&gt;Hermes’s Provider mechanism has an underrated advantage: &lt;strong&gt;you can switch between Providers without losing your memory.&lt;/strong&gt; That’s because built-in file memory is always in effect — even if you change Providers, the memories in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MEMORY.md&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;USER.md&lt;/code&gt; are still there. An external Provider is an additive layer, not a replacement layer.&lt;/p&gt;

&lt;p&gt;This lowers the cost of trial and error: you can try Mem0 first, and if you’re not satisfied, switch to Honcho or Holographic — file memory is always there.&lt;/p&gt;

&lt;h2 id=&quot;6-the-complete-architecture-litellm--mem0--hermes&quot;&gt;6. The Complete Architecture: LiteLLM + Mem0 + Hermes&lt;/h2&gt;

&lt;p&gt;If you’ve read the earlier LiteLLM article, here is the complete puzzle of an enterprise AI Agent infrastructure:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;企业内网
│
├── Model Access Layer: LiteLLM Gateway
│   ├── Unified OpenAI-compatible API
│   ├── Multi-tenant budget control (by team/project)
│   ├── Automatic Fallback (public→public, intranet→intranet)
│   └── Cost tracking and audit logs
│
├── Model Inference Layer
│   ├── Local vLLM (Qwen / DeepSeek)
│   └── Public models (unified access via LiteLLM)
│
├── Agent Layer: Hermes Agent
│   ├── Agent A (code review)── MyMemory Provider → Mem0
│   ├── Agent B (deployment)─── MyMemory Provider → Mem0
│   └── Agent C (operations)─── MyMemory Provider → Mem0
│
└── Memory Layer: Mem0 + Qdrant
    ├── Multi-Agent shared memory
    ├── Multi-tenant isolation (user_id / agent_id)
    ├── Hybrid retrieval (vector + BM25 + Reranker)
    └── Operation audit logs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Every layer is decoupled, every layer is replaceable. That’s the mindset of “AI infrastructure”: locked into no single component, dependent on no single vendor, with every capability closed-loop inside the intranet.&lt;/p&gt;

&lt;h2 id=&quot;7-summary&quot;&gt;7. Summary&lt;/h2&gt;

&lt;p&gt;What I most want to stress in this article isn’t actually how good Mem0 is — though it really is good — but the fact that “&lt;strong&gt;the tool you’re using may be more powerful than you imagine&lt;/strong&gt;”.&lt;/p&gt;

&lt;p&gt;Among Hermes users, how many know that it supports 9 external memory backends? How many know that a single configuration key can solve multi-Agent knowledge sharing? I believe the proportion is low. That’s not the users’ fault — tools iterate fast, and the documentation is plentiful but scattered, so the people who truly go deep on a given feature are always few. But if you want to genuinely put AI Agents to work in an enterprise, you can’t stay at the level of “I can use the tool” — you have to reach the level of “I understand the tool’s architectural design”.&lt;/p&gt;

&lt;p&gt;Back to Memory Provider: its value lies in &lt;strong&gt;upgrading “the Agent’s memory” from “an incidental feature of a single program” to “an infrastructure capability that can evolve independently”.&lt;/strong&gt; Today you’re using Mem0, and six months from now you may swap in a better backend — the Agent code won’t need to change. That’s what a good abstraction should look like.&lt;/p&gt;

&lt;p&gt;A small step forward every day is a new height!&lt;/p&gt;
</description>
        <pubDate>Sun, 24 May 2026 00:00:00 +0000</pubDate>
        <link>https://tangmoying.com/en/post/ai-hermes-memory-provider-mem0</link>
        <guid isPermaLink="true">https://tangmoying.com/en/post/ai-hermes-memory-provider-mem0</guid>
        
        <category>AI</category>
        
        <category>Hermes</category>
        
        <category>Memory Provider</category>
        
        <category>Mem0</category>
        
        <category>Agent</category>
        
        <category>Knowledge Sharing</category>
        
        <category>Enterprise AI</category>
        
        <category>Memory Systems</category>
        
        
        <category>ai</category>
        
      </item>
    
      <item>
        <title>Hermes Profile — Multiple Agent Personas, One Set of Tools Does It All</title>
        <description>&lt;p&gt;You’ve raised an Agent well inside Hermes — it remembers your code style, your project structure, your frequently used commands. Now you have a second need: an Agent dedicated to managing deployments, and one more dedicated to ops inspections. They’re three completely different roles, needing different memories, different skills, and even different model preferences. Surely you’re not going to reinstall Hermes every time? That’s exactly what Hermes’s Profile mechanism was designed for.&lt;/p&gt;

&lt;!--以上为摘要内容--&gt;

&lt;h2 id=&quot;1-the-problem-one-hermes-isnt-enough&quot;&gt;1. The Problem: One Hermes Isn’t Enough&lt;/h2&gt;

&lt;p&gt;A quick recap: Hermes Agent stores each Agent’s configuration, memory, and skills under the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.hermes/&lt;/code&gt; directory:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;~/.hermes/
├── config.yaml       # main config (model, tools, etc.)
├── .env              # environment variables (API Key, etc.)
├── MEMORY.md         # episodic memory
├── USER.md           # user profile
├── SKILL.md          # procedural memory
└── skills/           # skills directory
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This structure is good — for one Agent, one role. But the real-world scenario is that you might need:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;A code review Agent&lt;/strong&gt;: leaning toward code quality and refactoring suggestions, using a model with a large context&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;A deployment Agent&lt;/strong&gt;: leaning toward K8s and CI/CD operations, using a small, fast model&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;An ops inspection Agent&lt;/strong&gt;: running inspection tasks on a schedule and logging inspection records, using the cheapest model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The three roles’ memory contents are completely different. The code review Agent remembers your coding conventions, the deployment Agent remembers your K8s cluster topology, and the ops Agent records historical failure patterns. Mix them together and the memory files turn into a pot of porridge.&lt;/p&gt;

&lt;p&gt;You might think: then why not copy &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.hermes/&lt;/code&gt; three times, one for each role? Sure, that works — but that’s exactly what Profile does, &lt;strong&gt;only it manages it better for you&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;2-what-is-a-profile&quot;&gt;2. What Is a Profile&lt;/h2&gt;

&lt;p&gt;A Profile is Hermes’s built-in &lt;strong&gt;configuration isolation mechanism&lt;/strong&gt;. Each Profile is an independent Hermes configuration environment, containing its own config files, memory files, skills directory, and environment variables. One Profile = one logical Agent persona.&lt;/p&gt;

&lt;p&gt;To put it more plainly: &lt;strong&gt;without installing three Hermes instances, you can have three completely independent Agents.&lt;/strong&gt; Each keeps its own memories, configures its own model, and installs its own skills.&lt;/p&gt;

&lt;p&gt;The Profile mechanism is everywhere in Hermes. With the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--profile&lt;/code&gt; (or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-p&lt;/code&gt;) global parameter, you can make any command act on a specified Profile; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hermes profile use&lt;/code&gt; you can switch the default Profile:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;hermes profile create my-agent                  &lt;span class=&quot;c&quot;&gt;# create a new profile&lt;/span&gt;
hermes &lt;span class=&quot;nt&quot;&gt;--profile&lt;/span&gt; my-agent chat &lt;span class=&quot;s2&quot;&gt;&quot;帮我看一段代码&quot;&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# run one conversation with the specified profile&lt;/span&gt;
hermes profile use my-agent                     &lt;span class=&quot;c&quot;&gt;# switch the default profile (omit --profile from then on)&lt;/span&gt;
hermes &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; my-agent config &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;model qwen2.5     &lt;span class=&quot;c&quot;&gt;# configure using the -p shorthand&lt;/span&gt;
hermes &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; my-agent skill add k8s-deploy-guide   &lt;span class=&quot;c&quot;&gt;# install a skill for the specified profile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When you don’t specify &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--profile&lt;/code&gt;, Hermes uses the currently active default Profile (named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default&lt;/code&gt; by default).&lt;/p&gt;

&lt;h2 id=&quot;3-hands-on-creating-your-first-persona&quot;&gt;3. Hands On: Creating Your First Persona&lt;/h2&gt;

&lt;p&gt;Assume your default Profile is already a code review Agent. Now create a deployment Agent.&lt;/p&gt;

&lt;h3 id=&quot;31-creating-a-new-profile&quot;&gt;3.1 Creating a New Profile&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# create a blank profile named deployer&lt;/span&gt;
hermes profile create deployer
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can also use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--clone&lt;/code&gt; to copy the configuration from the current Profile:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# copy the current profile&apos;s config.yaml, .env and SOUL.md into the new profile&lt;/span&gt;
hermes profile create deployer &lt;span class=&quot;nt&quot;&gt;--clone&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# a full clone — memories, sessions and skills copied along too&lt;/span&gt;
hermes profile create deployer &lt;span class=&quot;nt&quot;&gt;--clone-all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After creation, a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;deployer/&lt;/code&gt; subdirectory is generated under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.hermes/profiles/&lt;/code&gt;, containing a brand-new set of config and memory files.&lt;/p&gt;

&lt;p&gt;The two Profiles are completely isolated:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;~/.hermes/
├── config.yaml          # the default profile&apos;s configuration
├── MEMORY.md            # the default profile&apos;s memory
├── USER.md              # the default profile&apos;s user profile
├── SKILL.md             # the default profile&apos;s skills
├── .env                 # the default profile&apos;s environment variables
│
├── profiles/
│   └── deployer/
│       ├── config.yaml  # the deployer profile&apos;s configuration (separate!)
│       ├── MEMORY.md    # the deployer profile&apos;s memory
│       ├── USER.md
│       ├── SKILL.md
│       └── .env
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;32-switching-profiles&quot;&gt;3.2 Switching Profiles&lt;/h3&gt;

&lt;p&gt;There are two ways. One is to set the default Profile, after which all commands point to it automatically:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# set deployer as the default profile&lt;/span&gt;
hermes profile use deployer

&lt;span class=&quot;c&quot;&gt;# from now on, chat directly and deployer is used automatically&lt;/span&gt;
hermes chat &lt;span class=&quot;s2&quot;&gt;&quot;Write a K8s deployment template for me&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The second is to specify it temporarily for each command, without changing the default:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# temporarily chat with the deployer profile&lt;/span&gt;
hermes &lt;span class=&quot;nt&quot;&gt;--profile&lt;/span&gt; deployer chat &lt;span class=&quot;s2&quot;&gt;&quot;帮我写一个 K8s deployment 模板&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# temporarily install a skill with the deployer profile&lt;/span&gt;
hermes &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; deployer skill add k8s-deploy-guide
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;33-viewing-all-profiles&quot;&gt;3.3 Viewing All Profiles&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# list all profiles&lt;/span&gt;
hermes profile list
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The output is something like:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;* default
  deployer
  maintainer
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The one with a leading &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*&lt;/code&gt; is the currently active default Profile. Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hermes profile use &amp;lt;name&amp;gt;&lt;/code&gt; to switch.&lt;/p&gt;

&lt;h3 id=&quot;34-deleting-a-profile&quot;&gt;3.4 Deleting a Profile&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# delete the deployer profile (removing all of its configuration and memory)&lt;/span&gt;
hermes profile delete deployer
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note: a delete operation is irreversible. But each Profile is independent, and deleting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;deployer&lt;/code&gt; won’t affect &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;4-isolation-and-sharing-between-profiles&quot;&gt;4. Isolation and Sharing Between Profiles&lt;/h2&gt;

&lt;p&gt;We just said “completely isolated,” but some things are shared:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Item&lt;/th&gt;
      &lt;th&gt;Isolated?&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Conversation history&lt;/td&gt;
      &lt;td&gt;Isolated&lt;/td&gt;
      &lt;td&gt;Each Profile has its own conversation records&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;MEMORY.md / USER.md / SKILL.md&lt;/td&gt;
      &lt;td&gt;Isolated&lt;/td&gt;
      &lt;td&gt;Each keeps its own&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Model configuration&lt;/td&gt;
      &lt;td&gt;Isolated&lt;/td&gt;
      &lt;td&gt;Each Profile can use a different model&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Skill files&lt;/td&gt;
      &lt;td&gt;Isolated&lt;/td&gt;
      &lt;td&gt;Each Profile installs different skills&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Global plugins&lt;/td&gt;
      &lt;td&gt;Shared&lt;/td&gt;
      &lt;td&gt;Plugins are global and shared by all Profiles&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Tool authorization&lt;/td&gt;
      &lt;td&gt;Isolated&lt;/td&gt;
      &lt;td&gt;Each Profile authorizes tool permissions independently&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;You can configure them flexibly according to each role’s needs:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;The code review Agent uses Qwen2.5-14B, with file operation tools enabled&lt;/li&gt;
  &lt;li&gt;The deployment Agent uses DeepSeek-V3, with Shell execution tools enabled&lt;/li&gt;
  &lt;li&gt;The ops Agent uses a small local model, with only log-reading tools enabled&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Three Profiles, three sets of memories, three differently positioned Agents — but all on the same machine, under the same Hermes installation.&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;5-why-profiles-are-the-foundation-for-multi-agent-collaboration&quot;&gt;5. Why Profiles Are the Foundation for Multi-Agent Collaboration&lt;/h2&gt;

&lt;p&gt;By now you might feel that three Agents running independently, each keeping its own memories, is already pretty good. But once you really step into a multi-Agent collaboration scenario, you’ll find a key problem: &lt;strong&gt;they need to share some knowledge.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;The code review Agent remembers that “your microservices are written in Go and deployed on K8s”&lt;/li&gt;
  &lt;li&gt;The deployment Agent needs to know this background when writing deployment templates&lt;/li&gt;
  &lt;li&gt;The ops Agent also needs to know that Go and K8s are in use when troubleshooting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Although the three Profiles isolate memory at the file level, you can absolutely &lt;strong&gt;connect them to the same external Memory Provider&lt;/strong&gt; — that’s the Hermes Memory Provider mechanism we’ll cover in the next article, and in particular the Mem0 enterprise-grade memory layer.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# connect all profiles to the same Mem0 instance&lt;/span&gt;
hermes &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; code-reviewer config &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;memory.provider mem0
hermes &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; deployer config &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;memory.provider mem0
hermes &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; maintainer config &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;memory.provider mem0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Profiles provide the foundation of “independent roles,” and the Memory Provider provides the bridge for “shared knowledge.” Put the two together and you have the complete solution for multi-Agent collaboration.&lt;/p&gt;

&lt;h2 id=&quot;6-summary&quot;&gt;6. Summary&lt;/h2&gt;

&lt;p&gt;Profiles are the most easily skipped feature in Hermes — introductory tutorials usually don’t need it, and one Agent is enough. But once you start seriously doing multi-role Agent collaboration, it becomes a foundation you can’t route around.&lt;/p&gt;

&lt;p&gt;Its value lies in this: &lt;strong&gt;it gives you “persona” capability without changing your Hermes installation.&lt;/strong&gt; One machine, one Hermes, running three completely differently positioned Agents. And each Agent’s configuration, memory, and skills are cleanly isolated from one another, with no cross-contamination.&lt;/p&gt;

&lt;p&gt;This sets up the next article’s connection — the Memory Provider lets these personas share an enterprise-grade knowledge layer beyond their file-based memory.&lt;/p&gt;

&lt;p&gt;A small step forward every day is a new height!&lt;/p&gt;
</description>
        <pubDate>Sat, 23 May 2026 00:00:00 +0000</pubDate>
        <link>https://tangmoying.com/en/post/ai-hermes-profile</link>
        <guid isPermaLink="true">https://tangmoying.com/en/post/ai-hermes-profile</guid>
        
        <category>AI</category>
        
        <category>Hermes</category>
        
        <category>Config Files</category>
        
        <category>Multi-Agent</category>
        
        <category>Role Personas</category>
        
        <category>DevOps</category>
        
        
        <category>ai</category>
        
      </item>
    
      <item>
        <title>LiteLLM: A Unified LLM Gateway — From API Key Chaos to Enterprise AI Infrastructure</title>
        <description>&lt;p&gt;When you start using large models for real work — not the occasional API call for fun, but treating them as team infrastructure — you soon hit a wall. Not the “the models aren’t strong enough” wall, but the “I can’t keep this under control” wall: API keys scattered everywhere, no idea who spent how much, one model goes down and the whole business goes dark, and no audit trail at all for whether data ever left the intranet. What you need at that point is not a stronger model, but a gateway that can manage models. LiteLLM is that gateway.&lt;/p&gt;

&lt;!--以上为摘要内容--&gt;

&lt;h2 id=&quot;1-the-problem-when-you-start-treating-ai-as-infrastructure&quot;&gt;1. The Problem: When You Start Treating AI as Infrastructure&lt;/h2&gt;

&lt;p&gt;Over the past two years, the path large models have taken into production has looked roughly like this: first individuals chatting with ChatGPT → then teams using APIs for various experiments → then AI capabilities gradually embedding into daily workflows and becoming a “standard component”, just like your database or your message queue.&lt;/p&gt;

&lt;p&gt;The problems start at the third step. Once AI goes from a “personal toy” to “enterprise infrastructure”, you’ll find:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario one: API keys scattered everywhere.&lt;/strong&gt; The algorithm team uses OpenAI, the product team uses Claude, intranet scenarios use a local vLLM. Every team manages its own keys and writes its own calling code. One department with five project teams may have five different ways of integrating models. Ops wants unified management? Sorry — nobody can say with confidence how many keys are actually in circulation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario two: the cost black hole.&lt;/strong&gt; No idea which team spent how many tokens. At month-end the OpenAI bill is one big lump: you can’t tell which project ran batch offline jobs on GPT-4, and which team used a flagship model for simple translation. Budget control can only be all-or-nothing; there’s no way to be precise down to team, project or environment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario three: a chain of single points of failure.&lt;/strong&gt; OpenAI goes down and every service depending on it grinds to a halt. A local vLLM instance restarts and the running jobs all fail together. There is no fallback mechanism at all — not because nobody thought of it, but because “the cost of changing code to add a Fallback is too high”.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario four: compliance audit blind spots.&lt;/strong&gt; There’s no unified log of who called which model and what data they sent. Whether sensitive data was sent out to a public model API is completely untraceable after the fact — not because you don’t want to control it, but because you can’t.&lt;/p&gt;

&lt;p&gt;These four scenarios point to the same root cause: &lt;strong&gt;the model-calling layer lacks a unified gateway.&lt;/strong&gt; Just as in a microservices architecture you would never let every service manage its own database connections and message queues — you add a layer in between. The same goes for large models: you need a unified access layer between your business and the models. LiteLLM is that access layer.&lt;/p&gt;

&lt;h2 id=&quot;2-what-litellm-is&quot;&gt;2. What LiteLLM Is&lt;/h2&gt;

&lt;p&gt;LiteLLM is an open-source LLM gateway (GitHub 20K+ stars), maintained by the BerriAI team. Its core idea is simple: &lt;strong&gt;unify all model APIs into the OpenAI-compatible format — one endpoint, one interface protocol, calling 100+ kinds of models.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It has two usage modes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;SDK mode&lt;/strong&gt;: a pure Python library, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pip install litellm&lt;/code&gt;, suited to local scripts and small tools.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Proxy mode&lt;/strong&gt;: a standalone HTTP service, the standard way to run it in production.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article focuses on &lt;strong&gt;Proxy mode&lt;/strong&gt; — that’s the way enterprises use it. Proxy mode not only provides a unified interface, but also a full set of gateway capabilities: routing, rate limiting, multi-tenant budgets, audit logs, Fallback and more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why the “OpenAI-compatible format”?&lt;/strong&gt; It’s the de facto industry standard today. The vast majority of AI tools — Hermes Agent, OpenClaw, Cline, OpenHands — support connecting to models through an OpenAI-compatible interface. LiteLLM wraps all your models (whether the backend is OpenAI, Claude, a local vLLM or Azure) into the OpenAI-compatible format. On the business side you only change &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;base_url&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;api_key&lt;/code&gt;; you don’t touch application code.&lt;/p&gt;

&lt;h2 id=&quot;3-hands-on-deploying-a-litellm-gateway-quickly&quot;&gt;3. Hands On: Deploying a LiteLLM Gateway Quickly&lt;/h2&gt;

&lt;p&gt;Let’s start with a minimal version that unifies the models, then add features step by step.&lt;/p&gt;

&lt;h3 id=&quot;31-minimal-working-configuration&quot;&gt;3.1 Minimal Working Configuration&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config.yaml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;model_list&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;model_name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;fast&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;litellm_params&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;openai/gpt-4o-mini&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;api_key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;os.environ/OPENAI_API_KEY&lt;/span&gt;

  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;model_name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;smart&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;litellm_params&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;openai/gpt-4o&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;api_key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;os.environ/OPENAI_API_KEY&lt;/span&gt;

  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;model_name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;local-qwen&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;litellm_params&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;openai/qwen2.5-14b-instruct&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;api_base&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;http://vllm-internal:8000/v1&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;api_key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;EMPTY&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;general_settings&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;master_key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;sk-master-xxxxxx&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;litellm_settings&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;drop_params&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Start it with a single Docker command:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; litellm &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; 4000:4000 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$PWD&lt;/span&gt;/config.yaml:/app/config.yaml &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$OPENAI_API_KEY&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    ghcr.io/berriai/litellm:v1.85.0 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--config&lt;/span&gt; /app/config.yaml &lt;span class=&quot;nt&quot;&gt;--num_workers&lt;/span&gt; 4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Verify it:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl http://localhost:4000/v1/chat/completions &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Authorization: Bearer sk-master-xxxxxx&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Content-Type: application/json&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;{&quot;model&quot;: &quot;fast&quot;, &quot;messages&quot;: [{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;Who are you?&quot;}]}&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You’ll get back a normal OpenAI-format response. To the business side, this is an “OpenAI service” — except that behind it, it may point at any model at all.&lt;/p&gt;

&lt;h3 id=&quot;32-connecting-a-local-vllm&quot;&gt;3.2 Connecting a Local vLLM&lt;/h3&gt;

&lt;p&gt;The most common intranet setup: local models as the mainstay, public models as a supplement. Add one entry to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;model_list&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;model_name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;local-qwen&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;litellm_params&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;openai/qwen2.5-14b-instruct&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;api_base&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;http://your-gpu-server:8000/v1&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;api_key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;EMPTY&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;model: openai/xxx&lt;/code&gt; means: call it using the OpenAI-compatible protocol. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;api_base&lt;/code&gt; points at your vLLM instance. For any tool that supports an OpenAI-compatible interface — Hermes, OpenClaw, Cline — just change &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;base_url&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://litellm:4000&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;33-key-security-settings&quot;&gt;3.3 Key Security Settings&lt;/h3&gt;

&lt;p&gt;A few details that are easy to overlook:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Pin the image version&lt;/strong&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v1.85.0&lt;/code&gt; rather than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;latest&lt;/code&gt;). In March 2026 there was an incident in which malicious code was injected into LiteLLM’s PyPI package; since then, pinning the version and verifying image signatures is strongly recommended.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Salt Key&lt;/strong&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LITELLM_SALT_KEY&lt;/code&gt; is used to encrypt the provider API keys in the database. Never change it once deployed — change it and all the historical encrypted data becomes undecryptable.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Master Key&lt;/strong&gt; is only for administrative operations, not for business calls. Use a Virtual Key for everyday calls.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;4-core-features-routing-and-fallback&quot;&gt;4. Core Features: Routing and Fallback&lt;/h2&gt;

&lt;p&gt;Providing a unified interface alone isn’t enough. The gateway’s real value lies in &lt;strong&gt;traffic control&lt;/strong&gt;.&lt;/p&gt;

&lt;h3 id=&quot;41-model-aliases--the-business-side-doesnt-notice-backend-changes&quot;&gt;4.1 Model Aliases — the Business Side Doesn’t Notice Backend Changes&lt;/h3&gt;

&lt;p&gt;Give a model a “business name” (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fast&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;smart&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;local&lt;/code&gt;); several backends can hang under the same business name:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;model_list&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;model_name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;fast&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;litellm_params&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;openai/gpt-4o-mini&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;model_name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;fast&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;litellm_params&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;azure/gpt-4o-mini&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;router_settings&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;routing_strategy&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;simple-shuffle&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Your business code calls &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;model=&quot;fast&quot;&lt;/code&gt;; whether that actually goes to OpenAI or Azure behind the scenes is decided by the gateway. Ops can switch the underlying model without the business changing a single line of code.&lt;/p&gt;

&lt;h3 id=&quot;42-fallback--a-dead-model-doesnt-affect-the-business&quot;&gt;4.2 Fallback — a Dead Model Doesn’t Affect the Business&lt;/h3&gt;

&lt;p&gt;This is a standard gateway capability:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;router_settings&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;fallbacks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;fast&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;smart&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;local-qwen&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;context_window_fallbacks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;fast&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;smart&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;content_policy_fallbacks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;smart&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;fast&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Three Fallback scenarios:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;The model is down&lt;/strong&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fallbacks&lt;/code&gt;): when OpenAI is unavailable, traffic automatically goes to Claude → local model. Users notice nothing.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Context limit exceeded&lt;/strong&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;context_window_fallbacks&lt;/code&gt;): when &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gpt-4o-mini&lt;/code&gt; runs out of context, automatically upgrade to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gpt-4o&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Content policy refusal&lt;/strong&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;content_policy_fallbacks&lt;/code&gt;): when a model refuses to answer for compliance reasons, retry with another model.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Fallback strategy advice for intranet DevOps scenarios&lt;/strong&gt;: in data-sensitive cases, fall back only to local models and never go to the public internet; in non-sensitive cases, public models can back each other up. Use model grouping and routing strategy for fine-grained control — for example, configure the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;local-*&lt;/code&gt; model group to fall back only within the group.&lt;/p&gt;

&lt;h3 id=&quot;43-choosing-a-routing-strategy&quot;&gt;4.3 Choosing a Routing Strategy&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Strategy&lt;/th&gt;
      &lt;th&gt;How it works&lt;/th&gt;
      &lt;th&gt;When to use it&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;simple-shuffle&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Simple round-robin (automatically weighted when there are rpm/tpm limits)&lt;/td&gt;
      &lt;td&gt;Small to medium scale, good enough&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;least-busy&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Routes to the backend with the lowest current load (requires Redis)&lt;/td&gt;
      &lt;td&gt;High concurrency, many replicas&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;usage-based-routing-v2&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Spreads pressure by TPM usage rate&lt;/td&gt;
      &lt;td&gt;Multi-instance deployments&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;latency-based-routing&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Selects by response latency&lt;/td&gt;
      &lt;td&gt;Latency-sensitive services&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;For a small team, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;simple-shuffle&lt;/code&gt; is enough. When you need to scale vLLM instances out horizontally, move up to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;least-busy&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;5-enterprise-features-multi-tenancy-and-budget-control&quot;&gt;5. Enterprise Features: Multi-Tenancy and Budget Control&lt;/h2&gt;

&lt;p&gt;This may be the most “valuable” part of LiteLLM for DevOps teams — &lt;strong&gt;managing AI resources as infrastructure&lt;/strong&gt;.&lt;/p&gt;

&lt;h3 id=&quot;51-production-deployment-add-postgres&quot;&gt;5.1 Production Deployment: Add Postgres&lt;/h3&gt;

&lt;p&gt;The minimal configuration has no database and can only be driven by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config.yaml&lt;/code&gt;. In production you need Postgres to store virtual keys, team configuration and spend logs:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;general_settings&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;master_key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;sk-master-xxxx&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;database_url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgresql://litellm:password@postgres:5432/litellm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A Docker Compose reference:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;postgres&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres:18-alpine&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;POSTGRES_DB&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;litellm&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;POSTGRES_USER&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;litellm&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${DB_PASSWORD}&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;pgdata:/var/lib/postgresql/data&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;healthcheck&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;CMD-SHELL&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;pg_isready&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-U&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;litellm&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;interval&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;5s&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;5s&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;retries&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;5&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;litellm&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ghcr.io/berriai/litellm:v1.85.0&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;depends_on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;postgres&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;condition&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;service_healthy&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;4000:4000&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./config.yaml:/app/config.yaml&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;DATABASE_URL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgresql://litellm:${DB_PASSWORD}@postgres:5432/litellm&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;LITELLM_MASTER_KEY&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${LITELLM_MASTER_KEY}&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;LITELLM_SALT_KEY&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${LITELLM_SALT_KEY}&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${OPENAI_API_KEY}&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;--config /app/config.yaml --num_workers &lt;/span&gt;&lt;span class=&quot;m&quot;&gt;4&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;pgdata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With a database in place, all of LiteLLM’s operation history is recorded automatically in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LiteLLM_SpendLogs&lt;/code&gt; table, and from then on API calls have a complete “ledger”.&lt;/p&gt;

&lt;h3 id=&quot;52-the-admin-panel--day-to-day-ops-without-curl&quot;&gt;5.2 The Admin Panel — Day-to-Day Ops Without curl&lt;/h3&gt;

&lt;p&gt;Once the database is configured, LiteLLM’s built-in web admin panel is activated. Visit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://litellm:4000/ui&lt;/code&gt; and log in with the Master Key, and you’ll see a complete admin interface. Here you can:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Create and manage Teams, setting a per-team budget cap and a whitelist of available models&lt;/li&gt;
  &lt;li&gt;Generate, revoke and edit Virtual Keys — bind them to a Team, set a spending limit, configure rate limits&lt;/li&gt;
  &lt;li&gt;View the live request stream, per-model QPS and P95 latency&lt;/li&gt;
  &lt;li&gt;Browse spend reports and break spending down by team, Key and model&lt;/li&gt;
  &lt;li&gt;Search call logs to quickly locate errors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is what ops looks like when it follows engineering practice. Day-to-day operations — issuing a Key for a new project, adjusting a team budget, revoking a leaked Key — are all done with a few clicks in the UI. No digging through docs to type curl commands.&lt;/p&gt;

&lt;p&gt;The API examples below are kept for two purposes: first, to show the full meaning of every parameter (the UI and the API correspond one-to-one), and second, to make it easy for you to automate operations in CI/CD or ops scripts.&lt;/p&gt;

&lt;h3 id=&quot;53-team--resource-isolation-by-team&quot;&gt;5.3 Team — Resource Isolation by Team&lt;/h3&gt;

&lt;p&gt;Creating a Team in the admin panel takes only a name, a budget and the allowed models. The API parameters behind it are as follows:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Create the backend team&lt;/span&gt;
curl http://litellm:4000/team/new &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Authorization: Bearer sk-master-xxxx&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;{
    &quot;team_alias&quot;: &quot;backend-team&quot;,
    &quot;max_budget&quot;: 500.0,
    &quot;budget_duration&quot;: &quot;30d&quot;,
    &quot;models&quot;: [&quot;fast&quot;, &quot;smart&quot;, &quot;local-qwen&quot;]
  }&apos;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Create the product team (cheap models only)&lt;/span&gt;
curl http://litellm:4000/team/new &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Authorization: Bearer sk-master-xxxx&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;{
    &quot;team_alias&quot;: &quot;product-team&quot;,
    &quot;max_budget&quot;: 100.0,
    &quot;budget_duration&quot;: &quot;30d&quot;,
    &quot;models&quot;: [&quot;fast&quot;, &quot;local-qwen&quot;]
  }&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;One budget cycle is 30 days; requests are automatically rejected once the budget is exceeded, so there are no end-of-month reconciliation shocks.&lt;/p&gt;

&lt;h3 id=&quot;54-virtual-key--fine-grained-control-by-environment-and-by-service&quot;&gt;5.4 Virtual Key — Fine-Grained Control by Environment and by Service&lt;/h3&gt;

&lt;p&gt;In the admin panel, open a Team’s detail page and click “Create Key” to generate a Virtual Key — just fill in an alias, budget, rate limits and a model whitelist. The corresponding API parameters are as follows:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Production: permissive&lt;/span&gt;
curl http://litellm:4000/key/generate &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Authorization: Bearer sk-master-xxxx&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;{
    &quot;key_alias&quot;: &quot;backend-prod&quot;,
    &quot;team_id&quot;: &quot;team-id-xxx&quot;,
    &quot;max_budget&quot;: 400.0,
    &quot;budget_duration&quot;: &quot;30d&quot;,
    &quot;tpm_limit&quot;: 200000,
    &quot;rpm_limit&quot;: 1000,
    &quot;models&quot;: [&quot;fast&quot;, &quot;smart&quot;, &quot;local-qwen&quot;]
  }&apos;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Development: rate-limited, to prevent misuse&lt;/span&gt;
curl http://litellm:4000/key/generate &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Authorization: Bearer sk-master-xxxx&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;{
    &quot;key_alias&quot;: &quot;backend-dev&quot;,
    &quot;team_id&quot;: &quot;team-id-xxx&quot;,
    &quot;max_budget&quot;: 100.0,
    &quot;budget_duration&quot;: &quot;30d&quot;,
    &quot;tpm_limit&quot;: 20000,
    &quot;rpm_limit&quot;: 100,
    &quot;models&quot;: [&quot;fast&quot;, &quot;local-qwen&quot;]
  }&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;55-using-it-from-the-business-side--completely-transparent&quot;&gt;5.5 Using It from the Business Side — Completely Transparent&lt;/h3&gt;

&lt;p&gt;Once you have a Virtual Key, the business side simply uses it as an OpenAI key:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;openai&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OpenAI&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OpenAI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;base_url&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://litellm-internal:4000/v1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;api_key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;sk-xxxx-backend-prod-virtual-key&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;completions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;fast&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;messages&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Write a unit test for me&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;From the business side it’s completely invisible — they neither know nor care whether the backend is OpenAI, Claude or a local vLLM. They only need to know “same place, same Key, same format”.&lt;/p&gt;

&lt;h2 id=&quot;6-cost-tracking-and-observability&quot;&gt;6. Cost Tracking and Observability&lt;/h2&gt;

&lt;p&gt;Once LiteLLM is connected to Postgres, every API call is recorded in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LiteLLM_SpendLogs&lt;/code&gt; table. The core fields: which Team, which Key, which model, input/output tokens, the amount spent (US dollars), and a timestamp.&lt;/p&gt;

&lt;h3 id=&quot;61-the-admin-panel--check-spending-in-the-ui-day-to-day&quot;&gt;6.1 The Admin Panel — Check Spending in the UI Day to Day&lt;/h3&gt;

&lt;p&gt;The admin panel (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://litellm:4000/ui&lt;/code&gt;) comes with a Usage page, so checking spending day to day doesn’t require leaving the browser:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Spend&lt;/strong&gt;: view team spending trends by day/week/month, and see at a glance how much each Key has spent&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Usage&lt;/strong&gt;: request volume, token consumption and average latency per model&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Logs&lt;/strong&gt;: detailed logs for every call — request content, response, cost, latency — with filtering and search&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Teams / Keys&lt;/strong&gt;: budget utilization per team, so you can see directly which one is about to exceed its budget&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Checking spending, reconciling, tracking down “why did this Key spend so much today” — the UI handles all of it. This is the way that matches engineering instincts: first give people something to look at, then talk about advanced tricks.&lt;/p&gt;

&lt;h3 id=&quot;62-going-further-query-the-database-directly&quot;&gt;6.2 Going Further: Query the Database Directly&lt;/h3&gt;

&lt;p&gt;When the UI’s aggregation dimensions aren’t enough — say you want to group by project tag (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;metadata&lt;/code&gt;), export CSV for finance, or feed an internal data platform — you can query the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LiteLLM_SpendLogs&lt;/code&gt; table directly:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;-- Team spend ranking for the last 7 days&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;team_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;SUM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;spend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_spend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;COUNT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num_requests&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;LiteLLM_SpendLogs&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;startTime&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NOW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;INTERVAL&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;7 days&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;team_id&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_spend&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DESC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;-- Daily spend trend for a given Key&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;DATE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&quot;startTime&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;SUM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;spend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;daily_spend&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;LiteLLM_SpendLogs&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;api_key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;sk-xxxx-backend-prod&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;day&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;day&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DESC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For DevOps, SQL is flexible for aggregating by any dimension and for join analysis. But day-to-day ops doesn’t need it — the UI is enough.&lt;/p&gt;

&lt;p&gt;Beyond that, if you already have a monitoring stack such as Grafana, LiteLLM can also expose Prometheus metrics, which can be wired into your existing dashboards.&lt;/p&gt;

&lt;h3 id=&quot;63-alerting-strategy-advice&quot;&gt;6.3 Alerting Strategy Advice&lt;/h3&gt;

&lt;p&gt;The same thinking as for any other infrastructure you operate — key metrics must have alerts:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A single team’s daily spend exceeds 60% of its budget → notify the team lead&lt;/li&gt;
  &lt;li&gt;A Key’s RPM suddenly jumps 5x → risk alert (could be a code bug or a leaked Key)&lt;/li&gt;
  &lt;li&gt;A backend model’s error rate stays above 10% for 5 minutes → ops alert&lt;/li&gt;
  &lt;li&gt;A local vLLM instance becomes unavailable → alert immediately (the intranet model is a core dependency)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;7-a-kubernetes-production-deployment-reference&quot;&gt;7. A Kubernetes Production Deployment Reference&lt;/h2&gt;

&lt;p&gt;If your team uses K8s, here’s a reference structure:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;apps/v1&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Deployment&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;litellm&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;replicas&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;selector&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;matchLabels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;litellm&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;litellm&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;containers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;litellm&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ghcr.io/berriai/litellm:v1.85.0&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--config=/etc/litellm/config.yaml&quot;&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--num_workers=4&quot;&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--port=4000&quot;&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;containerPort&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;4000&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;envFrom&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;secretRef&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;litellm-secrets&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;volumeMounts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;config&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;mountPath&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/etc/litellm&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;readOnly&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;livenessProbe&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;httpGet&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/health/liveliness&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;4000&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;readinessProbe&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;httpGet&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/health/readiness&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;4000&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;resources&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;requests&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;cpu&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;500m&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;512Mi&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;limits&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;cpu&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;memory&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;2Gi&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;config&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;configMap&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;litellm-config&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A few key points:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Multiple replicas&lt;/strong&gt;: LiteLLM itself is stateless (state lives in Postgres), so multiple replicas natively support high availability. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--num_workers 4&lt;/code&gt; is the number of workers inside each replica.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Secret management&lt;/strong&gt;: the Master Key, Salt Key and every provider’s API Key all come from K8s Secrets — never hard-code them into the ConfigMap.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Health checks&lt;/strong&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/health/liveliness&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/health/readiness&lt;/code&gt; are LiteLLM’s own standard endpoints.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Redis&lt;/strong&gt;: in multi-replica scenarios, add Redis (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;router_settings.redis_host&lt;/code&gt;) so that replicas share routing state.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;8-lessons-from-the-trenches&quot;&gt;8. Lessons from the Trenches&lt;/h2&gt;

&lt;p&gt;Pitfalls that are easy to hit during an actual deployment:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The side effects of drop_params.&lt;/strong&gt; Once enabled, parameters that the target model doesn’t support are silently dropped. In multi-model scenarios this is genuinely convenient — you pass &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reasoning_effort=high&lt;/code&gt; to GPT-4o, and it’s automatically dropped when the request routes to Claude, without an error. But during debugging, turn it off first, or you’ll find yourself asking “I clearly passed the parameter, so why isn’t it taking effect?”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Postgres connection bloat.&lt;/strong&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;num_workers=4&lt;/code&gt; + 2 replicas = 8 database connection pools. If your Postgres is configured with a low &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;max_connections&lt;/code&gt;, you may run out of connections. The solution is to put PgBouncer in front, or lower &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;database_connection_pool_limit&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. A Fallback may be more expensive.&lt;/strong&gt; The fallback model may cost more than the primary — for example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gpt-4o-mini&lt;/code&gt; goes down and traffic falls back to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gpt-4o&lt;/code&gt;, a 10x price difference. When configuring a Fallback chain, confirm the price of each model and prefer ones in the same price range.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. The Salt Key cannot be changed.&lt;/strong&gt; Generate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LITELLM_SALT_KEY&lt;/code&gt; before deployment, and never change it afterwards. Once changed, every encrypted Provider Key in the database becomes undecryptable, and you need to wipe the database and rebuild it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Don’t mix configuration sources.&lt;/strong&gt; After &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;store_model_in_db: true&lt;/code&gt;, model configuration is loaded from the database. At that point, don’t modify &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config.yaml&lt;/code&gt; and add or remove models through the API at the same time — the two configuration sources overwrite each other, and debugging that is painful. Pick one approach and stick with it to the end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Don’t expose local models to the public internet.&lt;/strong&gt; LiteLLM can mix local vLLM and public models on one gateway. Take care to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;model.info.access_groups&lt;/code&gt; for access control — make sure call chains carrying sensitive data only route to local models.&lt;/p&gt;

&lt;h2 id=&quot;9-wrapping-up-back-to-infrastructure-thinking&quot;&gt;9. Wrapping Up: Back to “Infrastructure” Thinking&lt;/h2&gt;

&lt;p&gt;Going from large-model APIs to a unified gateway, the essence of that leap is a switch in thinking — from “using AI tools” to “operating AI infrastructure”. Just as you wouldn’t hard-code a database connection string into every microservice, you shouldn’t let every team manage its own API Keys, maintain its own Fallback logic and do its own reconciliation.&lt;/p&gt;

&lt;p&gt;The problems LiteLLM solves are exactly the ones you inevitably run into on the way from “one person calling an API” to “a team using AI”: a unified entry point, traffic control, cost accounting, security auditing. It doesn’t produce stronger AI, but it makes your AI infrastructure as manageable, measurable and auditable as your database or your message queue.&lt;/p&gt;

&lt;p&gt;The deployment cost isn’t high — one Docker Compose (Postgres + LiteLLM) plus the vLLM instance you already have, and it’s running on your intranet in half an hour. Worth a try.&lt;/p&gt;

&lt;p&gt;A small step forward every day is a new height!&lt;/p&gt;
</description>
        <pubDate>Sat, 16 May 2026 00:00:00 +0000</pubDate>
        <link>https://tangmoying.com/en/post/ai-litellm-gateway</link>
        <guid isPermaLink="true">https://tangmoying.com/en/post/ai-litellm-gateway</guid>
        
        <category>AI</category>
        
        <category>LiteLLM</category>
        
        <category>API Gateway</category>
        
        <category>Enterprise AI</category>
        
        <category>DevOps</category>
        
        <category>Cost Control</category>
        
        <category>Multi-Model Routing</category>
        
        <category>Infrastructure</category>
        
        
        <category>ai</category>
        
      </item>
    
      <item>
        <title>Local LLM in Practice (Part 6): Hermes Agent — a Self-Evolving AI Agent with Memory and Learning</title>
        <description>&lt;p&gt;When we talked about OpenClaw last time, I mentioned that its biggest weakness is its memory system — you have to teach the Agent all over again every time, and it can’t remember your preferences or your project context. This isn’t a problem unique to OpenClaw; it’s a common failing of most AI Agents today: models keep getting stronger, yet every time you chat, the Agent acts like it’s meeting you for the first time. Hermes Agent was built precisely to tackle this problem. Its core selling point isn’t “a stronger AI” but “an AI that understands you better the more you use it”.&lt;/p&gt;

&lt;!--以上为摘要内容--&gt;

&lt;h2 id=&quot;1-what-is-hermes-agent&quot;&gt;1. What Is Hermes Agent&lt;/h2&gt;

&lt;p&gt;Hermes Agent is developed by the Nous Research team, released in February 2026, and open-sourced under MIT. Nous Research has a solid reputation in the open-source large-model scene — the Hermes series of models they released (fine-tuned from Llama) stand out in tool calling and instruction following. Hermes Agent is their foray into the Agent space, extending “tool-calling ability at the model level” into “a persistent Agent system with memory”.&lt;/p&gt;

&lt;p&gt;As of June 2026, Hermes Agent has tens of thousands of GitHub stars. That doesn’t match OpenClaw’s 190,000, but its focus is narrower — &lt;strong&gt;memory and self-learning&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;2-the-three-layer-memory-model&quot;&gt;2. The Three-Layer Memory Model&lt;/h2&gt;

&lt;p&gt;The most central design of Hermes Agent is its memory architecture — the “tripartite memory model”:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Procedural memory (SKILL.md)&lt;/strong&gt;: the operational skills the Agent has mastered. Things like “how to create a Git branch”, “how to deploy a service on K8s”, “how to analyze the performance bottleneck of a piece of code”. These are all reusable operational knowledge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Episodic memory (MEMORY.md)&lt;/strong&gt;: the historical experience of the Agent’s interactions with the user. Things like “last time you refactored UserService because you found its time complexity was O(n²)”, “you prefer ciphertext comparison over direct string comparison for security checks”. These are experiences automatically extracted and distilled from one conversation after another.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Semantic memory (USER.md)&lt;/strong&gt;: the user profile. Things like “you are a backend developer, mainly using Go and Python”, “your code style preference is concise naming and thorough unit tests”, “you don’t like JWT and prefer to use Session”.&lt;/p&gt;

&lt;p&gt;The division of labor between the three layers is clear: procedural memory is “I know how to do it”, episodic memory is “I remember what you’ve done”, and semantic memory is “I know who you are”.&lt;/p&gt;

&lt;h2 id=&quot;3-hands-on-installation-and-first-use&quot;&gt;3. Hands On: Installation and First Use&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Install&lt;/strong&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pip &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;hermes-agent
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Initialize&lt;/strong&gt; (create your personal Agent):&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;hermes init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This command walks you through configuring a model (OpenAI-compatible interfaces are supported, so you can point it at a local vLLM/Ollama), setting a working directory, and creating the initial SKILL.md / MEMORY.md / USER.md files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connect to the local vLLM from the first article&lt;/strong&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;hermes init &lt;span class=&quot;nt&quot;&gt;--provider&lt;/span&gt; openai_compatible &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--base-url&lt;/span&gt; http://your-gpu-server:8000/v1 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--model&lt;/span&gt; qwen2.5-14b-instruct &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--api-key&lt;/span&gt; not-needed
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;First interaction&lt;/strong&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;hermes chat &lt;span class=&quot;s2&quot;&gt;&quot;map out the current project&apos;s directory structure and tell me what each module does&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After the interaction ends, check &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.hermes/MEMORY.md&lt;/code&gt; — you should be able to see that the Agent has already automatically extracted the key information from this conversation.&lt;/p&gt;

&lt;h2 id=&quot;4-active-learning-not-just-remembering-but-learning-on-its-own&quot;&gt;4. Active Learning: Not Just “Remembering” but “Learning on Its Own”&lt;/h2&gt;

&lt;p&gt;The three-layer memory taxonomy isn’t anything fresh — plenty of systems have a similar layering of memory. The real highlight of Hermes Agent is its &lt;strong&gt;active learning mechanism&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Most Agents’ memory is passive: it remembers whatever you tell it. If you don’t tell it, it doesn’t know. OpenClaw works this way — its memory files are maintained by hand.&lt;/p&gt;

&lt;p&gt;Hermes Agent does it differently. Every time an interaction with you ends, it &lt;strong&gt;asynchronously reviews the conversation in the background&lt;/strong&gt;, automatically extracting reusable knowledge from it:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;对话内容：
你: &quot;帮我把这段数据处理的时间复杂度从 O(n²) 优化到 O(n log n)&quot;
Hermes: [完成优化，使用哈希表替代了嵌套循环]

后台学习：
Hermes 从这次交互中自动提取：
→ added to MEMORY.md: &quot;the user cares about algorithmic performance and asked for O(n log n) complexity in the last data-processing task&quot;
→ if a similar pattern appears repeatedly, it is auto-generated into SKILL.md: &quot;performance optimization preference - prefer hash tables over nested loops&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;By the time the next, completely different task comes along, the Agent’s behavior has already been shaped by the earlier experience:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;新对话（完全不同的任务）：
你: &quot;帮我写一个数据去重的方法&quot;
Hermes: [生成使用 Set 的 O(n) 方案，而不是简单的 O(n²) 双重循环]
        [because it learned your performance preference from previous experience]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s the value of active memory: &lt;strong&gt;the Agent doesn’t just answer questions — it comes to understand you better with every interaction.&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;5-automatic-skill-creation&quot;&gt;5. Automatic Skill Creation&lt;/h2&gt;

&lt;p&gt;The extension of active learning is &lt;strong&gt;automatic skill creation&lt;/strong&gt;. Once you and the Agent have solved a complex problem together, the Agent can distill the whole process into a reusable skill:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You teach the Agent a workflow of “code review → run tests → check coverage → submit a PR”&lt;/li&gt;
  &lt;li&gt;The Agent automatically packages that workflow into a Skill&lt;/li&gt;
  &lt;li&gt;Next time you say “review this PR using the same process as last time”, the Agent runs the whole workflow automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hermes Agent’s skills follow the &lt;strong&gt;agentskills.io&lt;/strong&gt; open standard. This isn’t a standard you can only use on Hermes — in theory any Agent framework that supports the standard can load these skills. Skills are shareable across platforms.&lt;/p&gt;

&lt;h2 id=&quot;6-other-capabilities&quot;&gt;6. Other Capabilities&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Parallel sub-Agents&lt;/strong&gt;. Hermes Agent can spawn isolated sub-Agents to handle tasks in parallel. Sub-Agents consume almost none of the shared context window between them, which means you can push several independent tasks forward in parallel without your token consumption rising.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-platform messaging gateway&lt;/strong&gt;. Supports Telegram, Discord, Slack, WhatsApp, CLI and other platforms. It doesn’t connect as many platforms as OpenClaw’s 50+, but the coverage is already enough for everyday use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scheduled automation&lt;/strong&gt;. A built-in cron scheduler supports unattended tasks such as overnight backups, daily reports and scheduled inspections.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MLOps support&lt;/strong&gt;. Hermes Agent can generate tool-call traces for model fine-tuning and reinforcement learning training. If you plan to train your own Agent model, Hermes can provide the training data. Most users won’t need this capability for now, but it embodies an idea — “an Agent can improve itself”: once enough data has accumulated, you can use it to train a model that understands you better.&lt;/p&gt;

&lt;h2 id=&quot;7-comparison-with-openclaw&quot;&gt;7. Comparison with OpenClaw&lt;/h2&gt;

&lt;p&gt;The previous article covered OpenClaw in detail; here is a direct comparison:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Dimension&lt;/th&gt;
      &lt;th&gt;OpenClaw&lt;/th&gt;
      &lt;th&gt;Hermes Agent&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Core positioning&lt;/td&gt;
      &lt;td&gt;Local Agent gateway (execution)&lt;/td&gt;
      &lt;td&gt;Self-evolving Agent (memory + learning)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Memory system&lt;/td&gt;
      &lt;td&gt;Passive (files maintained by hand)&lt;/td&gt;
      &lt;td&gt;Active (tripartite model + automatic extraction)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Learning ability&lt;/td&gt;
      &lt;td&gt;No automatic learning&lt;/td&gt;
      &lt;td&gt;Self-learning loop + automatic skill creation&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Skill ecosystem&lt;/td&gt;
      &lt;td&gt;13,000+ community skills on ClawHub&lt;/td&gt;
      &lt;td&gt;agentskills.io open standard, skills generated automatically&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Messaging platforms&lt;/td&gt;
      &lt;td&gt;50+&lt;/td&gt;
      &lt;td&gt;6+ (Telegram/Discord/Slack/WhatsApp/CLI)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Development language&lt;/td&gt;
      &lt;td&gt;TypeScript (Node.js)&lt;/td&gt;
      &lt;td&gt;Python&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Enterprise investment&lt;/td&gt;
      &lt;td&gt;Higher (needs a server)&lt;/td&gt;
      &lt;td&gt;Lower (a $5/month VPS is enough)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Strengths&lt;/td&gt;
      &lt;td&gt;Broad: many platforms, many Agents, many skills&lt;/td&gt;
      &lt;td&gt;Deep: strong memory, self-learning, better with use&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The two tools are almost complementary in positioning. OpenClaw is strong in &lt;strong&gt;“breadth”&lt;/strong&gt; — many platforms, many Agents, an enterprise-grade gateway. Hermes is strong in &lt;strong&gt;“depth”&lt;/strong&gt; — self-evolving memory, getting smarter with use, low-cost and lightweight.&lt;/p&gt;

&lt;p&gt;This isn’t a question of “which is better”. It’s whether you want a gateway that can connect to everything but has an average memory, or an assistant that lives only in the terminal but understands you better and better.&lt;/p&gt;

&lt;h2 id=&quot;8-why-memory-is-the-agents-moat&quot;&gt;8. Why Memory Is the Agent’s Moat&lt;/h2&gt;

&lt;p&gt;Finally, I want to expand on a question that’s easy to overlook: &lt;strong&gt;why is the memory system the Agent’s real moat?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You might think an Agent’s core competitiveness is model capability — whoever has the stronger model has the better Agent. But model capability is a commodity. Today your Agent uses GPT-5; tomorrow Hermes supports it too, and the day after OpenClaw connects to it as well. The gap at the model level will only get smaller.&lt;/p&gt;

&lt;p&gt;But memory doesn’t commoditize. After 100 interactions with your Hermes, it has remembered your preferences, your project structure, your code style, your frequently used commands — and this Hermes becomes irreplaceable to you. Even if another Agent connects to a stronger model, you won’t switch, because switching means &lt;strong&gt;teaching an apprentice from scratch all over again&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It’s like a good work partner. After three months on a project together, he knows when you need a code review, what kind of commit message format you like, and which kind of Bug you hate most. Swap him for someone else — maybe more capable — and the rapport has to be rebuilt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory is the rapport between an Agent and its user.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is also why, although Hermes wasn’t as hot as OpenClaw when it launched, many OpenClaw users end up noticing Hermes after using it heavily — because once they get used to “having an Agent do things for me”, they naturally ask: can this Agent remember everything I’ve done? And Hermes happens to be the one that pushes the single dimension of memory to its extreme.&lt;/p&gt;

&lt;h2 id=&quot;9-summary&quot;&gt;9. Summary&lt;/h2&gt;

&lt;p&gt;From OpenCode to Cline to OpenHands to OpenClaw to Hermes Agent, six articles cover the complete chain from “using AI to write code” to “letting AI remember you”:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Tool&lt;/th&gt;
      &lt;th&gt;Problem it solves&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;OpenCode&lt;/td&gt;
      &lt;td&gt;Write code in the terminal without vendor lock-in&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Cline&lt;/td&gt;
      &lt;td&gt;Program autonomously in the IDE with Plan/Act mode&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;OpenHands&lt;/td&gt;
      &lt;td&gt;Let AI complete development tasks autonomously in a sandbox&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;OpenClaw&lt;/td&gt;
      &lt;td&gt;Let AI do work beyond programming and chain tools together&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Hermes Agent&lt;/td&gt;
      &lt;td&gt;Let AI remember your preferences and get smarter with use&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;All six tools are open source and all support local deployment. As a DevOps engineer building AI infrastructure on an intranet, you don’t need to adopt every one of them — but you do need to know what problem each one solves and when to use which. My own take: figure out each tool’s positioning first, then choose based on your team’s actual pain points, rather than chasing stars blindly.&lt;/p&gt;

&lt;p&gt;A small step forward every day is a new height!&lt;/p&gt;
</description>
        <pubDate>Sat, 09 May 2026 00:00:00 +0000</pubDate>
        <link>https://tangmoying.com/en/post/ai-local-llm-06-hermes</link>
        <guid isPermaLink="true">https://tangmoying.com/en/post/ai-local-llm-06-hermes</guid>
        
        <category>AI</category>
        
        <category>Hermes</category>
        
        <category>AI Agent</category>
        
        <category>Memory Systems</category>
        
        <category>Self-Learning</category>
        
        <category>Skill Creation</category>
        
        <category>Open Source</category>
        
        
        <category>ai</category>
        
      </item>
    
      <item>
        <title>Local LLMs in Practice (Part 5): OpenClaw — A Local Agent Gateway That Gives LLMs &quot;Digital Hands and Feet&quot;</title>
        <description>&lt;p&gt;The previous three articles all covered agents in the programming domain — OpenCode writes code, Cline executes tasks inside the IDE, and OpenHands autonomously completes the whole development workflow. But an LLM’s abilities don’t stop at programming. If you want an LLM to manage files for you, drive a browser, run scheduled inspections of your services, or even respond to your instructions at any moment inside the messaging apps you already use (WeChat / Feishu / DingTalk) — what you need is an agent gateway, not a programming tool. That’s exactly what OpenClaw does.&lt;/p&gt;

&lt;!--以上为摘要内容--&gt;

&lt;h2 id=&quot;1-the-story-of-190000-stars-in-three-months&quot;&gt;1. The Story of 190,000 Stars in Three Months&lt;/h2&gt;

&lt;p&gt;In January 2026, an open-source project called ClawdBot suddenly went viral — it gained 60,000 GitHub stars in 72 hours. It was briefly renamed Moltbot, and finally settled on the name OpenClaw. In three months, its star count passed 190,000.&lt;/p&gt;

&lt;p&gt;That pace is rare among open-source projects. Why did it take off? Because it solves a very direct need: &lt;strong&gt;it lets your LLM stop just chatting and actually get things done.&lt;/strong&gt; And it does so running locally, without sending your data to the cloud, and usable straight from WeChat / Feishu / DingTalk.&lt;/p&gt;

&lt;p&gt;OpenClaw’s author is Peter Steinberger, an independent developer from Austria (and the founder of PSPDFKit). The project is written in TypeScript (Node.js), and its core design idea is the “agent gateway” — it doesn’t provide AI capability itself, but builds a bridge between the AI models you already have and your digital life.&lt;/p&gt;

&lt;h2 id=&quot;2-what-openclaw-is&quot;&gt;2. What OpenClaw Is&lt;/h2&gt;

&lt;p&gt;OpenClaw’s official positioning is &lt;strong&gt;“a platform-agnostic, local-first AI agent”.&lt;/strong&gt; In plain terms: it’s an agent gateway that runs on your own device, can connect to all kinds of LLMs, link up all kinds of tools and platforms, and execute tasks for you automatically.&lt;/p&gt;

&lt;p&gt;Three things are key to understanding it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, it doesn’t provide AI capability itself.&lt;/strong&gt; OpenClaw is not a model; it doesn’t train and it doesn’t infer. What it does is connect to your LLM (whether a self-hosted Ollama model or the Claude or GPT API) and then give that model “hands and feet” — the ability to work with the file system, run commands, drive a browser and call APIs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second, it’s local-first.&lt;/strong&gt; All your data lives on your own device and never passes through any third-party server. Deploy OpenClaw on an intranet server, hook it up to a local LLM, and the whole system is completely offline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third, it connects to many platforms.&lt;/strong&gt; OpenClaw supports more than 50 messaging platforms — WeChat, Feishu, DingTalk, Telegram, Discord, WhatsApp, Slack, iMessage, Google Chat… You don’t need to open a dedicated app; you can drive your agent from the chat software you already use.&lt;/p&gt;

&lt;h2 id=&quot;3-hands-on-deployment-and-configuration&quot;&gt;3. Hands On: Deployment and Configuration&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Installation&lt;/strong&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl &lt;span class=&quot;nt&quot;&gt;-fsSL&lt;/span&gt; https://openclaw.ai/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After installation, run the setup wizard, which walks you through configuring your model API key and messaging platforms. You can also edit the configuration file by hand to connect a local Ollama:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# ~/.openclaw/config.yaml example&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;models&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;provider&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ollama&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;qwen2.5:14b&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;base_url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;http://localhost:11434&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;gateways&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;telegram&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;YOUR_BOT_TOKEN&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;chat_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;YOUR_CHAT_ID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Your first automated task&lt;/strong&gt; — in Telegram, tell the agent:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;每隔一小时检查 https://example.com 能不能访问，
如果 HTTP 状态码不是 200，就发消息提醒我。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The agent writes a cron job on its own and starts the inspection.&lt;/p&gt;

&lt;h2 id=&quot;4-core-capabilities&quot;&gt;4. Core Capabilities&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Giving the model “digital hands and feet”.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With a traditional AI chat tool, you say something and it answers. OpenClaw is different — it’s an agent that lives on your device and can actually do these things:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;File system operations&lt;/strong&gt;: read and write files, organize directories, search content&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Browser control&lt;/strong&gt;: open web pages, take screenshots, scrape content&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;System command execution&lt;/strong&gt;: run scripts, manage processes, schedule jobs&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Account management&lt;/strong&gt;: manage API keys, passwords and account information&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;External API calls&lt;/strong&gt;: integrate with any HTTP API and chain different systems together&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, you can send the agent a message in WeChat:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&quot;帮我看看那台服务器的磁盘使用情况，超过 80% 就发告警&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The agent logs into the server over SSH, runs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;df -h&lt;/code&gt;, and if disk usage is above 80% it automatically posts an alert to the group chat. You don’t have to write a single script for any of this — you describe the requirement in natural language and the agent carries it out itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A skill marketplace (ClawHub).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;OpenClaw has a community skill marketplace called ClawHub, with 13,000+ community-contributed skills. You don’t have to start from scratch — install one with a click and the agent gains a new ability. Weather lookups, stock alerts, GitHub project management, Jira ticket operations… all of these have ready-made skill plugins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-agent gateway architecture.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;OpenClaw can manage several independent agent instances at the same time, each with its own workspace, memory files and configuration. In an enterprise setting, members of different teams can use their own separate agents on different messaging channels without interfering with one another.&lt;/p&gt;

&lt;p&gt;The technical architecture has three layers:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Gateway layer&lt;/strong&gt;: a central router that connects to all kinds of messaging channels and external platforms&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Agent Runtime layer&lt;/strong&gt;: manages multiple independent agent instances&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Skill plugin layer&lt;/strong&gt;: extends the agent’s abilities through the skill ecosystem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Scheduled automation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It has a built-in cron scheduler. You can have the agent run inspections daily, generate a report weekly or check service status once an hour — completely unattended.&lt;/p&gt;

&lt;h2 id=&quot;5-what-using-it-looks-like&quot;&gt;5. What Using It Looks Like&lt;/h2&gt;

&lt;p&gt;How you use OpenClaw is a bit unusual — it has no traditional “IDE plugin” or “desktop client”. It works like this:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You deploy OpenClaw on a server (your own PC, a Raspberry Pi, an intranet server)&lt;/li&gt;
  &lt;li&gt;You configure the AI model to connect to (a local Ollama or a cloud API)&lt;/li&gt;
  &lt;li&gt;You configure the messaging platforms to connect to (WeChat / Feishu / Telegram and so on)&lt;/li&gt;
  &lt;li&gt;And then you can interact with the agent inside those messaging platforms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What does that feel like? Like having an AI assistant that’s online around the clock. It lives on a server, you can reach it through any chat app, and you can put it to work for you.&lt;/p&gt;

&lt;h2 id=&quot;6-the-memory-systems-design&quot;&gt;6. The Memory System’s Design&lt;/h2&gt;

&lt;p&gt;Here we have to touch on a topic that’s often used for comparison: OpenClaw’s memory system.&lt;/p&gt;

&lt;p&gt;OpenClaw’s memory is file-system based — each agent has a set of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SOUL.md&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AGENTS.md&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;USER.md&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MEMORY.md&lt;/code&gt; files that store state and context. The design is simple and direct, but it comes with limitations:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Memory is &lt;strong&gt;passive&lt;/strong&gt;. You have to hand-write AGENTS.md to tell the agent how it should behave. The agent doesn’t learn lessons from its interactions on its own.&lt;/li&gt;
  &lt;li&gt;Cross-session context is unstable. A preference you gave the agent last week may be “forgotten” this week — because maintaining the memory files is essentially manual.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the point OpenClaw gets criticized for most, and it’s why the Hermes Agent we’ll cover next has been able to rise on its back.&lt;/p&gt;

&lt;h2 id=&quot;7-what-its-suited-to&quot;&gt;7. What It’s Suited To&lt;/h2&gt;

&lt;p&gt;Given its characteristics, OpenClaw fits the following scenarios better:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;You need an automation assistant that’s online around the clock.&lt;/strong&gt; Scheduled inspections, anomaly alerts, data synchronization — these don’t need a good memory, just the ability to execute.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;You need the same agent across several messaging platforms.&lt;/strong&gt; Feishu at the office, WeChat when you’re out, Telegram at home — the same agent, reached from different platforms.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;You need to chain several tools and systems together.&lt;/strong&gt; GitHub → Jenkins → Slack → Feishu, with the agent as the glue in the middle.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Data can’t go to the cloud.&lt;/strong&gt; Local deployment + local model + local execution, fully offline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Where it doesn’t fit so well: if you want an agent that understands you better the more you use it and remembers your preferences and working habits, OpenClaw’s passive memory model will disappoint you. That’s also why many OpenClaw users end up paying attention to the Hermes Agent.&lt;/p&gt;

&lt;h2 id=&quot;8-summary&quot;&gt;8. Summary&lt;/h2&gt;

&lt;p&gt;If you line up the four tools we’ve covered and compare their form factors:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Tool&lt;/th&gt;
      &lt;th&gt;Interaction form&lt;/th&gt;
      &lt;th&gt;Positioning&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;OpenCode&lt;/td&gt;
      &lt;td&gt;Terminal TUI&lt;/td&gt;
      &lt;td&gt;A coding assistant in the terminal&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Cline&lt;/td&gt;
      &lt;td&gt;IDE plugin&lt;/td&gt;
      &lt;td&gt;A coding agent inside the IDE&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;OpenHands&lt;/td&gt;
      &lt;td&gt;Web GUI + SDK&lt;/td&gt;
      &lt;td&gt;An autonomous engineer in a sandbox&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;OpenClaw&lt;/td&gt;
      &lt;td&gt;Messaging platforms&lt;/td&gt;
      &lt;td&gt;A local, all-purpose execution gateway&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;OpenClaw is not a programming tool; it’s a gateway that gives an LLM “hands and feet”. Its value lies in its breadth — many platforms, many skills, many agents. But its memory system is passive: what you haven’t told it, it won’t remember.&lt;/p&gt;

&lt;p&gt;Is there a tool that can make up for that shortcoming — one that’s not just “you do, I say”, but that “learns from interactions on its own”? Next article: the Hermes Agent.&lt;/p&gt;

&lt;p&gt;A small step forward every day is a new height!&lt;/p&gt;
</description>
        <pubDate>Sat, 02 May 2026 00:00:00 +0000</pubDate>
        <link>https://tangmoying.com/en/post/ai-local-llm-05-openclaw</link>
        <guid isPermaLink="true">https://tangmoying.com/en/post/ai-local-llm-05-openclaw</guid>
        
        <category>AI</category>
        
        <category>OpenClaw</category>
        
        <category>AI Agent</category>
        
        <category>Agent Gateway</category>
        
        <category>Local Execution</category>
        
        <category>Messaging Platforms</category>
        
        <category>Open Source</category>
        
        
        <category>ai</category>
        
      </item>
    
  </channel>
</rss>
