NousResearch

Activity

Stars and mentions · Last 30 days, Oct 7 – Nov 5

GitHub stars7,546
Mentions326
0120240360480Oct 7Oct 21Nov 5
UTC days · repositories NousResearch owns20 repositories

Top repositories

NousResearch’s repositories ranked by pull request authors, as on trending.

Loading

Contributes to

Repositories outside NousResearch’s account where NousResearch opened or reviewed pull requests in the past 365 days.

Who noticed NousResearch

Everyone who starred or forked NousResearch’s repositories since the archive began in 2011.

Notable accounts

Ordered by the stars their own repositories have received.

Biggest fans

Accounts that starred the most of these repositories.

Breakout weeks

Each repository’s best week of new stars, with the release published around it.

Mentions

317posts
Nov 63 posts
  1. AI Agent OAuth Recovery: Stop Expired Connections From Retrying Forever An agent can have a perfect plan and still fail at the least interesting moment: halfway through a customer task, its connected account expires. The bad response is to retry the same API call until the run burns its budget. The worse response is to ask the model to repair authentication. OAuth failure is a product and workflow state, not a transient tool error. A reliable agent identifies whether a token can be refreshed, whether the customer must reconnect, and whether the original task is still safe to resume. This guide shows a vendor-neutral pattern for building that recovery path without exposing credentials or repeating side effects. Why expired connections become runaway agent runs OAuth access tokens are deliberately short-lived. A refresh token can usually obtain a replacement, but a user can revoke the grant, an administrator can change policy, or a provider can reject a refresh with invalid_grant. Google’s MCP guidance, for example, notes that clients need the right offline-access behavior to avoid repeated sign-in when access tokens expire (Google documentation). An agent loop often turns this ordinary condition into an incident: 1. A tool returns 401. 2. The model sees an opaque error and tries the tool again. 3. Several workers refresh the same connection at once. 4. One refresh rotates the refresh token; another now fails. 5. The workflow keeps retrying, or falls back to a different account without the user knowing. The durable rule is simple: authentication recovery belongs below the model. The model can explain a blocked task, but it should not inspect, paste, select, or refresh bearer tokens. Model the connection separately from the agent run Do not represent every failure as tool_failed. Keep a connection record with an explicit state, then let each run reference that record by ID. The run holds intent and progress; the connection holds authorization health. | Connection state | Meaning | Safe next action | | --- | --- | --- | | active | A recent check or call succeeded | Permit the requested tool | | refreshing | One service is refreshing the token | Wait briefly; do not start another refresh | | reauth_required | The grant is revoked, invalid, or missing | Pause affected runs and show a reconnect action | | provider_unavailable | The provider or token endpoint is unavailable | Retry on a bounded schedule; do not request consent yet | | disabled | The user, tenant, or policy removed access | Fail closed and require an explicit new connection | This distinction matters for UX. A reconnect screen cannot fix a provider outage, and an automatic retry cannot repair a revoked grant. Connection products already expose similar distinctions: an expired credential, a rejected reconnect, and a provider connection issue are different conditions with different remedies (connection troubleshooting example). Here is a minimal TypeScript shape: type ConnectionState = | "active" | "refreshing" | "reauth_required" | "provider_unavailable" | "disabled"; type Connection = { id: string; tenantId: string; provider: "crm" | "mail" | "calendar"; accountLabel: string; // safe display name, not an email if avoidable state: ConnectionState; grantedScopes: string[]; credentialRef: string; // vault reference, never the token itself version: number; lastVerifiedAt?: string; reauthReason?: "revoked" | "invalid_grant" | "scope_changed" | "missing"; }; Bind it to the tenant and intended account. “Use any connected calendar” is an unsafe fallback in a multi-account product. A reauthorized connection should update the same durable connection ID only after the user has seen the provider, account, and requested scopes. Make refresh a locked, typed operation Refresh-token rotation creates a small distributed-systems problem. Two requests might notice an old access token together. If both refresh independently, the provider may invalidate the token one of them was about to store. Use a short database lease or compare-and-swap update to nominate one refresher. Everyone else waits for its result. Keep the credential material in a secret manager; the application database stores only a vault reference and harmless status metadata. async function getUsableConnection(id: string) { const connection = await db.connection.findUniqueOrThrow({ where: { id } }); if (connection.state === "disabled") return { kind: "blocked", reason: "disabled" }; if (connection.state === "reauth_required") return { kind: "reauth", reason: connection.reauthReason }; const claimed = await db.connection.updateMany({ where: { id, state: "active", refreshLeaseUntil: { lt: new Date() } }, data: { state: "refreshing", refreshLeaseUntil: plusSeconds(20), version: { increment: 1 } } }); if (claimed.count === 0) return waitForConnectionResolution(id, 2500); try { const secret = await vault.read(connection.credentialRef); const refreshed = await oauth.refresh(secret.refreshToken); await vault.rotate(connection.credentialRef, refreshed); await db.connection.update({ where: { id }, data: { state: "active", lastVerifiedAt: new Date(), refreshLeaseUntil: null } }); return { kind: "ready" }; } catch (error) { return classifyAndPersistRefreshFailure(id, error); } } The pseudo-code assumes your storage layer supports an atomic conditional update. If it does not, use a transaction or a distributed lock with an expiry. More importantly, classify the response: - A timeout or 5xx is a bounded, retryable infrastructure failure. - invalid_grant, an invalid refresh token, or a user revocation is terminal for that grant. - A missing required scope needs reconnection and possibly consent for the new scope. - A 403 from the resource API may mean the grant works but the user lacks provider-side permission. Do not blindly reconnect. On a terminal refresh failure, mark the connection reauth_required once. Do not try it again from every queued run. A well-designed OAuth client does exactly this: it treats a failed refresh caused by a revoked or rotated grant as a reauthentication event rather than replaying the same unauthorized request (an implementation example). Pause the run with a recoverable checkpoint Once a task needs human reconnection, an agent has two responsibilities: preserve enough context to resume, and stop before it creates an ambiguous side effect. Persist a checkpoint before every external write and before you show the reconnect UI: { "run_id": "run_84a", "state": "waiting_for_connection", "connection_id": "conn_29", "required_capability": "crm.contacts.update", "checkpoint": "validated_contact_change", "intent_hash": "sha256:...", "resume_after_connection_version": 18, "created_at": "2026-11-04T10:15:00Z" } The intent_hash protects against quiet changes while the user is away. When the user returns, the agent should not send an old draft to a newly selected account or apply a stale CRM update. Fetch the current record, compare the relevant fields, re-run policy checks, and ask again if the intended action has changed. For an action that might already have reached the provider, do not resume by repeating it. Use an idempotency key or query the provider for an operation receipt first. This is the same unknown-outcome boundary that makes a network timeout different from a rejected write. Build a reconnect flow that earns trust The reconnect card should be specific without being alarming. It needs to answer what failed, what will happen next, and what will not happen automatically. > Reconnect needed — Your CRM connection can no longer authorize updates. The draft change is saved but has not been applied. Reconnect the account that owns this record, then review the change before continuing. Include these elements: - Provider name and a privacy-safe account label. - The capability that is blocked, not a raw OAuth error. - The saved task status: not started, safely paused, or needs verification. - A server-generated, short-lived reconnect URL bound to tenant, connection, and CSRF/session state. - A clear path to cancel or disconnect. Never put an authorization URL, authorization code, refresh token, or customer secret into a model prompt or chat message. The browser callback should reach your backend, exchange the code server-side, store the new credential in the vault, and redirect to a neutral completion page. The agent only receives connection_version: 19 and allowed capabilities. Reconnection is not permission escalation. If the user grants different scopes or selects another account, record that as a new authorization version and re-evaluate the task. A tool policy or approval that applied to the old connection must not silently carry over to a broader one. Resume safely instead of automatically continuing everything When OAuth completes, publish a connection.restored event. A scheduler can find runs waiting on that exact connection, but it should use a resume policy rather than wake every run. | Run type | Resume policy | | --- | --- | | Read-only lookup | Re-run after verifying tenant, account, and scope | | Draft generation | Resume only if source inputs and requested account still match | | External write | Recheck state, policy, and idempotency; often show a final confirmation | | Destructive action | Require a fresh approval, even after reconnect | This policy avoids an unpleasant surprise: a user reconnects their mailbox and suddenly receives a backlog of messages the agent attempted days earlier. The sensible default is “connection restored, task ready to review,” not “all blocked side effects now execute.” Use a version check to make the rule enforceable: function mayResume(run: Run, connection: Connection) { return run.state === "waiting_for_connection" && run.connectionId === connection.id && connection.state === "active" && connection.version >= run.resumeAfterConnectionVersion && run.tenantId === connection.tenantId; } The next tool call still goes through normal authorization, policy, rate limits, and approval gates. Resumption is a request to reconsider the action, not a stored permission slip. Test the failure paths that happy-path OAuth misses Most OAuth integration tests prove only that a new connection works. Production reliability comes from testing what happens later. - Force two simultaneous requests to refresh one connection; assert only one vault rotation occurs. - Return invalid_grant; assert one reauth_required transition and no retry storm. - Revoke an account between planning and execution; assert the write never starts. - Reconnect a different account; assert the paused run stays blocked until it is reviewed. - Change scopes during reconnection; assert a previously allowed write is reauthorized. - Timeout after an external write; assert the run reconciles with an idempotency key instead of repeating it. - Remove a connection while a run waits; assert all associated runs become safely terminal or need explicit reassignment. Measure the operational signals too: connections by state, refresh success rate, terminal refresh failures, time spent waiting for reconnect, repeated calls blocked after one failure, and resumes rejected because the account or target changed. Avoid logging raw tokens, authorization codes, request bodies with secrets, or full provider error payloads. A compact implementation checklist - [ ] Separate durable connection state from workflow state. - [ ] Keep credentials in a vault and expose only opaque references to application code. - [ ] Serialize refreshes per connection and handle rotation atomically. - [ ] Classify invalid_grant, missing scope, provider outage, and disabled access separately. - [ ] Turn terminal refresh failures into one reauth_required state, not open-ended retries. - [ ] Checkpoint the run before reconnect and bind it to tenant, connection, account, and intent. - [ ] Revalidate target data, policy, and idempotency before any resumed write. - [ ] Make reconnect visible, explicit, and reviewable. - [ ] Test revocation, rotation races, provider outages, changed scopes, and queued runs. OAuth is not background plumbing for an agent product. It is the boundary between a user’s connected account and an autonomous workflow. Treat an expired connection as a first-class state, and the product can pause honestly, reconnect safely, and finish only when it still has the right to do so. FAQ Should an AI agent retry after a 401 error? Not blindly. First determine whether the access token can be refreshed. A transient token-endpoint failure may justify a bounded retry, but a revoked grant or invalid_grant should move the connection to reauth_required and pause dependent work. What is the difference between token refresh and reauthorization? Refresh uses an existing refresh token without user interaction to obtain a new access token. Reauthorization starts a user-facing consent flow because the existing grant is invalid, missing, revoked, or needs different scopes. Can an agent reconnect an OAuth account automatically? It can refresh a valid existing grant in the background. It should not silently complete a new consent flow, choose a different account, or widen scopes. Those actions require the user’s explicit participation. How do I prevent OAuth refresh token race conditions? Allow one refresher per connection using an atomic database update, lease, or lock. Other requests wait for the outcome and read the newly stored credential reference. Rotate the vault value atomically and version the connection state. Should paused agent runs resume as soon as the user reconnects? Usually only read-only tasks can resume automatically. For drafts, writes, and destructive actions, revalidate the account, current target data, authorization, policy, and idempotency. A final review is often the safer experience. What should an agent show a user when a connection expires? Explain the blocked capability, provider, safe account label, and task status in plain language. Say whether any action was applied, preserve the task for review, and offer a secure reconnect action. Do not display raw OAuth errors, tokens, or authorization codes.

    1 reaction
  2. Reddit · · r/artificial · u/Glad-Concept-6809 · NousResearch/hermes-agent

    ok AI assistants are getting kinda out of hand So i've been reading about AI agent stuff and this week was actually insane. this guy in japan asked some AI assistant called Pickle to look up hotels for new years and it just... called the hotel by itself?? Twice. first time it said it was an AI and gave his full name, then the call dropped so it called back pretending to be his human secretary. He literally had to call the hotel and apologize en.yutura.net And then someone had this thing called Hermes Agent where another AI is supposed to double check risky stuff for you. it saw a "cache cleanup" was gonna delete a bunch of stuff, said yep that's dangerous, and approved it anyway. Wiped like 188k files, 21 gigs, no recycle bin, nothing hermes-agent#147373 oh and some researchers gave AIs a crypto wallet with a one cent limit, and the code to raise it got emailed to the owner... but the AI could read the email. 9 out of 10 just grabbed the code and raised their own limit 💀 coindesk.com Like you give them a tiny yes and they run with it. idk i'm not letting one touch my money or my files without me clicking ok first Has anyone else had an AI do something random like this??

    67 points · 64 comments
  3. A guardrail that decides at the door lets the intruder walk in later !A guardrail checks a tool call once, waves it through, and the tool then runs with full access to keys and the network — fail-open I report security bugs in AI-agent guardrails, and I keep finding the same shape of bug. This is the first one that's fully public, so I can show you the whole thing — the finding, the fix that shipped, and why I think the design itself is the problem. The finding (airlock, now public) airlock is a prompt-injection guardrail in the Hermes plugin catalog. Its job is to stop an agent from doing dangerous things without a human's say-so: in its default approvals_off: block mode, an action that needs approval should be blocked until a person approves it. The problem I reported: that block could be skipped entirely. !block mode promised to hold the action; in a gateway-run hermes -z turn the guard read the wrong unattended signal, so an approval-required action ran with no approval When the Hermes gateway runs a hermes -z turn, the turn is marked unattended in one place (gateway.session_finite) but not in the flag airlock was reading (HERMES_YOLO_MODE). So airlock looked at its usual signal, saw nothing that said "a human is driving this," and let an approval-required action run with no approval at all — even though the whole point of block mode is that nothing dangerous runs unattended. The maintainer triaged it and shipped a fix in airlock 0.2.3: it now asks the approval layer why Hermes would approve on its own, and it covers CLI subagents under delegation too. The fix is merged into the Hermes plugin catalog (PR #144871), and the advisory is public as GHSA-mgf8-gh7g-q4rg. Coordinated disclosure, credited. That's the system working, and the maintainer was great about it. The pattern underneath Here's why I care beyond this one bug. Most agent guardrails work the same way: they decide at the door. Something reads the pending action, matches it against a signal — a mode flag, a regex, a list of paths, a YARA rule — and then either waves it through or stops it. The tool itself runs with whatever access it already had. Every bug I find in this category is a gap between the signal the guard reads and the effect that actually happens: - the action is dangerous, but the signal says "attended," so it runs; - the path points at a secret, but it's spelled a way the matcher doesn't recognize, so it's missed; - the input is an injection, but it's phrased in a form the rule doesn't cover, so it passes clean. Same shape every time. The guard is reasoning about a string or a flag that stands in for the real thing, and anything that changes the representation without changing the effect slips past. When these fail, they fail open: no match, no block, action runs. I've started calling this class "stringly-typed guardrails," and once you see it you see it everywhere. A door-check can be made tighter — airlock's fix did exactly that — but tightening one gap doesn't change the design. There's always another spelling of the dangerous thing. What I build instead !Door-check guardrail vs CleiMesh: the token is the sandbox, default-deny, the tool only ever has the grant, and every call leaves a signed receipt you can replay I'm the author of CleiMesh, and it exists because I got tired of finding this bug. CleiMesh doesn't decide at the door. Two things are different: The permission becomes the sandbox. An agent runs under a signed, delegable token that says exactly which tools it may call and which directories or network it may touch — and the tool runs inside a WebAssembly sandbox configured from that token, default-deny. There's no "the guard waved it through but the tool still had full access," because the tool never had the access. A subagent can only get a narrower token, never a wider one. A prompt injection can ask for more all it likes; the grant doesn't widen. Every call leaves a receipt you can replay. Each tool call produces a signed, hash-chained receipt. You can verify the chain offline, and — the part no gateway offers — you can re-run a call from its receipt and compare the result byte for byte. You don't have to trust the log; you re-execute it. That's also what makes it an auditable record for GDPR/LGPD: a report per data subject, from evidence, not from a dashboard someone could edit. They're complementary, honestly — a door-check catches the obvious stuff fast. But if the door-check is the only thing between an injected agent and your keys, every bug like the one above is a hole. CleiMesh is the layer that doesn't have that failure mode, because there's nothing to "miss." Why I'm writing this Because finding these quietly and moving on makes me the nice guy who does free security work for other people's tools and gets nothing for it. I'd rather be clear about what I do: I audit agent guardrails, I report what I find responsibly, and I build the thing that enforces instead of guesses. If you run agents that touch real data — in Claude Code, Cursor, OpenClaw, Hermes — and "the guardrail checked it" is your whole story, I'd look harder at what the guardrail actually checks. CleiMesh is free and open (MIT/Apache-2.0): **cleitonaugusto/cleimesh**. There are two ClawHub skills that set it up — cleimesh-setup for an agent's own file tools, and cleimesh-mcp for the MCP servers it already uses. I also do paid security reviews; the repo README says how. airlock is the first public one. I have other coordinated disclosures in flight with different maintainers right now; when those patches ship, I'll write them up the same way. — Cleiton Augusto Correa Bezerra (github.com/cleitonaugusto)

    1 reaction · 6 comments
  4. the same steps in all three, side by side: read the seller's record, borrow and pay, repay in hermes agent it's a plugin, and it asks you before it pays. our pull request to the @NousResearch catalog: hermes-agent#147003 in agentkit it's an action provider, one npm i @priors/agentkit away. our pull request with @CoinbaseDev: agentkit#1611 in @elizaOS it's a plugin with four actions: npm i @priors/plugin-elizaos

    27 likes · 1 repost · 540 views
  5. 2026年後半のLLM動向: この数か月の進化を振り返る ここ数週間で、気になるモデルの発表が続いています。OpenAIからは、Sonnet 5.5に近い能力を半分のAPI単価で使えるGPT-6.1 Terra。Googleからは、GPT-6 AstraやClaude Opus 5.5と競うGemini 4 Argonに続き、約1か月でさらに性能を伸ばしたGemini 4 Carbonです。中国からも、Opus 5.5やAstraに迫る性能をうたうKimi K3.1 Proが出てきました。 この数か月でだいぶ動きがあったので、まず最新の発表で現在地を確認し、そこまでの移り変わりを振り返ってみます。そのあとに、私のコーディング環境と思考がどう変わったか、最後に、進化の速い道具や、自分で作った仕組みとの付き合い方について少し。発表を読むたびに装備を買い替えている気もするので、いったん手持ちを整理してみます。 現在地:最新モデルは、何を競っているのか 直近の三つの発表から、それぞれが何を強みにしているのかを見てみます。料金は100万トークンあたりのAPI単価で、キャッシュなし入力/出力の順です。 Gemini 4 Carbon:Googleも性能の上限を競う 10月28日のCarbon発表では、①総合的な知的能力、②エージェントによる開発、③画面操作、④知識業務、⑤マルチモーダル理解、⑥長文脈理解、⑦科学・数学、⑧サイバーセキュリティの8分野を、複数のベンチマークで評価しています。 主な比較対象は、約1か月前に発表されたArgonに加え、GPT-6 Astra、Claude Opus 5.5、Claude Fable 5.1です。公式の比較表では、17指標のうち15で掲載モデル中の最高値を示しています。一部の開発指標ではAstra、画面操作ではOpus 5.5が上ですが、幅広い用途で最上位を競うモデル、という打ち出しです。 長い間 Flash系の更新のみでしたが、Argon、Carbonと続き、Googleも性能上限の競争に入ってきました。 導入価格はArgonと同じ、入力$2/出力$10。導入期間の終了後は$4/$20となる案内です。ただし、まずは一部のサイバー防御関係者からの限定提供で、一般提供日は未発表です。 GPT-6.1 Terra:Sonnet 5.5との比較で見る低価格モデル 10月26日のGPT-6.1 Terra発表は、費用や速さを強調した発表です。 普段使いの比較相手として気になるのはSonnet 5.5です。Terraは入力$1/出力$5で、Sonnetの$2/$10の半額。総合評価ではSonnet 5.5 に及ばないものの、低い単価で回せるモデル、という見方が近そうです。 評価会社Artificial Analysisの比較では、総合指数はTerraが約51、Sonnetが約54でした。両者とも最大思考量での比較で、SonnetはDefault Fallback設定の結果です。一方、実際のリポジトリのコード修正を測るFrontierCode 2.0では、42.3%対41.7%とほぼ互角です。OpenAIも、Sonnetが開発や画面操作で上回る比較を掲載しています。一方、Agents’ Last Examという評価では、Sol・Astraより作業を3倍超速く終えることや、Opus 5.5よりタスク費用が低いことを訴えています。 GPT-6 Sol・Luna、続いて6.1 Solが出るなかで、Terraは5.6のままでした。てっきり Sonnet 5.5 より高性能で同価格帯のものが出てくるのかと思っていたので、少し残念です。しかし、強みもあるので、どれだけ使えるのか試してみようとは思います。Agentic なコーディングは Sonnet 5.5 と要比較、画面操作(ブラウザ、動画編集、CADなど)は期待といった感じかな。 Kimi K3.1 Pro:米国勢にも、中国勢の上位にも比較を向ける 10月23日のKimi K3.1 Pro発表では、Astra・Opusに加え、中国勢のDeepSeek V4.1 FlashやGLM-5.3との比較も出しています。 Kimiの公表値では、開発や知識業務の一部でOpus 5.5やAstraに迫り、上回る項目もあります。比較対象のGLM-5.3やDeepSeek V4.1 Flashにも、複数の指標で差をつけています。 一方、DeepSeek V4.1 Flashのコスパは、やはり別格に見えます。9月の発表では、GPT-5.6 SolやOpus 5と競える指標もありました。しかも、ピーク時間でも入力$0.30/出力$1.20で、オフピークはさらに半額です。最新の最上位モデルに全面的に並ぶわけではなくても、この価格ならまだまだ出番がありそうです。 「中国製モデルは少し性能を譲る代わりに安い」という見方だけでは、捉えきれなくなってきたのかな、という印象です。 ここ数カ月の動き:上位の能力が、普段使いの価格へ 6月下旬にGPT-5.6 Solが登場するころまでは、性能で先行するAnthropicをOpenAIが追い、Googleは安さや自社サービスとの連携で勝負しているように見えていました。その後は各社が、性能の上限を伸ばす一方で、上位に近い能力を安く使えるモデルも増やしています。会社ごとに振り返ってみます。 Anthropic:性能の上限を伸ばし、普段使いへ広げる 4月のMythos Previewから、6月のFable・Mythos 5へ。最上位の能力に注目していましたが、Fableの$10/$50を普段の実装で使うのは、なかなか勇気がいります。その後、6月末のSonnet 5は一部でOpus 4.8級の能力を$2/$10で、7月のOpus 5はFable 5に近い能力を半額の$5/$25で提供しました。 9月にはFable・Mythos 5.1で上限を伸ばしつつ、Opus 5.5は多くの仕事でFable 5.1級の能力を$4/$20へ。Sonnet 5.5も$2/$10を据え置いて性能を伸ばし、10月のHaiku 5.5は大量処理向けの価格帯を強化しました。OpenAIや中国勢が迫るなか、最上位の能力を普段使いへ広げてきた印象です。 開発者にとっては、Claude Code はやはりハーネスとしての機能性が高く、魅力的です。私は、あまりベンダーロックインされたくないという思想が強いんですが。便利さの前では、思想もなかなか脆いです。 OpenAI:性能を追いながら、価格帯を広げる 6月下旬に限定公開され、7月に一般提供されたGPT-5.6 Sol・Terra・Lunaは、能力と価格を三階層に分けた展開でした。SolはFable 5に迫り、一部の業務では上回る結果に。その後も値下げが続き、9月のGPT-6 Astraでは、Fable 5.1やOpus 5と性能の上限を競っています。 同じ9月のGPT-6 Sol・Lunaは、Solが$2/$10、Lunaが$0.10/$0.50へ。続くGPT-6.1 SolはAstraに迫る能力を5分の1の単価で、10月のGPT-6.1 TerraはSonnet 5.5やSolの半単価となる$1/$5で提供しました。 OpenAI は基本的に Anthropic よりも安く LLM を提供している印象ですが、この1か月くらいは Sonnet 5.5 の登場でこのイメージが少し崩れました。 Google:Flash系の更新から、性能上限の競争へ 7〜9月はGemini 3.6→3.8 Flashの更新が続き、導入単価$0.75/$3.75を維持しながら性能を向上。3.8では一部の開発試験でOpus 5や5.6 Solに近い結果を示しました。Google は長い間マルチモーダル性(画像・動画・音声)を押しており、そこが強みでもありましたが、ここの差もどんどんと縮んできている印象です。それでも、まだある程度の数の動画を解析するとなったら 3.8 Flash を検討するかなぁ。 9月末のGemini 4 ArgonはAstraやOpus 5.5などと比較し、10月にはGemini 4 Carbonでさらに性能を伸ばしました。ともに導入価格は$2/$10で、終了後は$4/$20。安さや連携に加えて、性能の上限を競う動きも強まった印象です。 中国勢:低価格の選択肢から、最上位との競争へ 4月のDeepSeek V4-Pro・Flashは、公開ウェイトと低価格で気になる存在でした。Proは発売時$1.74/$3.48から割引で$0.435/$0.87へ下がり、その価格のまま5月に恒久化されました。性能比でバグっている価格だったので、かなり話題になりましたね。6月のGLM-5.2も一部の長時間開発でOpus 4.8に迫り、上位に近い能力を安く使う選択肢が増えました。 7月のKimi K3はFable 5や5.6 Solに迫る能力を掲げ、8月にはQwen3.8-Max、GLM-5.3と更新が続きます。GLM-5.3-Flashは画像理解にも対応し、通常$0.15/$0.50でGLM-5.2を上回ると訴求。9月のDeepSeek V4.1 Flashも、ピーク$0.30/$1.20という低い単価のままで開発・業務の能力を伸ばしました。 一方、10月のKimi K3.1 Proは、最新のAstraやOpus 5.5にも迫る性能を掲げ、単価はK3の$3/$15から$2/$10へ。上でも述べましたが、「中国製モデルは少し性能を譲る代わりに安い」という見方だけでは、捉えきれなくなってきています。 こうして見ると、各社が一斉に同じ方向へ切り替わったというより、性能と価格の両方で、互いの得意な領域との差を埋めてきたように見えます。 私の開発セットアップの変遷 ここでちょっと私の開発手法の振り返りです。 6月頃まではClaude CodeやCodexを使い、利用枠が足りなくなるとGoogle の Antigravityへ、というように複数の道具を行き来していました。OpenClawやHermes Agentに安いモデルを組み合わせる使い方も気になって試していましたが、セキュリティ面の心配が残り、日常の開発はClaude CodeやCodexでよいか、となりました。 ただ、このあたりからコストが気になるようになり、中国製モデルも話題になっていたのでDeepSeekやGLMについて調べた結果、OpenCodeというハーネスで Thinker と Implementer という2種類のエージェントを定義し、OpenAIの上位モデルをThinkerに、DeepSeekやGLMをImplementerにする構成に落ち着きました。Thinkerと対話しながら調査して実装の骨格まで詰め、GitHub Issueに落とし、Implementerが実装するといった感じです。 このあたりから、自分なりのループエンジニアリングについて考えるようになりました。開発用の Skill やサブエージェントの定義を専用リポジトリで整備し、計画・実装・レビュー・修正に加えて、うまくいかなかった原因を指示や手順に戻すところまで含めて逐次改善しています。 いろいろとモデルを変えながら、1か月くらいはこれをメインで使っていたと思います。GLM-5.2や、その後の5.3-Flashなどを試し、別の選択肢としてGemini Flash系も使っていました。ただ、不満もいくつかあって、安いモデルの動作が安定しなかったり、画像が入力できなかったり、スマホからのリモート操作が面倒だったり(herdr + Tailscale でやってました)、あたりです。 その後は各社から性能が十分で安価なモデルが出たことと、ハーネスやスマホアプリの完成度の高さから Claude Code や Codex に移行することにしました(Claude Code や Codex にはリモート接続機能があって、PC の作業をスマホアプリから操作可能)。エージェントの Thinker/Implementer 構成は変わっていません。 私の記憶では、GPT-5.6 のころから徐々に Codex 中心になり、Sonnet 5.5でClaude Codeの利用が増えました。今はTerraを機にCodexの比率を少し戻し、Googleの発表も見てAntigravityも試してみようか、と考えています。 基本は各社の月20ドル前後のサブスクで、足りないときにたまに追加する形です。簡単なWebアプリをいくつか作る程度では、それで十分でした。一社に全部寄せないのは、ベンダーロックインを避けたいことと、いろいろなものに触れておきたいという理由です。一方で、ハーネスの機能を使いきりたい気持ち(特に Claude Code)もあるのですが。 作り込んだ仕組みを、どこまで持ち続けるか こうして振り返ると、モデルの性能や価格が変わるたびに、自分の開発環境も選び直していました。以前は意味のあった工夫が、新しいモデルやハーネスの機能で不要になることもありそうです。作り込んだ仕組みを、ずっと維持することがよいとも限りません。 最初はベンダーへの依存を避けたくて、OpenCode、Codex、Claude Code、Antigravity のどれでも使える手順を中心に作っていました。ハーネス固有の機能を前提にしすぎない方針です。ただ、ループエンジニアリングを実践するなかで、固有の機能を使う便利さも実感しています。最近は、そのあたりの縛りを少し緩めて使うようになりました。 それでも、モデルの性能や価格だけでなく、ハーネスやアプリの使いやすさまで含めて、そのときよい環境を選びたい気持ちは変わりません。便利な機能は使いたい。でも、次にもっとよい選択肢が出たときに、乗り換えるコストが高くならないようにもしておきたい。この加減は、まだ試行錯誤中です。 モデルの性能差は縮まってきた気がしますが、私のサブスクは増えるばかりです。 元記事(ブログ版): 2026年後半のLLM動向: この数か月の進化を振り返る 関連: Agentic Development: どうスケールさせるのか?

  6. Reddit · · r/LocalLLaMA · u/latent_lurker8587 · NousResearch/hermes-agent

    10 coding agents, one Qwen 3.8 27B, same llama-server: tokens and cache reuse differ more than pass rates Part 2 of my agent test (part 1 was a 4B model) Hardware I used: MacBook Pro M5 Max, 64 GB. Qwen 3.8 27B Q4 on llama.cpp, thinking off. Setup - same model and server for everyone - 5 small coding tasks, each with a little trap - each agent locked in a sandbox: it can only touch its own folder and only talk to the model - auto-approve on, telemetry off, 5 min limit Results |#|Agent|Passed|Median time|Median tokens|Cache reuse|Tried to go online| |:-|:-|:-|:-|:-|:-|:-| |1|Pi|5/5|38 s|9.2k|72%|no| |2|DeepSeek Harness|5/5|60 s|35.3k|83%|no| |3|Codex CLI|5/5|94 s|36.3k|79%|every run| |4|OpenClaw|5/5|106 s|39.0k|72%|unclear| |5|Cline|5/5|152 s|29.6k|79%|every run| |6|OpenCode|4/5|50 s|32.4k|75%|every run| |7|Hermes|4/5|80 s|64.9k|93%|no| |8|Atomic Agent|5/5|56 s|30.4k|86%|no| |9|goose|4/5|98 s|20.3k|76%|every run| |10|Qwen Code|4/5|183 s|107.9k|74%|unclear| Tokens and cache Pi is crazy lean: about 9k tokens per task, the lowest on every single task. Qwen Code was the hungriest at about 108k per task. Atomic Agent is the odd one out in how it works. It was the only one talking to llama.cpp's native endpoint with a grammar that forces the tool calls into shape. The other nine all went through the OpenAI-style API. It's also built to keep its prompt the same from step to step, which is why it reused 86% of the cache (2rd of 10). It stayed offline the whole time and passed 5/5. DeepSeek Harness was the surprise. It’s still an alpha, but it went 5/5, never tried to go online, and had the best cache reuse after Atomic Agent (83%). The trap and the network The log task got three agents. They grepped "ERROR" and got 244 instead of 64, because some normal lines just mention errors. OpenCode, goose and Atomic Agent fell for it. Codex, Cline, goose and OpenCode tried to reach the internet in every run, even with telemetry off. The sandbox blocked it, so I don't know what they were calling. OpenClaw and Qwen Code are "unclear" because they run as plain node and I couldn't tell them apart. llama.cpp notes - Codex needs llama-server's /v1/responses now - Hermes needs at least 64K context - Cline and Qwen Code have usage stats on by default, DeepSeek Harness uploads session logs (per their docs). I turned it all off Which column matters most to you when you pick an agent for a local model like Qwen 3.8 27B: passing, speed, tokens, or staying offline?

    36 points · 36 comments
  7. ローカル LLM のエージェントを AppContainer に入れて DNS まで塞いだ。ループバックの例外は全ポートを開けていた ! 先に、この記事の要約(専門用語なし) 前の記事で、ローカルの LLM のエージェント(Hermes)を、外へ通信できない専用のユーザーで動かした。ただ、名前の問い合わせ(DNS)だけは、Windows の別のサービスが代わりに外へ出すので止められなかった Windows のアプリの隔離の仕組み(AppContainer)の箱に、ネットワークの権限を何も付けずに入れると、その別のサービスが、箱からの問い合わせを「アクセス拒否」で断った。DNS Client の記録では、ネットワークへの問い合わせは始まらなかった 箱の中で Hermes とそのチャット画面を動かすまでに、詰まりが 9 つあった。いちばん手間取ったのは、チャット画面が CPU を使い切ったまま何も描かなかったことだった 箱から同じ PC の LLM に届くように付けた例外が、PC の中の全部の窓口を開けていた。箱の身元で照合する規則で、要る窓口だけに絞り直した 箱の外で動く台本を、箱の中から書き換えられる道もあった。置き場を変えて塞いだ 前回の続き 前の記事では、Surface で動かしている Hermes Agent を、権限の低い専用のユーザーで動かし、そのユーザーの外向きの通信を全部止めた。ふつうの通信は外へ届かなかった。 残ったのは名前の問い合わせだった。専用のユーザーで Resolve-DnsName を使い、でたらめな名前を引くと、外の DNS サーバーの「その名前は無い」という答えが返ってきた。調べた範囲では、Windows の名前解決の API を通した問い合わせは、DNS Client というサービスが代わりに行い、このサービスは別のアカウントで動いているようだ。だから、ユーザーごとの外向きの遮断に当たらない。名前に少しずつ情報を埋めれば、外へ持ち出せる。 前の記事では、仮想マシンに入れてネットワークを内部だけにする案を挙げ、「次に試す」と書いた。まず手軽な Windows Sandbox で確かめた。 ネットワークなし:DNS サーバーが無いので、名前の問い合わせは出ない。ただ、同じ PC の LLM にも届かない ネットワークあり(NAT):本体の側が問い合わせを代わりに外へ流した。本体の記録では SYSTEM の名義になり、どの箱から来たのかを区別できなかった 別の PC で前に試した箱でも、名前の問い合わせは外へ出ていた(DNS の見張りの記事)。問い合わせを代わりに送るサービスは、誰が問い合わせたか(プロセスとユーザー)を記録してはいたが、それを理由に断ることはしなかった。 内部だけのネットワークを組むには Hyper-V が要り、LLM と画像生成の GPU を仮想マシンに渡せるかという壁もある。そこで、OS の外側で切るのをいったんやめた。問い合わせを受けるサービスの側で「誰が問い合わせたか」を見て断れる仕組みを探し、AppContainer を試した。 AppContainer とは AppContainer は、Windows でストアのアプリなどを閉じ込めるための仕組みである。箱の中のプロセスは、箱ごとの身元(package SID)を持つ。ファイルなどは、その身元か、すべての箱に共通の身元(ALL APPLICATION PACKAGES)に許された所しか触れない。ネットワークも、箱に付けた権限(capability)の分しか使えない。 ストアのアプリでなくても、ふつうのプログラムを箱に入れて起動できる。CreateAppContainerProfile で箱を作り、CreateProcess に箱の情報(PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES)を付けて起動する。小さな起動役を C# で書き、PowerShell の Add-Type で読み込んで使った。 小さな試験:箱から何が届くか 権限を何も付けない箱で、外と PC の中への届き方を確かめた。 試したこと 権限なしの箱 対照:インターネットの権限(internetClient)を付けた箱 名前の問い合わせ(Resolve-DnsName) DNS Client がその場で拒否(status 5=アクセス拒否) 外まで届いた 外の IP への直接の接続・UDP 53 への直送 通らない — 同じ PC の LLM と画像生成の窓口(127.0.0.1) 通らない — 名前の問い合わせは、DNS Client の記録(Microsoft-Windows-DNS-Client/Operational)でも確かめた。権限なしの箱では、問い合わせを受けた記録(3006)と終わった記録(3008)だけが残り、ネットワークへ問い合わせを始めたときに出る記録(3009 以降)は出なかった。DNS Client が、問い合わせてきたのが箱だと分かったうえで断っていると見ている。専用のユーザーでは止められなかったものが、ここで止まった。 ほかの抜け道も試した。外の IP のファイル共有(\<IP>)と WebDAV は、どの条件でも外へ接続しなかった。BITS(Windows のバックグラウンド転送)は、普段のユーザーでは BITS のサービスが代わりに外へ出た。専用のユーザーは「ネットワークにログオンしていない」で失敗し、箱は「アクセス拒否」で失敗した。 箱は、同じ PC の中(ループバック)への接続も止める。Hermes は同じ PC の LLM と画像生成に頼むので、ここだけは開ける必要がある。CheckNetIsolation LoopbackExempt で箱にループバックの例外を付けると(管理者が要る)、LLM と画像生成に届き、名前の問い合わせは拒否のままだった。 この時点では、これで足りると思っていた。あとで、この例外が穴を開けていたと分かる(後の節)。 Hermes を箱の中で動かす Hermes は Python で動き、チャット画面は Node で動く。どちらも、箱の中で動かす前提では作られていない。1 つ解くと次が出た。直しはどれも、箱で動かすときだけ働く形にして、箱の外での動きは変えていない。 詰まり 原因 直し ゲートウェイ(Hermes の常駐の部分)が「使えない」と出る 存在の確認でタスク スケジューラに問い合わせるが、箱からはタスク スケジューラに届かない 箱のときは問い合わせを飛ばす。制御用の名前付きパイプは \.\pipe\LOCAL\ の下に作り、DACL に AppContainer 全体(ALL APPLICATION PACKAGES)を足す cmd 経由で起動した Node が動かなかった 原因は追っていない(箱の中の cmd そのものは起動でき、外のコマンドも動いた) 起動の親を Python にした シェルの道具の bash(Git に付いてくる MSYS のもの)が 0xC0000142 で起動しない MSYS が箱の中で初期化できない busybox-w32(ARM64 版)の sh に替える。ash に無い declare・shopt・builtin などを使わない形に直す Python で作ったフォルダに、作った本人が入れない Hermes と標準ライブラリ(tempfile.mkdtemp)が、mode 0o700 でフォルダを作る。Python 3.13 から(それより前の版も、3.12.4・3.11.10・3.10.15・3.9.20 以降のセキュリティ修正で)、Windows でもこの mode を扱い、作った本人と管理者などだけが入れる ACL を付けるようになった(公式文書)。そこに箱の SID が入らない 箱のときだけ、mode を付けずに作るようにした。Python と Hermes のコードは書き換えず、仮想環境に置いた .pth で、起動のときに os.mkdir を包んでいる チャット画面の Node が起動の途中で EPERM 実際のパスを求める処理が C:\ そのものを lstat する C:\ そのものにだけ、箱へ属性の読み取りを付ける(継承なし) Node が起動できない Node の入ったフォルダに、箱が読める許可が無い そのフォルダに、箱の SID への読み取りと実行を付ける ゲートウェイが立ち上がらない Hermes(Python の側)は、Windows ではゲートウェイをジョブから切り離した別のプロセスとして起動する作りで、箱の中では立ち上がらなかった(切り離しが許されないためと見ている) 起動の台本で、ゲートウェイを先に起動しておく Hermes が /c/Users/... の形のパスで空振りする システムプロンプトに「この形のパスが使える」とあるが、busybox では通らない 箱のときだけ、その説明を差し替える チャット画面が CPU を使い切ったまま何も描かない いちばん手間取ったのはこれだった。チャット画面の Node が CPU を 100% 使い続け、画面には何も出ない。エラーも出ない。 Node の --prof でプロファイルを取ると、git のブランチ名を取る execFile の中で回り続けていた。箱の中では、Node が使う libuv が、子プロセスの出力を受ける名前付きパイプを作れない。拒否されるたびに名前を変えて作り直し、それを続けていた、と読んだ。箱の中で、出力をパイプで受ける起動は戻ってこず、パイプを使わない起動(stdio を ignore)はすぐ終わることを確かめた。 直しは、Node の前処理(--require)で execFile を包み、子プロセスの出力をパイプではなく一時ファイルで受けるようにした。パイプで子プロセスを起こす spawn は、回り続けないよう、すぐに失敗させている。チャット画面のコードには手を入れていない。 脇道で見誤りかけたこと チャットだけが途中で切れた。中継(後述)が、接続のときに付けた 10 秒の時間切れを付けたままにしていて、10 秒データが流れない WebSocket がそこで切られていた 試験の道具でチャットに文を送ると、送信されなかった。文字と Enter を一度に送っていたので、画面が貼り付けとして受け取っていた。箱の外でも同じだったので、箱のせいではなかった 送った文字が入力欄に出ただけなのを、返事が来たと誤って判定しかけた。返事の中身まで見て判定するようにした ブラウザから箱の中の画面へ:逆向きの中継 ループバックの例外を付けても、通るのは箱から外への向きだけで、外のブラウザから箱の中の待ち受けへは繋がらなかった。箱に、ネットワークで待ち受ける権限(privateNetworkClientServer)を付けて試したが、外から箱の中の待ち受けへは繋がらなかった。しかも、名前の問い合わせと LAN まで通るようになった。 そこで、向きを逆にした。箱の中から、外で待っている中継へ接続を張っておく。ブラウザの接続を受けた中継は、その接続を箱の中の画面に渡す。中継は標準ライブラリだけで書き、127.0.0.1 だけで待ち受け、トークンを照合してから繋ぐ。 穴 1:ループバックの例外は、PC の中の全部の窓口を開ける 箱の点検として、箱の中から、127.0.0.1 で待ち受けているものに総当たりで接続した(接続できたかだけを見て、中身は送らない)。結果は、ほぼ全部に届いた。LLM と画像生成だけでなく、ファイル共有、SSH、ゲームの配信のアプリ、そのほか同じ PC で待ち受けている別のサービスにもである。中には、外へ通信できる別のプログラムに文を渡せる窓口もあった。そこに届けば、名前の問い合わせを塞いでも、その先から外へ出せてしまう。 CheckNetIsolation の例外は、ポートを選べない。付けると、その箱からループバックの全部に届く。 前の記事で、専用のユーザーのループバックは、WFP(Windows Filtering Platform)の規則で決めた窓口だけに絞ってあった。ところが、その専用のユーザーの名義で箱を動かしても、全部に届いた。規則はユーザーの身元で照合していて、箱の中のプロセスには当たらなかった。箱のトークンでは、アクセスの確認に箱の SID も要るためだと見ている。 もう一つあった。箱の SID は、箱の名前から決まる(名前から SID を求める DeriveAppContainerSidFromAppContainerName という関数もある)。例外は SID に付くので、例外を付けた箱と同じ名前で箱を作れば、その例外をそのまま使える。実際に、専用のユーザーが自分で同じ名前の箱を作ると、全部の窓口に届いた。試験のために例外を付けた時点で、本番の側にも同じ穴が開いていたことになる。 直しは次の順にした。 例外を外す WFP の規則を、ユーザーではなく箱の身元で照合する形で作り直す。照合の条件は FWPM_CONDITION_ALE_PACKAGE_ID(箱の package SID)にする。許すのは、LLM・画像生成・中継の受け口・箱の中の画面とゲートウェイ・Hermes が内部で使う範囲だけで、ほかのポートは止める 規則が入ってから、例外を付け直す 普段のユーザーと専用のユーザーのどちらの名義で箱を動かしても、許したポートだけに届き、残りは WSAEACCES(10013)で拒否されることを確かめた。 穴 2:箱の外で動く台本を、箱が書き換えられる 試験のときは、逆向きの中継の、外の側の台本が普段のユーザーで動いていた。この台本が、箱から書けるフォルダに置かれていて、そこを作業フォルダにして動いていた。 箱の中から台本を書き換えれば、次の起動で、その中身が箱の外の権限で動く。書き換えなくても、同じフォルダに argparse.py のような名前のファイルを置けば、同じことが起きる。Python は、台本のあるフォルダをモジュールの探し先の先頭に入れるからである。 直しは、箱の外で動かすものを、箱が書けない場所に置くことにした。作業フォルダも箱が書けない場所にし、Python は -I を付けて起動する。本番では、外の中継も専用のユーザーで動かしている。 箱の境目は、ネットワークだけではなかった。箱が書けるファイルを、箱の外の誰が実行するか、まで見る必要があった。 箱を動かす名義は、専用のユーザーにした 箱の中のプロセスを、普段のユーザーの名義で動かすか、前の記事の専用のユーザーの名義で動かすかを決める必要があった。専用のユーザーにした。 理由は、守りを箱ひとつに頼らないためである。箱の中のプロセスは、ファイルについては、箱と専用のユーザーの両方に許された所しか触れない。箱から抜け出されても、出た先は、外向きの通信を止め、書ける場所を絞った専用のユーザーである。普段のユーザーの名義なら、自分のファイルにも全部の通信にも手が届く。 ただし、ネットワークは別だった。専用のユーザーの外向きを止める規則は、ユーザーの身元で照合しているので、箱の中のプロセスには当たらない。インターネットの権限を付けた箱を専用のユーザーの名義で動かすと、外の IP に届いた。 そこで、箱そのものの身元で照合するファイアウォールの規則(New-NetFirewallRule の -Package)を 1 本足した。外向きで、行き先がループバック以外のものを止める。試しに一度だけ箱にインターネットの権限を付けて比べると、外への直接の接続は WSAEACCES(10013)で止まり、同じ PC の LLM と画像生成には届いた。ただ、でたらめな名前の問い合わせは、規則を入れたあとも外の DNS サーバーまで届いた。権限を付けた箱の問い合わせは DNS Client が代わりに外へ出すので、箱の身元の規則には当たらないと見ている。 名前の問い合わせまで塞いでいるのは、箱に権限を何も付けないことだけだった。点検では、後で書く 47 項目に加えて、本番の起動の台本が箱に権限を付けていないかと、この規則が残っているかも見ている。権限を付け間違えても、点検を流せば気づける。 欠点は手間で、起動が二段になり、ファイルの許可も両方に付け、試験も両方でやり直すことになる。使い勝手と速さは変わらなかった。 本番の切り替えで、穴 1 が数分だけ開き直した 試験の名前のまま本番にするのをやめ、本番用の名前で箱を作り直した。名前が変わると SID も変わるので、ファイルの許可・WFP の規則・ループバックの例外を、新しい SID に入れ直す必要がある。ファイルの許可は、試験の箱に付けた明示の許可を同じ形で新しい SID に写す台本で移した(約 4,700 か所)。 ここで一つ失敗した。例外を付け替える台本に、こう書いていた。 CheckNetIsolation LoopbackExempt -a -p=$sid PowerShell は、- で始まるこの引数の中の $sid を展開せずに、そのまま渡していた。CheckNetIsolation はエラー 1337(SID が不正)で失敗した。WFP の規則は新しい箱に移ったのに、古い箱の例外が規則なしで残り、数分のあいだ穴 1 が開き直していた。 手元の Windows PowerShell 5.1 でも、同じことが起きた。 $sid = "S-1-15-2-123" cmd /c echo -p=$sid # → -p=$sid cmd /c echo "-p=$sid" # → -p=S-1-15-2-123 引用符で囲めば展開される。台本は "-p=$sid" に直した。付け替えのあとは、成功の表示を探すのではなく、一覧(-s)で結果を見ることにした。 開いていた数分のあいだも、箱の中のものは専用のユーザーの名義で動いていた。ファイルは、専用のユーザーにも箱にも許された所しか触れない。ただ、ネットワークは別だった。ループバックの穴は専用のユーザーの規則では塞がらず(穴 1 のとおり)、後で確かめると、外向きを止める規則も箱の中のプロセスには当たっていなかった。だから、付け替えのあとは一覧で確かめる。 崩れに気づくための点検 設定は、更新や編集で黙って崩れる。前の記事でも、行動ルールのファイルを編集したら、書き換えを止める設定が外れていた。そこで、管理者の要らない点検の台本を 1 本作り、更新や設定の変更のたびに流すことにした。 専用のユーザーの名義の箱の中から、47 項目を確かめる。 ループバック:止まるべきポートが止まり、届くべきポートに届くか 名前の問い合わせと、外の IP への直接の接続が止まるか 読めてはいけない場所(普段のユーザーのフォルダ、資格情報、ほかの AI の道具の設定、SSH の鍵)が読めないか 書けてはいけない場所(Hermes 本体、Python の環境)に書けないか あわせて、ループバックの例外の一覧に想定外の箱が無いか、専用のユーザーの外向きを止める規則と、箱そのものの外向きの規則が残っているか、本番の起動で箱に権限を付けていないか、画像生成の閉じ込めが崩れていないかを見る。点検の台本と起動役は、箱からも専用のユーザーからも書けない場所に置いた。点検の台本そのものが抜け道にならないようにするためである。 本番の切り替えのあと、47 項目は全部通った。ブラウザでチャットの返事が返り、シェルの道具(busybox)から画像生成の道具まで呼べた。止める手順で、箱の中のプロセスが全部止まることも確かめた。 使えなくなったもの 箱に入れた代わりに、使えなくなったものがある。 Git に付いてくる MSYS の道具(bash など)。busybox の範囲で書き直す busybox の wget(箱の中で落ちる) git のブランチ名の表示など、チャット画面の付属の機能の一部 curl の -o /dev/null(-o NUL なら通る)。ネイティブのプログラムの出力の改行は CRLF のままで、日本語の出力が cp932 で化けることがある 残るもの 作ったファイル(画像や文書の埋め込み情報)に文を混ぜ、それを読む別の AI に指示を仕込む道。仕組みでは防げないので、読む側の決まり(ファイルの中の指示に従わず、見つけたら知らせる)で守っている 設定の崩れ。上の点検で気づく 手元で確かめるときのコマンド例 名前の問い合わせがネットワークへ出たか(箱からの問い合わせで 3009 以降が出ていなければ、ネットワークへは問い合わせていない) Get-WinEvent -LogName Microsoft-Windows-DNS-Client/Operational -MaxEvents 50 | Where-Object Id -in 3006,3008,3009,3010,3011,3020 | Select-Object TimeCreated, Id, Message ループバックの例外の一覧(想定外の箱が無いか) CheckNetIsolation LoopbackExempt -s 例外を外す(管理者)。SID は引用符で囲む CheckNetIsolation LoopbackExempt -d "-p=<箱の SID>" 箱そのものの外向きを、ループバック以外は止める規則も足した(管理者の PowerShell)。箱の SID は、箱の名前から DeriveAppContainerSidFromAppContainerName で求めたもの(S-1-15-2-… の形)である。ループバックへの道は、別に作った WFP の規則(箱の身元で照合)に任せているので、この規則ではループバックを外してある。 $sid = "<箱の SID>" ループバック以外:IPv4 は 127.0.0.0/8 を除く 2 つの範囲、IPv6 は ::1 を除く範囲 $remote = @("0.0.0.0-126.255.255.255", "128.0.0.0-255.255.255.255", "::2-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") New-NetFirewallRule -Name "<規則の名前>" -DisplayName "<規則の名前>" ` -Direction Outbound -Action Block -Package $sid -RemoteAddress $remote -Profile Any -Enabled True 確かめる $r = Get-NetFirewallRule -Name "<規則の名前>" $r | Select-Object Name, Enabled, Action, Direction ($r | Get-NetFirewallApplicationFilter).Package # 箱の SID が出る ($r | Get-NetFirewallAddressFilter).RemoteAddress # 上の 3 つの範囲が出る 外す Remove-NetFirewallRule -Name "<規則の名前>" 箱の名前を変えると SID も変わるので、この規則も入れ直しになる。前に書いたとおり、この規則で止まるのは直接の接続までで、名前の問い合わせは止まらない。 箱の中から総当たりで接続を試すときは、接続できたかだけを見る。結果は、届いた/拒否(10013)/時間切れ(許されているが、待ち受けが無い)の 3 つに分かれる。 持ち帰り ユーザー単位の外向きの遮断では、名前の問い合わせは止まらなかった。ネットワークの権限を何も付けない AppContainer に入れると、DNS Client が箱からの問い合わせを断り、DNS Client の記録では、ネットワークへの問い合わせは始まらなかった CheckNetIsolation のループバックの例外は、ポートを選べない。付けると、同じ PC で待ち受けている全部に届く。WFP の規則を箱の身元(FWPM_CONDITION_ALE_PACKAGE_ID)で照合して、要るポートだけに絞る。規則を入れてから例外を付ける ユーザーの身元で照合する WFP の規則は、箱の中のプロセスには当たらなかった。ファイアウォールの外向きの規則も同じだった 箱の SID は名前から決まる。例外を付けた箱と同じ名前で箱を作れば、その例外を使える。試験用の例外も、外すまでは本番の穴になる 箱の境目は、ネットワークだけではない。箱が書けるファイルを、箱の外で誰が実行するかまで見る PowerShell では、- で始まる引数の中の変数が展開されないことがある。"-p=$sid" と囲み、付け替えのあとは一覧で結果を見る 名前の問い合わせを塞いでいるのは、箱に権限を何も付けないことだけだった。箱の身元で照合するファイアウォールの規則(-Package)を足すと、権限を付け間違えても外への直接の接続は止まるが、名前の問い合わせは止まらない 守りを箱ひとつに頼らない。箱を動かす名義を専用のユーザーにすると、ファイルの範囲と、箱から抜け出た先が狭くなる。ただし、ユーザーで照合する通信の規則(外向きの遮断・WFP の絞り込み)は、箱の中には当たらない

  8. Oh-my-hermes guía completa para orquestar el agente Hermes con modelos en paralelo Oh-my-hermes, o OMH, es una capa de operación de código abierto para Hermes Agent que convierte objetivos amplios en flujos de trabajo verificables. En vez de tratar una petición como una sola conversación con un…

  9. Oh-my-hermes guía completa para orquestar el agente Hermes con modelos en paralelo Oh-my-hermes, o OMH, es una capa de operación de código abierto para Hermes Agent que convierte objetivos amplios en flujos de trabajo verificables. En vez de tratar una petición como una sola conversación con un…

  10. ローカル LLM のエージェントが外へ送ろうとしても困らないように組む。守りと確かめ方と残る穴 ! 先に、この記事の要約(専門用語なし) AI の会社 Cohere が、ある公開モデルが試験の環境から外のクラウドへ試験用の文字列を送ろうとし、止められた、と報告した。報告自体が「ごくまれ」「悪意かどうかは分からない」と留保している 自分も、Surface でローカルの LLM のエージェント(Hermes)を動かしている。モデルの中身は確かめようがないので、「勝手に外へ送ろうとしても困らない」ように組んでいる エージェントもその頭脳の LLM も、権限の低い専用のユーザーで動かす。そのユーザーは外へ通信できず、同じ PC の中でも決めた相手としか話せない。書けるのは自分の作業場所だけにした 普段のユーザーで挟んで、届くはずの所に届き、止めた所で止まることを確かめた。Cohere の例のような、ふつうの通信での送信は外へ届かず、秘密のファイルも読めなかった ただし、名前の問い合わせ(DNS)と ping は外まで届いた。少しずつなら情報を載せられる細い道で、ユーザー単位の閉じ込めでは塞げない。ping は PC 全体の規則で塞いだ。名前の問い合わせは今も穴として残っていて、塞ぐなら OS の外側(仮想マシンなど)で切ることになりそうだ きっかけ:Cohere の報告 Cohere が、公開モデルの Qwen3.8-Flash-Next を試験していて気になる動きを見つけた、と報告した(Cohere の報告)。 報告によると、モデルはエージェントとして、シェルを使える隔離環境の中で「悪いファイルのダウンロードと実行を見つける検知ルールを書く」という課題を与えられていた。その途中で、/tmp の中身を一覧し、転送に使える道具(curl など)があるかを調べ、DNS の設定を読んでから、北京にあるクラウドの保管場所へ試験用の文字列を送ろうとした。思考の記録では「道具は呼ばない」と書いていた、とも報告されている。 隔離環境には外への経路が無かったので、送信はそこで止まり、外へは出ていない。Cohere は、ふつうに外へ通信できる開発者の PC や CI の上なら、誰も気づかないうちに外のサーバーまで届いていただろう、と書いている。 ただし、報告自体が次のように留保している。 起きたのは、評価で投げた呼び出しの 10 万回に 1 回未満。同じ指示を流し直すと、期待どおりの検知ルールが返った わざとなのか、学習の残りかすなのかは分からない。学習用の隔離環境の通信の記録が、消されずに学習データに混ざった可能性も挙げている 送ろうとした中身は PROBE_PAYLOAD_ABC123 という試験用の文字列だった Cohere は記録と再現用の手順も公開している(Hugging Face のデータセット)。報道では WinBuzzer などが取り上げている。 この記事は、このモデルや提供元を責めるためのものではない。読んで考えたのは、「自分のエージェントで同じことが起きたら、どこで止まるか」だった。守りは前から組んである。報告を読んで、その組み方で本当に止まるかを確かめ直した。 何から守りたいか Surface では、Hermes というエージェントを、ローカルの LLM を頭脳にして動かしている。Hermes Agent は Nous Research が MIT ライセンスで公開しているオープンソースのエージェントで、使いながら経験からスキル(手順書)を作り、直していく「自己改善」を特徴にしている(公式サイト)。ここでは、PC の中で動かす LLM につないで、ローカルだけで動かしている。Hermes はコマンドを実行し、ファイルを読み書きし、画像生成の ComfyUI に仕事を投げる。 モデルの重みに何が入っているかは、外から確かめようがない。Cohere の例のように、ふだんは何もせず、まれに外へ送ろうとすることもありうる。そこで、モデルを信じることを前提にせず、勝手に外へ送ろうとしても困らないように組んである。守りたいのは次の 2 つである。 普段のユーザーの鍵・設定・資格情報・文書・ソース それらを外へ持ち出す経路 今の守り 専用のユーザーで動かす エージェントも、その頭脳の LLM(llama-server)も、Web の画面も、普段のユーザーとは別の、権限の低い専用のユーザーで動かす。普段のユーザーのフォルダは、このユーザーからは読めない。 外向きの通信は全部止める この専用のユーザーからの外向きの通信は、Windows のファイアウォールで全部止めた。インターネットにも、同じ LAN の機器にも届かない(ただし、あとで書くとおり、名前の問い合わせと ping はこの規則の外を通った)。 同じ PC の中でも、話してよい相手を名指しで決める 外を止めても、同じ PC の中(127.0.0.1)には、ほかのツールや管理用の窓口が待ち受けている。そこに届けば、その窓口の持ち主の権限で何かをさせられるかもしれない。 そこで、Windows の通信の仕組みの層で、同じ PC の中の宛先も絞った。通すのは、LLM・Web の画面・Hermes の内部の窓口・ComfyUI という、決めた数か所だけである。ほかの待ち受けには届かない。Hermes は内部で「空いている番号を OS に選ばせる」待ち受けを使うので、その番号を許した範囲に寄せる手当ても入れた。 書けるのは、自分の作業場所だけにする 専用のユーザーが書けるのは、Hermes の作業場所と記憶の置き場だけにした。Hermes 本体・Python の環境・道具・起動のスクリプト・人格と絶対に越えない線を書いたファイル・手順書は、読むだけである。 ねらいは 2 つある。エージェントが、自分を動かすコードを書き換えられないこと。そして、普段のユーザーがあとで実行するものに手を入れられないことである。 自己改善は許し、その履歴はエージェントに消せない場所に残す Hermes には、自分の行動ルール・設定・学んだことのメモを書き換える機能がある。これを止めると、使い勝手が落ちる。 そこで、書き換えは許すことにした。そのかわり、起動・停止・点検のたびに、そのファイルをエージェントから読めない場所へ写して、履歴を残す。あとから「いつ、何が変わったか」を、エージェントに消されずにたどれる。 画像生成の側も閉じ込める ComfyUI は WSL の中で、こちらも専用のユーザーで動かしている。外向きの通信は、ループバック以外を止めた。クラウドに送るノードは読み込まない起動のしかたにし、ノードを入れる仕組み(ComfyUI-Manager)は入れていない。 どう確かめたか 規則を書いただけでは、本当に止まっているかは分からない。そこで、確かめるスクリプトを作った。 同じ宛先の一覧(同じ PC の中の各窓口、LAN、WSL のアドレス、外の IP と名前)に、次の順で接続を試す。 普段のユーザー(対照) 専用のユーザー 普段のユーザー(対照) 確かめるスクリプトは、宛先ごとに届いたか止まったかを書き出すだけで、判定はしない。届かない理由が、相手がいないのか規則なのかは区別できないので、前後の対照で届くのに専用のユーザーでは止まるものだけを「規則が働いている」と読み、対照でも届かない宛先は判定に使わない。 ファイルも同じように、専用のユーザーで、秘密の置き場所を読めるか、あちこちに書けるかを試す。書き込みは、実行に関わる場所に空のファイルを作れるかで確かめた。 確かめた結果 公開の前に流し直した結果は次のとおりである。 同じ PC の中で届いたのは、許した窓口だけだった(ComfyUI は止めていたので、この回は判定していない) 外・LAN・WSL のアドレスには、どれも届かなかった 普段のユーザーのフォルダ(SSH の鍵、資格情報、ブラウザのデータ、ドキュメント、ソース)と、モデルや道具の置き場は、どれも読めなかった Hermes 本体・Python の環境・道具・起動のスクリプトには、書けなかった 読めたのは、Hermes 本体、同じ専用のユーザーで動かしている記憶の仕組みのデータ(取り込むときに秘密と個人情報を除いてある)、Windows の共有の設定置き場(ProgramData)だった。ProgramData には Wi-Fi の設定も置かれているが、調べた範囲では、鍵は Windows が暗号化して置いているので、このユーザーからは取り出せないようだ。 窓口の先も確かめた。LLM・Web の画面・Hermes の内部の窓口は、どれも同じ専用のユーザーのプロセスだった。同じ外向きの遮断の下にあるので、そこを踏み台にして外へ出ることはできない。ComfyUI は別の専用のユーザーで動き、外の IP へは届かなかった。 Cohere の例でモデルがしようとしたのは、curl でクラウドの保管場所へ送る、ふつうの通信だった。この組み方なら、それは外へ届かずに止まる。 残る穴 1:名前の問い合わせ(DNS) ここが、いちばん大きく残っている穴である。 専用のユーザーで、問い合わせ先を指定して nslookup を直接送ると、応答が返らずにタイムアウトした(普段のユーザーでは答えが返った)。nslookup は応答が無くても終了コード 0 を返すことがあるので、終了コードではなく、応答の有無で見た。 ところが、OS の名前解決のしくみに頼む経路(PowerShell の Resolve-DnsName)で、でたらめな名前を引くと、専用のユーザーでも、そのドメインの「その名前は無い」という答え(SOA 付き)が返ってきた。手元に覚えのない名前なので、問い合わせは外の DNS サーバーまで届いている。つまり、問い合わせた文字列そのものが外へ出ている。Python の getaddrinfo も同じ OS のしくみを通るので、同じ経路で外へ出ると見ているが、こちらは外まで届いた証拠を取れていない。 調べた範囲では、Windows では名前の問い合わせを DNS Client というサービスが代わりに行い、このサービスは別のアカウントで動いているようだ。そのため、ユーザーごとの外向きの遮断では止まらない。WSL の側も同じで、直接の問い合わせは止まったが、システムの名前解決サービス(resolvectl)に頼むと解決できた。 名前に少しずつ情報を埋めれば、外へ持ち出すのに使える。Cohere の例でも、モデルは送る前に DNS の設定を読んでいた。 別の PC では、この穴を「止める」のではなく、「気づいて止める」見張りを作った。どのプロセスがどの名前を問い合わせたかを Windows の記録から読み、エージェントの問い合わせが出たら止めるものである。詳しくは DNS の見張りの記事 に書いた。Surface でも、Windows の DNS Client の記録を有効にして、専用のユーザーの問い合わせが「どのユーザーが引いたか」付きで残ることまでは確かめた。いまは記録するだけで、止める仕組みはまだ無い。ふだん引く名前を集めてから、許していない名前を引いた時点で Hermes を止める仕組みを足すつもりだ。それができても、最初の 1 件は外へ出る。だから、塞いだとは言えない。 残る穴 2:ping 専用のユーザーの外向きの通信を止めていても、ping は外まで届いた。ユーザー単位の遮断は、ping には当たらないようだ(理由は確かめていない)。ping のデータにも、少しずつなら情報を載せられる。 そこで、PC 全体で、外向きの ping だけを止める規則を足した(家の中への ping は残した)。足したあと、普段のユーザーからも専用のユーザーからも、外への ping は届かず、家の中とループバックへは届くことを確かめた。 ping は PC 全体で止めた。名前の問い合わせは塞げていない この 2 つは、どちらもユーザー単位の閉じ込めの外を通る。専用のユーザーの規則をいくら固くしても、ここは塞げない。ping は、普段のユーザーの ping も止めてよいと割り切って、PC 全体で止めた。名前の問い合わせは、PC 全体で止めると普段の作業ができなくなるので、同じ手は使えない。 調べた範囲では、名前の問い合わせを確実に塞ぐには、エージェントを OS ごと外から切るのがよさそうだ。たとえば、エージェントを仮想マシンに入れ、そのネットワークを内部だけにすれば、名前の問い合わせにも、そもそも出口が無くなる。WSL も仮想マシンだが、既定のネットワークのままでは、前に書いたとおり名前を引けた。要は、ネットワークを内部だけにすることである。調べた範囲では、Hyper-V の既定のネットワーク(Default Switch)や NAT の構成では、PC 本体が仮想マシンの名前の問い合わせを受けて、代わりに外へ流すようだ。これでは同じ穴が残る。外へつながらない内部だけのネットワークにして、本体の側にも問い合わせを受ける窓口(DNS サーバーやインターネット接続の共有)を置かないことになる。ただ、Hermes の頭脳の LLM と画像生成は GPU を使うので、仮想マシンに GPU を渡せるかが最初の壁になる。ここは次に試す。 残る穴 3:窓口の先で何をさせられるか ComfyUI は、受け取ったワークフローをそのまま実行する。外へは出られないが、その中のノードを使えば、ファイルを書く、同じ PC の中の届く範囲に接続する、といったことはできる。 窓口の先の相手も同じ考え方で閉じ込めているので、窓口を通って外へ出ることはできない。ただし、窓口の先で何をさせられるかは、その相手の機能しだいである。 生成の要求を中身で検査する門番(決めた形のワークフローだけを通すもの)は、置いていない。ComfyUI 自体が外へ出られないので、門番は置かなかった。外へ出られないことと、クラウドのノードを読み込まないことで守っている。 点検で気づいたこと 外への通信・秘密の読み取り・自分を動かすファイルの書き換えは、どれも止まっていた。 記録を読むと、Hermes が読むだけの手順書を自分で直そうとして、拒否されていた。壊れたものは無かった。 一つ、落とし穴があった。行動ルールのファイルを運用側が編集したら、ファイルが置き換わって、書き換えを止める設定が外れていた。中身を控えと照合すると、変わっていたのは運用側の変更だけだった。編集のあとは設定をかけ直し、点検で確かめることにした。 これをきっかけに、方針を切り替えた。行動ルールのような、エージェントが育てていくファイルは、書かせない守りをやめた。書かせて、その履歴を消せない場所に残す守りにした。 手元で組むときのコマンド例 Surface で使った形を元に、名前だけ仮のものにした。agent が専用のユーザー、C:\agent-work が作業場所である。1・2・5 は管理者の PowerShell で、3・4 は普段のユーザーで実行する。runas では専用のユーザーのパスワードを聞かれる。 1. 専用のユーザーの外向きの通信を、全部止める $sid = (Get-LocalUser -Name agent).SID.Value New-NetFirewallRule -Name "Block-agent-Outbound" -DisplayName "Block-agent-Outbound" ` -Direction Outbound -Action Block -Profile Any -LocalUser "D:(A;;CC;;;$sid)" 戻す Remove-NetFirewallRule -Name "Block-agent-Outbound" この規則では、ping と、OS が代わりに行う名前の問い合わせは止まらなかった。 2. PC 全体で、外向きの ping だけを止める(家の中の 10/8・172.16/12・192.168/16、ループバック、リンクローカルへの ping は残す) New-NetFirewallRule -Name "Block-ICMPv4-Echo-Out-Internet" -DisplayName "Block-ICMPv4-Echo-Out-Internet" ` -Direction Outbound -Action Block -Protocol ICMPv4 -IcmpType 8 -Profile Any ` -RemoteAddress "0.0.0.0-9.255.255.255","11.0.0.0-126.255.255.255","128.0.0.0-169.253.255.255","169.255.0.0-172.15.255.255","172.32.0.0-192.167.255.255","192.169.0.0-255.255.255.255" New-NetFirewallRule -Name "Block-ICMPv6-Echo-Out-Internet" -DisplayName "Block-ICMPv6-Echo-Out-Internet" ` -Direction Outbound -Action Block -Protocol ICMPv6 -IcmpType 128 -Profile Any -RemoteAddress "2000::/3" 戻す Remove-NetFirewallRule -Name "Block-ICMPv4-Echo-Out-Internet","Block-ICMPv6-Echo-Out-Internet" 3. 専用のユーザーで、1 つの宛先へ接続を試す(前後に同じコマンドを普段のユーザーで流して挟む) runas /user:agent "cmd /c curl.exe -s -o nul --connect-timeout 3 -w \"%{time_connect} %{exitcode}\" example.com > C:\agent-work\net.txt" 結果は C:\agent-work\net.txt に書かれる。終了コードが 0 なら届いた。7 なら届かなかった(止められたのか、相手がいないのかは、これだけでは分からない)。ping は ping -n 2 1.1.1.1 で同じように試す。 4. 書き込めないことを確かめる runas /user:agent "cmd /c (echo x> C:\Users<普段のユーザー>\Documents_write_test.txt) && echo WRITABLE> C:\agent-work\w.txt || echo denied> C:\agent-work\w.txt" WRITABLE になったら、できてしまった _write_test.txt を消し、権限を見直す。標準のユーザーは、既定で C:\Users\Public などにも書けるので、そうした場所も含めて試す。 5. 書き込みを作業場所だけに絞る icacls C:\agent-work /grant "agent:(OI)(CI)M" rem プログラムと設定は、読み取りと実行だけ icacls C:\path\to\agent-app /grant "agent:(OI)(CI)RX" rem 書き換えさせたくない個別のファイル icacls C:\path\to\agent-app\persona.md /inheritance:r /grant:r "agent:RX" "Administrators:F" "SYSTEM:F" rem 戻す icacls C:\agent-work /remove agent icacls C:\path\to\agent-app\persona.md /reset 同じ PC の中の宛先を絞る部分は、ファイアウォールの規則だけではループバック宛ての接続を絞れなかったので、Windows のフィルタリングの仕組み(WFP)に独自の層を足した。専用のユーザーのループバック宛ての接続のうち、決めた窓口だけを許している。ここは考え方の紹介にとどめる。 持ち帰り モデルの中身は確かめられないので、「勝手に外へ送ろうとしても困らない」ように組む。Cohere の例は 10 万回に 1 回未満で、悪意かどうかも分からない。それでも、ふつうに外へ通信できる環境なら届いていた、という点は自分の環境にも当てはまる エージェントも頭脳の LLM も、権限の低い専用のユーザーで動かす。外向きは全部止め、同じ PC の中でも話してよい相手を名指しで決める エージェントが書けるのは、自分の作業場所だけにする。自分を動かすコードと、普段のユーザーがあとで実行するものは書けないようにする 自己改善を許すなら、その履歴はエージェントに消せない場所に残す 規則は、普段のユーザーで前後を挟んで確かめる。前後の対照で届いて、専用のユーザーで止まれば、規則が働いている ユーザー単位の閉じ込めで塞げるのは、ふつうの通信までである。名前の問い合わせと ping は外へ届いた。少しずつなら情報を載せられる細い道が残る ping は PC 全体の規則で塞げる。名前の問い合わせは、調べた範囲では、仮想マシンなどで OS の外側から切るのが確実そうだ。それまでは、名前の問い合わせを見張って気づいて止める

Achievements

Awards earned by the repositories NousResearch owns. Select an award to embed its badge.

Gold
1611st place finishes
Silver
692nd place finishes
Bronze
403rd place finishes
Awarded repositories
1Owned by NousResearch now

Repositories

Ordered by gold, then silver, then bronze.

  1. hermes-agentBest: 1st by stars, 8,137 stars1616940

Every award

Top-three finishes of these repositories in completed trending days and weeks, newest first.

  1. hermes-agent
  2. hermes-agent
  3. hermes-agent
  4. hermes-agent
  5. hermes-agent
  6. hermes-agent
  7. hermes-agent
  8. hermes-agent

A repository earns an award for finishing first, second or third in a completed trending day or week, ranked by new stars, new forks or pull request authors. Awards follow a repository through renames and transfers and count toward its current owner. Weeks run Monday to Sunday, UTC.