<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/scripts/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:h="http://www.w3.org/TR/html4/"><channel><title>Midnight&apos;s blog</title><description>Stay hungry, stay foolish</description><link>https://www.gtcc.tech</link><item><title>Agent基础概念扫盲</title><link>https://www.gtcc.tech/blog/agent-basics</link><guid isPermaLink="true">https://www.gtcc.tech/blog/agent-basics</guid><description>从 Agent Loop 出发，梳理 Tool、Hooks、ToDo、Subagent、Skills、System Prompt 以及 Context / Memory 等基础概念。随着知识的学习和实践，后面可能会有新的理解，写错勿怪。</description><pubDate>Wed, 15 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、Hello Agent&lt;/h2&gt;
&lt;p&gt;先认识一下 Agent：它不是「更会聊天的模型」，而是能带着目标连续干活的程序。&lt;/p&gt;
&lt;p&gt;核心就一件事——循环：&lt;/p&gt;
&lt;p&gt;想下一步 → 调用工具 → 观察结果 → 再想 → 再调用…直到完成或停下。&lt;/p&gt;
&lt;p&gt;和普通对话模型的区别很直接：后者问一句答一句就结束，Agent 会围着目标转好几圈，中间可能读文件、跑命令、查信息，再根据结果调整。这个圈一般叫 Agent Loop。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;while not done:
    think()   # 推理
    act()     # 调 Tool
    observe() # 看结果
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以学 Agent，不必一上来堆概念：先记住它是个循环；Tool、Hooks、ToDo、Subagent、Skills等都是后来挂在这个循环上的能力与约束。&lt;/p&gt;
&lt;h2&gt;二、工具&lt;/h2&gt;
&lt;h3&gt;2.1 Tool&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&quot;LLM 决策，Tool 落地&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;从聊天对框到终端干活，第一个重要一步就是Tool，读文件、改代码、跑命令从此开始有了动手能力。模型通过 Tool Calling / Function Calling 选出工具并传入参数，由运行时真正执行，再把结果回传给模型，形成 [ 推理 → 调用 → 观察 → 再推理 ] 的循环。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;tools = [
    {
        &quot;type&quot;: &quot;function&quot;,
        &quot;function&quot;: {
            &quot;name&quot;: &quot;read_file&quot;,                    # 工具名
            &quot;description&quot;: &quot;读取指定路径的文件内容&quot;,  # 给模型看的说明
            &quot;parameters&quot;: {                            # 参数 schema（结构化约束）
                &quot;type&quot;: &quot;object&quot;,
                &quot;properties&quot;: {
                    &quot;path&quot;: {&quot;type&quot;: &quot;string&quot;, &quot;description&quot;: &quot;文件路径&quot;}
                },
                &quot;required&quot;: [&quot;path&quot;]
            }
        }
    }
]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 Hooks&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;“管理边界能力”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;有了 Tool，Agent 能读改文件、执行命令，能力明显变强；随之而来的是风险：路径搞错、误执行破坏性操作、越权访问敏感资源。&lt;/p&gt;
&lt;p&gt;很多人会想：用提示词写清&lt;code&gt;不许删文件、不许乱改&lt;/code&gt;行不行？提示词只能做 &lt;strong&gt;软约束&lt;/strong&gt;——模型仍是概率生成，同一句话不能保证每次都遵守。&lt;/p&gt;
&lt;p&gt;要把&lt;code&gt;尽量别乱来&lt;/code&gt;变成&lt;code&gt;违规就拦住&lt;/code&gt;，需要挂在工具调用前后的&lt;strong&gt;硬约束&lt;/strong&gt;机制，这就是 &lt;strong&gt;Hooks&lt;/strong&gt;（钩子）：在执行前审计/拦截，在执行后记录或补救，把不确定的生成行为收进可强制执行的边界里。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# hooks.json
# {
#   &quot;version&quot;: 1,
#   &quot;hooks&quot;: {
#     &quot;beforeShellExecution&quot;: [
#       { &quot;command&quot;: &quot;python3 .cursor/hooks/guard_rm.py&quot; }
#     ]
#   }
# }
data = json.load(sys.stdin)
cmd = data.get(&quot;command&quot;, &quot;&quot;)
# 硬约束：命中危险模式就拒绝
if &quot;rm &quot; in cmd:
    # permission=deny：这次命令不会真正执行
    print(json.dumps({
        &quot;permission&quot;: &quot;deny&quot;,
        &quot;user_message&quot;: &quot;禁止删除命令&quot;
    }))
else:
    # permission=allow：放行
    print(json.dumps({&quot;permission&quot;: &quot;allow&quot;}))
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、规划与协作&lt;/h2&gt;
&lt;h3&gt;3.1 ToDo&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&quot;按清单推进，而不是想到哪做到哪&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;举个例子：你说&quot;帮我推荐一件衣服&quot;。&lt;/p&gt;
&lt;p&gt;没有 ToDo 时，Agent 多半只能靠已有知识&lt;code&gt;顺着你说&lt;/code&gt;——听起来很会聊，其实像在哄人，缺少推销员那种主动采集信息、对比筛选的过程，推荐也容易飘、容易空。&lt;/p&gt;
&lt;p&gt;有了 ToDo，它会先把路径摊开：了解喜好 → 结合季节 → 确认体态/尺码 → 检索候选 → 清洗筛选 → 给出推荐；若你觉得步骤不对，还可以改清单。每一步可追踪，结果才更靠谱。&lt;/p&gt;
&lt;p&gt;人和 Agent 不一样：人可以边想边做；Agent 没有真正的自主意识，长链路里更容易跑偏。所以在 Agent 开发里，规划不是加分项，而是把能力收成&lt;strong&gt;可控流程&lt;/strong&gt;的关键一步——ToDo 做的，就是把模糊目标拆成可执行、可修正的清单。&lt;/p&gt;
&lt;h3&gt;3.2 Subagent&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&quot;主 Agent 编排，Subagent 干活&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;还是买衣服：你要的不只是推荐一件，还要比价、看评价、盯库存。&lt;/p&gt;
&lt;p&gt;没Subagent ，全压在一个 Agent 身上——边聊边搜边比，上下文越堆越乱，最后可能记混了，推荐也糊。&lt;/p&gt;
&lt;p&gt;有Subagent，主Agent 像店长：一个小弟专搜款式，一个专比价格，一个专看评价；各自干完交结果，店长再汇总给你。人可以一个人干到底；Agent 不行，单会话塞太多容易懵。所以复杂任务要&lt;strong&gt;拆出去、并行干、再汇总&lt;/strong&gt;——这就是 Subagent。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 主 Agent 派活，Subagent 专项执行后交回结果
task = &quot;推荐一件春季外套&quot;
# 并行委派（示意）
results = {
    &quot;style&quot;:  subagent_explore(&quot;搜春季外套款式&quot;),   # 翻资料
    &quot;price&quot;:  subagent_shell(&quot;比价脚本&quot;),            # 跑工具
    &quot;review&quot;: subagent_general(&quot;总结口碑&quot;),          # 综合分析
}
# 主 Agent 只负责编排 + 最终答复
answer = synthesize(task, results)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.3 Skills&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&quot;把会做，变成按标准做&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;还是买衣服：老销售心里有套固定流程——先问预算、再看场合、再给三档选择。新人全靠临场发挥，今天一套说辞明天一套。&lt;/p&gt;
&lt;p&gt;没有 Skills 时，Agent 每次都靠&lt;strong&gt;临场发挥&lt;/strong&gt;，同一类活质量忽高忽低。&lt;/p&gt;
&lt;p&gt;有了 Skills，等于给它一本标准作业书：什么场景触发、先问什么、输出长什么样。人可以凭经验自由发挥；Agent 没有真正经验，要把经验写成可复用规范，它才能稳定复现——这就是 Skills。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Skill = 可复用的标准（触发条件 + 步骤 + 输出要求）
skill = {
    &quot;name&quot;: &quot;recommend-clothes&quot;,
    &quot;when&quot;: &quot;用户要推荐衣服&quot;,           # 何时启用
    &quot;steps&quot;: [
        &quot;确认预算与场合&quot;,
        &quot;结合季节缩小范围&quot;,
        &quot;给出高中低三档推荐并说明理由&quot;,
    ],
    &quot;output&quot;: &quot;列表 + 理由，不要空洞夸奖&quot;,
}
# 命中场景就按手册走，而不是纯靠临场发挥
if match(user_query, skill[&quot;when&quot;]):
    follow(skill[&quot;steps&quot;], skill[&quot;output&quot;])
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.4 System prompt&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&quot;按需组装&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;很多人以为 System Prompt 就是开头硬编码一大段&lt;code&gt;你是谁、你要怎样&lt;/code&gt;。能跑，但不灵活：任务变了、Skill 变了、记忆变了，整段都得改。&lt;/p&gt;
&lt;p&gt;更常见的做法是组装（Assembly）：运行前把几块拼起来，再塞进系统位。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;角色与边界（你是谁、不能干什么）&lt;/li&gt;
&lt;li&gt;当前任务说明&lt;/li&gt;
&lt;li&gt;Skills / 规范片段（命中才挂上）&lt;/li&gt;
&lt;li&gt;记忆摘要 / 索引（见下文）&lt;/li&gt;
&lt;li&gt;输出格式要求&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# System Prompt = 拼装，不是写死
system_prompt = &quot;\n\n&quot;.join([
    role_rules,           # 角色与边界
    task_brief,           # 本次任务
    skill_snippets,       # 命中的 Skill
    memory_index,         # 相关记忆索引
    output_format,        # 输出要求
])
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;四、记忆&lt;/h2&gt;
&lt;h3&gt;4.1 context&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&quot;窗口信息&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Context 就是塞进模型里的东西(可重复的)：系统提示、对话内容(有用的)、Tool结果…Agent多跑几轮，文件内容、命令输出全堆在message里，窗口一满，服务就停止了。&lt;/p&gt;
&lt;p&gt;所以要压缩（Compaction）：裁减无关旧对话、把Tool结果总结、输出落库成文只能预览，还不够再让Agent做一轮摘要。原则很简单——便宜的先做(不设计Agent的能用代码搞定的)，贵的后做。&lt;/p&gt;
&lt;p&gt;代价也清楚：压缩是为了继续跑，细节会丢。用户偏好、关键约束一旦挤掉，下一圈可能就忘了——这就连到 Memory。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 每轮调用前：先腾地方，再问模型
context = cheap_compact(context)   # 裁剪 / 占位 / 文件落库
if too_long(context):
    context = summarize(context)   # 贵：LLM
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.2 memory&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&quot;压缩会丢细节，重要的是持久化&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;简单落地：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用 Markdown 文件持久化，按固定格式写（类型、内容、时间、适用范围）&lt;/li&gt;
&lt;li&gt;维护一份索引 md，只记有哪些记忆、大概讲什么&lt;/li&gt;
&lt;li&gt;组装 System Prompt 时注入索引（或相关条目），而不是整库硬塞进 Context&lt;/li&gt;
&lt;li&gt;分类：这类记忆该给谁——主 Agent、某个 Skill、某类任务、还是仅本会话&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;type: user_pref
scope: 主 Agent
content: 回答要简洁，少空话
updated: 2026-07-18
# 索引进提示词全部内容按需读
system_prompt = assemble(
    role_rules,
    memory_index,   # md 索引，渐进式披露
)
# 分类路由：该给谁，才给谁
route = {
    &quot;user_pref&quot;: &quot;主 Agent&quot;,
    &quot;project_fact&quot;: &quot;主 Agent + 相关 Skill&quot;,
    &quot;tool_lesson&quot;: &quot;同类任务再注入&quot;,
    &quot;temp_note&quot;: &quot;本会话用完可丢&quot;,
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>登陆凭证详解</title><link>https://www.gtcc.tech/blog/login-credentials</link><guid isPermaLink="true">https://www.gtcc.tech/blog/login-credentials</guid><description>从 Cookie/Session、Redis、JWT 到双 Token 与无感续签，梳理常见登录凭证方案、优缺点与适用场景。</description><pubDate>Sat, 11 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、什么是登陆凭证&lt;/h2&gt;
&lt;p&gt;**登录凭证（Login Credentials）**是向系统证明「我是我」的一组信息。系统不会凭空相信你，必须先核对凭证，才让你进入账户、访问数据、调用接口、分配相应权限。&lt;/p&gt;
&lt;p&gt;凭证一定要做好保护，无论是用户还是开发者。&lt;/p&gt;
&lt;p&gt;当用户凭证被攻击者拿走，就相当于账号被盗了。举个例子，Cursor 的用户凭证是存到 Cookie 中的信息，头是 &lt;code&gt;WorkosCursorSessionToken&lt;/code&gt;；当你把这个凭证放到互联网上（之前薅羊毛），群众中的坏人拿着你的 Token 就把你账号注销了。&lt;/p&gt;
&lt;p&gt;对于开发者来说更要把用户凭证做好保护，防止用户信息、财产损失。需要做好加密以及更新。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.gtcc.tech/_vercel/image?url=_astro%2F01-what-is-credential.CY9yEARY.png&amp;#x26;w=2048&amp;#x26;q=100&quot; alt=&quot;登录凭证示意&quot;&gt;&lt;/p&gt;
&lt;h2&gt;二、解决办法&lt;/h2&gt;
&lt;h3&gt;一般方案（Cookie、Session）&lt;/h3&gt;
&lt;p&gt;用户完成登陆 → 服务器保存用户信息（Session） → 并将 Session 信息返回给前端 → 前端将其保存至浏览器 Cookie 中 → 每次请求将 Cookie 信息携带至请求体发送给后端 → 后端每次进行验证。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;致命缺点：&lt;/strong&gt; 在多个服务器的情况下，Session 只保留在登陆的那台机器上面，后续请求打到别的机器上就判定为未登录。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.gtcc.tech/_vercel/image?url=_astro%2F02-cookie-session.B6ptP8vi.png&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;Cookie Session 方案&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Redis 保存 Session&lt;/h3&gt;
&lt;p&gt;将 Session 保存至 Redis 中，无论多少服务器都能在 Redis 中找到 Session 进行校验。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;需不需要设置过期时间？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这是肯定的。如果没有过期时间，用户岂不是一直处于登陆状态？这样可太坏了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;那过期时间设置多少呢？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;用户不可能到一定时间就要重新登陆，所以这个过期实际是一个假过期：当用户正常使用时，服务端需要刷新用户的 Token，确保用户这段时间是在用的。等用户一段时间不用了，Redis 过期时间会自动把用户踢出登陆。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;致命缺点：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;服务端每次验证都要去查 Redis，对双方的压力都是不小的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.gtcc.tech/_vercel/image?url=_astro%2F03-redis-session.D0CfhCUu.png&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;Redis 保存 Session&quot;&gt;&lt;/p&gt;
&lt;h3&gt;JWT&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://www.gtcc.tech/_vercel/image?url=_astro%2F04-jwt.CcBO1iIA.png&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;JWT 组成&quot;&gt;&lt;/p&gt;
&lt;p&gt;如果要减少 Redis 的压力，那么校验这一块就不能去 Redis 中。&lt;/p&gt;
&lt;p&gt;服务端通过 JWT，将用户信息保存到 Token 中返回给前端，后续每个请求只需要本地校验即可。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优势：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;减少服务端的压力、不需要查 Redis、扩展简单、适合分布式。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;缺点：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;JWT 是&lt;strong&gt;无状态&lt;/strong&gt;的。攻击者只要拿到了用户的 JWT 凭证，只要没有过期就存在危险。不能踢出用户、限制用户。&lt;/p&gt;
&lt;h3&gt;双 Token（AccessToken、RefreshToken）&lt;/h3&gt;
&lt;p&gt;服务端维护两套 Token。&lt;/p&gt;
&lt;p&gt;AccessToken 用于校验用户信息，过期后自动换新；如果丢失，只存在短时间危险。服务端如果要踢用户下线，只需要将保存的 RefreshToken 删除即可。&lt;/p&gt;
&lt;p&gt;| | AccessToken | RefreshToken |
| --- | --- | --- |
| 用途 | 日常访问 API | 只用来换新的 AccessToken |
| 有效期 | 短（1 ~ 2h） | 长（7天 ~ 30天） |
| 携带频率 | 每次请求都带 | 几乎不带，只在刷新时用 |
| 泄露风险 | 高（频繁传输） | 低（很少出现） |&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.gtcc.tech/_vercel/image?url=_astro%2F05-dual-token.MlaHx0bW.png&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;双 Token 流程&quot;&gt;&lt;/p&gt;
&lt;h3&gt;无感续签&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://www.gtcc.tech/_vercel/image?url=_astro%2F06-silent-renew.VsyK37qR.png&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;无感续签&quot;&gt;&lt;/p&gt;
&lt;h3&gt;总结&lt;/h3&gt;
&lt;p&gt;没有最好的方案，只有最适合的办法，结合场景和现在实际情况考虑。&lt;/p&gt;
&lt;h2&gt;三、后续升级&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;会话安全：&lt;/strong&gt; Token Family、Refresh Token 重用检测、单设备/全设备下线、Session 版本校验，使被撤销的 Access Token 可立即失效。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;安全防护与审计：&lt;/strong&gt; 补齐 CSRF、CORS、刷新接口限流、异常刷新检测，并记录登录、刷新、撤销日志。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;平台化演进：&lt;/strong&gt; 网关统一鉴权，Redis 管在线会话、数据库留审计历史；权限中心处理权限变更与缓存失效；高风险操作接入 MFA/风控，后续升级到 OAuth 2.1、OIDC、SSO。&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item></channel></rss>