Rethinking Hexagonal Architecture with Elegant Objects
Hexagonal architecture provides a useful foundation: business behavior should remain independent of the technologies used to expose it or persist its state. Ports define the interactions; adapters connect them to the outside world.
Yet this separation does not, by itself, tell us where behavior belongs.
One common implementation keeps the dependency directions of hexagonal architecture but puts most business decisions in use cases and services. Domain objects then act mainly as data structures, while other objects interpret and change their state. That is a valid design in some contexts, but it is not the only way to apply hexagonal architecture.
Reading Elegant Objects challenged how I approached this design. Its emphasis on encapsulation, composition, and small objects led me to ask: what changes when domain objects own more of the behavior that depends on their state?
I will explore that question through an e-commerce cart. The examples are deliberately small: supporting types and repetitive delegation methods are omitted.
Start with the responsibility
The usual suspects
A common implementation of “add an item to the cart” might distribute the work across:
- AddItemToCartUseCase, a port, and its implementation;
- CartController, a primary adapter that receives AddItemRequestDto;
- CartRepository, a secondary port, and JpaCartRepository, its adapter;
- CartEntity, the JPA model, and Cart, the domain model, connected by CartMapper;
- CartDto, which carries the response.
This can involve around ten types for one operation. Each can have a legitimate purpose. A persistence model may protect the domain from ORM constraints. A DTO may define a stable API contract. An application service may coordinate objects and transactions.
The design becomes harder to follow when those types divide one concept's behavior among them. For example, Cart may expose data while a use case decides what that data means and how it can change.
Hexagonal architecture does not require this arrangement. It permits a rich domain model. The question is whether our implementation makes that model visible.
Before adding another layer, I want to establish what a cart is responsible for.
Give the cart an explicit contract
For this example, a cart knows its contents and total. It can accept or remove an item and initiate checkout:
public interface Item {
String sku();
Money price();
}
public interface Items {
Iterable<Item> all();
Item item(String sku);
}
public interface Cart {
Items items();
Money total();
void add(Item item);
void remove(Item item);
Order checkout(Payment payment);
}
The examples below omit repetitive methods such as remove() and checkout(), along with error handling and transaction boundaries.
The caller asks the cart to perform an operation. It does not retrieve a mutable list, modify it, and ask a repository to save the result. This gives the cart a boundary for rules about its contents.
The distinction goes beyond method names. Renaming getTotal() to total() changes little if the calculation still lives elsewhere. What matters is that the cart owns the meaning of its total and hides how it is obtained.
The contract exposes the values needed by callers through sku() and price(), while keeping cart operations behind its own boundary.
Each item represents one cart entry. Quantities, discounts, and price changes belong in explicit concepts such as a CartLine.
checkout() makes the cart responsible for coordinating payment and order creation. An application service can coordinate the wider workflow, while the cart owns rules about its contents.
Keep persistence behind the contract
Once the behavior is defined, we can choose how to implement it. Here, PgCart represents a user's cart stored in PostgreSQL:
public final class PgCart implements Cart {
private final DataSource db;
private final long user;
public PgCart(final DataSource db, final long user) {
this.db = db;
this.user = user;
}
@Override
public void add(final Item item) {
new JdbcSession(this.db)
.sql("INSERT INTO cart_item (user_id, sku) VALUES (?, ?)")
.set(this.user)
.set(item.sku())
.insert(Outcome.VOID);
}
// Other Cart methods omitted.
}
The constructor captures the dependencies and the cart's identity. Database work happens when an operation is invoked.
This implementation removes the need for a separate persistence entity and its mapper in this example. PgCart encapsulates access to persistent state and exposes it through the Cart contract. It is not a persistence-ignorant domain object: it combines domain behavior with database access, and a call such as items() may involve database queries.
One important distinction: its fields are final, but the cart's observable state can change. Adding an item changes the database, and a later call may return different contents. Fixed references do not make a persistent object an immutable value, nor do they guarantee thread safety.
The Cart contract knows nothing about SQL or DataSource, and PgCart depends on that contract. Cart is a domain abstraction, not an application port. PgCart is both a secondary adapter and a domain object: the dependency points inward, but domain behavior and persistence are deliberately combined.
The hexagon remains at the system boundary: TkCart translates HTTP requests, and PgCart connects the cart to PostgreSQL. Inside that boundary, this design keeps the domain abstraction and persistence behavior together rather than introducing a separate persistence port.
The roles in this example are:
- TkCart is the primary HTTP adapter.
- JsonCart defines the JSON representation.
- Cart, NonEmptyCart, and LoggedCart form the domain contract and its decorators.
- PgCart is both the secondary PostgreSQL adapter and a domain object.
- FakeCart is a test implementation of Cart.
Compose behavior around the object
With a behavioral interface in place, additional responsibilities can be expressed through composition.
Suppose checkout must reject an empty cart. A decorator can make that rule explicit:
public final class NonEmptyCart implements Cart {
private final Cart origin;
public NonEmptyCart(final Cart origin) {
this.origin = origin;
}
@Override
public Order checkout(final Payment payment) {
if (!this.origin.items().all().iterator().hasNext()) {
throw new IllegalStateException("The cart is empty");
}
return this.origin.checkout(payment);
}
// Other Cart methods delegate to origin.
}
We can then assemble the cart where the application's dependencies are wired:
new LoggedCart(
new NonEmptyCart(
new PgCart(db, user)
),
log
)
The composition shows which responsibilities apply: persistent storage, an empty-cart check, and logging. Each wrapper depends on Cart, so its behavior can be tested with a small fake implementation such as FakeCart.
Order matters: logging surrounds validation and records rejected checkout attempts as well as successful ones.
The empty-cart check and checkout are separate calls, so another request could modify the cart between them. PgCart must enforce the invariant atomically with the relevant database changes.
A real checkout must also account for payment failures and retries. A database transaction cannot atomically include an external payment provider. Moving behavior into objects makes these responsibilities easier to locate, but does not remove them.
Give HTTP representation its own responsibility
The next boundary is the API response. The domain should remain independent of HTTP and JSON libraries.
The Takes framework's RsJSON example provides a useful starting point: a response wraps an object capable of producing JSON. I use that approach through a dedicated representation object, keeping framework dependencies outside the Cart contract.
public final class JsonItem implements RsJSON.Source {
private final Item item;
public JsonItem(final Item item) {
this.item = item;
}
@Override
public JsonObject toJSON() {
return Json.createObjectBuilder()
.add("sku", this.item.sku())
.add("price", this.item.price().toString())
.build();
}
}
public final class JsonCart implements RsJSON.Source {
private final Cart cart;
public JsonCart(final Cart cart) {
this.cart = cart;
}
@Override
public JsonObject toJSON() {
final JsonArrayBuilder items = Json.createArrayBuilder();
for (final Item item : this.cart.items().all()) {
items.add(new JsonItem(item).toJSON());
}
return Json.createObjectBuilder()
.add("total", this.cart.total().toString())
.add("items", items)
.build();
}
}
JsonCart owns the response structure. It wraps a cart instead of copying its values into a second model before serialization. Formatting money as a string keeps this example short; a public API may need separate amount and currency fields.
The HTTP entry point wires the same decorators around the persistent cart before returning the representation:
public final class TkCart implements Take {
private final DataSource db;
private final Logger log;
public TkCart(final DataSource db, final Logger log) {
this.db = db;
this.log = log;
}
@Override
public Response act(final Request req) throws Exception {
return new RsJSON(
new JsonCart(
new LoggedCart(
new NonEmptyCart(
new PgCart(this.db, new UserOf(req).id())
),
this.log
)
)
);
}
}
TkCart receives the shared database connection and logger when it is constructed. For each request, it extracts the user's id, builds the same LoggedCart(NonEmptyCart(PgCart(...))) composition, and wraps it in the JSON representation. The HTTP adapter connects request identity, cart behavior, and response without making the domain contract depend on HTTP.
The response would look something like this, with illustrative prices for IT books about Elegant Objects and hexagonal architecture:
{
"total": "69.98 EUR",
"items": [
{ "sku": "ELEGANT-OBJECTS", "price": "34.99 EUR" },
{ "sku": "HEXAGONAL-ARCHITECTURE", "price": "34.99 EUR" }
]
}
This gives the API representation a clear place to evolve. A different response format can be introduced without changing Cart. The mapping responsibility still exists; it now belongs to an object that produces the representation directly.
An alternative is to pass the result of Json.createObjectBuilder() to a mapToJson method on Item and Cart. Each object would then choose which parts of its state to expose, at the cost of coupling business interfaces to the JSON API. Here, JsonItem and JsonCart keep that representation responsibility outside the domain contract.
A DTO remains useful when an explicit snapshot is required. In this example, items() and total() are separate reads, so concurrent changes could produce an inconsistent response. A DTO alone does not solve that problem; the implementation needs a consistent read strategy or snapshot if the API promises that both describe the same instant.
Use names to communicate responsibility
Some naming rules of thumb I now follow:
- Prefer names that say what an object is. Names ending in -er, such as Controller, Mapper, Manager, or Handler, describe what an object does to others. They point toward procedures organized around someone else's data. Prefer the underlying concept: Payment, Outbox, or Cart.
- Use technology prefixes to distinguish implementations. PgCart identifies the PostgreSQL implementation; TkCart and RsJSON identify framework-facing objects. StripePayment is clearer than CardPayment when naming an implementation tied to a payment provider. In a codebase using Takes, Fk already means Fork (FkRegex, FkTypes), while Takes calls its test doubles RqFake and RsFake; FakeCart avoids giving one prefix two meanings. Prefixes help only when the team shares their meaning.
- Use adjectives to describe decorators. NonEmptyCart and LoggedCart make added behavior visible; CachedItems would communicate the same idea for a cached collection.
The other names in this example follow the same intent: Cart defines the business contract, JsonCart identifies an API representation, and FakeCart provides a test implementation.
Evaluate the trade-offs
The main benefit of this experiment is a more direct relationship between a business concept and its behavior. We can find cart operations through Cart, persistence through PgCart, and response formatting through JsonCart.
This does not guarantee fewer classes. Decorators and representation objects can increase their number. The useful measure is how much of the system a developer must understand to change a rule safely.
Several constraints deserve attention:
- Data access: small objects can hide repeated queries. Measure query behavior and choose batching or a deliberate loading strategy. Caching requires a clear lifetime and invalidation policy.
- Consistency: composition does not define transaction boundaries. Business invariants must hold under concurrent access and partial failure.
- Testability: FakeCart lets tests exercise decorators such as NonEmptyCart without infrastructure. PgCart owns database-backed behavior, including total(), so tests of those rules require PostgreSQL. This design makes some behavior easy to test in isolation; it does not make the persistent cart infrastructure-free.
- Framework integration: frameworks built around mutable entities, reflection, and generated mappings may make this approach costly to introduce.
- Navigation: many small objects can reduce local complexity while making the overall behavior harder to follow. Shared conventions and selective decomposition matter.
For an existing application, start with one bounded behavior and assess the result: can the team locate the rule more easily, test the right boundary, and understand the dependencies? Those answers provide better guidance than adherence to a naming rule or a target class count.
Bring the question back to your code
Choose a business concept in your application: a cart, an invoice, or an order. Follow one operation from the entry point to persistence and identify where each decision is made.
Does the object own its rules, or must its callers understand its internal state to apply them?
When a decision depends on the object's own state, moving it behind the object's contract can strengthen encapsulation. When an operation coordinates several objects or external systems, an application service may still be the clearest place for that coordination.
Hexagonal architecture helps us control the boundary between the application and its environment. Elegant Objects encourages us to examine the responsibilities within that boundary. Together, they offer a useful way to question familiar structures and make design choices explicit.
The goal is a system whose behavior can be located, understood, and changed with confidence.
Mentions
昨日しらべたtop githuber(スター数多い人限定だけど)の中で良さげなのは linterでskills.md, AGENTS.md, rules とかの記述の質を一定に保たせるアイデアかな yegor256/dogent
GitHubで年5万コントリビューションを超える10人の、AIエージェントのハーネスを読んだ はじめに 株式会社DeploAIで代表取締役CTOをしている袖山剛です。 営業を科学するプロダクトの開発と、お客様の業務にAIを組み込む仕事をしています。開発では Claude Code や Codex を並べて動かし、その周りの仕組み(ハーネス)と、エージェントに自律的に作業を回させる仕組み(ループ)を自分たちで作っています。 エージェントに開発を任せるようになってから、私の GitHub の contributions も増え、直近1年で 47,331 件(2026-11-03 時点)と5万に近づいてきました。1年で5万件は、1日平均で約140件です。同じくらい、あるいはそれ以上に手を動かしている人たちは、どんなハーネスやループで開発しているのか。それが気になって調べてみました。 GitHub の国別ランキングで、直近1年の contributions が5万を超える人を全員洗い出しました。その中に、AIエージェントの周りの道具を作っている人がどれだけいて、何を作っているのかを見ました。 結論:「確かめる手段」「止め方」「規則の検査」を先に作っている 69人のうち、AIエージェントに関わる自作のリポジトリがあったのは18人でした。その中から10人の取り組みを読み、共通していた設計を5つにまとめます。 エージェントが自分で確かめられる手段を、先にそろえている。 画面取得、本物のブラウザの操作、試験用のマシン、CI 上のシミュレータなどです。道具は MCP より先に CLI で作り、エージェントがそのまま実行して出力を読めるようにしています。 ループの止め方を、回数ではなく条件で決めている。 「新しい発見がない回が続いたら止める」「予算切れで止まったら完了ではなく不明として扱う」「同じところを行ったり来たりしたら別の考え方に回す」などです。 指示文書を機械で検査している。 CLAUDE.md や SKILL.md を linter で CI に掛けたり、規則を hook と審査役のエージェントで強制したりしています。書いた規則が守られているかを、人の目ではなく仕組みで確かめます。 判定は、毎回同じ結果になるものに寄せている。 検出は決まったルールで行い、AI は絞り込みと修正に使う。合否は各ステップの宣言した条件で決める。品質の指標は記録値を信用せず毎回測り直す、といった形です。 危ない操作の判定は、エージェントの申告ではなくホスト側の記録で行う。 入れ子の呼び出しの拒否、スレッドの自動解決、書き換えの許可を、エージェントが渡す引数ではなく、ホスト側の登録簿や修正の証拠、明示フラグで決めています。 どうやって調べたか 元のランキングから機械的に抜き出し、AIエージェント関連の取り組みは README と記事を読んで確かめました。 対象: gayanvoice/top-github-users の138か国分のリストで、total contributions が5万を超える人です。44か国の69人が該当しました。このランキングは国ごとにフォロワー数の下限で絞られているので、フォロワーの少ない人はもともと入っていません。 現在の値: 2026-11-03 に、各人の GitHub プロフィールに出る直近1年の contributions を取り直しました。今も5万を超えていたのは59人です。 取り組みの判定: リポジトリ名と説明で候補を絞り、README・ドキュメント・本人のブログを読みました。fork は本人の仕事に数えていません。スター数は 2026-11-03 時点の値です。 対象 今も直近1年で5万超 X を GitHub に登録 AIエージェント関連の自作あり 69人(44か国) 59人 33人 18人 一つ注意があります。contributions の多さと、開発の中身は比例しません。 直近1年で30万件(1日平均約820件)を超える人が10人いて、手作業だけの量とは限りません。今回はこの数字を「たくさん手を動かしている人の候補」として使い、評価には使っていません。 誰が、何を作っているのか 取り上げた10人の国と代表作は次のとおりです。 国はランキングの掲載国、X は本人が GitHub に登録しているアカウントです。 GitHub 国 X 代表作 ★ steipete イギリス @steipete openclaw 393,800 yegor256 中国 @yegor256 prompt 166 shuvonsec マレーシア @shuvonsecc Agentic-Bug-Hunter 5,567 nazt タイ @nazt maw-js 107 shaun0927 韓国 @nraycast openchrome 236 noahgift スペイン — paiml-mcp-agent-toolkit 169 PrzemyslawKlys ポーランド @PrzemyslawKlys IntelligenceX 11 danlex ルクセンブルク — autoresearch 6 bannzai 日本 @_bannzai_ simtunnel 2 rldyourmnd カザフスタン — ai-stp 7 スター数には大きな差がありますが、この記事ではスター数で比べず、README から読み取れる設計の考えを見ます。次の節で1人ずつ見ます。 1人ずつ見る 各節は、代表作のカード、プロフィール、主な取り組みの表、参考になる点の順に並べました。 発信(記事や講座)は折りたたんであります。 steipete:エージェントに「確かめる手段」を渡す道具を量産する OpenClaw の作者 Peter Steinberger は、エージェントが自分の変更を自分で確かめられる道具を次々に公開し、その使い方を記事で書いています。 openclaw/openclaw 名前 国 所属・肩書き 区分 リンク Peter Steinberger イギリス OpenAI 所属。以前は PSPDFKit の創業者 AIエージェント・ハーネス・ループ GitHub · X @steipete · steipete.me 主な取り組み 取り組み ★ 何をするか 面白い点 openclaw 393,800 ローカル常駐型の汎用 AI エージェント スキル登録所 clawhub(★9,544)、ワークフロー用シェル lobster(★1,280)まで周辺がそろう agent-scripts 7,439 Codex と Claude Code で共有する AGENTS.MD とスキル群 同期スクリプトと検証フック付き。複数エージェントで道具を共有する実物 agent-rules 5,671 Claude Code・Cursor 向けの規則と知識 規則ファイルを配る形の早い例 CodexBar 22,965 Codex・Claude Code の使用量をメニューバーに表示 並列運用で枠を管理する道具 oracle 4,060 行き詰まったエージェントがファイルと質問を GPT-5 Pro に投げる CLI 上位モデルへの相談をエージェントに任せる clawsweeper 2,005 Issue・PR を定期的に見直し、確度の高いものだけ安全ゲート付きで閉じる 定期実行の保守ループ clawpatch 815 機能ごとに切ってレビュー→修正→再検証を回す 途中から再開できるループ設計 crabbox 1,514 エージェントに試験用のマシンを貸し出す 検証環境を道具として渡す Peekaboo / mcporter 5,424 / 5,101 macOS の画面取得 CLI・MCP/MCP を TypeScript や CLI から呼ぶ 「見て確かめる」手段と MCP の CLI 化 sweetlink 168 今開いているタブの Web アプリにエージェントをつなぐ 説明文自体が "Close the loop" 発信(記事・講座) Just Talk To It:エージェントを3〜8本並べて同じフォルダで動かす。外部ハーネスより CLI。動くアプリは chrome-devtools-mcp で調べさせてループを閉じる。 Shipping at Inference Speed:新しい道具はまず CLI で作り、エージェント自身が実行して確かめられるようにする。 記事一覧:「Claude Code is My Computer」「Commanding Your Claude Code Army」など。 Latent Space のまとめ:エージェントが内側のループ、人が外側のループを回すという考え方。 ! 参考になる点 道具は MCP より先に CLI で作る。エージェントがそのまま実行し、出力で自分の変更を確かめられる。 画面取得・ブラウザ接続・試験マシンなど、「確かめる手段」を先にそろえる。 保守ループには「確度の高いものだけ自動で閉じる」安全ゲートを付ける。 yegor256:規律を書き、linter で守らせ、毎日回す Yegor Bugayenko は、エージェントへの規律を「書く・検査する・回す」の一続きで運用しています。 yegor256/prompt 名前 国 所属・肩書き 区分 リンク Yegor Bugayenko 中国 『Elegant Objects』『Angry Tests』の著者。zerocracy の創業者 ハーネス・ループ GitHub · X @yegor256 · yegor256.com · zerocracy 主な取り組み 取り組み ★ 何をするか 面白い点 prompt 166 ~/.claude/CLAUDE.md に置くコーディング規律の manifesto(TDD、PDD、範囲外の変更をしない、など) 著書の設計思想をそのままエージェントへの指示にしている dogent 8 SKILL.md・CLAUDE.md・AGENTS.md を行ごとに合否判定する linter 指示文書そのものを CI で検査する。agnix や AgentLinter との違いも整理 zerocracy/zealot 4 Claude Routines で毎日動かす skill 群 コメント応答→triage→PR review→bug 報告→PR 1本の定型の連鎖で保守を自律化 plugins 23 自作 Claude Code plugin の marketplace courier(PR 作成)、bugscribe(Issue 起票)、critic、purist、witness(Telegram 報告) jinka 2 「願い」の Markdown を置いて実行するだけのオーケストレータ(2026-10-30 作成) 内部で Claude Code を使う rultor / 0pdd 573 / 144 以前からの merge bot と、コード内の TODO から Issue を作る bot AI 以前から bot で回す運用をしていた土台 発信(記事・講座) Help the Agent to Feel(2026-05-29):CLAUDE.md の manifesto を「飴」、自作 linter を「鞭」としてエージェントに美意識を教える。 Couriers, Not Coders(2026-05-03):AI 時代には動くコードは最低条件で、貢献者には規律が求められる。 ブログ一覧:「Comments Considered Harmful in the Age of LLMs」など 2026 年は AI 関連が続く。 ! 参考になる点 指示文書(CLAUDE.md・SKILL.md)を linter で CI に掛け、規則が守られているかを機械で確かめる。 自律ループを「毎日、決まった順の5段」と小さく固定し、成果物を PR 1本に限る。 結果の報告先(witness → Telegram)も plugin として切り出す。 shuvonsec:検出は決まったルール、AI は絞り込みと修正 Shuvon は、バグバウンティ(脆弱性報奨金)の調査を Claude Code のプラグインとスキルで自動化しています。分野はセキュリティです。 awarexone/Agentic-Bug-Hunter 名前 国 所属・肩書き 区分 リンク Md Shariar Shanaz Shuvon マレーシア AwareXone の CEO AIエージェント・ハーネス(セキュリティ) GitHub · X @shuvonsecc · shuvonsec.com · awarexone 主な取り組み 取り組み ★ 何をするか 面白い点 Agentic-Bug-Hunter 5,567 CLI の bughunter hunt と Claude Code の /hunt で、偵察→発見→検証→報告を1本の流れで回す 無料プロバイダでも動き PyPI で配布。fork 979、外部コントリビューターも多い AXguard 25 AI に書かせたアプリの公開前検査 コマンド31・スキル38・承認付き MCP ツール31。検出は決定的ルール47本、AI は絞り込みと修正だけ public-skills-builder 249 公開済みの HackerOne 報告や writeup から脆弱性分類ごとにスキルを自動生成 過去の知見をスキルに蒸留する web3-bug-bounty-hunting-ai-skills 178 Immunefi の報告 2,749 件などから作ったスマートコントラクト監査用スキル18個 大量の事例から領域知識をスキル化 発信(記事・講座) shuvonsec.com:本人のサイト。 ! 参考になる点 検出は決定的なルール、AI は絞り込みと修正だけという役割分担で、結果の再現性を保つ。 公開された大量の事例を分類ごとのスキルに蒸留する。自社の過去の障害や PR でも同じことができる。 「公開前の関門」を各エージェントのコマンドとして配り、どのツールからでも同じ検査を通す。 nazt:ループは「新しい発見がなくなったら」止める Nat は、tmux 上でマシンをまたいでエージェントを動かす maw-js と、並列エージェントの運用をまとめた本を書いています。 Soul-Brews-Studio/maw-js 名前 国 所属・肩書き 区分 リンク Nat タイ Chiang Mai Maker Club AIエージェント・ハーネス・ループ GitHub · X @nazt · Soul-Brews-Studio 主な取り組み 取り組み ★ 何をするか 面白い点 maw-js 107 tmux の窓ごとにエージェントを置き wake・hey・peek・done で操作。Claude Code・Codex・Aider・OpenCode に対応 ノード間は署名付き HTTP、チームを YAML で宣言。plugin 89個 arra-oracle-skills-cli 125 25個の skill を19種類のエージェントに入れる /rrr(振り返り)、/forward(引継ぎ)、/round-table(3〜8体で合議)など arra-oracle-v3 90 SQLite の記憶・検索層。HTTP・MCP・CLI から同じ記憶を使う 上書きは取り消せる履歴として残し出所も記録 本『Agents in Parallel』の要点 並列化で得るのは速さより正しさ。手法の違うエージェントの結果が食い違うところが問題の場所(3・4章)。 The Team Is a File: 人が書く charter は git で管理し、機械が書く実行状態は捨てて作り直せるものにする(5章)。 Verify the Invariant, Not the Roster: 名簿では3体とも別 worktree だったが、OS に聞くと同じパスで上書きし合っていた(7章)。 Loop-Until-Dry: 新しい発見のない回が K 回続いたら止める。却下した候補も重複除去に残す。約400サービスの例では新規が 61→19→4→0→0 と減って84件で停止。予算切れは「完了」ではなく「不明」(9章)。 発信(記事・講座) Agents in Parallel:並列エージェントの運用パターンをまとめた本。上の要点はここから。 ! 参考になる点 ループの止め方を回数ではなく「新しい発見のない回が K 回」にし、却下したものも記録する。予算切れと完了を区別する。 「稼働中」の表示を信用せず、各 worker の作業ディレクトリとプロセスを OS から読み戻す。 チームは宣言ファイルで定義し、実行状態は捨てて作り直せるようにする。 shaun0927:ループの暴走を、ホスト側の記録で止める Junghwan は、本物の Chrome を操作する MCP「openchrome」の作者で、自己改善ループの基盤 ouroboros の2番目に多い貢献者です。 shaun0927/openchrome 名前 国 所属・肩書き 区分 リンク Junghwan 韓国 DaedalGames 所属 ハーネス・ループ GitHub · X @nraycast 主な取り組み 取り組み ★ 何をするか 面白い点 openchrome 236 Claude Code や Codex から、ログイン済みの本物の Chrome を CDP で操作する MCP。v1.15.0(2026-09-24) 複数クライアントは1つの所有者+broker で共有。YAML 手順の合否は LLM でなく各ステップの assert で決める opensafari 16 Xcode Simulator 上の iOS Safari を並列で操作する MCP openchrome の iOS 版 Q00/ouroboros(他人のリポジトリ) 6,308 聞き取り→仕様固定→実行→評価→次の世代へ、を回す自己改善ループ。収束は類似度 0.95 以上、上限30世代 shaun0927 は 670 コミット、PR 472件で2位 ouroboros に出した、ループ制御の PR(すべて merge 済み) #2722: Ralph の入れ子を、エージェントが渡す引数ではなくホスト側のセッション登録簿で拒否する。 #1294: 1回分の timeout を既定1800秒ではなく全体予算から割り当てる。 #1175: 行ったり来たりを検出したら、すぐ止めずに水平思考のペルソナへ回す。 #1178: 実行時間の上限(既定4時間)を設け、超えたら理由を記録して止める。 #1144: イベント項目の長さと入れ子の深さ(最大32)に上限を設ける。 ! 参考になる点 止まった理由を決まった種類に絞り、「予算切れ」なら BLOCKED、「行ったり来たり」なら別の考え方へ、と次の対応を分ける。 再帰の防止は、エージェントが渡す引数ではなくホスト側の記録で判定する。 1回分の時間上限は全体の予算から割り当て、ブラウザのような共有資源は「1つの所有者と broker」にする。 noahgift:品質の関門は「悪化だけを止める」 Noah Gift は、エージェントのコーディングを決まった検査で縛る Rust 製のツール PMAT を開発しています。 paiml/paiml-mcp-agent-toolkit 名前 国 所属・肩書き 区分 リンク Noah Gift スペイン Pragmatic AI Labs の創業者。AWS ML Hero ハーネス GitHub · paiml.com · paiml 主な取り組み 取り組み ★ 何をするか 面白い点 PMAT(paiml-mcp-agent-toolkit) 169 1つのバイナリで CLI・MCP を提供。技術負債の採点(A+〜F)、変異テスト、162項目の検査、品質と故障履歴で関数を順位付けする検索。v3.42.0 下の「設計の要点」を参照 rust-mcp-sdk 51 Rust 製の MCP SDK。v2.23.0(2026-10-28) 主な開発者は別の人で、本人は138コミット pforge 3 YAML 定義から MCP サーバーを作る MCP を宣言で作る sbx-c2-agent-sandboxes — AI が生成したコードを bubblewrap・Landlock・seccomp の中で動かす実行器(講座用) エージェントの隔離 PMAT の設計の要点 ラチェット: 各指標に再計測コマンドを書き、ゲートは記録値を信用せず毎回測り直す。悪化だけを止め、測れなければ不合格。閾値を緩めるときは理由が必須。 決定論: 採点の出力をバイト単位で毎回同じにし、複数のエージェントの結論をそろえる。 pmat verify が通れば CI も通る。pmat kaizen は安全な範囲の自動修正から push まで行う。 pmat-book 第67章: 自分のツールを5ラウンド使って58件の欠陥を出した記録。リリース前は多エージェントで111コマンドを並列に試し、懐疑役のエージェントに反証させてから直す。(開く) 発信(記事・講座) Claude From Zero(Coursera):CLAUDE.md、skills、出力の形を契約で決めた sub-agent、hook によるゲート。 ai-tooling の講座群:「Deterministic LLM Programming」など。 ! 参考になる点 品質ゲートは固定の閾値ではなく「再計測コマンド付きのラチェット」にし、悪化だけ止める。 判定の出力を決定的にして、エージェント同士の結論がずれないようにする。 手元で CI と同じゲートを先に通す入口を用意する。 PrzemyslawKlys:スレッドを閉じるのは、修正の証拠があるときだけ Przemysław Kłys は、Codex を使う PR レビューの仕組み IntelligenceX を自分で運用しています。 EvotecIT/IntelligenceX 名前 国 所属・肩書き 区分 リンク Przemysław Kłys ポーランド Evotec 所属。Microsoft MVP ハーネス・ループ GitHub · X @PrzemyslawKlys · evotec.xyz · intelligencex.dev 主な取り組み 取り組み ★ 何をするか 面白い点 IntelligenceX 11 GitHub Actions 上の PR レビュアー、Windows の常駐チャット、ツールパック、CLI・.NET・PowerShell をまとめた基盤 サーバーを持たず、認証情報と GitHub App は利用者側が用意 keep-codex-fast 1 Codex のローカル状態を保守する skill 最初は見るだけ。古い会話は引継ぎメモを書いてからアーカイブ PSPublishModule PR #998 — 1つの正本から Codex 用と Claude 用の plugin を生成・検証 複数エージェント向けの配布を一本化 MCP サーバーの追加 — OfficeIMO・Mailozaurr・DesktopManager をエージェントから使えるようにする DesktopManager は既定が読み取り専用で、書き換えは --allow-mutations が必要 レビューのループの流れ diff を選んで分割し、秘密情報を伏せる。 モデルを呼んで指摘を構造化し、要約コメントと inline コメントを付ける。 既存スレッドを分類し、修正 push の後に再レビュー。「Todo List」「Critical Issues」は merge を止め、「Other Issues」は提案扱い。 bot のスレッドを自動で解決するのは修正の証拠があるときだけ。予算ガード、信頼できない PR には秘密情報と書き込み権限を渡さない、workflow だけの PR はレビューしない。 発信(記事・講座) IX Reviewer in Action:2026年2〜3月に reviewer・セキュリティ設定・チーム導入の記事。 reviewer の概要:provider の選択肢とレビューの流れ。 ! 参考になる点 レビューのスレッドを自動で解決するのは、修正の証拠があるときだけにする。 CI の失敗を「直すべきもの」と「インフラ要因」に分け、後者でコードをいじらせない。 MCP は既定を読み取り専用にし、書き換えは明示フラグで解放する。 danlex:審査役の全員一致で merge し、改善しなければ捨てる Alexandru Dan は、Claude Code を hook と審査役のエージェントで縛る仕組みと、審査役付きの自律的な調査ループを試作しています。 danlex/autoresearch 名前 国 所属・肩書き 区分 リンク Alexandru Dan ルクセンブルク — ハーネス・ループ GitHub · alexandrudan.com 主な取り組み 取り組み ★ 何をするか 面白い点 autoresearch 6 goal.md を起点に Issue 生成→claude -p で調査→PR→Opus 審査員3名の全会一致で merge スコアが改善しなければ branch を破棄。Ralph 型ループに検証の門を付けた構成 agentic-contract 1 契約文書、PreToolUse hook による遮断・確認、Stop hook と読取専用の審査 subagent の3層 「お願い」を強制力のある規則に変える forge 0 Claude が監督役と教師役を務め、Qwen3 1.7B を採点して LoRA 微調整を重ねる tmux 上の3者がファイル経由でやり取りする自己改善ループ ethicalhive 1 別 context の Claude が草稿を敵対的に監査 25件で精度 80% と、自分の限界を自己申告 claurn 0 会話記録から3回以上繰り返す型を拾い、hook・skill・/loop の導入を提案 ハーネス自体を育てるコーチ役 council 1 Claude Code・Codex・Gemini CLI に同じ問いを投げて結果を合成 複数エージェントの合議 発信(記事・講座) alexandrudan.com:本人のサイト。上の取り組みが載っている。 ! 参考になる点 merge を「審査員複数の全会一致」と「スコアが改善したときだけ」にし、改善しなければ捨てる。 守らせたい規則は文書だけでなく、実行前の hook と終了時の審査の両方に掛ける。 繰り返しの作業を会話記録から拾い、ハーネスの改善候補にする。 bannzai:重い検証環境を CI に追い出し、作業ごとに割り当てる bannzai は、Claude Code や Codex を並べて動かす開発者向けに、動作確認・レビュー・状況把握を支える小さな道具を作っています。 bannzai/simtunnel 名前 国 所属・肩書き 区分 リンク bannzai 日本 — ハーネス GitHub · X @_bannzai_ · Zenn 主な取り組み 取り組み ★ 何をするか 面白い点 simtunnel 2 GitHub Actions の macOS ランナー上で iOS シミュレータを起動し、Tailscale 経由で手元のエージェントから操作 worktree ごとに1セッション。手元の Mac を占有せずに実機に近い環境で確かめられる webtunnel 0 simtunnel の Web 版。ランナー上の Chromium を agent-browser から操作 録画を PR に残せる fusen 0 VS Code 拡張+MCP。人がコード行に付けた付箋をエージェントに渡し、エージェントも行に返す 人が承認・却下する双方向レビュー agent-timeline 0 Claude Code と Codex のセッションを X 風のタイムラインで表示 スレッドに返信すると tmux の pane へ指示が届く pre-github 0 GitHub 互換 API で PR・Issue を公開前にプレビュー 個人情報や秘密情報らしい文字列を目立たせる tansu 0 ローカルの Notion 風データベース CLI と MCP からエージェントが読み書き 発信(記事・講座) Zenn:本人の記事一覧。 ! 参考になる点 重い検証環境(iOS シミュレータ、ブラウザ)を CI のランナーに追い出し、worktree ごとに割り当てる。 人とエージェントのやり取りを「コード行の付箋」や「タイムラインへの返信」にして、指示の粒度を細かくする。 エージェントが外へ出すもの(PR・Issue)を公開前に検査する。 rldyourmnd:ハーネスの構成一式を、版を固定して検証してから入れる Danil Silantyev は、ハーネスの構成一式を部品のパッケージとして検証し、エージェント自身に入れさせる CLI を作っています。 ai-engineers-guild/ai-stp 名前 国 所属・肩書き 区分 リンク Danil Silantyev カザフスタン NDDev の CEO ハーネス GitHub · nddev.ai · ai-engineers-guild 主な取り組み 取り組み ★ 何をするか 面白い点 ai-stp 7 instruction・skill・MCP・hook・command・agent・plugin・setting を版固定の部品のグラフとして検証・組立・導入 想定利用者はエージェントで、help --agent --json のような機械向けの契約を持つ ai-stp-experiments 0 8種類の部品と組み合わせについて81件の実験を宣言的に書いた素材集 ハーネスの構成を実験で比べる hack-setup 1 Codex の版と道具一式を固定した共有エージェント環境を1コマンドで再現 gh のログインとメンバーの照合でコミットの取り違えを防ぐ 発信(記事・講座) nddev.ai:本人の会社のサイト。 ! 参考になる点 ハーネス設定を版固定のパッケージとして扱い、導入前に組み合わせを検証する。 道具の利用者をエージェントと定め、JSON の help など機械が読める契約を先に用意する。 チームのエージェント環境を1コマンドで再現できるようにする。 自分たちのループに持ち込むなら、何から始めるか まず「止め方」と「確かめ方」を、エージェントの申告に頼らない形で決めるのが先です。 10人の取り組みを、自分たちの開発に当てはめると次の順になります。 止まった理由を決まった種類に分ける。 完了・予算切れ・行ったり来たり・新しい発見なしを区別し、それぞれ次の扱いを決めます(nazt、shaun0927)。 動いている証拠を、表示ではなく実物から読む。 作業ディレクトリ、プロセス、修正の証拠を、ホスト側で読み戻します(nazt、shaun0927、PrzemyslawKlys)。 指示文書と品質の関門を機械で検査する。 規則は linter や hook に掛け、品質は悪化だけを止めます(yegor256、danlex、noahgift)。 確かめる道具を CLI で渡す。 画面、ブラウザ、試験用の環境を、エージェントが自分で呼べるようにします(steipete、shaun0927、bannzai)。 参考資料 gayanvoice/top-github-users(total_contributions) Peter Steinberger「Just Talk To It - the no-bs Way of Agentic Engineering」「Shipping at Inference Speed」 Yegor Bugayenko「Help the Agent to Feel」「Couriers, Not Coders」 Nat「Agents in Parallel」 Pragmatic AI Labs「pmat-book 第67章」 IntelligenceX「Reviewer overview」「IX Reviewer in Action」 営業の受注率を上げるAIネイティブなサービス Intelligent Sales を開発しています。 筆者:袖山剛(株式会社DeploAI CTO) 線形代数・微積分・最適化・統計・ベイズ統計を学び直した記録を書いています。
Rethinking Hexagonal Architecture with Elegant Objects Hexagonal architecture provides a useful foundation: business behavior should remain independent of the technologies used to expose it or persist its state. Ports define the interactions; adapters connect them to the outside world. Yet this separation does not, by itself, tell us where behavior belongs. One common implementation keeps the dependency directions of hexagonal architecture but puts most business decisions in use cases and services. Domain objects then act mainly as data structures, while other objects interpret and change their state. That is a valid design in some contexts, but it is not the only way to apply hexagonal architecture. Reading Elegant Objects challenged how I approached this design. Its emphasis on encapsulation, composition, and small objects led me to ask: what changes when domain objects own more of the behavior that depends on their state? I will explore that question through an e-commerce cart. The examples are deliberately small: supporting types and repetitive delegation methods are omitted. Start with the responsibility The usual suspects A common implementation of “add an item to the cart” might distribute the work across: - AddItemToCartUseCase, a port, and its implementation; - CartController, a primary adapter that receives AddItemRequestDto; - CartRepository, a secondary port, and JpaCartRepository, its adapter; - CartEntity, the JPA model, and Cart, the domain model, connected by CartMapper; - CartDto, which carries the response. This can involve around ten types for one operation. Each can have a legitimate purpose. A persistence model may protect the domain from ORM constraints. A DTO may define a stable API contract. An application service may coordinate objects and transactions. The design becomes harder to follow when those types divide one concept's behavior among them. For example, Cart may expose data while a use case decides what that data means and how it can change. Hexagonal architecture does not require this arrangement. It permits a rich domain model. The question is whether our implementation makes that model visible. Before adding another layer, I want to establish what a cart is responsible for. Give the cart an explicit contract For this example, a cart knows its contents and total. It can accept or remove an item and initiate checkout: public interface Item { String sku(); Money price(); } public interface Items { Iterable<Item> all(); Item item(String sku); } public interface Cart { Items items(); Money total(); void add(Item item); void remove(Item item); Order checkout(Payment payment); } The examples below omit repetitive methods such as remove() and checkout(), along with error handling and transaction boundaries. The caller asks the cart to perform an operation. It does not retrieve a mutable list, modify it, and ask a repository to save the result. This gives the cart a boundary for rules about its contents. The distinction goes beyond method names. Renaming getTotal() to total() changes little if the calculation still lives elsewhere. What matters is that the cart owns the meaning of its total and hides how it is obtained. The contract exposes the values needed by callers through sku() and price(), while keeping cart operations behind its own boundary. Each item represents one cart entry. Quantities, discounts, and price changes belong in explicit concepts such as a CartLine. checkout() makes the cart responsible for coordinating payment and order creation. An application service can coordinate the wider workflow, while the cart owns rules about its contents. Keep persistence behind the contract Once the behavior is defined, we can choose how to implement it. Here, PgCart represents a user's cart stored in PostgreSQL: public final class PgCart implements Cart { private final DataSource db; private final long user; public PgCart(final DataSource db, final long user) { this.db = db; this.user = user; } @Override public void add(final Item item) { new JdbcSession(this.db) .sql("INSERT INTO cart_item (user_id, sku) VALUES (?, ?)") .set(this.user) .set(item.sku()) .insert(Outcome.VOID); } // Other Cart methods omitted. } The constructor captures the dependencies and the cart's identity. Database work happens when an operation is invoked. This implementation removes the need for a separate persistence entity and its mapper in this example. PgCart encapsulates access to persistent state and exposes it through the Cart contract. It is not a persistence-ignorant domain object: it combines domain behavior with database access, and a call such as items() may involve database queries. One important distinction: its fields are final, but the cart's observable state can change. Adding an item changes the database, and a later call may return different contents. Fixed references do not make a persistent object an immutable value, nor do they guarantee thread safety. The Cart contract knows nothing about SQL or DataSource, and PgCart depends on that contract. Cart is a domain abstraction, not an application port. PgCart is both a secondary adapter and a domain object: the dependency points inward, but domain behavior and persistence are deliberately combined. The hexagon remains at the system boundary: TkCart translates HTTP requests, and PgCart connects the cart to PostgreSQL. Inside that boundary, this design keeps the domain abstraction and persistence behavior together rather than introducing a separate persistence port. The roles in this example are: - TkCart is the primary HTTP adapter. - JsonCart defines the JSON representation. - Cart, NonEmptyCart, and LoggedCart form the domain contract and its decorators. - PgCart is both the secondary PostgreSQL adapter and a domain object. - FakeCart is a test implementation of Cart. Compose behavior around the object With a behavioral interface in place, additional responsibilities can be expressed through composition. Suppose checkout must reject an empty cart. A decorator can make that rule explicit: public final class NonEmptyCart implements Cart { private final Cart origin; public NonEmptyCart(final Cart origin) { this.origin = origin; } @Override public Order checkout(final Payment payment) { if (!this.origin.items().all().iterator().hasNext()) { throw new IllegalStateException("The cart is empty"); } return this.origin.checkout(payment); } // Other Cart methods delegate to origin. } We can then assemble the cart where the application's dependencies are wired: new LoggedCart( new NonEmptyCart( new PgCart(db, user) ), log ) The composition shows which responsibilities apply: persistent storage, an empty-cart check, and logging. Each wrapper depends on Cart, so its behavior can be tested with a small fake implementation such as FakeCart. Order matters: logging surrounds validation and records rejected checkout attempts as well as successful ones. The empty-cart check and checkout are separate calls, so another request could modify the cart between them. PgCart must enforce the invariant atomically with the relevant database changes. A real checkout must also account for payment failures and retries. A database transaction cannot atomically include an external payment provider. Moving behavior into objects makes these responsibilities easier to locate, but does not remove them. Give HTTP representation its own responsibility The next boundary is the API response. The domain should remain independent of HTTP and JSON libraries. The Takes framework's RsJSON example provides a useful starting point: a response wraps an object capable of producing JSON. I use that approach through a dedicated representation object, keeping framework dependencies outside the Cart contract. public final class JsonItem implements RsJSON.Source { private final Item item; public JsonItem(final Item item) { this.item = item; } @Override public JsonObject toJSON() { return Json.createObjectBuilder() .add("sku", this.item.sku()) .add("price", this.item.price().toString()) .build(); } } public final class JsonCart implements RsJSON.Source { private final Cart cart; public JsonCart(final Cart cart) { this.cart = cart; } @Override public JsonObject toJSON() { final JsonArrayBuilder items = Json.createArrayBuilder(); for (final Item item : this.cart.items().all()) { items.add(new JsonItem(item).toJSON()); } return Json.createObjectBuilder() .add("total", this.cart.total().toString()) .add("items", items) .build(); } } JsonCart owns the response structure. It wraps a cart instead of copying its values into a second model before serialization. Formatting money as a string keeps this example short; a public API may need separate amount and currency fields. The HTTP entry point wires the same decorators around the persistent cart before returning the representation: public final class TkCart implements Take { private final DataSource db; private final Logger log; public TkCart(final DataSource db, final Logger log) { this.db = db; this.log = log; } @Override public Response act(final Request req) throws Exception { return new RsJSON( new JsonCart( new LoggedCart( new NonEmptyCart( new PgCart(this.db, new UserOf(req).id()) ), this.log ) ) ); } } TkCart receives the shared database connection and logger when it is constructed. For each request, it extracts the user's id, builds the same LoggedCart(NonEmptyCart(PgCart(...))) composition, and wraps it in the JSON representation. The HTTP adapter connects request identity, cart behavior, and response without making the domain contract depend on HTTP. The response would look something like this, with illustrative prices for IT books about Elegant Objects and hexagonal architecture: { "total": "69.98 EUR", "items": [ { "sku": "ELEGANT-OBJECTS", "price": "34.99 EUR" }, { "sku": "HEXAGONAL-ARCHITECTURE", "price": "34.99 EUR" } ] } This gives the API representation a clear place to evolve. A different response format can be introduced without changing Cart. The mapping responsibility still exists; it now belongs to an object that produces the representation directly. An alternative is to pass the result of Json.createObjectBuilder() to a mapToJson method on Item and Cart. Each object would then choose which parts of its state to expose, at the cost of coupling business interfaces to the JSON API. Here, JsonItem and JsonCart keep that representation responsibility outside the domain contract. A DTO remains useful when an explicit snapshot is required. In this example, items() and total() are separate reads, so concurrent changes could produce an inconsistent response. A DTO alone does not solve that problem; the implementation needs a consistent read strategy or snapshot if the API promises that both describe the same instant. Use names to communicate responsibility Some naming rules of thumb I now follow: - Prefer names that say what an object is. Names ending in -er, such as Controller, Mapper, Manager, or Handler, describe what an object does to others. They point toward procedures organized around someone else's data. Prefer the underlying concept: Payment, Outbox, or Cart. - Use technology prefixes to distinguish implementations. PgCart identifies the PostgreSQL implementation; TkCart and RsJSON identify framework-facing objects. StripePayment is clearer than CardPayment when naming an implementation tied to a payment provider. In a codebase using Takes, Fk already means Fork (FkRegex, FkTypes), while Takes calls its test doubles RqFake and RsFake; FakeCart avoids giving one prefix two meanings. Prefixes help only when the team shares their meaning. - Use adjectives to describe decorators. NonEmptyCart and LoggedCart make added behavior visible; CachedItems would communicate the same idea for a cached collection. The other names in this example follow the same intent: Cart defines the business contract, JsonCart identifies an API representation, and FakeCart provides a test implementation. Evaluate the trade-offs The main benefit of this experiment is a more direct relationship between a business concept and its behavior. We can find cart operations through Cart, persistence through PgCart, and response formatting through JsonCart. This does not guarantee fewer classes. Decorators and representation objects can increase their number. The useful measure is how much of the system a developer must understand to change a rule safely. Several constraints deserve attention: - Data access: small objects can hide repeated queries. Measure query behavior and choose batching or a deliberate loading strategy. Caching requires a clear lifetime and invalidation policy. - Consistency: composition does not define transaction boundaries. Business invariants must hold under concurrent access and partial failure. - Testability: FakeCart lets tests exercise decorators such as NonEmptyCart without infrastructure. PgCart owns database-backed behavior, including total(), so tests of those rules require PostgreSQL. This design makes some behavior easy to test in isolation; it does not make the persistent cart infrastructure-free. - Framework integration: frameworks built around mutable entities, reflection, and generated mappings may make this approach costly to introduce. - Navigation: many small objects can reduce local complexity while making the overall behavior harder to follow. Shared conventions and selective decomposition matter. For an existing application, start with one bounded behavior and assess the result: can the team locate the rule more easily, test the right boundary, and understand the dependencies? Those answers provide better guidance than adherence to a naming rule or a target class count. Bring the question back to your code Choose a business concept in your application: a cart, an invoice, or an order. Follow one operation from the entry point to persistence and identify where each decision is made. Does the object own its rules, or must its callers understand its internal state to apply them? When a decision depends on the object's own state, moving it behind the object's contract can strengthen encapsulation. When an operation coordinates several objects or external systems, an application service may still be the clearest place for that coordination. Hexagonal architecture helps us control the boundary between the application and its environment. Elegant Objects encourages us to examine the responsibilities within that boundary. Together, they offer a useful way to question familiar structures and make design choices explicit. The goal is a system whose behavior can be located, understood, and changed with confidence.
Working with tiny maps in Rust? `micromap` is a lightning-fast linear map alternative that skips the heap entirely and avoids hashing altogether, making it blazing fast for maps under 20 items. 🦀 yegor256/micromap