Timee Product Team Blog

タイミー開発者ブログ

AI × Platform Engineeringで実現する、スケーラブルな開発組織の未来

こんにちは。タイミーでPlatform Engineeringグループのマネージャーを務めている橋本です。先日Architecture Conference 2025で「AI × Platform Engineeringでスケーラブルな組織を作るには」というテーマで発表する機会がありました。本記事では、その内容をもとにブログ記事として詳細にお話できる部分も交えてお伝えします。

登壇内容詳細は以下リンクのspeakerdeckも見ていただけると嬉しいです 😊 speakerdeck.com

とはいえ長いので、以下に登壇内容をサマライズしたグラフィックレコーディング風の一枚絵(feat. Google Nano Banana Pro)も掲載します。

登壇内容のサマリー

サマライズしすぎると解像度が粗くなってしまうので(& サマライズに少し誤りが含まれるため 😢)文章で補足をさせてください。

成長するプロダクトと組織が直面する課題

他の会社組織でも見られることかもしれませんが、プロダクトの成長に伴い、機能の増加や依存関係の複雑化、ガバナンス要求の増加などが発生し、開発者の認知負荷が高まります。

タイミーにおけるPlatform Engineering(PFE)チームの役割は、こうした認知負荷をオフロードし、開発者が価値提供に集中できるスケーラブルな基盤を提供することです。しかし、ここに大きなジレンマがありました。

PFE × スケーラブルのジレンマ

現在、PFEメンバーは開発組織全体の約5%程度で、開発者と20倍近い人数差があります。開発者を手厚く支援しようとするとPFE自体がボトルネックになってしまいますが、かといって人を増やすのも簡単ではない。いわば「がっつり支援したいけれど、やりすぎるとパンクしてしまう」というジレンマです。

特定チームに深く入り込む「Embedded」型の支援は手厚いですがコストが高く、リソースが枯渇しやすい。一方、ツールや基盤を提供する「XaaS」型はスケーラブルですが、個別具体のコンテキストに対応しにくいという限界がありました。この一例として、弊社、古屋のブログ記事に書かれており、以下のようなお話です。

第一段階は私が作った本人でもあることから期待通りになることはすんなり確認できましたが、第二段階ではやはり S3 に関する知識が十分にない状態ではいくらサンプルコードがあっても書くのは難しいと改めてわかりました。

さて、現状はXaaS化を強力に推進しなければチームリソースが枯渇して回らなくなる、というところまでには至っていません。しかし将来、プロダクトや組織のスケーラビリティにPFEのスケーラビリティが追いつけなくなることが想定されます。

AIで突破する:新しい支援モデルの構想

そこで現在進めているのが、「このジレンマ、AIでなんとかならないか?」というアプローチです。とはいえ、この考え自体はあまり目新しさはないかもしれません。

具体的には、DevinのようなAIエージェントをチームの一員として機能させます。以下の絵が全体的なイメージです。なお、Devinを必ずしも使う必要はなく、CursorやClaude CodeなどのCoding AgentとコードのCloneによっても代替可能です。ここでは、ChatOpsな体験が良いこともあり、Devinを採用しています。

そして、左右で仕組みのカタマリが異なり、以下の絵のようになります。

右側:AIが調査・ドキュメント化

現在のリソース設定(As-Is)をAIが出力する仕組みです。ここでの”AI調査”はKiro CLI(Q Developerと呼ばれていたプロダクト)を中心に実現されます。なお詳細は後述します。AIはプロンプトに従ってAWS Resource Explorerなどを通じて、実際のクラウドリソースの設定状況(As-Is)を調査し、ドキュメントを生成します。

左側:人間が補足を記述

右側で生成されたドキュメントを元に「なぜこの設定なのか」というコンテキスト(Design Addendum)をPFEエンジニアが書き加えるフェーズです。ここで最大のポイントは、「設定値(As-Is)」だけでは不十分だということです。「なぜその設定になっているのか(Why)」という背景情報は、設定ファイルの外側にしかありません。

ドキュメント化のループと回答フロー

以下のようなループが回るイメージです。

  1. AIが調査・ドキュメント化
  2. 人間が補足(Addendum)を記述
  3. 1 - 2を繰り返すループ
  4. AIが統合: これらを統合して、AIが開発者の質問に答える

開発者がAIに質問を投げかけると、Devinを通じてGitHub上の「As-Isの設定ドキュメント」と「Whyの補足情報」が統合されて回答されます。

例えば、開発者が「本番環境の〇〇という名前のS3バケット、なんでPublic Accessブロックされてるの?」と聞いた時に、AIが「それは〇〇というセキュリティ要件のためです」と即答できるようになります。また、ここまで背景を理解していれば、IaCコードの自動生成もスムーズになるため、柔軟な要件定義と実装出力(IaCコードを生成するなど)をAIが行うことも可能になるのではないかと考えています。

細かな実装について

ここで、登壇で細かくお話できなかった以下の図の赤い点線枠内にフォーカスを当ててお話しします。

詳細なフロー図は以下になります。

Kiro CLI(Q Developer)は必要か?

今までの流れで「Kiro CLIを使うの?terraform/HCL + Coding Agent + MCPでもできるのでは?」と思われた方もいるかと思います。このブログ執筆時点では、Kiro CLIの方が適していました。AWSの設定確認をAIが行う上で必須となるawscli等の知識をKiro CLIは事前学習済みであることがスムーズな仕組みに繋がっています。その他のCoding Agentを用いた場合でもAWS Knowledge MCP ServerやInternet上の情報等を使うことによって同様の動きにはなるのですが、オーバーヘッド・試行錯誤が多発する点が無視できない(図で言う"いろいろ大変"な)部分でした。

なぜAWS Resource Explorerを使うの?

Resource Explorerの詳細については、弊社、佐藤のブログ記事をご覧ください。AIにAWSリソースを調査させる場合、問題になるのはどのリソースが作成されているかという前提知識がないことです。正攻法でいくのであればAWSの全サービスに対してCrawling(list API)をしていくことになりますが、オーバーヘッドが大きく、regionも加わると対象は膨大です。Resource Explorerが管理するリソースは、ユーザー(我々)が作成したリソースがほとんどであり、Index化されたリソース情報を容易にリストで取得できることが利点です。Resource Explorerは各リソースの情報は持たず、あくまでどんなリソースがどのARNで作成されているかを得るためのIndexとして使っています。

SQLite(sqlite3)は何のために?

当初実装時は、AIにプロンプトを通じてResource Explorerを参照させていました。ただ、APIの返却上限が1,000件のリミットがあったり、ページネーションの挙動などが間に挟まっていると取得挙動が安定せず、あるタイミングで実行した場合には5,000件のリソース取得が期待されるのに、次は3,800件しか取れていないなど、件数が毎回変わる不安定さが見られました。

そこで、ローカルキャッシュとしてSQLiteを使い、ツールで定型的にResource Explorerから取得しsqlite3にデータを保持する仕組みとしました。AIはスキーマやデータ構造をコンテキストとして渡した上で対象をグループ(S3、IAM、RDSなど)化してSQLで取得し、調査をさせることが容易になりました。(もちろん、これらのツールもAIに生成・メンテナンスさせます)

いろいろお膳立てをして満を持してAIが

上記のように、アレコレとお膳立てをしてAIが処理しやすい前捌きをしながら、ドキュメント化をさせる機構を作り上げています(まだ絶賛作っている最中で、試行錯誤のタイミングです)。今年の夏頃から徐々に作ったり試行錯誤をしていますが、AIの進化(Q Developerの登場)やAIが取り扱えるコンテキスト量の増加(Claude Sonnet4.5の1Mなど)などの外部環境により改善が一気に進むことを目の当たりにしています。CodingAgent / AI Modelの進化と一緒に暫くは改善・改良を進めていくことになりそうです。

AI時代の新しいPlatform Engineering

この仕組みによって、これまで「コストが高いから乱発できない」としていた「Embedded(埋め込み型支援)」を、AIが肩代わりしてくれることが期待されます。AIがドメイン知識やインフラの現状を把握していれば、極端ですが24時間365日、各チームに「Embedded」し続けることができるのです。

これにより期待できる効果は以下のようなものであり、開発者に対する支援だけではなく、PFEチーム内の知識共有やマネージャー・監査部門による確認なども、より気軽に・低コストにできる未来があるかもしれません。

  • 確認・学習コストの低下: 開発者がAI経由で「Why」を学べる
  • 知識共有の加速: PFE内の知識がAIに集約され、担当交代やスケーリングが容易になる
  • 監査・統制: 設定意図が明確になり、監査にも活用できる

まとめ

Platform Engineeringは開発者の認知負荷を下げるためにありますが、PFEチーム自体がボトルネックになりがちです。この「人のスケーラビリティ」の限界を、「AI支援によって突破する」。これこそが、AI時代の新しいPlatform Engineeringの形になるかもしれないと考えています。

AIを活用することで、これまでコスト面で難しかった「手厚い支援」を擬似的に実現し、開発者がフルサイクルに活躍できる環境を作る。タイミーでは、こうした新しいチャレンジに一緒に取り組んでくれる仲間を絶賛募集中です!

プロダクト採用サイトTOP

カジュアル面談申込はこちら

社内AIエージェントを支える開発プラットフォームの紹介

この記事は Timee Advent Calendar 2025 シリーズ 2 の18日目の記事です。

はじめに

こんにちは、タイミーのDREチームのchanyouです。データ基盤の開発・運用を行っています。

社内向けのAIエージェントの開発プラットフォームを構築したので、その内容をご紹介します。

なお、この記事は人力で書きました。

「AIエージェント開発プラットフォーム」を作った背景

データ活用の新たなアプローチとして AI エージェントを位置づけ、そのために AI エージェント開発プラットフォームを作りました。ここでは、その背景に触れていきます。

データ基盤のこれまでと生成AI

タイミーでは、RDB に保存されたマッチングの記録やCRM に保存された営業の商談記録といった多様なデータを BigQuery に集約し、あらゆる業務で活用しています。

DRE チームではデータの集約を担当しつつ、 BigQuery に集まったデータを dbt を使ってセマンティックレイヤー1という形で整え、 Looker で BI として社内に提供しています。

これまでは人間向けにセマンティックレイヤーを作り込んできましたが、複雑なロジックでも一貫した結果を出力できるセマンティックレイヤーは、生成 AI にとっても有用2だと考えています。

特に AI エージェントは業務に与えるインパクトが大きいと思われるので、セマンティックレイヤーをAIエージェントが参照できるようにしたいと考えました。

外部サービスを導入するか、内製か

外部サービスの導入で解決できる領域も広い一方で、社内データをセマンティックレイヤーを通して生成AIで利用するソリューションには、まだ決定的なものがない印象です。

生成AIを取り巻く環境の変化は早いとはいえ、ソリューションを待つのも時間がもったいないです。ここは内製に振り切って、AIエージェントが使える未来に到達してしまって、課題を先回りしてつぶしておきたいと考えました。

気軽に作って壊せる開発プラットフォームを作る

単体の AI エージェントで Looker に接続できる構成を磨き込む、という選択もできました。

ただ、将来を見据えると AI エージェントで解ける課題は山積しており、AI エージェントをいくつもデプロイできる構成のほうが望ましいです。技術的にも、 A2A プロトコルの実証など、試したいことは多くあります。

このことから、カジュアルに作って壊せる、社内向けの AI エージェントを早く手軽に開発・運用できるプラットフォームを作りました。

「AIエージェント開発プラットフォーム」の解説

利用イメージ

社内利用のためリッチなUIは求めておらず、普段の業務の延長で AI エージェントを利用できる状態を作りたかったので、ユーザーインターフェイスは Slack に振り切りました。

以下の画像のように Slack で利用できるようになっています。

Devin のようにSlack Bot にメンションをするとスレッドが作成されて、AIエージェントとやり取りできます。 画像にはありませんが、人間と会話しているスレッドの途中で AI エージェントにメンションすると、新しいスレッドを作成して会話を行うこともできます。

AI エージェントとの会話の単位をセッションと呼びますが、このプラットフォームでは Slack のスレッドごとにセッションを作成する方針としました。Slack の同一スレッドであれば、AI エージェントはコンテキストを汲み取って会話を継続してくれます。

Slack に振り切ることで、Webでチャットインターフェースを構える場合と違い、実際にAIエージェントが利用されている様子が確認できて、その場でフィードバックを受け取れたりサポートできたりして、開発者としても体験が良かったです。

インフラアーキテクチャ

インフラアーキテクチャは以下の通りです。

graph RL
    subgraph Slack["Slack Workspace"]
        Bot[Slack Bot]
        User[User]
    end

    subgraph GCP["Google Cloud Platform"]
        AE[Vertex AI Agent Engine<br/>ADK]
        CR[Cloud Run<br/>Slack Bolt App]
    end
    
    User --> |Mention| Bot
    Bot --> |POST /slack/events| CR
    CR --> |Query| AE
    AE --> |Use tool| AE
    AE --> |Response| CR
    CR --> |Reply| Bot
    Bot --> |Display| User
    
    style CR fill:#ffeaa7
    style AE fill:#74b9ff
    style Bot fill:#00b894

ユーザーインターフェイスとして Slack Bolt アプリケーションを構築し、その背後に AI エージェントフレームワークである ADK(Agent Development Kit)の実行環境を構築する構成としました。

Slack Bolt アプリケーションの実行環境は Cloud Run を、 ADK の実行環境は Vertex AI Agent Engine を採用しました。

Agent Engine には、AI エージェントとの継続した会話に必要なセッションストレージも内包されており、永続化のためのデータベースを別途用意する必要がなく、非常に手軽に AI エージェントを運用できます。

Cloud Run と Agent Engine が各エージェントごとにデプロイされる構成としています。

リクエストからレスポンスまでの流れは以下の図の通りです。

sequenceDiagram
    participant User as User
    participant Slack as Slack Bot
    participant CR as Cloud Run<br><br>Slack Bolt App
    participant AE as Vertex AI Agent Engine<br><br>ADK App
    
    User->>Slack: "@hello-world-agent 天気を教えて"
    Slack->>CR: POST /slack/events
    
    CR->>AE: クエリ送信
    AE->>AE: エージェント処理<br/>ツール実行
    AE-->>CR: 応答
    
    CR-->>Slack: 応答を返す
    Slack-->>User: 応答を表示

コード管理

コード管理はモノレポ構成を採用しています。

Slack Bolt アプリケーションのコードは共通化しており、Slack Bot の挙動(何をトリガーに、どのように返信するか)は AI エージェントによらず共通としています。

AI エージェントの開発者は、 Slack のイベントハンドリングを意識せず、エージェントの開発に集中することができます。

プレビュー環境

AIエージェントの開発に限らず、Coding Agentのおかげで日々の開発スピードは格段に向上しました。

ただ PR がすぐに作れてしまうので、コードレビューがボトルネックになってくる場面が増えてきました。

レビュー負荷を軽減するために PR ごとにプレビュー環境を作成して、各ブランチの動作確認を Slack でできるようにしました。

Slack Bot 宛のメッセージで@hello-world-agent [PR-123] こんにちは のように [PR-番号] を含めることで、その PR の AI エージェントとして振る舞ってくれます。

Slack Bolt アプリケーションで PR タグの有無を確認して、タグを含む場合はプレビュー環境宛にルーティングする仕組みとしています。

プレビュー環境へのリクエストからレスポンスまでの流れは以下の図の通りです。

sequenceDiagram
    participant User as User
    participant Slack as Slack
    participant CR as Cloud Run<br><br>main
    participant PreviewCR as Preview Cloud Run<br><br>PR-123
    participant PreviewAE as Preview Agent Engine<br><br>PR-123
    
    User->>Slack: "@hello-world-agent [PR-123] 新機能をテスト"
    Slack->>CR: POST /slack/events
    
    CR->>CR: PR-123 タグを検出
    CR->>PreviewCR: HTTP転送
    
    PreviewCR->>PreviewAE: クエリ送信
    PreviewAE->>PreviewAE: エージェント処理<br/>新機能で応答
    PreviewAE-->>PreviewCR: 応答
    
    PreviewCR-->>Slack: 応答を返す
    Slack-->>User: 応答を表示

「AIエージェント開発プラットフォーム」の成果

DRE チームではこの開発プラットフォーム上で、Looker で参照できるデータをもとに営業活動をさらにスムーズに行えるような AI エージェントを開発しています。全社的に展開して利用いただいており、まだ伸びしろはありますが、社内業務の効率化に寄与しています。

Looker を参照しているため、 AI エージェントの応答に含まれる数値に一定の信頼が置けています。これを取得したい指標ごとにクエリを用意するなどしていたら、心が折れていたと思います。セマンティックレイヤー様々です。

また開発者目線では、MCP Tool と Function Tool の使い分けやユーザーの認証情報の取り回し、マルチエージェントシステムのデザインパターンなど、 AI エージェントを使ったアプリケーションを開発する上で学ぶべき点が多々あります。これらの手法をすぐに試して勘所をスピーディに掴めるようになりました。

今後の課題

AIエージェント開発プラットフォームにより、作って使えるまでは爆速で行えるようになりました。

一方で、まだAIエージェントを評価する環境がプラットフォームとして整えられていません。

AIエージェントの評価は、プロンプトのチューニングや基盤モデルの変更の際のリグレッションを防ぐためにも重要なので、評価手法を確立してプラットフォームで下支えしていきたいと思います。

まとめ

今回開発したプラットフォームを通して、データ活用のアプローチとしてもAI エージェントは非常に強力であることが分かりました。しかし、AI エージェントがデータを使った業務を支えるためには、一貫した結果を扱えることが前提にあると実感しました。(毎回ブレブレの回答をされると信頼できなくて使えない)

AI エージェント開発プラットフォームが整ったことで、さらにセマンティックレイヤーの重要性が増したと思いました。引き続きプラットフォームを育てつつ、セマンティックレイヤーの拡充も進めていきたいと思います。

AIエージェントを通したデータ基盤のさらなる活用に興味のある方、ご興味があればぜひお話しましょう!

下記よりカジュアル面談がお申込みいただけます。

product-recruit.timee.co.jp

プロダクト採用サイトは以下よりご覧いただけます。

product-recruit.timee.co.jp


  1. セマンティックレイヤーとは、データ加工処理において、データをビジネス用語や概念に紐づけるレイヤーのことを指します。ビジネス用語や概念の定義をコードで一元管理し、タイミーでは Looker から参照するようにしています。これにより、例えば「売上」といった単純そうで意外と定義が揺れがちな指標の定義を固めておくことで、全社員が Looker を通して共通の指標を見ながら会話することができます。
  2. もちろんBigQuery へのクエリを直接 Text-to-SQL させるアプローチもありますが、複雑なドメインロジックを全て Text-to-SQL させるのは、クエリ結果の信頼性を担保するのが難しいと考えています。

自己評価業務をAIと伴走して乗り越えた方法と、その感想

この記事はTimee Product Advent Calendar 2025の18日目の記事です。

評価業務、面倒ですよね

社会人になってから、特に苦手になった言葉があります。 「個人目標」「評価」の2つです。

「自己評価」「振り返り」など会社によって呼び名は違えど、自身の四半期〜1年程度の期間に実施した業務や成果を上司に報告する機会は、正社員として働いている方にはほぼ必ず巡ってくることと思います。 私も例に漏れず、少なくとも15年以上は評価されたり評価したりしながら過ごしてきました。とはいえ、被評価者のキャリアアップなど目的は会社のためだけでは無いことは理解しつつ、評価期間に費やす時間・マインドシェアを削減して顧客への価値提供に投下するリソースを最大化する術が無いか、というテーマについて日々思考しています。

そんな中でやってきたのが、AI・LLMの波です。評価業務に割くリソースを最適化するチャンスと見て、私は飛びつきました。

というわけで、手探りで実施した自己評価業務におけるAI活用事例を紹介します。

自己評価におけるAIの使いどころ

GeminiやChatGPTのようなAIは「私の四半期分の自己評価をまとめたドキュメントを作って」とプロンプトで依頼すれば、過去に自分が彼らに相談した内容からそれっぽいものを作ってくれます。ただ、実際の自身の活動をまとめる上では与えている情報量が足りないので、もっともらしい誤りの内容が出力されてしまうんですよね。

そこで、いかに私の情報をAIにたくさん与えるか、という観点で考えるアプローチにしています。情報源は自らがActionとしてたくさん書き出しておき、それらを会社制度に合わせた観点でまとめてスコア付けをする箇所をAIに任せる形です。

先に言っておくと、このアプローチにかかる労力は決して軽くはありません…。ただし、精神的な負担はかなり軽減された、と私は感じています。

実施手順

以下は2025年10月時点で自己評価をする必要がある組織を想定して記載しています。 AIはCursorというツールでSonnet 4.5(2025年10月時点)を利用して実施しましたが、ローカルにあるファイルを参照できるツールであれば他のツールでも応用可能だと思います。

自己評価の準備手順

  1. 自己評価用のディレクトリを作成(1分)

    任意のディレクトリに作業用のディレクトリを作成( ~/Documents/workspace/cursor など)し、その直下に「202510_自己評価」というディレクトリを作成

  2. 評価に関係する社内ルールをMarkdown形式で書き出す(10分)

    • 「キャリアラダー」ディレクトリ
      • 社員が属するグレードごとに求められる要件をまとめた資料
      • 全グレード分用意
        • 面倒な場合は自分のグレード±1グレード分だけで良いかも
    • バリュー.md
      「理想ファースト」「オールスクラム」「バトンツナギ」「やっていき」
      ※2025年11月にバリューが更新されたため、現在のバリューとは異なっています
      • 2025年10月時点の弊社のバリューは上記の4つ
      • バリューのタイトルだけでなく、その詳細もセットで書いておく
  3. 個人目標ディレクトリを作成(1分)
    • OKR_FY2025_2ndHalfディレクトリに、今期用のOKR目標2つ分のMarkdownを保存
    • 弊社は「期待役割」という名称でも別の目標管理が実施されているため、期待役割_FY2025_2ndHalfディレクトリに今期用の期待役割3つ分のMarkdownを保存
  4. 日常的にアクションを書き溜めるためのディレクトリを作成(1分)

    「Actions_FY2025_2ndHalf」ディレクトリを作成しました。

  5. 自己評価用ディレクトリのルート階層に、サブディレクトリごとの説明を書いたREADME.mdファイルを作成(15分)

    上記1.で作成した作業用のディレクトリに内包しているディレクトリ・ファイルの説明用のMarkdownを「README.md」というファイル名で作成しました。

     このディレクトリに含まれるファイルの概要は以下の通りです。
    
     ## Actions_FY2025_2ndHalf
     FY2025下半期に実行した主たるアクションが一つ一つファイル化されているディレクトリ。
    
     ## キャリアラダー
     私が所属する企業であるタイミーの評価制度に用いられる、各グレードに期待される働きの定義群がまとまっているディレクトリ。
    
     ## バリュー.md
     私が所属する企業であるタイミーにおいて行動の基準となるバリューの定義。
    
     ## 期待役割_FY2025_2ndHalf
     FY2025下半期の自身のグレードや職能を踏まえて自身に期待されるであろう役割を自ら想定した内容が記載されているファイルをまとめたディレクトリ。
     期待役割は◯◯、◯◯、…のn軸から選択でき、その詳細は上記「キャリアラダー」ディレクトリに含まれるファイルにグレード毎に定義されている。
    
     ## OKR_FY2025_2ndHalf
     FY2025下半期の目標として定めて取り組むOKR目標とその結果をまとめるディレクトリ。
    
  6. 日常的にアクションを書き溜める

    評価対象期間(弊社の場合は3ヶ月)ずっと書き足していく必要があります。 1クォーターあたりだいたい10アクション程度の量感を目指して書いています。

  7. 個人目標に進捗があり次第、上記2.で書き出した個人目標のMarkdownファイルを更新

上記の手順をすべて実施すると、以下のようなディレクトリ構造が出来上がります。

202510_自己評価/
├── README.md
├── Actions_FY2025_2ndHalf/
│   ├── アクション1.md
│   ├── アクション2.md
│   ├── アクション3.md
│   └── ...
├── キャリアラダー/
│   ├── グレード1.md
│   ├── グレード2.md
│   ├── グレード3.md
│   ├── グレード4.md
│   ├── グレード5.md
│   └── ...
├── バリュー.md
├── 期待役割_FY2025_2ndHalf/
│   ├── 期待役割1.md
│   ├── 期待役割2.md
│   └── 期待役割3.md
└── OKR_FY2025_2ndHalf/
    ├── OKR1.md
    ├── OKR2.md
    └── OKR3.md

自己評価をまとめる手順

  1. 「202510_自己評価」ディレクトリにself-assessment.mdを作成
  2. self-assessment.mdに以下の情報を記入

     # 前提
     私のグレードは以下のとおりです。
    
     - Grade: ◯◯
     - Rank: ◯◯
    
     # 自己評価スコア
     > Self assessment of Abilityは、以下の7項目から選択して採点して下さい。
     > 期待通りが4で、それを軸に期待を上回ったか、下回ったを総合的に判断して下さい。
     >
     > - 7. Extremely exceeds expectations
     > - 6. Greatly exceeds expectations
     > - 5. Exceeds expectations
     > - 4. Meets expectations
     > - 3. Less than expectations
     > - 2. Greatly less than expectations
     > - 1. Extremely less than expectations
     >
     > Self assessment of Valueは、以下の5項目から選択して採点して下さい。
     > 期待通りが3で、それを軸に期待を上回ったか、下回ったを総合的に判断して下さい。
     >
     > - 5. Greatly exceeds standard
     > - 4. Exceeds standard
     > - 3. standard 
     > - 2. Less than standard
     > - 1. Greatly less than standard
    
     ## Self assessment of Ability
    
     **## Self assessment of Value(理想ファースト)
    
     ## Self assessment of Value(やっていき)
    
     ## Self assessment of Value(バトンツナギ)
    
     ## Self assessment of Value(オールスクラム)
    
     # 上記のスコアを付けた理由の深堀り
     ## 自己評価の背景
    
     ## 自身の振り返り
    
     ### 期待役割に対する振り返り
    
     ### OKRに対する振り返り
    
     ### 総合的な振り返り**
    
  3. Cursorにself-assessment.mdを執筆するよう指示する

     私はiOSアプリエンジニアをしています。
     この会社の評価制度の中で、現在は自己評価期間となっています。2025年5月から2025年10月までの間に実施した業務と、期首に設定したOKRや期待役割の定義、更にキャリアラダーに基づいて、自己評価をself-assesment.mdファイルにまとめようとしているところです。
     202510_自己評価ディレクトリのファイルを網羅的に参照して、私の自己評価業務を手伝うべく、このself-assessment.mdファイルの内容を埋めて下さい。
    
     202510_自己評価ディレクトリに保存されているファイル群の概要は、202510_自己評価/README.mdにまとめてあります。
    
  4. 記入された自己評価をレビューし、違和感がある箇所を後続のプロンプトで手直しさせる

    初回は自身の直感と異なるスコア付けがされていたので、こんなプロンプトで調整を加えていきました。

     嬉しいんですが、本当にそうですか?さすがにほとんどの項目が最高評価だと、ちょっと疑わしいと感じてしまいます。もっと厳しい目線も加えてもらえませんか?
    
     今のSelf assessment of Abilitのスコアは◯◯ですが、このスコアを+1するとしたらどんな懸念がありますか?
    
  5. 会社が用意した壁打ち用のAIにself-assessment.mdをレビューしてもらい、受けたフィードバックのうち妥当だと感じるものをCursorにプロンプト経由でフィードバックする

     会社が用意した壁打ち用の生成AIに現状のself-assessment.mdをレビューしてもらいました。
     以下のフィードバックを取り入れたいと思っています。
    
     (以下、壁打ちAIのフィードバックのうち自身が共感した内容をコピー&ペースト)
    
  6. 以降、Cursorが手直ししたself-assessment.mdと壁打ち用AIの間を気が済むまで往復する

今期の自己評価のために用意したファイル群は、来期の目標設定でも役立つ

自己評価が終わるとすぐに待っているのが、目標設定業務です。

前期の評価に対して上司からもらったフィードバックを202510_自己評価/self-assessment.md に記入しておくと、目標設定業務でそれが大いに役立つことになります。

具体的には、202604_自己評価ディレクトリを作って202510_自己評価と同様のディレクトリ構成を完成させた上で、以下のようなプロンプトでCursorに相談して目標設定を進めました。

@202510_自己評価/self-assessment.md ここに前期の自己評価と、私の上長から頂いたフィードバックが記載されています。

この内容を基に、202604_自己評価ディレクトリにOKRと期待役割を記入したいです。
どのように進めるのが良いでしょうか?

目標設定については詳細を省きますが、AIが目標設定の進め方を作業レベルまで落とし込んだり、OKR・期待役割のdraft版を提案してくれたりしたので、それらをレビューして調整を繰り返す形で目標設定を終えることができました。

感想

事前準備が全手動なので、トータルの労力としてはそれほどラクではありません。しかし、その事前準備で一番労力のかかる“日常的なアクションの記録”は、例えAIを活用しない場合であっても有効な手段ですし、実際に私は評価業務にAIを活用する前から実施していたことなので、AIを使うために大変になったわけではありません。

そして前述の通り、評価業務に挑む気持ちの面ではかなりラクになったと感じています。人間は事実を記録することに専念して、その事実が会社のキャリアラダーやバリューに沿っているかを客観的に評価する仕事をAIに任せたことで、評価業務においてクリエイティブな領域の多くをAIに頼れたからです。「やれば終わる」という作業的な仕事なら見積もり・見通しも立てやすいですしね。

直近の評価期間から、会社から公式に壁打ち用のAIが提供されたことも追い風になったなと感じています。 会社の評価指標情報は自身でそれなりに網羅的にAIに提供できているつもりですが、それでも壁打ち用AIにレビューさせてみると、別の観点でのフィードバックを受けることが多かったです。そういった評価観点の欠損をAI間で補うループが作れたことで、自身の労力を多く割かずともどんどん自己評価の精度が上がっていく工程は、気持ち良さすら感じる時間でした。

…ただ、たまに「来期は◯◯の研修を受けます」といった私が希望していないNext ActionもAIによって記入されていたので、AIによって記入された内容のレビューには気を抜けません。

こうしてAIを活用して制作した私の自己評価は上司のレビューを比較的スムーズに通過し、結果的に評価期間中も私が本来やりたいと感じていた業務に多くの時間を割くことができました。

開発業務をAIの力で効率化する話はよく挙がりますが、会社員として働くエンジニアの仕事は開発だけではないはずです。 この記事を読んだ皆さんに「自分が苦手な業務をどうにかAIに助けてもらえないか」と思考をしてみるきっかけにこの記事がなっていれば幸いです。

お話ししましょう!

私、岐部(@beryu)はタイミーでiOSアプリエンジニアをしています。iOSアプリ開発の中でのAI活用の情報共有だったり、評価業務の悩みを誰かに話したいだけでも結構ですのでw、ご興味があればぜひお話ししましょう!

プロダクト採用サイトTOP カジュアル面談申込はこちら

技術とコミュニティの熱気に触れて:try! Swift Tokyo & DroidKaigi 参加記

この記事はTimee Product Advent Calendar 2025 16日目の記事です。

はじめに

株式会社タイミーでAndroidエンジニアをしているみかみです。今年からはiOSの機能開発にも携わっています。社外ではKotlin Multiplatformを用いた開発やDroidKaigiスタッフとしての活動など、技術とコミュニティの両面から開発に関わっています。

今年は、try! Swift Tokyo 2025とDroidKaigi 2025の2つのカンファレンスに参加しました。どちらも現地ならではの熱気があり、エンジニアとしての視野を広げる貴重な経験でした。技術に向き合う姿勢や、コミュニティの多様さに触れて多くの刺激をもらいました。

本記事では、それぞれのカンファレンスを通じて感じたことや印象に残った出来事、そして今後の活動について振り返ります。

try! Swift Tokyo 2025

去年に引き続き、try! Swift Tokyo 2025に参加しました。英語での発表や海外からの参加者も多く、他の国内カンファレンスと比べても国際的な雰囲気が際立っていました。セッション後のASK THE SPEAKERエリアでは、登壇者に質問をする参加者の姿も見られ、登壇者と参加者の交流が活発だったのも印象的です。さらに、カンファレンスアプリには翻訳サービスのFlittoが導入され、セッションの内容をリアルタイムで翻訳して表示する機能が提供されていました。

tryswift.jp

セッションはモバイル開発を中心に、Swiftという言語がもつ表現力や技術の進化を感じさせるものが多くありました。Androidエンジニアとして特に気になったのは、「Swift × Android: Skipが切り拓くクロスプラットフォーム開発の未来」や「Foreign FunctionとMemory APIとSwift/Java相互運用性」といったセッションです。2025年には、SwiftでAndroid開発を公式にサポートすることを目的とした「Android Workgroup」の発足も発表されたことも記憶に新しく、Swiftのモバイル領域における立ち位置が変わってきていると実感します。

また、元AppleのデザイナーであるSebastiaan de Withさんの「素早く実現する優れたアプリデザイン」も印象的でした。このセッションでは、アプリを単なるツールではなく「アート」として捉え、作り手の情熱やこだわりがユーザーの感情に「共感」する体験を生み出すことの大切さが語られました。こうした視点は、日々の開発業務に追われているとつい忘れがちになるため、プロダクト開発の原点に立ち返る良いきっかけとなりました。

www.youtube.com

セッション以外にも、参加者同士の交流を目的としたイベントがいくつかあり、Day 2のパーティやSTORESさん主催のランチイベントにも参加しました。さまざまな企業のiOSエンジニアと開発の話や日常の雑談を交わせたのは貴重な機会でした。立場やプラットフォームを越えた交流を通じて、多くの新しい視点や刺激を得られました。

DroidKaigi 2025

続いて、DroidKaigi 2025にもオフラインで参加しました。

今年もPRチームのボランティアスタッフとして、SNSでの広報や動画収録、YouTube配信のサポートなど、さまざまな活動に携わりました。会場ではカメラを手に歩きながら、たくさんの人でにぎわう様子を眺めたり、スタッフ同士で撮影の段取りを確認したりしていました。

スタッフ業務が中心だったためセッションはあまり聞けませんでしたが、YouTubeでの公開が非常に早く、後からセッションを視聴できたのは助かりました。扱われていたトピックは幅広く、Androidの技術的な深掘りだけでなく、デザインやプロダクト開発、チーム運営やキャリアなど、多様なテーマが取り上げられていました。セッションの感想については、ブログ「DroidKaigi 2025 参加レポート〜Part 1〜」でも紹介しています。

tech.timee.co.jp

また、今年の登壇者でありAndroidのGDEでもある skydoves さんが執筆した「Manifest Android Interview」の日本語翻訳を、DroidKaigiに合わせて進めました。会期中に直接お話しする機会もあり、言語や立場を越えて技術を共有する楽しさを改めて感じました。

引用元:DroidKaigi 2025

エンジニアだけでなく、デザイナーや学生、スポンサー企業の方々など、多様な立場の人がそれぞれの形で関わり合い、学びや交流を楽しんでいたのが印象的でした。技術を軸にしつつも、人とのつながりや会場の温かい雰囲気が心に残るカンファレンスでした。

参加を通して感じたこととこれから

カンファレンスに参加すると、技術への意欲が改めて湧いてきます。近年は国内外を問わず、多くのカンファレンスでセッション動画がすぐにYouTubeに公開され、オンラインでも学びやすい環境が整ってきました。

それでも、会場での偶発的な出会いや、リアルタイムで熱を共有する体験には、やはり特別な価値があると感じます。多くの人とコミュニケーションを取る中で、技術やコミュニティに向けられた熱意を肌で感じました。そうした空気に触れるうちに、自分も少しずつ新しいことに挑戦していきたいという気持ちが生まれました。前々から関心のあったOSSへのコントリビュートや海外カンファレンスへの参加など、機会を見つけて取り組んでいけたらと思います。

現在は技術コミュニティで繋がったメンバーを中心に、自分たちが「今欲しい!」と思えるAndroid開発書籍の執筆を進めています。主にテストに関する章を担当しており、着実に形にしていけるよう取り組んでいます。

技術の進化が加速する中で、学ぶことの楽しさや、人とのつながりから生まれる刺激の大きさを改めて感じました。興味を持ったことには素直に手を伸ばしながら、学びや挑戦の輪を少しずつ広げていければと考えています。

おわりに

改めて、技術カンファレンスは知識を得るだけでなく、人とつながり、自分の視野を広げるための場だと感じました。try! Swift TokyoもDroidKaigiも、参加するたびに多くの刺激と学びをもらっています。これからも、その刺激を自分の原動力として、日々の活動を続けていきたいと思います。

最後になりますが、株式会社タイミーでは、現在iOS/Androidエンジニアを募集しています。 「まずは話を聞いてみたい」というだけでも大歓迎ですので、少しでも興味を持っていただけたら、ぜひ採用ページをのぞいてみてください!

hrmos.co

hrmos.co

dependabot PRのAI自動レビューをCI完了後に実施させる

こんにちは、タイミーでバックエンドのテックリードをしている新谷 (@euglena1215) です。

タイミーでは、 dependabot による依存ライブラリのアップデート PR に対して、Devin による自動レビューを導入しています。しかし、CI が失敗しているにもかかわらず、「本プロジェクトへの影響は低いと判断します」というコメントがついてしまうケースがありました。

これはレビュー時点では CI が完了しておらず、Devin がコードの差分だけを見てレビューしていたことが原因です。この問題を解決するために、CI 完了後にレビューを実施するよう、ワークフローを修正しました。

方法

従来の実装

従来は pull_request イベントの opened をトリガーにしていました。

on:
  pull_request:
    types:
      - opened

この場合、PR が作成された直後にレビューが開始されるため、CI の結果を考慮することができません。

改善後の実装

workflow_run イベントを使用して、CI ワークフローの完了をトリガーにするように変更しました。

on:
  workflow_run:
    workflows: ["ci branch"]
    types:
      - completed
    branches:
      - 'dependabot/**'

workflow_run イベントは別のワークフローの完了を待ってから実行されるため、CI の結果を含めたレビューが可能になります。

PR情報の取得

workflow_run イベントでは github.event.pull_request が使えないため、gh コマンドで PR 情報を取得する必要があります。

- name: Get PR info
  id: pr-info
  env:
    GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  run: |
    PR_INFO=$(gh pr list \\
      --repo my-org/my-repo \\
      --head "${{ github.event.workflow_run.head_branch }}" \\
      --state open \\
      --json number,title,url \\
      --jq '.[0]')

    if [ -z "$PR_INFO" ]; then
      echo "No open PR found for branch ${{ github.event.workflow_run.head_branch }}"
      echo "skip=true" >> "$GITHUB_OUTPUT"
      exit 0
    fi

    {
      echo "skip=false"
      echo "number=$(echo "$PR_INFO" | jq -r '.number')"
      echo "title=$(echo "$PR_INFO" | jq -r '.title')"
      echo "url=$(echo "$PR_INFO" | jq -r '.url')"
    } >> "$GITHUB_OUTPUT"

ポイントは github.event.workflow_run.head_branch でブランチ名を取得し、そこから gh pr list で該当する PR を検索している点です。

CI結果の取得

CI の結果は github.event.workflow_run.conclusion で取得できます。さらに、失敗したジョブの一覧も取得してレビューの参考情報として渡しています。

- name: Get CI check results
  id: ci-checks
  run: |
    CI_CONCLUSION="${{ github.event.workflow_run.conclusion }}"
    echo "conclusion=${CI_CONCLUSION}" >> "$GITHUB_OUTPUT"

    # 失敗したジョブ一覧を取得
    FAILED_JOBS=$(gh pr checks "$PR_NUMBER" \\
      --repo my-org/my-repo \\
      --json name,state \\
      --jq '[.[] | select(.state == "FAILURE")] | map(.name) | join(", ")' \\
      2>/dev/null || echo "取得失敗")

これにより、Devin へのプロンプトに CI 結果と失敗したジョブ名を含めることができます。

- CI結果: ${CI_CONCLUSION}
- 失敗したジョブ: ${FAILED_JOBS:-なし}

レビュー指示にも CI 結果を考慮するよう追記しました。

- CI結果を確認し、失敗している場合はCIのログを読み込んで原因を分析する
- CIが失敗している場合、ライブラリの更新が原因かどうかを判断する
- CIが失敗している場合は、失敗の原因とマージ前に対応が必要かどうかを明記する

まとめ

workflow_run イベントを使うことで、CI 完了後に別のワークフローを実行できるようになります。dependabot PR の自動レビューのように、CI の結果を考慮したい処理に便利です。

ただし、pull_request イベントとは異なり PR 情報を直接取得できないため、gh コマンドで別途取得する必要がある点には注意が必要です。

参考:

Kotlin で MCP サーバー作ってみた

Android Chapter の tick-taku です。

この記事はTimee Product Advent Calendar 2025の15日目の記事です。

はじめに

2025年は AI コーディング・AI エージェント元年であり黎明期の渦中 と言っても過言ではない年だったと感じます。

各社からこぞって AI エージェントがリリースされ、その後も息つく暇もなくアップデートされてきました。単純なコード補完ではなくタスクを任せられるようになった昨今の AI エージェントは今ではすっかりコーディングの相棒です。

実際今年の DroidKaigi や iOSDC でも AI 関連のセッションが増えていた気がします。

そんな中 KotlinFest などで MCP サーバーを作った話を聞いて「この時代に生きてるんだから自分も触れておかなければ(使命感)(遅い)」と思い、せっかく1年も終わろうとしているのでいい機会だし触ってみようとまずは MCP サーバーを作ってみました。

ちなみにこの記事を書くにあたっても AI のサポートを最大限利用しています。時代ですねぇ。。。

Kotlin で MCP サーバー作ってみる

では実際に作成していきます。

ハンズオン的なドキュメントを公式が用意してくれているのでそちらも参考になります。

modelcontextprotocol.io

今回は timee-android で運用している LADR を提供する MCP サーバーを作成してみることにしました。LADR については こちら数日前にチームのリードエンジニアが取り組みについて投稿 しているためぜひご覧ください。

この記事では jar ファイルをローカルに作成し Claude Code と接続することを目標とします。

事前準備

以下のようなディレクトリ構成を想定します。

timee-ladr
├── ladr
│   ├── 0000-ladr-template.md
│   ├── ~~~
│   └── assets
└── mcp
    └── app

また、Kotlin で作成するので MCP Kotlin SDK を利用します。build.gradle はこちら。

dependencies {
    implementation("io.modelcontextprotocol:kotlin-sdk:0.7.7")
}

base {
    archivesName.set("ladr-mcp")
}

application {
    mainClass = "timee.ladr.AppKt"
    applicationName = "ladr-mcp"
}

Primitives

実装に移る前に MCP サーバーがコンテキストを提供するための重要な要素に触れておきます。

MCP には Resources, Tools, Prompts と呼ばれる要素があり、この記事で触れる Resources, Tools についてはそれぞれ以下のように定義されています。(Prompts は今回触れないため割愛します)

  • Resources: LLM に追加のコンテキストを提供する読み取り専用のデータまたはコンテンツ
  • Tools: LLM がアクションを実行するための関数のようなもの

今回作る MCP サーバーに当てはめると、LADR の md ファイルを Resources として提供し、LADR ファイルを新規作成したり更新したりする機能を Tools として提供することになります。

Server の初期化

まずは MCP サーバーを初期化します。

fun main() {
    val server = Server(
        serverInfo = Implementation(name = "ladr-mcp", version = "1.0.0"),
        options = ServerOptions(
            capabilities = ServerCapabilities(
                resources = ServerCapabilities.Resources(
                    subscribe = false,
                    listChanged = false
                ),
                tools = ServerCapabilities.Tools(
                    listChanged = false
                )
            )
        )
    ) { "timee-android 内の LADR を提供する MCP サーバー" }
}

ここで出てきた Capabilities とはこの MCP サーバーが LLM に対して何を提供しているかを示すものであり、今回の場合は Resources と Tools を提供していることを表しています。

Resources の Capabilities を示す data class の引数はそれぞれ以下のような意味を持っています。

プロパティ 説明
subscribe クライアントが特定リソースの変更を購読できるか
listChanged リソース一覧が変更されたときに通知を送るか

今回のサーバーは静的な LADR の md ファイルを提供することが目的なので基本的に更新系は false です。

Resource の登録

LADR のファイルを Resource に登録していきます。

今回は jar を作成し単独でローカルで動作させることを目指しているため、jar 内の resources/ 配下に md ファイルを格納し読み取ることにしました。この辺の実装は AI 頼りです。ありがとう🙏

fun main() {
    /** Server の初期化 */

    getLadrFilePathsFrom()?.forEach { filePath ->
        val fileName = filePath.substringAfterLast("/")
        server.addResource(
            uri = "ladr:///$fileName",
            name = fileName,
            description = "LADR: $fileName",
            mimeType = "text/markdown"
        ) { request ->
            ReadResourceResult(
                contents = listOf(
                    TextResourceContents(
                        uri = request.uri,
                        mimeType = "text/markdown",
                        text = readResourceFile(filePath) ?: "File not found: $fileName"
                    )
                )
            )
        }
    }
}

private fun getLadrFilePaths() = object {}.javaClass.protectionDomain?.codeSource?.let {
    JarFile(it.location.toURI().path).getLadrFiles()
}

private fun JarFile.getLadrFiles() = entries().asSequence()
    .filter { !it.isDirectory && it.name.startsWith("ladr/") && it.name.endsWith(".md") }
    .map { it.name }
    .sorted()
    .toList()

private fun readResourceFile(filePath: String) = object {}.javaClass.classLoader.getResourceAsStream(filePath)?.let { stream ->
    BufferedReader(InputStreamReader(stream, Charsets.UTF_8)).use { it.readText() }
}

Tool の登録

チームで運用している LADR のテンプレートに沿って登録しているため少し長いですが、addTool 内で input に対して output を定義するだけです。

JsonObject の用意が大変ですね…

fun main() {
    /** Server の初期化 */

    server.addTool(
        name = "create-ladr",
        description = "与えられたコンテキストから新しい LADR (Lightweight Architecture Decision Record) ドキュメントを作成します。テンプレートに基づいてMarkdown形式のLADRを生成します。",
        inputSchema = Tool.Input(
            properties = buildJsonObject {
                putJsonObject("title") {
                    put("type", "string")
                    put("description", "LADRのタイトル(何に対しての意思決定を行ったのかがわかるタイトル)")
                }
                putJsonObject("number") {
                    put("type", "string")
                    put("description", "LADRの通し番号(4桁のゼロ埋め、例: 0001, 0042)")
                }
                putJsonObject("status") {
                    put("type", "string")
                    put("description", "採用状況: Accepted(採用), Rejected(不採用), On holding(保留)のいずれか")
                    putJsonArray("enum") {
                        add(JsonPrimitive("Accepted"))
                        add(JsonPrimitive("Rejected"))
                        add(JsonPrimitive("On holding"))
                    }
                }
                putJsonObject("context") {
                    put("type", "string")
                    put("description", "文脈: 経緯(なぜこの意思決定が生まれたのか)、観点(判断した観点)、事情(検討した事情)、参考情報など")
                }
                putJsonObject("specification") {
                    put("type", "string")
                    put("description", "仕様、やること、やらないこと")
                }
                putJsonObject("design") {
                    put("type", "string")
                    put("description", "設計: 特筆すべき設計や設計意図")
                }
                putJsonObject("decision") {
                    put("type", "string")
                    put("description", "決定事項: 想定している運用や制約、再評価となる条件など")
                }
                putJsonObject("consequences") {
                    put("type", "string")
                    put("description", "結果: 意思決定の結果を評価したタイミングで追記")
                }
                putJsonObject("link") {
                    put("type", "string")
                    put("description", "関連するLADRへのリンク")
                }
            },
            required = listOf("title", "number", "context", "specification")
        )
    ) { request ->
        val args = request.arguments
        val ladrContent = buildString {
            val title = requireNotNull(args["title"]?.jsonPrimitive?.content)
            val number = requireNotNull(args["number"]?.jsonPrimitive?.content)
            appendLine("# $number-$title")
            appendLine()

            appendSection(
                title = "## Date - 日付",
                text = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))
            )
            appendSection(
                title = "## Status - 採用状況",
                text = args["status"]?.jsonPrimitive?.content
            )
            appendSection(
                title = "## Context - 文脈",
                text = requireNotNull(args["context"]?.jsonPrimitive?.content)
            )
            appendSection(
                title = "## Specification - 仕様、やること、やらないこと",
                text = requireNotNull(args["specification"]?.jsonPrimitive?.content)
            )
            appendSection(
                title = "## Design - 設計",
                text = args["design"]?.jsonPrimitive?.content
            )
            appendSection(
                title = "## Decision - 決定事項",
                text = args["decision"]?.jsonPrimitive?.content
            )
            appendSection(
                title = "## Consequences - 結果",
                text = args["consequences"]?.jsonPrimitive?.content
            )
            appendSection(
                title = "## Link - 関連するLADRへのリンク",
                text = args["link"]?.jsonPrimitive?.content
            )
        }

        CallToolResult(content = listOf(TextContent(ladrContent)))
    }
}

private fun StringBuilder.appendSection(title: String, text: String?) = apply {
    appendLine(title)
    appendLine()
    if (text.isNullOrEmpty().not()) {
        appendLine(text)
        appendLine()
    }
}

本来はファイルを作成するところまでできるとよさそうなんですが、 LADR の性質上 jar の中にファイルを作成しても仕方なく、テンプレートに当てはめたテキストを返すのみとなっています。

Resource の登録と合わせて、本格的にチームで運用する場合はリポジトリをクローンしていることを前提に LADR を格納しているディレクトリの path を環境変数などで MCP サーバーに伝えるなどの方法もありそうですね。

Server の起動

最後に準備した Server を起動させます。

本来 MCP サーバーのトランスポートの方式はいくつかありますが、今回はローカルに build した jar との接続を想定しているのでドキュメント通りSTDIO形式で起動します。

fun main() {
    /** Server の初期化 */

    val transport = StdioServerTransport(
        inputStream = System.`in`.asSource().buffered(),
        outputStream = System.out.asSink().buffered()
    )
    runBlocking {
        val session = server.createSession(transport)
        val done = Job()
        server.onClose {
            done.complete()
        }
        done.join()
    }
}

以上でサーバーの実装は完了です🎉

動作確認

作成したサーバーが正しくレスポンスを返してくるか検証しましょう。MCP の検証には MCP Inspector が提供されています。

MCP Inspector を起動する前に以下のコマンドでアプリケーションを用意します。

./gradlew installDist

app/build/install/ 以下に作成されるので、作成されたアプリケーションの path を Inspector の起動時に指定します。

npx @modelcontextprotocol/inspector mcp/app/build/install/ladr-mcp/bin/ladr-mcp

ブラウザが起動するので左ペインの Connect を実行すると正しく作成できていれば成功します。

List Resources を実行すると登録された Resource の一覧が確認できます。

MCPInspecter/Resources

また、Tools タブ内で List Tools で登録された Tool の一覧が確認できます。

右側のペインに inputSchema に指定した property を入力でき、実行するとレスポンスが返ってきます。enum で指定した status はドロップダウンで選択できるようになっていて便利ですね。

MCPInspecter/Tools

Claude Code との接続

では最後に Claude Code と接続して LADR が確認できるか試してみます。

接続の前に jar ファイルを build します。ただし、普通に jar ファイルを build すると resource ファイルなどは格納されないため fatJar(shadowJar) を作成する必要があります。

これは自分が Server Side Kotlin の知見に乏しいので AI に聞いて作成 task を用意してもらいましたが、もっといい方法があるかもしれません。

fatJar 作成の gradle task

tasks.register<Jar>("fatJar") {
    archiveClassifier.set("all")
    archiveVersion.set("")
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE

    manifest {
        attributes["Main-Class"] = "timee.ladr.AppKt"
    }
    
    from(sourceSets.main.get().output)
    
    dependsOn(configurations.runtimeClasspath)
    from({
        configurations.runtimeClasspath.get()
            .filter { it.name.endsWith("jar") }
            .map { zipTree(it) }
    })
}

jar ファイルが作成できたら ~/.claude.json に設定を追加します。

"mcpServers": {
  "ladr-mcp": {
    "type": "stdio",
    "command": "java",
    "args": [
      "-jar",
      "/path/to/timee-ladr/mcp/app/build/libs/ladr-mcp-all.jar"
    ],
    "env": {}
  }
}

Claude Code を起動して /mcp list を確認して connected になっていたら成功です。試しに timee-android が LADR を採用した経緯を聞いてみると readMcpResource を利用してまとめてくれました。うちの Claude お姉ちゃんは優秀です。

ClaudeCodeによるLADRのサマライズ

最後に

「MCP ってなんかすごいやつでいい感じになんかやってくれるんだな〜」くらいのイメージでいましたが、実際に作って触れてみるとなるほどコンテキストを取り扱うためのものなんだなということがよく分かりました。MCP がいい感じにしてくれるのではなく、コンテキスト接続のプロトコルなのであくまで外部からデータを与えたり作成したりするためのものであり、いい感じにしてくれるのはエージェントだと身をもって理解できました。

秩序という意味でも “AI における USB-TypeC” とはよく言ったものです。

となると次は AI エージェントの作成にチャレンジしてみるしかありませんね。次回は Koog を利用して今回作成した MCP サーバーを利用していい感じに LADR を作成してくれる AI エージェントを試してみようと思います。最近は KMP も熱く、全てが Kotlin で書けるのでもう全部 Kotlin でいいじゃんですね🤗

実際に手を動かしてみることで学びにはなりましたが GitHub のリポジトリに上がっているものなら GitHub CLI で済みそうな気がするので、社内 DB などよりクローズドなデータなら MCP サーバーとして用意する価値があるのかもしれません。とは言え、そんなデータをエージェントにくわせていいのかは…🤔

社内データを活用するために MCP サーバーを自作しておくと色々と捗りそうですが、一方で MCP サーバーはコンテキストを消費するのであんまり追加してもエージェントが処理しきれなくて意味がないと言う主張もあるそうです。

これからますます便利になっていくであろう AI エージェント界隈、今後の進化を楽しみながら注視していきたいですね!


タイミーでは日々の業務に積極的に AI を活用しています。ご興味があればぜひお話ししましょう!

プロダクト採用サイトTOP

カジュアル面談申込はこちら

「シングルスレッドリーダー」が事業推進のエンジンになる

こんにちは、タイミー取締役 事業統括兼CPOのShunです。Timee Product Advent Calendar 2025の12/14担当ということで、最近意識しているテーマ「シングルスレッドリーダー」について書きたいと思います。

まず前提として、一定の規模を超えた事業がさらに非連続な成長を遂げるためには(いわゆるSカーブの成長曲線の再現)、往々にして相応の大きな挑戦が必要だと思っています。そのためには、強力なアイデアを形にし、提供価値を進化させ、販促や営業の型をつくり、それをスケールさせるオペレーションを構築しなければなりません。つまり既存のやり方の地続きではない「変革」が必要です。

しかし、変革に着手するのは容易ではありません。なぜなら、一人ひとりが日々の仕事に忙殺されてしまっているからです。顧客対応も、開発も、運用も止められない。止めた瞬間に組織や個々人の目標達成は危うくなり、売上や各種KPIに影響を及ぼす可能性もあります。

そう、みんな、すでに忙しい。けれども変革には地続きでない追加の努力が必要です。変革は、余裕ができたときに取り組む追加タスクではなく、日常業務そのものを変えるような力学が働かなければ生まれません。新しい価値や仕組み、意思決定、そして組織の再編まで求められる。では、誰がそれをできるのでしょうか?

推進力を設計するという発想:シングルスレッドリーダーとは

ここで役に立つと僕が信じているのが、シングルスレッドリーダーの考え方です。シングルスレッドリーダーとは、「特定の事業領域の成功に必要な権限とリソースを与えられた、兼任ではない専任のリーダー」を指す、Amazonで提唱された考え方です。僕の前職であるGoogleにおいても、DRI(Directly Responsible Individual)という名称でほぼ同様に運用されていました。日本人としてはシングルスレッドの方が言いやすいので、僕はそちらの呼び方を使っています。

僕はこれを、単なる「責任者を置く」話だとは捉えていません。シングルスレッドとは、開始点と終着点があるプロジェクト管理の話ではなく、「推進力の設計」の話だと思っています。

この仕組みが解決するのは、「スキル・経験がある人ほど兼務状態になりがち」問題です。「話が早いから」「結果が期待できるから」といった理由から、短期的には合理的な側面もある一方で、兼務状態の膨張は、言わずもがな以下のような弊害を生むリスクを抱えています。

  • 当事者の集中が分断される(思考やアクションが積み上がらない)
  • 優先順位が毎回揺れる(何かを決めるまでが長い)
  • 責任が薄まる(最後に腹を括る点が曖昧になる)

その結果、複数の領域それぞれの雑務に追われて推進力が弱まり、全体的なアウトカムにも影響してしまうと思っています。

それゆえ、真逆のアプローチで、スキル・経験が豊富な人こそ思い切って「専任である一つのコトに向き合ってもらう」。それだけで、一人ひとりの目線も、スピードも、アウトプットの質も段違いで変わるというのを目の前で幾度となく見てきています。

タイミーでの実装

タイミーでは、過去1年間で、既存事業とは別に実験的に 6つのシングルスレッド組織をつくり、それぞれに事業オーナーを立てて推進してきました。

シングルスレッドリーダーたちは、事業の責任者としてバーティカルに特定の領域を管掌し、責任と意思決定権限をダイレクトに持ちます。もし任用時点でそれ以外の事業テーマも管掌していた場合には、任用のタイミングでそれらをすべて手放してもらいます(その領域は別の専任オーナーや既存の事業ラインに委譲し、推進力が分散しない構造に切り替えられるよう、最大限努力します)。多くのケースで戦略策定機能、企画・推進機能、そして営業機能はこのシングルスレッドリーダーの組織に属して、「管掌する事業」にフルコミットでフォーカスします。

一方で、プロダクトやマーケティングはシングルスレッドリーダーの管掌下には置かず、それぞれ独立した本部として残しています。その上で、ホリゾンタルにそれぞれの事業に対して担当を配置していきます。これはよく取られている形式だと思いますが、これらの機能を一箇所に固めた方が、採用・育成・型化・横展開の生産性が高くなることが大きな理由です。

この組み合わせはいわゆるメッシュ構造とも言えますが、古くからあるマトリックス組織との違いは何でしょうか。それは、シングルスレッドリーダーの組織においては「責任の一本化」と「専任性の強制力」が決定的に違うと思っています。マトリックスは事業と機能を掛け合わせる強力な概念ですが、構造上どうしても“責任の曖昧さ”が残り、責任が2軸に分散しやすく、意思決定も調整コストが高くなりがちです。シングルスレッドリーダーの組織においては、この曖昧さを一掃し、責任とリソースと意思決定を1本に束ねるように設計しています。マトリックス型組織の弱点をアップデートした形であり、専任化によって“推進力の失血”を防ぐ仕組みとも言えます。

組織デザインは常にアップデートし続けるもの

もちろん、タイミーにおいても組織は日々形を変えています。シンプルなシングルスレッド体制が適する場面もあれば、複数の機能が密に連携した方が早いケースもある。成長フェーズや事業特性によって、求められる“最適な推進構造”は毎月のように変わります。だからこそ、組織デザインは一度つくって終わりではなく、事業の変化速度に合わせて絶えず再構築する「動的なシステム」であるべきだと思っています。

スポットワークというまだまだ進化を続ける業界だからこそ、戦略・戦術・組織あらゆる領域において「変革」に取り組み、事業の成長にあわせて組織も柔軟に形を変えながら、ワーカーの人生の可能性を広げるインフラづくりに真正面から取り組んでいきたいと思います。

タイミーのこういった様々な挑戦にご興味のある方、全方位で採用活動中ですので、ぜひお話ししましょう!

プロダクト採用サイトTOP

カジュアル面談申込はこちら