Technical Research: Memfit AI Production-Grade Autonomous Penetration Testing Agent Architecture
Does it sound like magic?
Background
Automated penetration testing has been pursued for many years. As is well known, most approaches basically follow one of two paths: either run a fixed script — the vulnerabilities it catches are all predetermined pattern matches; or use an LLM as an Agent to "plan" the penetration workflow, but it typically gets lost after running for less than ten minutes, repeating the same things, or simply forgetting what it had discovered earlier.
Our team wrestled with this direction for several months with a clear goal: let the AI work continuously like a real human penetration testing engineer, run for hours, adjust its strategy when it hits the unexpected, and when done, tell you what it did, how it did it, and where the evidence is.
Memfit AI is a new production-ready product from our Yak Project team. It can serve as a fully automated security penetration testing Agent — give it a target URL, and like a real penetration testing engineer, it autonomously plans, executes, and iterates over the attack chain. (Of course, it can do other things too; we use "penetration testing," the hardest problem, as the starting point to introduce this new project.)
Starting from the Task Executor
Architecturally, Memfit AI adopts a Task Executor as its core runtime engine. It is responsible for executing the actual penetration actions — including port scanning, fingerprinting, vulnerability probing, PoC validation, and even post-exploitation operations. The entire execution process is fully transparent to the user; the execution logic and real-time status of all subtasks can be viewed directly in the UI. More importantly, the user can intervene at any time — this is not a "fire-and-forget" automation tool, but a human-machine collaborative penetration testing partner. You can pause at any stage, adjust the strategy, supplement information, and then let the AI continue working.
Flexible and Adaptable: Dynamic Planning
The biggest pain point of traditional LLM Agent penetration solutions is that they forget what they were doing midway. ** Once the context window fills up, key findings discovered earlier are lost; once it hits a branch path, it dives in headfirst and can't find its way back. Our solution introduces a Dynamic Task Planning mechanism. Instead of fixing a penetration workflow from the very start, it works like a real penetration testing engineer:
-
Automatic subtask decomposition: After receiving the target URL, the AI first performs information gathering, then dynamically generates the next step's subtask list based on the results (open ports, web fingerprints, directory structure, etc.).
-
Real-time strategy adjustment: If a particular attack path doesn't work, the AI doesn't keep banging its head against it; it backtracks to the upper level and re-evaluates other possible attack surfaces.
-
Context memory is never lost: This is also the origin of the name "Memfit" (Memory + Fit) — through a specialized memory management mechanism, it ensures that during long-running operations (several hours), key findings and decision context are not lost due to context window limitations.
This article shares the current progress and core technical approach of this project.
Open the Folder and Take a Look
Talk is cheap, let's look at the real thing first.
I had Memfit AI perform a full penetration test against a local Vulinbox vulnerability lab (127.0.0.1:8787). After it finished, I opened the working directory:
10350_penetration_test_127_0_0_1_878_20260310_e5df9/
├── evidence/
├── exploit/
├── recon/
├── report/
├── vuln/
│ ├── sql_injection.md
│ ├── xss.md
│ └── ssrf.md
├── task_1-1_crawl_web_attack_surface/
│ ├── tool_calls/
│ │ └── 1_simple_crawler_crawl_vulinbox_attack_surface.md
│ ├── task_1_1_result_summary.txt
│ └── task_1_1_timeline_diff.txt
├── task_1-3_verify_sqli_vulns/
│ ├── tool_calls/
│ │ ├── 1_do_http_request_sqli_baseline_user_id.md
│ │ ├── 2_do_http_request_sqli_quote_test_user_id.md
│ │ ├── ...
│ │ └── 15_write_file_create_sqli_vuln_report.md
│ ├── task_1_3_result_summary.txt
│ └── task_1_3_timeline_diff.txt
├── task_1-5_verify_ssrf_vulns/
│ ├── tool_calls/ (11 个工具调用记录)
│ └── ...
└── task_plan-task/
└── loop_plan_action_calls/
├── 1_scan_port.md
└── 2_plan.md
This directory structure was created by the AI itself. evidence/, vuln/, exploit/, recon/, report/ — a standard penetration testing project layout. Each task_x-x_* folder corresponds to an executed subtask, containing tool call records, timelines, and result summaries.
Opening vuln/sql_injection.md, this is the vulnerability report the AI generated itself:
The report includes a complete PoC:
# 基础注⼊验证
curl "http://127.0.0.1:8787/user/id?id=1'"
# 预期响应: unrecognized token: "';"
# 布尔条件测试
curl "http://127.0.0.1:8787/user/name?name=admin' AND 1=1-- -"
curl "http://127.0.0.1:8787/user/name?name=admin' AND 1=2-- -"
The test matrix for each endpoint (baseline, single quote, Union Select, boolean blind, time blind), risk analysis, and remediation suggestions — all complete.
These were not written by a human. The AI itself executed 15 HTTP requests, analyzed response differences, determined the injection type, and finally wrote it into a structured report.
Task Planning: Scan First, Then Devise a Plan
The first thing the AI does after receiving the target is not to blindly start scanning — it first performs a port scan, gathers basic information, and then generates an execution plan.
This plan looks like the following (extracted from task_plan-task/loop_plan_action_calls/2_plan.md):
{
"main_task": "对127.0.0.1:8787 Vulinbox靶场进⾏完整渗透测试",
"main_task_goal": "完成系统化渗透测试,覆盖信息收集、Web侦察、漏洞验证全流程",
"tasks": [
{"subtask_name": "使⽤爬⾍⼯具收集Web应⽤的URL结构和攻击⾯", "depends_on": []},
{"subtask_name": "创建渗透测试项⽬标准⽬录结构", "depends_on": []},
{"subtask_name": "针对SQL注⼊场景构造请求验证漏洞",
"depends_on": ["使⽤爬⾍⼯具收集Web应⽤的URL结构和攻击⾯"]},
{"subtask_name": "针对XSS场景构造请求验证漏洞",
"depends_on": ["使⽤爬⾍⼯具收集Web应⽤的URL结构和攻击⾯"]},
{"subtask_name": "针对SSRF场景构造请求验证漏洞", "depends_on": ["..."]},
{"subtask_name": "验证⽂件上传接⼝的安全漏洞", "depends_on": ["..."]},
{"subtask_name": "验证Fastjson反序列化漏洞", "depends_on": ["..."]},
{"subtask_name": "验证Shiro框架的安全漏洞", "depends_on": ["..."]},
{"subtask_name": "验证JWT认证安全漏洞", "depends_on": ["..."]},
{"subtask_name": "验证命令注⼊安全漏洞", "depends_on": ["..."]},
{"subtask_name": "验证敏感信息泄漏和⽬录遍历", "depends_on": ["..."]},
{"subtask_name": "汇总漏洞评估结果⽣成渗透测试报告", "depends_on": ["以上所有任务"]} ]
}
13 subtasks with dependency relationships — crawler-based attack surface collection comes first, various vulnerability verification tasks depend on the crawler's results, and the final report depends on all verification tasks.
The underlying data structure is AiTask (from common/ai/aid/task.go):
type AiTask struct {
Index string `json:"index"`
Name string `json:"name"`
Goal string `json:"goal"`
SemanticIdentifier string `json:"semantic_identifier"`
ParentTask *AiTask `json:"parent_task"`
Subtasks []*AiTask `json:"subtasks"`
DependsOn []string `json:"depends_on,omitempty"`
StatusSummary string `json:"status_summary"`
TaskSummary string `json:"task_summary"`
ShortSummary string `json:"short_summary"`
LongSummary string `json:"long_summary"`
}
Index is a hierarchical number — "1-1", "1-2", "1-3"... SemanticIdentifier is used to generate directory names — so the task_1-1_crawl_web_attack_surface seen earlier is formed by concatenating the task number with the semantic identifier. The entire task tree is flattened into a linked list via DFS at runtime:
type runtime struct {
RootTask *AiTask
config *Coordinator
cursor int
TaskLink *linktable.LinkedList[*AiTask]
}
Then it executes along the TaskLink one by one. After each task finishes, it updates the status and advances the cursor. Note the figure below: the AI is progressively advancing tasks.
Dynamic Adjustment: The Plan Changes Mid-Run
The plan above looks perfect, but the defining characteristic of real penetration testing is "the unexpected."
Here are a few situations actually encountered during this test:
-
After the crawler finished, a pile of unexpected API endpoints were discovered — endpoints like /fastjson/json-in-form and /fastjson/json-in-body that were not originally planned for testing needed dedicated verification.
-
After the main file upload interface /upload/main was tested, other upload endpoints such as /upload/case/unsafe were found — tasks needed to be appended.
-
During SQL injection verification, the target was found to use SQLite, where the column count for Union Select is completely different from MySQL — the injection strategy needed adjustment.
-
While verifying Fastjson vulnerabilities, the loaded skill packs were found insufficient; additional deserialization-related reference materials needed to be loaded.
How to cope? Through TaskDelta.
type TaskDeltaOp string
const (
TaskDeltaInsertAfter TaskDeltaOp = "insert_after"
TaskDeltaAppend TaskDeltaOp = "append"
TaskDeltaRemove TaskDeltaOp = "remove"
TaskDeltaModify TaskDeltaOp = "modify"
TaskDeltaReplaceAll TaskDeltaOp = "replace_all"
)
type TaskDelta struct {
Op TaskDeltaOp `json:"op"`
RefTaskIndex string `json:"ref_task_index,omitempty"`
Tasks []TaskDeltaNewTask `json:"tasks,omitempty"`
UpdatedName string `json:"updated_name,omitempty"`
UpdatedGoal string `json:"updated_goal,omitempty"`
}
Five operations cover all runtime adjustment scenarios:
insert_after is the most commonly used. After testing /upload/main, the AI discovered that the crawler results still had uncovered endpoints like /upload/case/unsafe, so through TaskDelta it appended a "supplement testing of other file upload endpoints" task after the current one. This task did not exist in the original plan — it was dynamically added by the AI based on findings during execution.
In the code, this adjustment happens during the task review phase. After each subtask completes, the AI can trigger adjust_plan:
{
Value: "adjust_plan",
Prompt: "基于当前任务发现的新信息,后续计划需要调整(⽀持增删改查 delta 操作)", AllowExtraPrompt: true,
ParamSchema: schemaRePlanSuggestion,
},
The AI outputs TaskDelta operations, which the system parses and applies to hot-update the task tree — inserting new nodes, removing nodes, modifying goals, all done at runtime without needing to stop and replan.
This point is extremely critical for penetration testing scenarios. In penetration testing, information is gradually revealed, and each action may change the subsequent attack path. If the plan is rigid, there is no difference from a fixed script. This is the most noteworthy operation that distinguishes Memfit from ordinary Agents.
ReAct: Think Through Every Step Before Acting — Using FastJSON as an Example
The task tree solves the "what to do" problem; the specific "how to do it" relies on the ReAct loop.
Before each subtask executes, the system first runs a round of Intent Recognition. In the file system you can see task_1-1_intent/, task_1-3_intent/ and similar directories, which record the intent analysis process — confirming which capability packs this task needs.
Automatically querying the knowledge base, exploring SKILLS and various materials to try to solve FastJSON vulnerability detection.
For example, the skill loading process during the intent recognition phase when verifying a Fastjson deserialization vulnerability:
加载能⼒ vuln-assess [参考资料]
加载技能列表 vuln-assess, toolbox
思考: 需要了解Fastjson反序列化漏洞的检测⽅法和payload构造技术...
加载能⼒ vuln-assess [参考资料]
额外加载技能资源 @vuln-assess/web-vulns.md
加载能⼒ @ctf-web/web-vulns.md
额外加载技能资源 @ctf-web/web-vulns.md
load_skill_resources_pattern Fastjson|Jackson|反序列化|deserialization
It first loads the general vulnerability assessment capability pack vuln-assess, finds it insufficient, then loads web-vulns.md, still insufficient, and finally loads the specialized skill resources through pattern matching Fastjson|Jackson|反序列化|deserialization.
After skill loading completes, it enters the ReAct execution loop. At each step, the AI must decide what the next action is:
-
require_tool — call a tool (send HTTP requests, execute commands, read/write files, etc.)
-
tool_compose — compose multiple tools to execute together
-
load_capability — load new skill packs at runtime
-
require_ai_blueprint — call a predefined workflow ("blueprint")
-
request_plan_execution — when encountering a complex sub-problem, nest a sub-plan
-
finish — task complete
Next, let's look at the effect of AI "manual injection" for SQL injection.
Using SQL injection verification as an example, the AI's actual action sequence (15 tool calls):
1. do_http_request -- sqli_baseline_user_id (基线请求)
2. do_http_request -- sqli_quote_test_user_id (单引号注⼊)
3. do_http_request -- sqli_union_select_test_user_id
4. do_http_request -- sqli_error_based_test (报错注⼊)
5. do_http_request -- sqli_boolean_blind_test_user_id (布尔盲注 真值)
6. do_http_request -- sqli_boolean_blind_false_test (布尔盲注 假值)
7. do_http_request -- sqli_numeric_blind_true_test
8. do_http_request -- sqli_numeric_blind_false_test
9. do_http_request -- sqli_name_baseline_test (换端点 /user/name)
10. do_http_request -- sqli_quote_test_user_name
11. do_http_request -- sqli_union_select_test_user_name
12. do_http_request -- sqli_boolean_blind_true_test_username
13. do_http_request -- sqli_boolean_blind_false_test_username
14. do_http_request -- sqli_time_blind_test (时间盲注)
15. write_file -- create_sqli_vuln_report (写漏洞报告)
First it tests various injection methods on the /user/id endpoint, then switches to the /user/name endpoint for another round, and finally writes the results into a report. The request parameters, response content, and timing for each step are all recorded in separate Markdown files under the tool_calls/ directory.
These files are not just logs; they are evidence. Any vulnerability finding can be traced back to a specific request — what payload was used and what the server returned.
Of course, it's not only recorded in Markdown — we can open "HTTP traffic" to see a more complete traffic log:
Memory: Dynamic Memory Window, Cross-Task Reuse
After running continuously for 100 minutes, the system accumulated a great deal of memory. Of course, the execution memory is dynamically updated each time as well, as we can see here:
Why is memory needed? The context window is limited. Each subtask has its own ReAct loop and context; information transfer between tasks cannot rely solely on stuffing more text into the context — too much degrades model performance, too little loses information.
Our approach was to build a memory triage system. After each ReAct iteration, TimelineDiffer extracts the new information from this round, and through LiteForge performs memory triage, scoring each memory across seven dimensions:
// CORE PACT 七维记忆评分 (common/ai/aid/aimem/aimemory_build_memory.go) WithNumberParam("t", "时效评分:这个记忆应该如何被保留?"),
WithNumberParam("a", "可操作性评分:是否可以改进未来⾏为?"),
WithNumberParam("p", "个⼈偏好评分:是否绑定⽤户⻛格?"),
WithNumberParam("o", "来源确定性评分:信息有多可信?"),
WithNumberParam("e", "情感评分:⽤户情绪如何?"),
WithNumberParam("r", "相关性评分:对⽬标有多关键?"),
WithNumberParam("c", "关联度评分:与其他记忆如何关联?"),
Each memory also carries tags and potential_questions. tags are domain labels, and potential_questions are the questions this memory might answer — these two fields are prepared for subsequent RAG retrieval.
Take a concrete scenario: during task_1-3 SQL injection verification, the AI discovered the target database is SQLite (inferred from the error message "unrecognized token"). This finding is stored as a memory, with tags possibly being ["database", "sqlite", "target-info"]. Later, when task_1-5 does SSRF verification, the system retrieves this memory via RAG, and the AI knows the target uses SQLite without needing to re-probe.
When a subsequent task starts, the system uses the current task's description as a query, retrieves relevant memories via RAG (limited to 4KB), and injects them into the context. This way each subtask can "remember" important prior findings without being overwhelmed by irrelevant information.
Review and Deep Planning
In a production environment, fully unattended operation is unrealistic — at least not yet. The system is designed with four review checkpoints:
-
Plan review (plan_review) — after the AI generates an execution plan, a human confirms or adjusts it before execution.
-
Task review (task_review) — each subtask can be reviewed after completion.
-
Tool review (tool_use_review) — sensitive tool calls require human confirmation before execution.
-
Blueprint review (exec_aiforge_review) — before invoking a predefined workflow, the parameters can be reviewed.
During task review, the human has the following options:
var TaskReviewSuggestions = []*ReviewSuggestion{
{Value: "deeply_think",
Prompt: "思考不够深⼊,为当前任务拆分更多⼦任务"},
{Value: "inaccurate",
Prompt: "回答不够精准,存在未使⽤⼯具导致幻觉"},
{Value: "continue",
Prompt: "继续执⾏任务"},
{Value: "adjust_plan",
Prompt: "基于当前任务发现的新信息,后续计划需要调整"},
}
deeply_think and adjust_plan are the most interesting. deeply_think lets the AI further decompose the current task into subtasks — for example, "verify file upload vulnerabilities" is too coarse-grained; after deep thinking, it will be broken down into MIME bypass testing, NullByte truncation testing, .htaccess upload testing, path traversal testing, and other finer-grained subtasks.
adjust_plan triggers the TaskDelta mechanism described earlier, adjusting the subsequent plan at runtime.
During actual runs, you can see that the AI even references CVE numbers during the review phase. For example, in the task-review for file upload testing, the AI mentioned CVE-2017-15715 (Apache line parsing vulnerability) and accordingly adjusted the subsequent testing strategy.
The review policy is configurable — fully automatic, semi-automatic, or fully manual, depending on the use case. In internal testing we generally enable plan review + critical tool review; for daily vulnerability scanning, full automation works.
The Core of Dynamic Planning Is the Adjust Plan Mechanism
With all the groundwork laid above, we can now explain this clearly.
The reason Memfit AI can "adapt on the fly" is not because it replans everything from scratch every time it hits a surprise — that would be too costly, and the context-switching overhead would drag the entire system down. The real core is: it makes minimal incremental modifications on top of the existing plan. This is the design essence of the Adjust Plan mechanism.
Adjust Plan is not running all the time. It is triggered only at a very specific point: during the review phase after each subtask completes.
⼦任务执⾏完毕
↓
⽣成 result_summary(执⾏结果摘要)
↓
⽣成 timeline_diff(本轮新增发现)
↓
进⼊ task_review(任务审阅)
↓
AI / ⼈类 判断是否需要调整后续计划
↓
如果需要 → 触发 adjust_plan → 输出 TaskDelta → 热更新任务树
↓
推进游标,执⾏下⼀个任务
The key point: the AI in the review phase can see the complete execution results and memory context of the current task. It doesn't decide whether to change the plan out of thin air; it judges whether the subsequent plan is still reasonable based on "what this step actually produced."
The information the AI receives during the review phase includes three parts:
-
The current task's result_summary — what this task did, what it found, what the conclusion is.
-
timeline_diff — what key information was added compared to the previous round (new endpoints, new fingerprints, new vulnerability leads).
-
The remaining task list — what tasks are still queued up, and what their goals are.
The question the AI must answer is simple: "Based on what I just found, is the subsequent plan still sufficient? Is there anything redundant? Is anything missing? Does anything need to change?"
Here's a real example.
After the task_1-1 crawler finished, the AI discovered a large number of Fastjson-related endpoints on the target site (/fastjson/json-in-form, /fastjson/json-in-body, /fastjson/json-in-query, etc.), while the original plan had only a single generic "verify Fastjson deserialization vulnerability" task.
At this point the AI judged the granularity insufficient, and through adjust_plan split this task into multiple subtasks, each verifying a different parameter-passing method.
As another example, after task_1-3 completed SQL injection verification, the AI found that the /user/by-id-safe endpoint used parameterized queries and could not be injected. The original plan might have had a "deeply exploit SQL injection to obtain sensitive data" task depending on this endpoint — at this point the AI would use a modify operation to change that task's goal to target only the confirmed vulnerable endpoints, or directly remove it.
TaskDelta Execution Logic
Once the TaskDelta is in hand, how does the system apply it to the running task tree? The core logic resides in the runtime's plan-update method:
func(r *runtime) applyTaskDeltas(deltas []TaskDelta) error {
for _, delta := range deltas {
switch delta.Op {
case TaskDeltaInsertAfter:
// 找到 ref_task_index 对应的节点
// 在它后⾯插⼊新任务节点
// 同时更新 TaskLink 链表,保持遍历顺序正确
case TaskDeltaAppend:
// 在当前所有任务末尾(但在最终报告任务之前)追加新任务
case TaskDeltaRemove:
// 从任务树和链表中移除指定节点
// 如果有其他任务依赖它,需要处理依赖关系
case TaskDeltaModify:
// 更新指定任务的 Name 和 Goal
// 不改变它在树中的位置和依赖关系
case TaskDeltaReplaceAll:
// 核弹选项:清空当前游标之后的所有任务,重新⽣成
// 只在极端情况下使⽤
}
}
// 重新展平任务树为链表
r.TaskLink = r.RootTask.Flatten()
return nil
}
Several implementation details are worth noting:
First, the insert operation does not disrupt the records of already-executed tasks. Tasks before the cursor have already finished running; their results, evidence, and memories have all been persisted to the file system and the memory store. TaskDelta only operates on task nodes after the cursor; the completed parts are unaffected.
Second, dependency relationships are handled automatically. If a newly inserted task declares depends_on, the system checks whether the depended-upon task has already completed. If it has, the dependency is directly marked as satisfied; if not, the new task is queued after the dependency task.
Third, replace_all is a fallback option that is almost never triggered in practice. In our testing, the vast majority of adjustments are insert_after and modify, with an occasional remove. Full replacement is triggered only when the AI judges that "the entire previous direction was wrong" — for example, originally thinking the target was a Java application, only to discover it's actually Python, requiring the entire vulnerability verification strategy to be overhauled.
Actual Results
In this test against the Vulinbox vulnerability lab, within a hundred minutes, the AI triggered adjust_plan a total of 4 times:
4 micro-adjustments, not a single full replan.
The final 13 initial tasks became 16 actually executed tasks; the entire process took about 100 minutes, and the AI always knew what it was doing.
How Long Did It Run
This section doesn't rely on gut feeling; let's look directly at the directory evidence: /Users/v1ll4n/yakit-projects/aispace/10350_penetration_test_127_0_0_1_878_20260310_e5df9.
The timeline is very clear.
It started running around 10:53, and the final report report_20260310_13_28_27.txt was generated at 13:28 — the entire execution took about 2 hours and 35 minutes. This magnitude is no longer a ten-minute demo flow.
The tasks also didn't run in a straight line from start to finish.
The initial plan in task_plan-task/loop_plan_action_calls/2_plan.md had 13 subtasks, and the final report landed on 19. The extras added in between are supplementary tasks like 1-8-1~1-8-4, 1-10, 1-11, 1-13, and 1-14.
These supplements were not added by hand after the fact; they were inserted during runtime. In task_1_12_timeline_diff.txt and task_1_13_timeline_diff.txt you can see suggestion: "adjust_plan" and task_deltas, with the core operation being insert_after. The supplementary tests for Shiro and JWT were added into the main chain this way.
Looking at the scale of the artifacts, the directory allows direct reconciliation:
- The execution directory is about 4.7M in total size
- There are 23 main task directories (excluding _intent)
- Correspondingly, 23 result_summary files and 23 timeline_diff files
- Under tool_calls there are 206 Markdown records
- The final report tallies about 188 tool calls (of which do_http_request accounts for 156+)
- Under vuln/ there are 11 vulnerability documents (excluding README), with 18 vulnerability items in the final summary
So, the most critical question remains: did the context explode?
Looking at it this time, no. The reason is not complicated: the long chain is broken into task nodes; each round of changes settles into timeline_diff and result_summary; when deviations are found, TaskDelta is used for incremental correction rather than starting the whole game over.
So the value of this is not merely "running for a long time." The essence is "running for a long time while continuously keeping books, continuously correcting, and continuously reviewing." This is an engineering system, not a demo script.
The Generated Report
Finally, let's show everyone what our silicon-based hacker Memfit AI actually did during these two half-hour sessions.
Can You Use and Experience It?
Of course — unlike some competitors or other teams, we won't build something and then not let users use it. If you like it, go directly to https://memfit.ai/ to download Memfit AI. During the internal beta promotion phase, we won't charge any Token fees. Of course, if you want to use your own AI, you can also configure your AI API KEY directly in Memfit.
memfit:: A Knowledge Base That Remembers, Visible Execution Power
After installation, experience your own powerful silicon-based hacker. What we've introduced today is mostly meant to spark your interest in understanding our production-grade AI Agent.
We will cover more usage tips in subsequent articles.
The skills used in this system are available here; you can pick them up yourself via the "How to obtain" section below. If you want to use your own SKILLS, just like with Yakit, place them in the ~/yakit-projects/ai-skills/ folder and they will be automatically recognized!
How to obtain: Follow the Yak Project official account, reply "skills" in the background, click the link to download. YAKers, come claim yours!
Of course, we will also provide users with all the SKILLS we tested, and the memfit-standard-free standard model is already enabled in Memfit.
This article was first published on the Yak Project official account, read the original.

