Communitygithub.com

Kotlin Exposed パターン

JetBrains Exposed ORMのパターン集。DSLクエリ、DAOパターン、トランザクション、HikariCPコネクションプーリング、Flywayマイグレーション、リポジトリパターンを網羅。Exposed ORM(DSLまたはDAOクエリ、トランザクション、プーリング、マイグレーション)を扱う際に使用します。

Kotlin Exposed パターン とは?

このスキルは、JetBrains Exposed ORMを使用したデータベースアクセスに関する包括的なパターンを提供します。DSLクエリとDAOパターンの両方をサポートし、HikariCPによるコネクションプーリング、Flywayによるデータベースマイグレーション、リポジトリパターンによるビジネスロジックの分離を実現します。コルーチンセーフなトランザクション処理、基本的なCRUD操作から複雑な結合、集計、ページネーション、バッチ処理まで、実戦的なコード例が含まれています。このスキルはKotlinとExposed ORMを使用するあらゆるプロジェクトで活用でき、Spring Boot、Ktor、その他のKotlinフレームワークと組み合わせて使用できます。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/kotlin-exposed-patterns

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

Kotlin Exposed パターン は何をしますか?

JetBrains Exposed ORMを使用したデータベースアクセスのための包括的なパターン集。DSLクエリ、DAO、トランザクション、本番環境対応の設定を網羅します。

いつ使用するか

  • Exposedを使用したデータベースアクセスのセットアップ
  • Exposed DSLまたはDAOを使用したSQLクエリの記述
  • HikariCPを使用したコネクションプーリングの構成
  • Flywayを使用したデータベースマイグレーションの作成
  • Exposedを使用したリポジトリパターンの実装
  • JSONカラムと複雑なクエリの処理

仕組み

Exposedは、DSL(直接的なSQLライクな式)とDAO(エンティティライフサイクル管理)の2つのクエリスタイルを提供します。HikariCPは、HikariConfigを介して構成された再利用可能なデータベース接続のプールを管理します。Flywayは、起動時にバージョン管理されたSQLマイグレーションスクリプトを実行し、スキーマを同期させます。すべてのデータベース操作は、コルーチンセーフティとアトミック性のためにnewSuspendedTransactionブロック内で実行されます。リポジトリパターンは、Exposedクエリをインターフェースの背後にラップすることで、ビジネスロジックをデータ層から疎結合に保ち、テストではインメモリH2データベースを使用できます。

例

DSLクエリ

suspend fun findUserById(id: UUID): UserRow? =
    newSuspendedTransaction {
        UsersTable.selectAll()
            .where { UsersTable.id eq id }
            .map { it.toUser() }
            .singleOrNull()
    }

DAOエンティティの使用

suspend fun createUser(request: CreateUserRequest): User =
    newSuspendedTransaction {
        UserEntity.new {
            name = request.name
            email = request.email
            role = request.role
        }.toModel()
    }

HikariCP構成

val hikariConfig = HikariConfig().apply {
    driverClassName = config.driver
    jdbcUrl = config.url
    username = config.username
    password = config.password
    maximumPoolSize = config.maxPoolSize
    isAutoCommit = false
    transactionIsolation = "TRANSACTION_READ_COMMITTED"
    validate()
}

データベースセットアップ

HikariCPコネクションプーリング

// DatabaseFactory.kt
object DatabaseFactory {
    fun create(config: DatabaseConfig): Database {
        val hikariConfig = HikariConfig().apply {
            driverClassName = config.driver
            jdbcUrl = config.url
            username = config.username
            password = config.password
            maximumPoolSize = config.maxPoolSize
            isAutoCommit = false
            transactionIsolation = "TRANSACTION_READ_COMMITTED"
            validate()
        }

        return Database.connect(HikariDataSource(hikariConfig))
    }
}

data class DatabaseConfig(
    val url: String,
    val driver: String = "org.postgresql.Driver",
    val username: String = "",
    val password: String = "",
    val maxPoolSize: Int = 10,
)

Flywayマイグレーション

// FlywayMigration.kt
fun runMigrations(config: DatabaseConfig) {
    Flyway.configure()
        .dataSource(config.url, config.username, config.password)
        .locations("classpath:db/migration")
        .baselineOnMigrate(true)
        .load()
        .migrate()
}

// アプリケーション起動
fun Application.module() {
    val config = DatabaseConfig(
        url = environment.config.property("database.url").getString(),
        username = environment.config.property("database.username").getString(),
        password = environment.config.property("database.password").getString(),
    )
    runMigrations(config)
    val database = DatabaseFactory.create(config)
    // ...
}

マイグレーションファイル

-- src/main/resources/db/migration/V1__create_users.sql
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(100) NOT NULL,
    email VARCHAR(255) NOT NULL UNIQUE,
    role VARCHAR(20) NOT NULL DEFAULT 'USER',
    metadata JSONB,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_role ON users(role);

テーブル定義

DSLスタイルのテーブル

// tables/UsersTable.kt
object UsersTable : UUIDTable("users") {
    val name = varchar("name", 100)
    val email = varchar("email", 255).uniqueIndex()
    val role = enumerationByName<Role>("role", 20)
    val metadata = jsonb<UserMetadata>("metadata", Json.Default).nullable()
    val createdAt = timestampWithTimeZone("created_at").defaultExpression(CurrentTimestampWithTimeZone)
    val updatedAt = timestampWithTimeZone("updated_at").defaultExpression(CurrentTimestampWithTimeZone)
}

object OrdersTable : UUIDTable("orders") {
    val userId = uuid("user_id").references(UsersTable.id)
    val status = enumerationByName<OrderStatus>("status", 20)
    val totalAmount = long("total_amount")
    val currency = varchar("currency", 3)
    val createdAt = timestampWithTimeZone("created_at").defaultExpression(CurrentTimestampWithTimeZone)
}

object OrderItemsTable : UUIDTable("order_items") {
    val orderId = uuid("order_id").references(OrdersTable.id, onDelete = ReferenceOption.CASCADE)
    val productId = uuid("product_id")
    val quantity = integer("quantity")
    val unitPrice = long("unit_price")
}

複合テーブル

object UserRolesTable : Table("user_roles") {
    val userId = uuid("user_id").references(UsersTable.id, onDelete = ReferenceOption.CASCADE)
    val roleId = uuid("role_id").references(RolesTable.id, onDelete = ReferenceOption.CASCADE)
    override val primaryKey = PrimaryKey(userId, roleId)
}

DSLクエリ

基本的なCRUD

// 挿入
suspend fun insertUser(name: String, email: String, role: Role): UUID =
    newSuspendedTransaction {
        UsersTable.insertAndGetId {
            it[UsersTable.name] = name
            it[UsersTable.email] = email
            it[UsersTable.role] = role
        }.value
    }

// IDによる選択
suspend fun findUserById(id: UUID): UserRow? =
    newSuspendedTransaction {
        UsersTable.selectAll()
            .where { UsersTable.id eq id }
            .map { it.toUser() }
            .singleOrNull()
    }

// 条件付き選択
suspend fun findActiveAdmins(): List<UserRow> =
    newSuspendedTransaction {
        UsersTable.selectAll()
            .where { (UsersTable.role eq Role.ADMIN) }
            .orderBy(UsersTable.name)
            .map { it.toUser() }
    }

// 更新
suspend fun updateUserEmail(id: UUID, newEmail: String): Boolean =
    newSuspendedTransaction {
        UsersTable.update({ UsersTable.id eq id }) {
            it[email] = newEmail
            it[updatedAt] = CurrentTimestampWithTimeZone
        } > 0
    }

// 削除
suspend fun deleteUser(id: UUID): Boolean =
    newSuspendedTransaction {
        UsersTable.deleteWhere { UsersTable.id eq id } > 0
    }

// 行マッピング
private fun ResultRow.toUser() = UserRow(
    id = this[UsersTable.id].value,
    name = this[UsersTable.name],
    email = this[UsersTable.email],
    role = this[UsersTable.role],
    metadata = this[UsersTable.metadata],
    createdAt = this[UsersTable.createdAt],
    updatedAt = this[UsersTable.updatedAt],
)

高度なクエリ

// 結合クエリ
suspend fun findOrdersWithUser(userId: UUID): List<OrderWithUser> =
    newSuspendedTransaction {
        (OrdersTable innerJoin UsersTable)
            .selectAll()
            .where { OrdersTable.userId eq userId }
            .orderBy(OrdersTable.createdAt, SortOrder.DESC)
            .map { row ->
                OrderWithUser(
                    orderId = row[OrdersTable.id].value,
                    status = row[OrdersTable.status],
                    totalAmount = row[OrdersTable.totalAmount],
                    userName = row[UsersTable.name],
                )
            }
    }

// 集計
suspend fun countUsersByRole(): Map<Role, Long> =
    newSuspendedTransaction {
        UsersTable
            .select(UsersTable.role, UsersTable.id.count())
            .groupBy(UsersTable.role)
            .associate { row ->
                row[UsersTable.role] to row[UsersTable.id.count()]
            }
    }

// サブクエリ
suspend fun findUsersWithOrders(): List<UserRow> =
    newSuspendedTransaction {
        UsersTable.selectAll()
            .where {
                UsersTable.id inSubQuery
                    OrdersTable.select(OrdersTable.userId).withDistinct()
            }
            .map { it.toUser() }
    }

// LIKEとパターンマッチング — ワイルドカードインジェクションを防ぐため、ユーザー入力を常にエスケープする
private fun escapeLikePattern(input: String): String =
    input.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")

suspend fun searchUsers(query: String): List<UserRow> =
    newSuspendedTransaction {
        val sanitized = escapeLikePattern(query.lowercase())
        UsersTable.selectAll()
            .where {
                (UsersTable.name.lowerCase() like "%${sanitized}%") or
                    (UsersTable.email.lowerCase() like "%${sanitized}%")
            }
            .map { it.toUser() }
    }

ページネーション

data class Page<T>(
    val data: List<T>,
    val total: Long,
    val page: Int,
    val limit: Int,
) {
    val totalPages: Int get() = ((total + limit - 1) / limit).toInt()
    val hasNext: Boolean get() = page < totalPages
    val hasPrevious: Boolean get() = page > 1
}

suspend fun findUsersPaginated(page: Int, limit: Int): Page<UserRow> =
    newSuspendedTransaction {
        val total = UsersTable.selectAll().count()
        val data = UsersTable.selectAll()
            .orderBy(UsersTable.createdAt, SortOrder.DESC)
            .limit(limit)
            .offset(((page - 1) * limit).toLong())
            .map { it.toUser() }

        Page(data = data, total = total, page = page, limit = limit)
    }

バッチ操作

// バッチ挿入
suspend fun insertUsers(users: List<CreateUserRequest>): List<UUID> =
    newSuspendedTransaction {
        UsersTable.batchInsert(users) { user ->
            this[UsersTable.name] = user.name
            this[UsersTable.email] = user.email
            this[UsersTable.role] = user.role
        }.map { it[UsersTable.id].value }
    }

// Upsert(競合時に挿入または更新)
suspend fun upsertUser(id: UUID, name: String, email: String) {
    newSuspendedTransaction {
        UsersTable.upsert(UsersTable.email) {
            it[UsersTable.id] = EntityID(id, UsersTable)
            it[UsersTable.name] = name
            it[UsersTable.email] = email
            it[updatedAt] = CurrentTimestampWithTimeZone
        }
    }
}

DAOパターン

エンティティ定義

// entities/UserEntity.kt
class UserEntity(id: EntityID<UUID>) : UUIDEntity(id) {
    companion object : UUIDEntityClass<UserEntity>(UsersTable)

    var name by UsersTable.name
    var email by UsersTable.email
    var role by UsersTable.role
    var metadata by UsersTable.metadata
    var createdAt by UsersTable.createdAt
    var updatedAt by UsersTable.updatedAt

    val orders by OrderEntity referrersOn OrdersTable.userId

    fun toModel(): User = User(
        id = id.value,
        name = name,
        email = email,
        role = role,
        metadata = metadata,
        createdAt = createdAt,
        updatedAt = updatedAt,
    )
}

class OrderEntity(id: EntityID<UUID>) : UUIDEntity(id) {
    companion object : UUIDEntityClass<OrderEntity>(OrdersTable)

    var user by UserEntity referencedOn OrdersTable.userId
    var status by OrdersTable.status
    var totalAmount by OrdersTable.totalAmount
    var currency by OrdersTable.currency
    var createdAt by OrdersTable.createdAt

    val items by OrderItemEntity referrersOn OrderItemsTable.orderId
}

DAO操作

suspend fun findUserByEmail(email: String): User? =
    newSuspendedTransaction {
        UserEntity.find { UsersTable.email eq email }
            .firstOrNull()
            ?.toModel()
    }

suspend fun createUser(request: CreateUserRequest): User =
    newSuspendedTransaction {
        UserEntity.new {
            name = request.name
            email = request.email
            role = request.role
        }.toModel()
    }

suspend fun updateUser(id: UUID, request: UpdateUserRequest): User? =
    newSuspendedTransaction {
        UserEntity.findById(id)?.apply {
            request.name?.let { name = it }
            request.email?.let { email = it }
            updatedAt = OffsetDateTime.now(ZoneOffset.UTC)
        }?.toModel()
    }

トランザクション

サスペンドトランザクションのサポート

// 良い: コルーチンサポートのために newSuspendedTransaction を使用
suspend fun performDatabaseOperation(): Result<User> =
    runCatching {
        newSuspendedTransaction {
            val user = UserEntity.new {
                name = "Alice"
                email = "[email protected]"
            }
            // このブロック内のすべての操作はアトミックです
            user.toModel()
        }
    }

// 良い: セーブポイントを使ったネストされたトランザクション
suspend fun transferFunds(fromId: UUID, toId: UUID, amount: Long) {
    newSuspendedTransaction {
        val from = UserEntity.findById(fromId) ?: throw NotFoundException("User $fromId not found")
        val to = UserEntity.findById(toId) ?: throw NotFoundException("User $toId not found")

        // 引き落とし
        from.balance -= amount
        // 入金
        to.balance += amount

        // 両方成功するか、両方失敗するか
    }
}

トランザクション分離

suspend fun readCommittedQuery(): List<User> =
    newSuspendedTransaction(transactionIsolation = Connection.TRANSACTION_READ_COMMITTED) {
        UserEntity.all().map { it.toModel() }
    }

suspend fun serializableOperation() {
    newSuspendedTransaction(transactionIsolation = Connection.TRANSACTION_SERIALIZABLE) {
        // クリティカルな操作のための最も厳格な分離レベル
    }
}

リポジトリパターン

インターフェース定義

interface UserRepository {
    suspend fun findById(id: UUID): User?
    suspend fun findByEmail(email: String): User?
    suspend fun findAll(): List<User>
    suspend fun save(user: User): User
    suspend fun delete(id: UUID)
}

実装

class ExposedUserRepository : UserRepository {
    override suspend fun findById(id: UUID): User? = newSuspendedTransaction {
        UserEntity.findById(id)?.toModel()
    }

    override suspend fun findByEmail(email: String): User? = newSuspendedTransaction {
        UserEntity.find { UsersTable.email eq email }.firstOrNull()?.toModel()
    }

    override suspend fun findAll(): List<User> = newSuspendedTransaction {
        UserEntity.all().map { it.toModel() }
    }

    override suspend fun save(user: User): User = newSuspendedTransaction {
        val entity = if (user.id != null) {
            UserEntity.findById(UUID.fromString(user.id))?.apply {
                name = user.name
                email = user.email
                role = user.role
            } ?: throw NotFoundException("User with id ${user.id} not found")
        } else {
            UserEntity.new {
                name = user.name
                email = user.email
                role = user.role
            }
        }
        entity.toModel()
    }

    override suspend fun delete(id: UUID) {
        newSuspendedTransaction {
            UserEntity.findById(id)?.delete() ?: throw NotFoundException("User with id $id not found")
        }
    }
}

JSONカラムの処理

カスタムカラムタイプ

// カスタムJSONカラムタイプの定義
class JsonbColumn<T>(private val columnName: String, private val serializer: KSerializer<T>) : ColumnType() {
    override fun sqlType(): String = "JSONB"
    
    override fun valueFromDB(value: Any): T? {
        val json = when (value) {
            is String -> value
            is PGobject -> value.value
            else -> throw IllegalArgumentException("Unexpected value: $value")
        }
        return Json.Default.decodeFromString(serializer, json ?: "null")
    }
    
    override fun valueToDB(value: Any?): Any? {
        if (value == null) return null
        @Suppress("UNCHECKED_CAST")
        return Json.Default.encodeToString(serializer, value as T)
    }
    
    override fun notNullValueToDB(value: Any): Any {
        @Suppress("UNCHECKED_CAST")
        return Json.Default.encodeToString(serializer, value as T)
    }
}

// テーブルでの使用例
object EventsTable : UUIDTable("events") {
    val eventType = varchar("event_type", 100)
    val payload = jsonb<JsonElement>("payload", Json.Default)
    val createdAt = timestampWithTimeZone("created_at").defaultExpression(CurrentTimestampWithTimeZone)
    
    private fun jsonb<T>(name: String, json: Json) = registerColumn<T>(name, JsonbColumn(name, json.serializersModule.serializer()))
}

JSONBカラムのクエリ

// JSONBカラムの挿入とクエリ
suspend fun createEvent(type: String, payload: JsonElement) = newSuspendedTransaction {
    EventsTable.insert {
        it[eventType] = type
        it[this@createEvent.payload] = payload
    }
}

suspend fun findEventsByType(type: String): List<Event> = newSuspendedTransaction {
    EventsTable.selectAll()
        .where { EventsTable.eventType eq type }
        .map { row ->
            Event(
                id = row[EventsTable.id].value,
                eventType = row[EventsTable.eventType],
                payload = row[EventsTable.payload],
                createdAt = row[EventsTable.createdAt]
            )
        }
}

パフォーマンスに関する考慮事項

N+1クエリ問題を避ける

// 悪い: N+1クエリ — 各ユーザーの注文をフェッチするために個別のクエリを実行
suspend fun getUsersWithOrdersBad(): List<UserWithOrders> = newSuspendedTransaction {
    UserEntity.all().map { user ->
        UserWithOrders(
            user = user.toModel(),
            orders = user.orders.map { it.toModel() }  // ユーザーごとに個別クエリ
        )
    }
}

// 良い: 結合を使用してすべてのデータを一度に取得
suspend fun getUsersWithOrdersGood(): List<UserWithOrders> = newSuspendedTransaction {
    (UsersTable innerJoin OrdersTable)
        .selectAll()
        .map { row ->
            UserWithOrders(
                user = row.toUser(),
                orders = listOf(row.toOrder())  // 結合結果からのデータ
            )
        }
        .groupBy { it.user.id }
        .map { (_, userWithOrders) ->
            UserWithOrders(
                user = userWithOrders.first().user,
                orders = userWithOrders.flatMap { it.orders }
            )
        }
}

バッチ処理

// (前のセクションからの継続)
suspend fun processLargeDataset() = newSuspendedTransaction {
    var offset = 0L
    val batchSize = 1000
    
    while (true) {
        val batch = UsersTable.selectAll()
            .limit(batchSize)
            .offset(offset)
            .map { it.toUser() }
        
        if (batch.isEmpty()) break
        
        // バッチを処理
        processBatch(batch)
        offset += batchSize
    }
}

テスト

// インメモリH2データベースを使用したテスト
class UserRepositoryTest {
    private lateinit var database: Database
    private lateinit var repository: UserRepository
    
    @BeforeEach
    fun setUp() {
        database = Database.connect(
            url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1",
            driver = "org.h2.Driver"
        )
        
        // テーブルを作成
        transaction {
            SchemaUtils.create(UsersTable)
        }
        
        repository = ExposedUserRepository()
    }
    
    @Test
    fun `should create and find user`() = runBlocking {
        val user = repository.save(User(name = "Test", email = "[email protected]"))
        val found = repository.findById(user.id!!)
        assertEquals(user.name, found?.name)
    }
    
    @AfterEach
    fun tearDown() {
        transaction {
            SchemaUtils.drop(UsersTable)
        }
    }
}

ベストプラクティス

  1. トランザクション管理: 常に newSuspendedTransaction を使用して、コルーチンセーフなトランザクションを確保します。特に指定がない限り、分離レベルは TRANSACTION_READ_COMMITTED を使用します。

  2. 接続プール: 本番環境ではHikariCPを使用し、プールサイズやタイムアウトなどの設定を適切に調整します。

  3. マイグレーション: スキーマのバージョン管理にFlywayを使用し、マイグレーションは冪等にし、必要に応じてベースラインを設定します。

  4. リポジトリパターン: ビジネスロジックをデータアクセスから分離し、テスタビリティを向上させるためにリポジトリパターンを実装します。

  5. パフォーマンス: N+1クエリ問題を回避し、大規模なデータセットにはバッチ処理を使用します。

  6. セキュリティ: LIKEクエリでユーザー入力をエスケープして、ワイルドカードインジェクションを防ぎます。プレースホルダーを使用してSQLインジェクションを防止します。

  7. テスト: シンプルで高速なテストのためにインメモリH2データベースを使用します。各テスト実行前にテストデータベースがクリーンな状態であることを確認します。

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

関連スキル