Back to "StyloBot 系列发布丛书 @:在Long_-}寻找并控制无约束增长"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

Architecture ASP.NET Bot Detection Performance StyloBot

StyloBot 系列发布丛书 @:在Long_-}寻找并控制无约束增长

Monday, 01 June 2026

Long- 运行服务一般会慢慢地向无约束的内存漂移\ .\ 此处 @'}我如何发现这种飘移, 我是如何想在不打印它的情况下修补它的?,\ 以及 StyloBot' 矢量相似层的一个工作范例。 从“13” GB(大型物体升降)到 “6” MBZK8*

StyloBot

StyloBot 释放系列

  1. 行为@, @% 不是身份为何StyloBot以行为方式模拟客户?
  2. ASP=.@NET 用户界面@ : 服务器@ MS K1 在检测结果上方的表面
  3. 在LongM-Running .NET服务中查找和固定无约束增长@:@ 可靠性纪律 @ ( @ 工作示例 @ I:_ StyloBot)}
  4. 檔案型態Script UI: Express, fastifyMS K2 和浏览器组件
  5. 侧车建筑@ : 检测引擎如何连接到非@ MS K1NET 堆栈
  6. 学习加快速度“:”适应性学习系统“, 4 -”记忆和裁决缓存
  7. 静坐不动的测试: 校验纪律 @: 一个 BDF 文件驱动回溯@,load @MS K3和校准
  8. StyloExptracte {-} 本地学习 HTML 到 标记下转换器: HTML→ Markdown 层与探测器对齐 MSSK2 行尸虫清晰识别捕捉了, 和狗食环让它诚实

工作实例是 StyloBot 矢量层@,,但该学科是 @'tStyloBot-}特定 任何长的- 运行着“.NET” 服务,它积累了交通状态,最终会形成一种在测试或短程负载中可以捕捉到的错误=+'}.}但它只在实际流量数日后才会出现_.}这篇文章是我用来找到它的游戏手册

行为模式在 行为@, @% 不是身份;ASP.NET表面 ASP=.@NET 用户界面;来源于 GithubMS .comMS K1scottgal/stylobot (星际机器人).


问题类别

Long- 執行服務累积@ . @ 每個快取目錄@ I,} 每個學習商店@ MPK3 每個 "we' 只會保留最後的N要求 " 缓冲区只是一個微小的caculator MS K7 每個人都自己看起來很好

  • 在Dev, CI,和生产第一天的工作
  • 渐渐向OOM,}然后撞车或开始呼唤

你抓住这个 故意寻找它@,}关于一个过程的'* 运行时间足够长,足以让形状出现 .

步骤#1:}定期的可靠性审查 ―( 200)}寻找动作>)〞

本学科的第一个部分是“'@t technology @.}它的日历条目‘.’ 。

我停止添加功能, 只要看看运行系统 在现实的交通 #,#问 这看起来有错吗 ? 没有特定的bug@ , @ 没有目标 @ MS K1 @ 仅仅看 @ MPK2 @ 运行系统静悄悄地 *;} 他们没有大功告成 直到它们失败前

最新审查发现此文章的错误, Mostlylucid.BotDetection.Demo 以合成试验交通为例 .}该数字不能维持三十秒的注意

规则#: 在日历上设置一个重复的空格, 以读取您自己的标准@ . @ not to fix anything .} 只看 @ MS K2}

一步步2: 让我们来抵消你的惊喜

用于“"”的正确工具在 “.” NET 进程上是错的 @ ?"+% dotnet-counters@ . @ it' @ sfree@ MS K2} 它已经安装了 @ I' @ 并且它告诉了你 你的程序正在做什么的真相

稍后在 StyloBot 演示“:” 上发布一些命令

dotnet.gc.last_collection.heap.size[loh]   13,393,217,096 bytes (13.4 GB)
dotnet.gc.heap.total_allocated              97 MB/sec
dotnet.exceptions[SqliteException]          4/sec

{\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}这足够知道什么是错的 {\fn方正准圆简体\fs12\1cHC9ECC4}没有读出任何代码 大型天体 heap 正在不断地分配巨型物体 , 和Gená2GC可以跟得上它们

快速更新LOH @(#因为每个“.+NET Dev”都点击这个最终@)#

大型天体 heap 是您第一次描述一个长的“-”运行中 @.NET service@.}它最常见的惊喜之一, 因为几乎每个人都遇到困难的方式_:}

  • GGC有三代人 (Gen0,GENMS K2Gentñ2)正常短小的-活物}.}大部分分配款都是在Genç0,中生死存亡 这对收集.来说很便宜
  • 这几代人中 任何比85还大的 KB doesn't' 它直直飞向 大型天体 heap.
  • LOH 只在 Gen2GC这是最昂贵的收藏 .QNET does @.}Gen2}不常发生...
  • 更糟糕的, 默认LOH是 未压缩 当它被收集时 @ ; @ 它只是释放空槽@ MS K1 @ 所以即使一个 Gen2,} 即使LOH 获得零碎@ I: 您也有免费的空间@ MPK4} 但是它在错误的- 大小的洞里有用于下次分配的新大天体延长了堆积而不是再利用它的“ MSSK8” Name
  • 网效@: 继续以稳定的速率分配大型天体, 您的行程 @ , 和您的进程\ ' 内存足迹向上移动, 无论对象是否仍然被引用@ MS K3 它看起来就像一个泄漏 即使它是't one @.}

根据我的经验,LOH意外增长的最大来源 在真实的“.NET services ”,中

”源於「 」 为什么它最终会出现在LOH ‘ ’上?
JsonSerializer.Serialize(obj) 返回a string }一个 MB 对象变成 一个 MSC2 的 MB UTF=-16}字符串=,}全部在同一分配中 * *
MemoryStream 您让未绑定的“ ” 内部缓冲在 @ 85 KB 上双倍增长, 并留在 LOH +
byte[] buf = new byte[n] 用于大 n @ 直接LOH分配; 非常常见在文件中}♪/}网络读作@
List<T> 后补阵列横跨 @85}KB 然后降落在LOH +
string.Concat / StringBuilder 在大文本上方 ToString() 是单一的大笔拨款
XmlSerializer / DataContractSerializer 和 JSON 案的形状相同

如果你只记得一个大拇指规则 : 怀疑在定时器上或按要求生成单个毗连大物体的代码路径@. 和在StyloBot:中将要找到的形状

flowchart LR
    classDef input fill:none,stroke:#3b82f6,stroke-width:2px
    classDef store fill:none,stroke:#f59e0b,stroke-width:2px
    classDef async fill:none,stroke:#a855f7,stroke-width:2px
    classDef problem fill:none,stroke:#ef4444,stroke-width:2px

    A["Per-request handler<br/>Add to collection"]:::input --> B["Long-lived List / Dict / Buffer"]:::store
    C["Timer / autosave<br/>every N min"]:::async --> D["JsonSerializer / MemoryStream<br/>single &gt;85 KB allocation"]:::problem
    B --> D
    D --> E["Large Object Heap"]:::problem
    E --> F["Gen2 GC<br/>infrequent, expensive"]:::async
    F --> G["LOH not compacted by default<br/>fragments build up"]:::problem
    G --> H["Process RSS marches upward"]:::problem

帮助诊断它的工具@:

  • dotnet-counters -=YTET -伊甸园字幕组=- 翻译:

  • dotnet-gcdumpdotnet-dump 在视觉工作室或 PerfView@. 中您可以打开的快照

  • 《观察展望》 用于追踪下至堆叠的资源分配量@. @%

  • JEBrains CLI 定位仪 @ (} @ action: what I actually used to this rework=). {现在包装成适当的 @ . nET global moments@,}所以没有头部的@ MS K4 远程 @/\ CI 运行时描述不需要 Rider GUI_:}

    • JetBrains.dotMemory.GlobalTools@ : @ 附加@ MS K1 @ 抓取一个记忆快照@ I,} 打开 .dmw 工作空间后看哪些保留根持有LOH分配款@.
    • JetBrains.dotTrace.GlobalTools@ :} 用于取样的相同形状 @ I/ @ 跟踪@ MS K2 时间轴数据 * .} 当您需要堆叠而不是对齐时使用此形状 * @ MPK4 @ e @ MOSK5g. @ MASSK7 @ 什么是这个计时器实际上正在做\ ?".}
    dotnet tool install -g JetBrains.dotMemory.GlobalTools
    dotnet tool install -g JetBrains.dotTrace.GlobalTools
    
    dotMemory get-snapshot <pid> --save-to-dir=./snapshots
    dotTrace attach <pid> --profiling-type=Sampling --timeout=60s --save-to=./trace.dtp
    

    这项工作的分工 dotnet-counters 告诉我LOH是问题所在 ; 点记忆快照告诉我自动保存计时器就是原因

有逃生舱 如果你真的需要它们... . GCSettings.LargeObjectHeapCompactionMode = CompactOnce LOH 壓力-OffLOH clatition. RecyclableMemoryStream 从微软池库缓冲器中提取来避免最初的分配@. 两者都是石膏_. 真正的修复方法几乎总是要停止生产巨型物体_MS K2

回到诊断

所以只要你看到LOH在长期的-运行服务中稳步增长 ,,问题几乎总是相同 @'_%s 分配非常大型的物体@,}关于什么内容? 解答的形状 @(timer?}请求處理器@? recirizer @MS K3缓冲池_?) 指示您在代码中查找何处 。

例外处理太过重要, 4/sec 联 年 月 日 SqliteException 低到不显示在日志中任何人读@,}但高到可以告诉你代码路径是静悄悄地重试.}这样类型的线索定期复查会发现事故反应从来就不是 @.}

规则#: 当事情看起来不对劲时",}确认使用 dotnet-counters 在猜测“.”之前, 数字会将搜索半径缩小为星等的顺序@ .

错误的@- @abstraction smell

这里的学科变得有趣了 ,,因为诱惑是 补贴和 而非质疑抽象的症状....

Quick vocab {(} 用于接下来的几节 @ )

  • 矢量: 固定的- 浮标大小阵列 @- e.g}. float[64]. @ 每个槽都是请求的量度属性 @(@header count%,}时间,}IP family_,}等等□.).}两个要求""{eel 类似="}在“64-维空间”中产生彼此相近的矢量 float[].
  • 矢量相似性通常都是: 余弦相似性 “-”是两个矢量之间角的余弦,一种叫做“.”的方法。
  • 最近的近邻 @(_ANN)}: @ @ MS K1 @ given this containor % 1} 找到最接近的向量 % 2} find the closest N of million\ most ,}快速 ". Brute-}參考是 OQ}NQNSK7}每個查詢}.}ANN算法都以偶爾失蹤為代價 得到 sub=-milli秒的結果 真实 最贴近的匹配@. @%
  • HNSW 赫尔辛基: ANN 算法之一 @. 它构建了一个多层图, 上面一层有几口井@-连接的节点和下层填充细节. 您在顶部输入并在“.”中缩放为=%, 稳定 不是为 " 设计的矢量集.}每份请求都增加一个旧的, 移出@"}{-} 这整篇文章的相关点 @.}
  • 中枢: 一组矢量的平均值 @- 一种矢量来总结群集@. 如果您有“10,000 矢量看起来大致相似”, 您可以扔掉它们并保留一个中子{; 机器人失落了,但紧凑的代用品}. 压缩策略用它将很多原始历史转换成一些简略的历史_.

对于StyloBot, 来说,LOH的增长导致了三个在结构上完全相同的等级。 HNSW 赫尔辛基 “(”世界历史导航小世界“)”图表“Z(”原版 Malkov @&_# Yashunin 纸用于在签名@,}会话 @,}和意向矢量上进行类似搜索的 )}每人都有像:}这样的字段

private readonly List<float[]> _graphVectors = new();

每一个都由一个学习处理器喂养, LearningEventType.FullDetection火烧的, 每个 HTTP 请求@. @每一份请求都加上矢量@.}每一份请求都没有被逐出 .

每5分钟 , 自动存取定时器将整张图表连载到JSON 然后写成磁盘

private readonly TimeSpan AutoSaveInterval = TimeSpan.FromMinutes(5);

在演示规模下, signatures.vectors.json 在生产过程中 : intent.meta.json MB , intent.vectors.json 在“51”MB.和“JSON序列化”中,在“MS K2” MBZ.创建了一条毗连的内存字符串。

flowchart LR
    classDef input fill:none,stroke:#3b82f6,stroke-width:2px
    classDef async fill:none,stroke:#a855f7,stroke-width:2px
    classDef problem fill:none,stroke:#ef4444,stroke-width:2px

    R["HTTP request"]:::input --> H["LearningHandler<br/>FullDetection event"]:::async
    H --> L["List&lt;float[]&gt;<br/>graph vectors<br/>(no eviction)"]:::problem
    L -.5 min timer.-> J["JsonSerializer.Serialize<br/>~100 MB string"]:::problem
    J --> LOH["Large Object Heap"]:::problem
    L -->|grows every request| L
    LOH -.fragmentation.-> RSS["Process RSS climbs"]:::problem

简单修正是添加一个上限@. @% MaxVectors = 10_000校对:Portnoy

香港新南威尔士州是绝佳的技术,带有 Qdrant 的自定义“-” 矢量数据库, RAG混合搜索和索引, 最小可见图形). 松松, 断断, pgvictor 变量,和 解冻 内部所有使用@. @ but it'}但是它是一个 Cample_,}索引而不是一个缓存{,} 而StyloBot实际上需要的是一个缓存}=:} @" 用于每个活动指纹@, 保留一个小窗口的最近行为矢量, 这样检测可以比较当前请求与过去类似要求的行为=." 重覆 Bot 指纹 重复 ( 保持热); 人类指纹... *' t @(evict). 尽可能简单 @-_process HNSW 索引被列出为一个特性@;}这篇文章是随后的#-\up 承认错误的地方

上面有'QQQS a quick test of the smell@:}如果你想象一下在结构上打个硬盖... ...",\Q"是这个结果,你想要的是什么? ?

规则#: 当您看到无限制增长时 @, @ 问题不是 我们如何控制这个结构? 但是,但 @"#这是正确结构 我们做我们该做的: 'BARBAR?"} 第一个隐藏症状 .,第二个听到什么 它的'告诉你

步骤 4: 选择正确的形状, 不只是一个上限

当您在 StyloBot 上命名错误的抽象@,}替换者通常会自己写 @._StyloBot 是两层=:}

热层@: @% a 受约束的 BoundedVectorCache<TEntry>, 一个薄包装纸围绕 ConcurrentDictionary 保留计分器给 Bot- 分类条目“2x生存权重”\ , 因此在交通模式周围的缓存自=-组织@:

retentionScorer: (_, entry) => entry.WasBot ? 2.0 : 1.0

常态层(FOSSMS K1 3个新的 SQLite 表 @ ( @signature_centroids, session_centroids, intent_centroids) 储存压缩的中央机器人 从夜间 VectorCompactionService. 矢量作为原始浮标存储 MemoryMarshal.AsBytes:

internal static byte[] PackFloats(float[] v) =>
    MemoryMarshal.AsBytes(v.AsSpan()).ToArray();

常规二进制序列@. @No @100+_MB JSON字符串 @MS K2}没有LOH.{

存储阶梯注. SQLite 是指 FOSS @/ @ 单 @ - 二进制后端@-} 正确呼叫“ MS K3” 把前一号扔在Pi上, 并用 PostgreSQL 用 pgvictor 变量, 给予您适当的索引相似性搜索@ MS K1HNSW/IVFFlat 数据库内 @.}相同的建筑规则@,}不同的后端#. sqlite-vs 如果您超越 SQLite , 但想要保持单身\ - pinary; 更清洁的升级是 Postgres 梯级\ MPK2\ }

对自由和开放源码软件持久性层的相似性搜索是各行Brute-Force cosine,通过SIMD {(}加速。System.Numerics.Tensors.TensorPrimitives在压缩的-centroid size @MSK2100s 到几千行@),}一个完整的扫描在Pi~1-2ms上

注 注 L1/L2 压缩校对:Soup

  • L1:采取今天撰写的所有原始矢量@,组非常相似的,组, 并用一个小机器人来取代每组 {(平均=). cheapMS K5 常有MSSK6 略微大小減少{MS K7e MSKO8gMSQ9 NSK10x□).}
  • L2: 采取L1 自行相似的环球機器人 MS K2 週 . 侵略性;

这是同样模式的 LSM-}树枝存储引擎 @ (RocksDB, 级别 DBMPK3卡珊德拉MS K4用于SSTabletsMSKO5 等级@0 持有最新细微的-Grained dataMSQK8 下层持有压缩的数据,简略数据_.借用它,因为访问模式与最近的数据相同最常用的是点击最近的数据}有一小部分读数需要长的尾巴*;}保持每一条原始行永远<.>没有好处

flowchart LR
    classDef input fill:none,stroke:#3b82f6,stroke-width:2px
    classDef store fill:none,stroke:#f59e0b,stroke-width:2px
    classDef async fill:none,stroke:#a855f7,stroke-width:2px
    classDef good fill:none,stroke:#22c55e,stroke-width:2px

    R["HTTP request"]:::input --> FP["Fast path<br/>BoundedVectorCache.TryGet"]:::good
    FP -->|hit| S["Use similarity signal"]:::good
    FP -->|miss| N["null<br/>other detectors still run"]:::good
    R -.post-response.-> BG["Background learning handler"]:::async
    BG --> DB["SQLite (FOSS)<br/>or Postgres+pgvector (paid)"]:::store
    BG --> WARM["Warm cache for next time"]:::async
    WARM --> FP
    NC["Nightly VectorCompactionService<br/>L1 then L2"]:::async --> DB
    DB --> NC

常规形状 *\ (}稳定状态 {+ 为历史的 +}BAR 一个定期压缩器.}当有人找到HNSW或Pgvictor的工作量, 而工作量实际上是一个缓存@,}这是他们本该更便宜的、更简单的东西。

规则#: 更喜欢适合 运行时间模式“,”不是符合纸上的数据集的“'”大小

一步5:让快速路径能够容忍丢失

@,}因为它关于克制,而不是附加#.

如果缓慢的“-”和“fix 路径”路径包含一个数据库查询@,, 诱惑是将其上的快速路段设置为“ .唐't}.StyloBot 中的检测快路是同步的,, 而相似性检查是一个非“MS K6堵塞字典查找_:

if (!_cache.TryGet(signatureId, out var entry))
    return null; // no signal this request - other 48 detectors still run

遗漏意味着 没有任何相似性信号表示这一请求其它探测器仍在运行@.}在请求完成后背景处理器查询 SQLite, 并给下次的缓存热 ;}快速路径从不阻隔数据库查询.}

这是大多数缓存错误的地方 @ : @ 人们建立一个缓存@ , @ 使后退同步的“ I"” 为正确性 @ MPK3, 而错位路径则变成 “ MS K4+% s pQ99}pQMSK5 spignation_ . ” 该缓存旨在让事情更快地 @ OMK7 @ 代之以它使得最糟糕的情况更糟\ MSKO8 @

规则#: 如果您能忍受一个错误的, 您没有\ '" 没有缓存 . 你有一个漂亮的队列@.}

步骤#6:审计 每个 不只是响亮的

一旦你找到了一个未绑定的结构 , 假设还有其他结构

审计是机械化的“.”审计,对于每个长长的“-” 词典来说都是机械式的 “MSK1 活字典” “MS K2” 列表“,” 或者设置为“MSC4” 由谁来决定条目何时离开“,”和在敌对交通下最坏的情况是什么? 如果您能用一句话回答所有三句话“, ” 它“MS K2” 是一个漏出的 “.” “ @(Same colory as the single 短讯信号 模式@: @ 任何累积的必须衰变的东西@ ,_ 驱逐 @ MS K2} 或缩写@ MPK3 *

对于StyloBot, 大部分蓄积器已经很好了 -MSSK2

  • EphemeralPatternReputationCache“:”硬盖在“10,000”条目中,带有背景衰变和LRU驱逐“MS K2” NeutralSuspectConfirmedBad 和不对称歇斯底里住在一起 学习加快速度)
  • BehavioralPatternAnalyzer: IMemoryCache 以 per @-}身份限制 {(50} 路径_ _,}\ MS K4 时间 , NSK6min TTL)
  • DriftDetectionHandler: MS K1 型態× NSK3 樣本, TTL-pruned
  • SessionEscalationService@: @ @ 35- @ minute TTL 用计时机 @ I-# 驱动驱逐

超越HNSW班级需要的关注 MarkovTracker._cohortBaselines# .# Markov 链条 运行跟踪器维护 per-\ccohort 基线过渡矩阵@ (\sparate MaxTrackedSignatures“).” 组群基线“(one”,每个交通群组,如“MS K2 datacenter”、“-new @"}或“" SelfMaintenanceOptions.MarkovCohortSize.

规则#: 每个单顿收藏都回答三个问题, 或者它“'” 是一个bug:}是什么界限 它被驱赶到什么? “,” 什么?

步进“7:” 使每个绑定的配置都可设置

@ "No contribution@ MS K1之后的下一个失败模式是 @ I"bound: @ MPK3%s wrong for this general_ ". hard# -} MaxEntries = 10_000 直到有人在Pi4,或装有 @256MBZ.的容器上 运行您的服务为止

对于StyloBot MSMK0来说,每个限制都落到单线下 SelfMaintenanceOptions 区块内 appsettings.json. 默认值适用于标准服务器@. 限制的硬件 @' a LowMemory 固定预设@ : @

public static SelfMaintenanceOptions LowMemory => new()
{
    SignatureCacheSize  = 1_000,
    SessionCacheSize    = 500,
    IntentCacheSize     = 300,
    MarkovCohortSize    = 2_000,
    CacheSlidingExpiration = TimeSpan.FromHours(1),
};
builder.Services.AddBotDetection(opts =>
{
    opts.SelfMaintenance = SelfMaintenanceOptions.LowMemory;
});

或经由 appsettings.json 用于环境的 M- @ 特定调制@ : @

{
  "BotDetection": {
    "SelfMaintenance": {
      "SignatureCacheSize": 1000,
      "SessionCacheSize": 500,
      "IntentCacheSize": 300,
      "CentroidRetentionDays": 14
    }
  }
}

更深的点@ : @ 一个地方设置的绑定@ I'}% 1} 是操作员可以解释的关于 < . * 一个绑结,%'_ 覆盖了15个区域 const int @._Councilizing the knobs is half of yourself@-_BARBAR_没有人能解释这些声明的理由吗?

规则#: 每个操作员都可能想要改变 硬件使用寿命 在一个配置街区,}没有分散在代码库@.}

"#fixed_"}看起来像什么

StyloBot 具体为 @(_FOSS 建设@,}低记忆预设):}

@ }元件@ #之前 @MS K2_%
簽署 HNSW 索引 * 未到達的 LOH *MS K2 * @~256 KB 熱藏快取 *
会话 HNSW 指数 @ 无界LOH @MS K2@~258KB热缓存 @
本端 HNSW 指数 @ 无路的LOH @MS K2@~43KB热缓存 # #
@ _JSON 自动保存缓冲器 @MS K1}#100-500_MBLOH 每毫微秒$5}( Q)♫ @0 #
}MB MT MS K4QK cap)
  • 矢量层总计 * * | * * 总向量层 * * | * -=YTET -伊甸园字幕组=- 翻译: | <6甲基溴 |

检测模式没有变化.}改变的是 如果存在相似性证据当它“'”允许影响快速路径时“.”中心在SQLite {(}FOSZ})}或邮差+}pgvector @(}$paid_);}夜间压缩仍然生产着LMS K6LMSSK7}它们不需要无约束的内存.}

在Pi4中, 自由开放源码在“ 500 MBRS”下建起的座位。

一般性教训

添加上限约束内存而不改变架构@. @% 错误的抽象始终是错误的 @ ; @% 这个症状只会变得安静点 @ I.}下一个触摸系统的人会继承一个几乎能起作用的结构_ MS K3, 这比显然不具有' @ t.# 更糟糕

在长“-”正在运行的学习系统中循环的模式@:

  • Cap 热路径旨在容忍与工作量相符的驱逐政策。
  • 压缩历史: 常规二进制持久性存储@, 定期压缩 @, 访问请求线索
  • 审计其他一切@: @ 每一个累积器都回答它的界限是什么 @ ,} 驱赶它的东西是什麽?
  • 集中按钮@ : @ 1 config block@ MS K1 @ not 15 const瑞 s
  • 排查时间: 错误只存在于运行中的系统中

固定形状@,}不是症状=.}


进一步读

StyloBot 发射系列

HNSW 和此博客中的矢量搜索

外部参考资料

执行源来源 @: GithubMS .comMS K1scottgal/stylobot (星际机器人).+Live 引擎 @,_QTaptro@,#和商用控制 stylobot% . @ net.

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.