Skip to content

AIエージェント ​

AIエージェントを、人と同じ「テナントのメンバー」として参加させる仕組み。今後どの SaaS にもエージェントが参加する前提で、Lase の共通機能として持つ。

考え方 ​

  • エージェントは users の1行で表す。 名前を持って行動する主体という点で人と同じなので、別テーブルにしない。アプリ側の担当者・メンション・更新履歴などの仕組みがそのまま使える。
  • 人との違いは users.type だけで持つ。 HUMAN(既定)と AGENT(UserType)。users は複数のテナントにまたがるアカウントなので、ここには主体そのものの種類だけを持ち、管理者・ゲストのような権限の段階はテナントへの所属(tenant_users)に持つ。
  • エージェントはログインしない。 パスワードもメールアドレスも持たず、メールも届かない。認証はエージェントに紐づくトークン(下記)だけで行う。
  • エージェントは1つのテナントだけに属する。 作成時に tenant_users も一緒に作り、他テナントへは参加させない。
  • 責任者・説明などの付加情報は持たない。作成・停止は管理者が行い、誰が作ったかは監査ログで追う。

データ ​

テーブルカラム内容
userstypeHUMAN / AGENT(UserType)。既定は HUMAN
usersemailエージェントでは NULL(人は従来どおり必須。入力の検証はアプリ・API 側で行う)
mcp_service_tokensuser_idトークンの持ち主のエージェント。必須

ログインとメールの遮断 ​

User::isAgent() で判定し、次の入口でエージェントを拒否する。

  • パスワードログイン(Auth::attempt の条件に type = HUMAN を含める)
  • テナントへのトークンログイン(TenantLoginTokenService::canLogin)
  • 管理者の代理ログイン(impersonateByToken)

メールは User::routeNotificationForMail() がエージェントでは null を返すため、通知を送っても届かない。

メンバー画面との分離 ​

エージェントの操作は AgentController に限る。テナントのメンバー画面(TenantUserController)の一覧にはエージェントを出さず、TenantUserPolicy の閲覧・状態の変更・削除でも見つからない扱い(404)にする。トークンを残したまま所属だけを消したりできないようにするため。管理画面(Admin\TenantUserController)には従来どおり表示する。

エージェントの権限も、人と同じ役割(tenant_roles)で決める。役割は人と同じく1つ(役割なしもある)。役割の変更は AgentController::update(tenantRoleId)で行い、人の役割の変更と同じく TenantUserPolicy::changeRole と、付ける役割の TenantRolePolicy::assign で確認する(エージェントに許すのは changeRole だけで、閲覧・状態の変更・削除は 404 のまま)。

エージェントにはオーナー(全権限 *)の役割を付けられない。 トークンを持つエージェントが全権限を持たないようにするため。tenantRoleId にオーナーの役割を指定すると 422 になり、画面の選択肢(GET /api/tenant/agents の roles)にも出さない。

作成・変更・削除(AgentService) ​

操作内容
create(Tenant, name)エージェントの users と tenant_users(ACTIVE)を1トランザクションで作り、既定の役割を付ける(下記)。上限を超える場合は作らない
rename(TenantUser, name)名前を変える
setStatus(TenantUser, TenantUserStatus)tenant_users.status を ACTIVE / SUSPENDED に切り替える。停止中はトークンでの認証を拒否する
assignRole(TenantUser, ?tenantRoleId)役割を付け替える(null なら外す)。オーナーの役割の場合は ValidationException。権限のキャッシュも捨てる
assignableRoles()エージェントに付けられる役割(オーナーの役割を除く)
delete(TenantUser)トークンを無効化し、tenant_users と users を論理削除する

上限(料金) ​

人とは別の料金体系にするため、エージェントの数は Subscription のクオータで数える。config('lase.agents.quota_metric')(LASE_AGENT_QUOTA_METRIC)に metric 名を設定すると、作成済みのエージェント数 < 恒久クオータ + 契約クオータ のときだけ作成できる。未設定(既定)なら上限なし。料金が決まるまでは未設定で運用する。

既定の役割 ​

config('lase.agents.default_role_code')(LASE_AGENT_DEFAULT_ROLE_CODE)に役割の code(tenant_roles.code)を設定すると、作ったエージェントにその役割を付ける。未設定(既定)なら役割を付けない(権限を何も持たない)。設定した code の役割が無い場合と、オーナーの役割の場合は、エージェントを作らずに LogicException を投げる。

アプリは、エージェント用の役割をマイグレーションで code 付きで登録し、ここに設定する。

認証(エージェントのトークン) ​

MCP の Service Token は、必ずエージェントに紐づく「エージェントのトークン」とした。

  • 認証すると、トークンの持ち主のエージェントとして動く。McpPrincipalContext は type = agent、userId にエージェントの ID が入る。
  • 次のどれかに当てはまると拒否する: トークンの失効・期限切れ・IP 制限、エージェントが AGENT でない、ユーザーまたは所属(tenant_users)が ACTIVE でない、所属のテナントがトークンのテナントと違う。
  • 認証後は Auth::setUser() でエージェントを現在のユーザーにする。アプリ側は人のリクエストと同じく auth()->user() で主体を取れる。
  • OAuth(人の代理)は従来どおり type = user。clientId でどのアプリ経由かが分かる。

テナント API ​

権限はどちらも mcp.service-tokens:manage。

メソッドパス内容
GET/api/tenant/agentsエージェントの一覧(役割 role・トークンを含む)。roles に付けられる役割を、mcp に組織が MCP 連携を使えるか(available・reason・message)を返す
POST/api/tenant/agents作成(name)
PUT/api/tenant/agents/{agent}名前・状態・役割(name, status, tenantRoleId。null なら役割を外す)の変更。役割の変更には user:write も要る
DELETE/api/tenant/agents/{agent}削除
POST/api/tenant/agents/{agent}/tokensトークンの発行(name, allowedIps, expiresAt)。平文は応答で一度だけ返す。範囲は絞らず、エージェントの役割の権限で決まる。組織が MCP 連携を使えなければ 403(code: MCP_UNAVAILABLE)
POST/api/tenant/mcp-service-tokens/{token}/rotateローテーション
DELETE/api/tenant/mcp-service-tokens/{token}無効化

画面は組織の設定(createSettingsRoutes())の「エージェント」(settings.agents)。エージェントの作成・停止・削除・役割の付け外しと、トークンの発行・ローテーション・無効化を行う。組織が MCP 連携を使えないときは理由を示し、トークンの発行とローテーションを止める。アプリが userTabs で足したタブ(所属など)は、エージェントごとのボタンから開いて編集する。

エージェントの状態と役割は、この API だけで変える(メンバーの PUT /api/tenant/tenant-users/{id} はエージェントには 404 を返す)。