Communitygithub.com

Ktor サーバーパターン

ルーティング DSL、プラグイン、認証、Koin DI、kotlinx.serialization、WebSocket、testApplication テストを含む Ktor サーバーパターンです。Ktor サーバー(ルーティング、プラグイン、認証、DI、シリアライゼーション、テスト)を構築する際に使用します。

Ktor サーバーパターン とは?

このスキルは、Kotlin コルーチンを使用して堅牢で保守性の高い HTTP サーバーを構築するための包括的な Ktor パターンを提供します。推奨されるプロジェクト構造、ルーティング DSL、ContentNegotiation と kotlinx.serialization を使用したシリアライゼーション、JWT 認証、StatusPages によるエラーハンドリング、CORS 設定、Koin を使用した依存性注入、WebSocket、リクエスト検証、testApplication を使用したテストなど、主要な概念をカバーしています。各パターンは実際のコード例とともに提示されており、開発者がベストプラクティスを素早く実装できるようにします。このスキルは、Ktor を使用するあらゆるプラットフォーム(IntelliJ IDEA、VS Code、その他の Kotlin 対応エディタ)で使用でき、フルスタックの Web アプリケーション、REST API、リアルタイムサービス、またはマイクロサービスの構築に役立ちます。これらのパターンは再利用可能で設定可能なため、Ktor プロジェクトの標準的な土台としてすぐに使用できます。

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

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

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

ドキュメント

Ktor サーバーパターン は何をしますか?

Kotlin コルーチンを使用して堅牢で保守性の高い HTTP サーバーを構築するための包括的な Ktor パターン。

アクティブ化のタイミング

  • Ktor HTTP サーバーの構築
  • Ktor プラグインの設定(Auth、CORS、ContentNegotiation、StatusPages)
  • Ktor を使用した REST API の実装
  • Koin を使用した依存性注入の設定
  • testApplication を使用した Ktor 統合テストの作成
  • Ktor での WebSocket の使用

アプリケーション構造

標準的な Ktor プロジェクトレイアウト

src/main/kotlin/
├── com/example/
│   ├── Application.kt           # エントリポイント、モジュール設定
│   ├── plugins/
│   │   ├── Routing.kt           # ルート定義
│   │   ├── Serialization.kt     # コンテントネゴシエーションの設定
│   │   ├── Authentication.kt    # 認証設定
│   │   ├── StatusPages.kt       # エラーハンドリング
│   │   └── CORS.kt              # CORS 設定
│   ├── routes/
│   │   ├── UserRoutes.kt        # /users エンドポイント
│   │   ├── AuthRoutes.kt        # /auth エンドポイント
│   │   └── HealthRoutes.kt      # /health エンドポイント
│   ├── models/
│   │   ├── User.kt              # ドメインモデル
│   │   └── ApiResponse.kt       # 応答エンベロープ
│   ├── services/
│   │   ├── UserService.kt       # ビジネスロジック
│   │   └── AuthService.kt       # 認証ロジック
│   ├── repositories/
│   │   ├── UserRepository.kt    # データアクセスインターフェース
│   │   └── ExposedUserRepository.kt
│   └── di/
│       └── AppModule.kt         # Koin モジュール
src/test/kotlin/
├── com/example/
│   ├── routes/
│   │   └── UserRoutesTest.kt
│   └── services/
│       └── UserServiceTest.kt

アプリケーションエントリポイント

// Application.kt
fun main() {
    embeddedServer(Netty, port = 8080, module = Application::module).start(wait = true)
}

fun Application.module() {
    configureSerialization()
    configureAuthentication()
    configureStatusPages()
    configureCORS()
    configureDI()
    configureRouting()
}

ルーティング DSL

基本的なルート

// plugins/Routing.kt
fun Application.configureRouting() {
    routing {
        userRoutes()
        authRoutes()
        healthRoutes()
    }
}

// routes/UserRoutes.kt
fun Route.userRoutes() {
    val userService by inject<UserService>()

    route("/users") {
        get {
            val users = userService.getAll()
            call.respond(users)
        }

        get("/{id}") {
            val id = call.parameters["id"]
                ?: return@get call.respond(HttpStatusCode.BadRequest, "Missing id")
            val user = userService.getById(id)
                ?: return@get call.respond(HttpStatusCode.NotFound)
            call.respond(user)
        }

        post {
            val request = call.receive<CreateUserRequest>()
            val user = userService.create(request)
            call.respond(HttpStatusCode.Created, user)
        }

        put("/{id}") {
            val id = call.parameters["id"]
                ?: return@put call.respond(HttpStatusCode.BadRequest, "Missing id")
            val request = call.receive<UpdateUserRequest>()
            val user = userService.update(id, request)
                ?: return@put call.respond(HttpStatusCode.NotFound)
            call.respond(user)
        }

        delete("/{id}") {
            val id = call.parameters["id"]
                ?: return@delete call.respond(HttpStatusCode.BadRequest, "Missing id")
            val deleted = userService.delete(id)
            if (deleted) call.respond(HttpStatusCode.NoContent)
            else call.respond(HttpStatusCode.NotFound)
        }
    }
}

認証付きルートの整理

fun Route.userRoutes() {
    route("/users") {
        // 公開ルート
        get { /* ユーザー一覧 */ }
        get("/{id}") { /* ユーザー取得 */ }

        // 保護されたルート
        authenticate("jwt") {
            post { /* ユーザー作成 - 認証必要 */ }
            put("/{id}") { /* ユーザー更新 - 認証必要 */ }
            delete("/{id}") { /* ユーザー削除 - 認証必要 */ }
        }
    }
}

コンテントネゴシエーションとシリアライゼーション

kotlinx.serialization のセットアップ

// plugins/Serialization.kt
fun Application.configureSerialization() {
    install(ContentNegotiation) {
        json(Json {
            prettyPrint = true
            isLenient = false
            ignoreUnknownKeys = true
            encodeDefaults = true
            explicitNulls = false
        })
    }
}

シリアライズ可能なモデル

@Serializable
data class UserResponse(
    val id: String,
    val name: String,
    val email: String,
    val role: Role,
    @Serializable(with = InstantSerializer::class)
    val createdAt: Instant,
)

@Serializable
data class CreateUserRequest(
    val name: String,
    val email: String,
    val role: Role = Role.USER,
)

@Serializable
data class ApiResponse<T>(
    val success: Boolean,
    val data: T? = null,
    val error: String? = null,
) {
    companion object {
        fun <T> ok(data: T): ApiResponse<T> = ApiResponse(success = true, data = data)
        fun <T> error(message: String): ApiResponse<T> = ApiResponse(success = false, error = message)
    }
}

@Serializable
data class PaginatedResponse<T>(
    val data: List<T>,
    val total: Long,
    val page: Int,
    val limit: Int,
)

カスタムシリアライザー

object InstantSerializer : KSerializer<Instant> {
    override val descriptor = PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING)
    override fun serialize(encoder: Encoder, value: Instant) =
        encoder.encodeString(value.toString())
    override fun deserialize(decoder: Decoder): Instant =
        Instant.parse(decoder.decodeString())
}

認証

JWT 認証

// plugins/Authentication.kt
fun Application.configureAuthentication() {
    val jwtSecret = environment.config.property("jwt.secret").getString()
    val jwtIssuer = environment.config.property("jwt.issuer").getString()
    val jwtAudience = environment.config.property("jwt.audience").getString()
    val jwtRealm = environment.config.property("jwt.realm").getString()

    install(Authentication) {
        jwt("jwt") {
            realm = jwtRealm
            verifier(
                JWT.require(Algorithm.HMAC256(jwtSecret))
                    .withAudience(jwtAudience)
                    .withIssuer(jwtIssuer)
                    .build()
            )
            validate { credential ->
                if (credential.payload.audience.contains(jwtAudience)) {
                    JWTPrincipal(credential.payload)
                } else {
                    null
                }
            }
            challenge { _, _ ->
                call.respond(HttpStatusCode.Unauthorized, ApiResponse.error<Unit>("Invalid or expired token"))
            }
        }
    }
}

// JWT からユーザーを抽出
fun ApplicationCall.userId(): String =
    principal<JWTPrincipal>()
        ?.payload
        ?.getClaim("userId")
        ?.asString()
        ?: throw AuthenticationException("No userId in token")

認証ルート

fun Route.authRoutes() {
    val authService by inject<AuthService>()

    route("/auth") {
        post("/login") {
            val request = call.receive<LoginRequest>()
            val token = authService.login(request.email, request.password)
                ?: return@post call.respond(
                    HttpStatusCode.Unauthorized,
                    ApiResponse.error<Unit>("Invalid credentials"),
                )
            call.respond(ApiResponse.ok(TokenResponse(token)))
        }

        post("/register") {
            val request = call.receive<RegisterRequest>()
            val user = authService.register(request)
            call.respond(HttpStatusCode.Created, ApiResponse.ok(user))
        }

        authenticate("jwt") {
            get("/me") {
                val userId = call.userId()
                val user = authService.getProfile(userId)
                call.respond(ApiResponse.ok(user))
            }
        }
    }
}

ステータスページ(エラーハンドリング)

// plugins/StatusPages.kt
fun Application.configureStatusPages() {
    install(StatusPages) {
        exception<ContentTransformationException> { call, cause ->
            call.respond(
                HttpStatusCode.BadRequest,
                ApiResponse.error<Unit>("Invalid request body: ${cause.message}"),
            )
        }

        exception<IllegalArgumentException> { call, cause ->
            call.respond(
                HttpStatusCode.BadRequest,
                ApiResponse.error<Unit>(cause.message ?: "Bad request"),
            )
        }

        exception<AuthenticationException> { call, _ ->
            call.respond(
                HttpStatusCode.Unauthorized,
                ApiResponse.error<Unit>("Authentication required"),
            )
        }

        exception<AuthorizationException> { call, _ ->
            call.respond(
                HttpStatusCode.Forbidden,
                ApiResponse.error<Unit>("Access denied"),
            )
        }

        exception<NotFoundException> { call, cause ->
            call.respond(
                HttpStatusCode.NotFound,
                ApiResponse.error<Unit>(cause.message ?: "Resource not found"),
            )
        }

        exception<Throwable> { call, cause ->
            call.application.log.error("Unhandled exception", cause)
            call.respond(
                HttpStatusCode.InternalServerError,
                ApiResponse.error<Unit>("Internal server error"),
            )
        }

        status(HttpStatusCode.NotFound) { call, status ->
            call.respond(status, ApiResponse.error<Unit>("Route not found"))
        }
    }
}

CORS 設定

// plugins/CORS.kt
fun Application.configureCORS() {
    install(CORS) {
        allowHost("localhost:3000")
        allowHost("example.com", schemes = listOf("https"))
        allowHeader(HttpHeaders.ContentType)
        allowHeader(HttpHeaders.Authorization)
        allowMethod(HttpMethod.Put)
        allowMethod(HttpMethod.Delete)
        allowMethod(HttpMethod.Patch)
        allowCredentials = true
        maxAgeInSeconds = 3600
    }
}

Koin 依存性注入

モジュール定義

// di/AppModule.kt
val appModule = module {
    // データベース
    single<Database> { DatabaseFactory.create(get()) }

    // リポジトリ
    single<UserRepository> { ExposedUserRepository(get()) }
    single<OrderRepository> { ExposedOrderRepository(get()) }

    // サービス
    single { UserService(get()) }
    single { OrderService(get(), get()) }
    single { AuthService(get(), get()) }
}

// アプリケーションセットアップ
fun Application.configureDI() {
    install(Koin) {
        modules(appModule)
    }
}

ルートでの Koin の使用

fun Route.userRoutes() {
    val userService by inject<UserService>()

    route("/users") {
        get {
            val users = userService.getAll()
            call.respond(ApiResponse.ok(users))
        }
    }
}

テストでの Koin

class UserServiceTest : FunSpec(), KoinTest {
    override fun extensions() = listOf(KoinExtension(testModule))

    private val testModule = module {
        single<UserRepository> { mockk() }
        single { UserService(get()) }
    }

    private val repository by inject<UserRepository>()
    private val service by inject<UserService>()

    init {
        test("getUser はユーザーを返す") {
            coEvery { repository.findById("1") } returns testUser
            service.getById("1") shouldBe testUser
        }
    }
}

リクエスト検証

// ルートでリクエストデータを検証
fun Route.userRoutes() {
    val userService by inject<UserService>()

    post("/users") {
        val request = call.receive<CreateUserRequest>()

        // 検証
        require(request.name.isNotBlank()) { "名前は必須です" }
        require(request.name.length <= 100) { "名前は100文字以内である必要があります" }
        require(request.email.matches(Regex(".+@.+\\..+"))) { "メールアドレスの形式が無効です" }

        val user = userService.create(request)
        call.respond(HttpStatusCode.Created, ApiResponse.ok(user))
    }
}

// または検証拡張関数を使用
fun CreateUserRequest.validate() {
    require(name.isNotBlank()) { "名前は必須です" }
    require(name.length <= 100) { "名前は100文字以内である必要があります" }
    require(email.matches(Regex(".+@.+\\..+"))) { "メールアドレスの形式が無効です" }
}

WebSocket

fun Application.configureWebSockets() {
    install(WebSockets) {
        pingPeriod = 15.seconds
        timeout = 15.seconds
        maxFrameSize = 64 * 1024 // 64 KiB — プロトコルでより大きなフレームが必要な場合のみ増やす
        masking = false // サーバーからクライアントへのフレームは RFC 6455 に従いマスクされません。クライアントからサーバーへのフレームは常に Ktor によってマスクされます
    }
}

fun Route.chatRoutes() {
    val connections = Collections.synchronizedSet<Connection>(LinkedHashSet())

    webSocket("/chat") {
        val thisConnection = Connection(this)
        connections += thisConnection

        try {
            send("接続しました!オンラインユーザー数: ${connections.size}")

            for (frame in incoming) {
                frame as? Frame.Text ?: continue
                val text = frame.readText()
                val message = ChatMessage(thisConnection.name, text)

                // ロック下でスナップショットを取得して ConcurrentModificationException を回避
                val snapshot = synchronized(connections) { connections.toList() }
                snapshot.forEach { conn ->
                    conn.session.send(Json.encodeToString(message))
                }
            }
        } catch (e: Exception) {
            logger.error("WebSocket エラー", e)
        } finally {
            connections -= thisConnection
        }
    }
}

data class Connection(val session: DefaultWebSocketSession) {
    val name: String = "User-${counter.getAndIncrement()}"

    companion object {
        private val counter = AtomicInteger()
    }
}

@Serializable
data class ChatMessage(
    val user: String,
    val text: String,
    val timestamp: Long = Clock.System.now().toEpochMilliseconds(),
)

テスト

testApplication を使用したテスト

// build.gradle.kts の依存関係
testImplementation("io.ktor:ktor-server-test-host:3.0.3")
testImplementation("io.ktor:ktor-client-content-negotiation:3.0.3")
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("io.mockk:mockk:1.13.13")

// routes/UserRoutesTest.kt
class UserRoutesTest {
    @Test
    fun `GET /users should return list of users`() = testApplication {
        application {
            module()
        }

        val response = client.get("/users")
        assertEquals(HttpStatusCode.OK, response.status)
        assertEquals("""[{"id":"1","name":"John"}]""", response.bodyAsText())
    }

    @Test
    fun `POST /users should create user`() = testApplication {
        application {
            module()
        }

        val response = client.post("/users") {
            contentType(ContentType.Application.Json)
            setBody(CreateUserRequest(name = "Alice", email = "[email protected]"))
        }
        assertEquals(HttpStatusCode.Created, response.status)
    }
}

認証を必要とするエンドポイントのテスト

class AuthenticatedRoutesTest {
    @Test
    fun `POST /users without token should return 401`() = testApplication {
        application {
            module()
        }

        val response = client.post("/users") {
            contentType(ContentType.Application.Json)
            setBody(CreateUserRequest(name = "Bob", email = "[email protected]"))
        }
        assertEquals(HttpStatusCode.Unauthorized, response.status)
    }

    @Test
    fun `POST /users with valid token should succeed`() = testApplication {
        application {
            module()
        }

        val token = generateTestToken()
        val response = client.post("/users") {
            bearerAuth(token)
            contentType(ContentType.Application.Json)
            setBody(CreateUserRequest(name = "Bob", email = "[email protected]"))
        }
        assertEquals(HttpStatusCode.Created, response.status)
    }
}

モックを使用したサービステスト

class UserServiceTest : FunSpec(), KoinTest {
    override fun extensions() = listOf(KoinExtension(testModule))

    private val testModule = module {
        single<UserRepository> { mockk() }
        single { UserService(get()) }
    }

    private val repository by inject<UserRepository>()
    private val service by inject<UserService>()

    init {
        test("getAll はすべてのユーザーを返す") {
            coEvery { repository.findAll() } returns listOf(testUser)
            service.getAll() shouldBe listOf(testUser)
        }

        test("getById は見つかった場合はユーザーを返す") {
            coEvery { repository.findById("1") } returns testUser
            service.getById("1") shouldBe testUser
        }

        test("getById は見つからない場合は null を返す") {
            coEvery { repository.findById("999") } returns null
            service.getById("999") shouldBe null
        }

        test("create はユーザーを作成して返す") {
            val request = CreateUserRequest(name = "Charlie", email = "[email protected]")
            coEvery { repository.save(any()) } returns createdUser
            service.create(request) shouldBe createdUser
        }
    }

    companion object {
        private val testUser = User("1", "Test", "[email protected]", Role.USER, Clock.System.now())
        private val createdUser = User("2", "Charlie", "[email protected]", Role.USER, Clock.System.now())
    }
}

WebSocket テスト

class WebSocketTest {
    @Test
    fun `WebSocket chat should broadcast messages`() = testApplication {
        application {
            module()
        }

        val client1 = createWebSocketSession("/chat")
        val client2 = createWebSocketSession("/chat")

        client1.send("Hello!")
        val message = client2.incoming.receive() as Frame.Text
        assertTrue(message.readText().contains("Hello"))
    }
}

設定管理

// application.conf (HOCON 形式)
ktor {
    deployment {
        port = 8080
    }
    application {
        modules = [ com.example.ApplicationKt.module ]
    }
}

jwt {
    secret = "your-secret-key"
    issuer = "your-app"
    audience = "your-audience"
    realm = "your-realm"
    expiration = 3600000 // 1時間(ミリ秒)
}

database {
    driver = "org.postgresql.Driver"
    url = "jdbc:postgresql://localhost:5432/mydb"
    user = "dbuser"
    password = "dbpass"
}

環境変数

// AppEnvironment.kt
object AppEnvironment {
    val jwtSecret: String by lazy {
        System.getenv("JWT_SECRET") ?: error("JWT_SECRET 環境変数が設定されていません")
    }
    val jwtIssuer: String by lazy {
        System.getenv("JWT_ISSUER") ?: "https://api.example.com"
    }
    val databaseUrl: String by lazy {
        System.getenv("DATABASE_URL") ?: error("DATABASE_URL 環境変数が設定されていません")
    }
    val isDevelopment: Boolean by lazy {
        System.getenv("APP_ENV") != "production"
    }
}

環境変数を使用した設定

fun Application.configureAuthentication() {
    install(Authentication) {
        jwt("jwt") {
            realm = AppEnvironment.jwtRealm
            verifier(
                JWT.require(Algorithm.HMAC256(AppEnvironment.jwtSecret))
                    .withAudience(AppEnvironment.jwtAudience)
                    .withIssuer(AppEnvironment.jwtIssuer)
                    .build()
            )
            validate { credential ->
                if (credential.payload.audience.contains(AppEnvironment.jwtAudience)) {
                    JWTPrincipal(credential.payload)
                } else {
                    null
                }
            }
            challenge { _, _ ->
                call.respond(HttpStatusCode.Unauthorized)
            }
        }
    }
}

ロギング

// アプリケーションログ用の SLF4J ロガー
private val logger = LoggerFactory.getLogger("Application")

fun Application.configureMonitoring() {
    install(CallLogging) {
        level = Level.INFO
        filter { call -> call.request.path().startsWith("/api") }
        // デフォルトは Logback が使用され、logback.xml で設定されます
    }
}

スレッドモデル:io.ktor.network.selector とコルーチン

// デフォルトでは、Ktor は各リクエストを個別のコルーチンで処理します
// CPU バウンドなタスクには以下を使用します
fun Application.configureCustomDispatcher() {
    val myDispatcher = Dispatchers.IO.limitedParallelism(16)
    intercept(ApplicationCallPipeline.Plugins) {
        withContext(myDispatcher) {
            blockingDatabaseCall()
        }
    }
}

ベストプラクティス

1. 関心事の分離

  • ルートは薄く保ち、ビジネスロジックをサービスクラスに委譲する
  • プラグインを個別のファイルに分離する
  • データアクセスにリポジトリパターンを使用する

2. 堅牢なエラーハンドリング

  • すべてのパブリックエンドポイントに構造化されたエラーハンドリングを実装する
  • 一貫したエラー応答形式を返す
  • 機密情報を含む内部エラーを漏洩させない

3. API設計

  • 一貫した命名規則を使用する
  • RESTful なエンドポイント命名規則に従う
  • API の変更を管理するためにバージョニングを実装する

4. パフォーマンス

  • ブロッキング操作に適切なディスパッチャーを使用する
  • 大きなペイロードにはストリーミングを検討する
  • 高トラフィックのエンドポイントには接続プールとキャッシュを活用する

5. テスト

  • ルートテストとサービステストを対象に書く
  • 外部依存関係にはモックを使用する
  • 非同期待機を適切に処理するためにコルーチンテストを使用する

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.

関連スキル