AIを仕事に入れたい。そう考えた瞬間、多くの人はまず「賢いAIを1体作ればいい」と思う。だが、実務の現場に置くと、その発想はかなりの確率で破綻する。予定確認、メール返信、タスク通知、写真台帳、図面清書、顧客登録、見積作成、売上確認。これらを全部ひとつの巨大プロンプトに押し込むと、最初は感動するほど動くのに、業務が増えるほど崩れていく。だから自分は考え方を変えた。万能AIを1体作るのではなく、役割を持ったAI社員を複数つくり、DISCORDを受付にして“小さな会社”を組むことにした。
結果から言うと、このやり方はかなり現実的だった。ユーザーはDISCORDに「今日の予定見て」「このメールに返信文を作って」「写真台帳を進めて」「この顧客を登録したい」と雑に投げる。するとメイン秘書のようなAIが依頼を読み、担当のサブエージェントへ振り分ける。必要なら承認を止め、成果物はファイルで返し、ログはチャンネルに残す。単なるチャットボットではなく、入口、担当、手順、承認、成果物、記録を持ったチームとして動き始める。
本稿は、その設計を実装目線でかなり濃くまとめたものだ。夢の話ではない。どのように役割を分けたか、なぜそうしたか、どこが壊れやすいか、何をコードに寄せ、何をAIに任せ、どう運用に落とすかまで踏み込む。読者として想定しているのは、AI活用に興味のある個人開発者、小規模事業者、そして社内ツールを作るエンジニアだ。単に「AIすごい」で終わる話ではなく、明日から自分の業務に持ち込むならどう設計すべきかを持ち帰れる記事にしたい。
イントロイメージ。DISCORD上の依頼を、メイン秘書と専門サブエージェントが受け持つ“AI社員チーム”として設計する。
目次
- 強い導入――なぜ“万能AI”の発想を捨てたのか
- なぜ万能AI1体では破綻するのか
- AI社員チームという設計思想
- 全体アーキテクチャ
- エージェント定義
- SKILL.mdで業務マニュアル化する理由
- スキル読み込み実装
- メインプロンプトとJSON出力
- 承認フロー
- DISCORDを受付にする理由
- 作業室チャンネルと成果物運用
- メモリを社員別に持つ
- 自動社員
- 実処理をサービス層へ逃がす
- メール返信エージェントの出力制約
- 定型処理のショートカット
- 人格設計
- 個人用BOTと業務用BOTの分離
- 成果物保存場所
- エラー通知
- 新しい社員の増やし方
- 最小構成
- よくある失敗
- 実装で効いた設計原則
- 実際に作る順番
- まとめ
1.強い導入――なぜ“万能AI”の発想を捨てたのか
AIに業務を手伝わせる話になると、真っ先に出てくるのが「全部できる賢いAI」を作りたくなる誘惑だ。予定も見られる、メールも返せる、画像も理解できる、表も作れる、外部サービスも触れる。たしかに、モデルの能力だけ見れば、その夢はかなり現実に近づいている。だが、現場で必要なのは能力総量よりも運用単位に合った責務だ。
人間の会社で、総務・経理・営業・施工・秘書・顧客管理を全部1人にやらせると破綻する。たとえ優秀でも、文脈が混ざり、優先順位が狂い、責任線が曖昧になる。AIも同じだ。しかもAIは、もっともらしい顔で間違える。だから「何ができるか」だけでなく、「何だけを担当させるか」を決める必要がある。
この視点に立つと、AI活用の設計はぐっと実務的になる。問題は“脳を大きくすること”ではない。“組織図を作ること”だ。入口は誰か。仕分けは誰か。予定を見るのは誰か。成果物を返すのは誰か。危ない操作を止めるのは誰か。この問いに答えるように設計していくと、AIは単なるチャット相手から、毎日の業務に耐えるチームへ変わっていく。
2.なぜ万能AI1体では破綻するのか
最初にやりがちな失敗は、ひとつのシステムプロンプトにすべてのルールを入れてしまうことだ。予定確認のルール、メール返信の文体、Google Tasks通知の条件、写真台帳の手順、経費書類のフォーマット、顧客登録CSVの注意点、見積作成の計算式、売上確認の検索ルール。これらを一箇所に集約すると、最初は“全部入り”の安心感がある。しかし、それは長く続かない。
破綻の本質はトークン量ではない。文脈の混線だ。予定確認では「今日・明日はAsia/Tokyo基準で解釈する」が正しい。メール返信では「返信文だけを返し、解説を混ぜない」が正しい。写真台帳では「一度に複数の写真を要求しない」が正しい。どれも単独では正しいが、全部を同じ文脈へ入れると、AIは今どのルールを優先すべきかを迷いやすくなる。
そしてもうひとつの問題は、改善単位が壊れることだ。もし写真台帳だけを直したいのに、巨大プロンプトの中から該当箇所を探して修正し、その影響が予定確認やメール返信へ飛ぶかもしれないと考えなければならないなら、現場は改善を止めてしまう。AI運用が育たないのは、精度不足よりも、修正単位が不明瞭なことが大きい。
人間の業務設計でも同じで、役割が曖昧な組織はだいたい疲弊する。AIでも同じことが起きる。だから、ルールを増やすのではなく、ルールが効く範囲を狭める。それがメインエージェントとサブエージェントを分ける理由だ。
3.AI社員チームという設計思想
ここでの設計思想は単純だ。AIを“万能な一人”としてではなく、“役割を持った複数の社員”として扱う。中心にメインエージェントがいる。役割は受付、依頼の分解、サブエージェントの選択、結果の統合、承認線の管理だ。その下に、予定、タスク、メール返信、図面、写真台帳、経費、顧客登録、見積もり、売上確認といった専門社員を置く。
重要なのは、各社員に“できること”だけではなく、“守ること”“やってはいけないこと”も定義する点だ。メール返信エージェントは完成文だけ返す。予定エージェントは作成や変更を承認待ちにする。写真台帳エージェントは次に必要な写真を1枚だけ聞く。こうした仕事の作法が、社員ごとの個性であり、安全装置でもある。
さらに、各社員は自分専用の業務マニュアルを持つ。それが後述するSKILL.mdだ。人間の会社で言えば、受付が全部の手順を暗記しているのではなく、担当ごとに手順書を持っている状態に近い。メイン秘書は入口を軽く広く知り、担当者は自分の業務だけ深く知る。この構造にすると、新しい仕事を増やすときも“新しい社員を採用する”感覚で拡張できる。
中央のメイン秘書が依頼を解釈し、各専門サブエージェントへ振り分ける。巨大プロンプトではなく、組織構造として設計する。
4.全体アーキテクチャ
全体像は、思っているより会社的だ。ユーザーはDISCORDへ依頼を送る。DISCORD BOTがメッセージを受け取る。スラッシュコマンドやキーワードから、特定のサブエージェントを明示呼び出しするかどうかを判断する。たとえば「/予定確認」なら予定エージェント、「/写真台帳」なら写真台帳エージェント、「秘書」宛の通常依頼ならメインエージェントに渡す。
メインエージェントへ渡す入力は、単なるメッセージ本文だけではない。直近の会話、利用可能なエージェント一覧、各エージェントの役割、対応するスキル文書、過去メモリ、必要ならGoogle CalendarやTasksなどの現在状態。つまりAIは、ただ一文に反応するのではなく、いまこの会社で見えている情報一式をもとに判断する。
そして返り値は自然文だけにしない。最低でも、次の4つは持たせるのがおすすめだ。
- reply:ユーザーへ返す自然文
- approvalRequests:承認が必要な操作の一覧
- agentActivity:どの社員が何をしたかの作業ログ
- learnedMemory:次回以降へ残したい学習候補
この構造を最初から固定すると、BOT側で承認ボタンを出したり、作業ログを別チャンネルへ送ったり、学習候補を保存したりできる。要するに、AIの返答ではなくAI社員が働いた結果を受け取るわけだ。
src/
agents/
agent-definition.ts
registry.ts
prompt-builder.ts
skill-loader.ts
codex/
runner.ts
parse-response.ts
discord/
bot.ts
approval-buttons.ts
message-format.ts
agent-channel-manager.ts
approval/
approval-service.ts
calendar/
calendar-query.ts
tasks/
task-notification-service.ts
photo-ledger/
photo-ledger-service.ts
memory/
json-file-memory-adapter.ts
secretary-service.ts
index.ts
skills/
calendar_agent/calendar-read/SKILL.md
email_reply_agent/write-reply/SKILL.md
photo_ledger_agent/create-photo-ledger/SKILL.md
outputs/
photo-ledgers/
camera-layouts/
expense-reports/
customer-imports/ここで重要なのは、src/agents と skills を分けていることだ。前者はエージェントの定義とプロンプト組み立て。後者は人間可読な業務マニュアル。さらに、ExcelやCSVを実際に触る処理は各ドメインサービスへ逃がす。AIが判断する層と、再現性が必要な実処理層を混ぜないことが、後から効いてくる。
5.エージェント定義――“社員名簿”を作る
まず作るべきは、社員名簿にあたる定義だ。ここでのポイントは、単なる関数一覧にしないこと。名前、役割、最低限のスキル、常に守るルール、スキル文書へのパス、呼び出し名、スラッシュコマンド名などを持たせる。これにより、エージェントは“機能”ではなく“担当者”として扱えるようになる。
export type AgentDefinition = {
id: string;
name: string;
role: string;
defaultSkills: string[];
defaultRules: string[];
skillPaths?: string[];
invocationAliases?: string[];
slashCommandName?: string;
slashCommandDescription?: string;
};たとえばメイン秘書と予定エージェントは、次のように定義できる。
export const agentRegistry: AgentDefinition[] = [
{
id: "personal_secretary_agent",
name: "横断メイン秘書",
role: "ユーザー依頼を分解し、必要なサブエージェントを選び、最終回答を統合する。",
defaultSkills: [
"案件、人物、締切、承認待ちを横断して整理する",
"予定とタスクの複合依頼を一つの実行計画にまとめる"
],
defaultRules: [
"副作用のある操作は承認待ちとして提示し、承認なしに実行しない",
"情報不足がある場合は、仮定を短く明示する"
],
invocationAliases: ["秘書", "メイン秘書", "secretary"],
slashCommandName: "秘書",
slashCommandDescription: "メイン秘書に依頼して、内容に合うサブエージェントへ振り分けます。"
},
{
id: "calendar_agent",
name: "予定エージェント",
role: "予定の候補、変更、衝突、事前準備を整理する。",
defaultSkills: [
"予定候補を複数出す",
"予定前に必要な準備タスクを検出する"
],
defaultRules: [
"予定作成や変更は必ず承認対象にする"
],
skillPaths: ["skills/calendar_agent/calendar-read/SKILL.md"],
invocationAliases: ["予定", "予定確認", "カレンダー", "calendar"],
slashCommandName: "予定確認",
slashCommandDescription: "Googleカレンダーの予定確認、空き時間確認、予定変更の整理をします。"
}
];この定義で地味に効くのが、defaultRules だ。AIの設計では“何ができるか”ばかりに目が向きがちだが、実務では“何をしてはいけないか”のほうが重要な場面が多い。顧客を勝手に登録しない、外部へ勝手に投稿しない、予定は承認なしに変えない。こうした制約があるからこそ、現場へ入れやすくなる。
6.SKILL.mdで業務マニュアル化する理由
役割定義だけでも多少は動く。しかし実務はそこまで甘くない。写真台帳なら「最初に何の写真を聞くか」「複数枚送られたらどうするか」「完了と言われたら途中版を返すか」「原本Excelを直接編集してはいけないか」が必要になる。メール返信なら「前置きを一切入れない」「完成文だけ返す」「実送信はしない」。予定確認なら「今日・明日はAsia/Tokyo基準」「曜日指定は次に来る該当曜日」など、細かな作法がある。
こういう“業務の癖”をコードに書ききると、修正が重くなる。逆に口頭ルールにすると消える。そこで置くのが、SKILL.mdというMarkdownの業務手順書だ。人間が読める。AIも読める。レビューしやすい。業務の知識をコードへ閉じ込めすぎず、しかし説明文よりは強く保持できる。
SKILL.mdは、AIのための説明文であると同時に、人間にとってもレビュー可能な業務手順書になる。
--- name: calendar-read description: Discordで予定確認、今日・明日・曜日指定の予定確認、空き時間確認、予定追加相談を扱う。 --- # Calendar Read ## 役割 calendar_agentは、予定確認、空き時間確認、予定作成・変更の事前整理を担当する。 予定の読み取りはGoogle Calendar APIの実データを優先し、推測で予定を作らない。 ## 振り分け条件 - 「今日の予定教えて」 - 「明日のスケジュール確認して」 - 「月曜なにある?」 - 「カレンダー見て」 - 「空き時間ある?」 - 「この予定を追加したい」 - 「予定変更して」 ## 予定確認の手順 1. 読み取り依頼か、作成・更新を伴う依頼かを分ける。 2. 読み取り依頼なら、対象日を決める。 3. 今日・明日はAsia/Tokyo基準で解釈する。 4. 曜日指定は、今日から見て次に来る該当曜日として扱う。 5. Google Calendar APIで対象カレンダーを読む。 6. 予定は開始時刻順に並べる。 7. 予定がない場合も、対象カレンダーを見たことを明記する。 ## 作成・更新の手順 予定作成・更新は承認なしに実行しない。 必要な場合はapprovalRequestsに入れる。
ここで大事なのは、何でもSKILL.mdに寄せないことだ。判断や言い回し、業務フローはSKILL.mdが向いている。一方、Excelへ画像を貼る、CSVを出力する、Google Calendar APIを叩く、Discordにファイルを送る、といった処理はコードへ寄せたほうが安定する。境界は次のように考えると整理しやすい。
- SKILL.md:判断、確認、手順、言い回し、出力方針
- TypeScriptのサービス:外部API、ファイル操作、状態保存、再現性が必要な処理
7.スキル読み込み実装――必要な社員のマニュアルだけ読む
サブエージェント構成では、必要なときに必要なマニュアルだけを読むことが大事だ。/メール返信 と明示的に呼んだのに、予定確認や写真台帳のスキルまで読ませていたら、文脈もトークンも無駄になる。ユーザーが「今日は雑に秘書へ投げたい」のか、「今日はこの担当だけにやらせたい」のかを使い分けられる構成が実務では強い。
import fs from "node:fs/promises";
import path from "node:path";
import { agentRegistry } from "./registry.js";
export async function loadAgentSkillContext(input: {
agentId?: string;
workspaceDir: string;
}): Promise<string> {
const agents = input.agentId
? agentRegistry.filter((agent) => agent.id === input.agentId)
: agentRegistry;
const chunks: string[] = [];
for (const agent of agents) {
if (!agent.skillPaths || agent.skillPaths.length === 0) continue;
for (const skillPath of agent.skillPaths) {
const absolutePath = path.isAbsolute(skillPath)
? skillPath
: path.join(input.workspaceDir, skillPath);
const body = await fs.readFile(absolutePath, "utf8").catch((error) => {
return [
`# ${agent.id} skill load error`,
`path: ${skillPath}`,
`error: ${error instanceof Error ? error.message : String(error)}`
].join("\\n");
});
chunks.push([
`## ${agent.id}: ${skillPath}`,
body.trim()
].join("\\n"));
}
}
return chunks.length > 0
? chunks.join("\\n\\n")
: "サブエージェント専用スキルは未設定です。";
}この実装では、agentId があればそのエージェントのスキルだけを読み、なければ全エージェントの一覧を渡せる。実際にはメイン秘書が全体を見て振り分ける場合でも、必要な箇所だけを要約して差し込む工夫をしてもよい。重要なのは、いつでも全社員のマニュアルを丸ごと読ませないことだ。
8.メインプロンプトとJSON出力
AI社員チームをチャットボットで終わらせないための最大のポイントは、返答を自然文だけにしないことだ。業務では、承認待ち、作業ログ、学習候補、成果物情報などを後から拾える必要がある。だから最初から、AIには“業務結果”をJSONで返させる。
export function buildSecretaryPrompt(input: {
userMessage: string;
discordConversationContext: string;
memorySnapshot: string;
googleCalendarContext: string;
agentSkillContext: string;
forcedAgentId?: string;
}): string {
return [
"あなたはDiscordから呼ばれるローカル秘書エージェント組織です。",
"返答の基本形は「最初にやわらかく受け止める → 必要な情報を整理する → 最後に次の行動を添える」です。",
"",
"重要な安全ルール:",
"- メール送信、予定作成/変更、外部投稿、ファイル削除は実行しない。",
"- それらが必要な場合はapprovalRequestsに承認待ち操作として入れる。",
"- 外部サービスを操作したように嘘をつかない。",
"- ユーザーが修正した場合、learnedMemoryに反映候補を入れる。",
input.forcedAgentId
? `- 今回は ${input.forcedAgentId} を明示呼び出ししているため、そのサブエージェントだけの役割とスキルを優先する。`
: undefined,
"",
"利用できるサブエージェント:",
formatAgents(input.forcedAgentId),
"",
"# Sub-agent skills",
input.agentSkillContext,
"",
"返答は必ず以下のJSONだけにしてください。",
JSON.stringify({
reply: "Discordへ返す自然文",
approvalRequests: [],
agentActivity: [],
learnedMemory: []
}, null, 2),
"",
"# Agent memory",
input.memorySnapshot,
"",
"# Google Calendar context",
input.googleCalendarContext,
"",
"# Recent Discord conversation context",
input.discordConversationContext,
"",
"# User message",
input.userMessage
].filter(Boolean).join("\\n");
}構造化して返すと、BOT側では次のような処理がしやすくなる。
- reply をそのまま通常返信として送る
- approvalRequests があれば承認ボタンを表示する
- agentActivity を作業ログとして別チャンネルへ流す
- learnedMemory を社員別メモリへ保存する
- 必要なら outputFiles を成果物チャンネルへ投稿する
自然文だけではなく、承認・作業ログ・学習候補を構造化して返すことで、AIは“返答者”から“業務システムの一部”へ近づく。
ここで重要なのは、“JSONにしたらAIらしさが減る”と考えないことだ。むしろ逆で、構造化しておくからこそ、ユーザー向けの自然文は自然文として磨ける。システムが必要な情報は構造体で取り、人間が読むべき部分だけを返せるようになる。
9.承認フロー――AIに勝手にやらせない
業務AIで一番怖いのは「勝手にやる」ことだ。外部サービスを操作する処理ほど、失敗したときの影響が大きい。メール送信、予定作成、予定変更、外部投稿、ファイル削除、顧客CSVインポート、見積もりCSVインポート。これらはAIに直接実行させず、承認待ちとして返させる。
const approvalActionTypes = new Set<string>([
"send_email",
"create_calendar_event",
"update_calendar_event",
"create_task",
"complete_task",
"external_post",
"delete_file",
"import_customer_csv",
"import_estimate_csv",
"other"
]);
function normalizeApprovalActionType(value: string): ApprovalActionType {
return approvalActionTypes.has(value) ? (value as ApprovalActionType) : "other";
}返ってきた approvalRequests は、そのまま実行しない。一度保存し、DISCORD上に承認ボタンを出す。そしてユーザーが押したときだけ、実処理サービスを呼ぶ。この流れがあるだけで、AIは現場へずっと置きやすくなる。AIは提案する。人間が承認する。コードが実行する。ログが残る。この責任線は、ほぼ必須だと思っている。
const approvalRequests = await Promise.all(
parsed.approvalRequests.map((request) =>
approvals.createPending({
actionType: normalizeApprovalActionType(request.actionType),
agentId: request.agentId,
title: request.title,
summary: request.summary,
payload: request.payload,
requestedByDiscordUserId: input.discordUserId
})
)
);ここで承認の単位を細かく分けておくと、後から「予定は承認必須だが、単純な読み取りは不要」「外部投稿は二重承認にしたい」などの運用ポリシーも付けやすい。AIを使うとき、能力の問題より先に、どこで止めるかを設計したほうがいい。
10.DISCORDを受付にする理由
なぜDISCORDなのか。理由は非常に実務的で、最初のUIとして強すぎるからだ。通知が来る、チャンネルを分けられる、ファイル添付ができる、スラッシュコマンドを作れる、スマホからも使える。専用Webアプリを作るより圧倒的に立ち上がりが早い。
業務AIの失敗は、AIの賢さよりも“使うまでの距離”で起きることが多い。ログインして、画面を開いて、別のメニューへ行って、フォームに貼って……となると、結局誰も日常的に使わない。DISCORDなら、普段のチャットの延長で依頼できる。しかも、承認、添付、成果物受け取り、作業ログ確認まで同じ場所で回せる。
DISCORDは単なるチャットUIではなく、受付、通知、承認、ファイル受け渡し、ログ閲覧を兼ねる運用ハブになる。
client.on("messageCreate", async (message) => {
if (message.author.bot) return;
const forcedAgent = detectForcedAgent(message.content);
const response = await secretary.handleMessage({
message: message.content,
discordUserId: message.author.id,
discordChannelId: message.channel.id,
discordConversationContext: await buildRecentContext(message.channel),
forcedAgentId: forcedAgent?.id
});
for (const chunk of splitDiscordMessage(response.reply)) {
await message.channel.send(chunk);
}
if (response.approvalRequests.length > 0) {
await message.channel.send({
content: "承認が必要な操作があります。",
components: buildApprovalRows(response.approvalRequests)
});
}
await postAgentActivities(response.agentActivity);
await postOutputFiles(response.outputFiles ?? []);
});スラッシュコマンドをエージェント登録から自動で増やせるようにしておくのも効く。新しい社員を追加したら、UIも自動で増える。こういう“地味な自動化”が運用を楽にする。
11.作業室チャンネルと成果物運用
AI社員を運用するとき、通常の返信チャンネルだけではすぐに混ざる。ユーザー向けの返答、内部ログ、承認、成果物通知が全部同じ場所にあると、あとから見返しづらい。そこで作業室を分ける。たとえば、予定、タスク、メール返信、図面作成、写真台帳、経費、顧客登録、見積もり、売上確認、成果物、といった単位でチャンネルを切る。
こうしておくと、ユーザーへの返答は通常チャンネルへ、内部ログは作業室へ、成果物は成果物チャンネルへ、という整理ができる。人間の社員でも、仕事をしたら報告先や成果物の置き場所が必要だ。AI社員も同じで、あとから追えることは品質と同じくらい重要だ。
特に、写真台帳や見積もりのようにファイルが発生する業務では、返答文より成果物の置き場のほうが価値を持つことがある。「どこに保存されたか」「最新版はどれか」「途中版はあるか」が分かる構成にしておくと、AIの便利さが一気に現場へ馴染む。
12.メモリを社員別に持つ
AI社員は、一度の返答だけで終わらせるともったいない。使っていくと必ず、「次からこうして」が出てくる。メール返信では会社名を入れないでほしい。写真台帳では必ず1枚ずつ聞いてほしい。タスク通知はタイトルと時間だけでよい。こうしたルールは、次回以降に効くべきだ。
ただし、ここで全部をグローバルメモリへ入れると、また混線する。写真台帳の学習がメール返信へ混ざる必要はない。だからメモリは社員別に持つ。
{
"learnedMemory": [
{
"agentId": "photo_ledger_agent",
"kind": "rule",
"text": "写真台帳では、一度に複数枚を要求せず、次に必要な1枚だけ依頼する。"
}
]
}for (const learned of parsed.learnedMemory ?? []) {
const kind = toMemoryKind(learned.kind);
await memory.appendAgentMemory(learned.agentId, kind, learned.text);
}この構成が良いのは、改善の影響範囲が小さくなることだ。写真台帳の学習は写真台帳だけ、メール返信の学習はメール返信だけに効けばよい。人間の会社でも、営業の話し方を経理のマニュアルへ混ぜない。それと同じ考え方だ。
13.自動社員――タスク通知のように“自分から動く”AI
ここまでの話は、ユーザーが依頼したときに動く社員の話だった。しかしAI社員にはもうひとつの形がある。定期巡回して自分から動く社員だ。代表例がタスク通知エージェントである。
たとえばGoogle Tasksを見て、期限に達したタスクがあればDISCORDへ通知する。終わっていなければ、一定間隔で再通知する。ユーザーが「完了」と返したら、その直近通知タスクを完了扱いにする。「明日10時に変更」と返したら期限変更候補として処理する。これも立派なAI社員だが、会話型エージェントとは少し設計が違う。
大事なのは、大量通知しないことだ。一度に10件来ると人は見なくなる。だから1回の巡回で1件だけ。複数あるなら時間差で1件ずつ。AIの性能よりも、人間の受け止め方に合わせることが重要になる。
async function startTaskNotifications() {
await runTaskNotificationTick();
setInterval(() => {
void runTaskNotificationTick();
}, 60_000);
}
async function runTaskNotificationTick() {
if (taskNotificationRunning) return;
taskNotificationRunning = true;
try {
const notifications = await taskNotifications.buildDueNotifications();
const notification = notifications[0];
if (notification) {
await discordChannel.send(notification.content);
await notification.markDelivered();
}
} finally {
taskNotificationRunning = false;
}
}こういう自動社員は、AIにすべてを任せるより、巡回と状態管理はコードで堅く作り、通知文面や自然言語の解釈にAIらしさを使うのがバランスがよい。AIを派手に使うというより、AIを適切な場所へ置くという感覚が大切だ。
14.実処理をサービス層へ逃がす
AI社員を作っていると、「AIが判断するなら、そのまま実行もAIにやらせたい」と思いがちだ。しかし再現性が必要な処理ほど、サービス層に寄せたほうが安定する。写真台帳は典型例だ。AIが“写真を貼ります”と言うだけでは何も起きない。実際には、原本をコピーし、受け取った画像を読み込み、正しいセルへ貼り、保存して返す必要がある。
type PendingPhotoLedgerState = {
workbookPath: string;
cameraIndex: number;
slot: "installation" | "view";
updatedAt: string;
};
class PhotoLedgerService {
async startLedger(): Promise<{
reply: string;
state: PendingPhotoLedgerState;
}> {
const workbookPath = await this.copyTemplate();
return {
reply: "写真台帳を作り始めます。カメラ1の設置場所の写真を1枚添付してください。",
state: {
workbookPath,
cameraIndex: 1,
slot: "installation",
updatedAt: new Date().toISOString()
}
};
}
async handleImage(imagePath: string, state: PendingPhotoLedgerState) {
await this.insertImageToWorkbook({
workbookPath: state.workbookPath,
imagePath,
cameraIndex: state.cameraIndex,
slot: state.slot
});
const nextState = this.nextSlot(state);
return {
reply: this.buildNextQuestion(nextState),
state: nextState
};
}
}このときAIの仕事は、会話フローを自然にすることだ。今なにを待っているか。画像が添付されていないときにどう聞き返すか。完了と言われたら途中版を返すか。そこはSKILL.mdとAIが得意だ。一方、実際のExcel処理、ファイル保存、例外処理はサービス層が得意だ。この分業が、現場で壊れにくい。
15.メール返信エージェントの出力制約
社員ごとに“返し方”を変えるのも重要だ。メール返信はその代表で、ユーザーが欲しいのは解説ではなく、完成した返信文である。AIは親切に「以下のような返信がよいでしょう」と言いたがるが、実務ではそれが邪魔になる。コピペしにくいからだ。
# メール返信文作成スキル ## 出力ルール - replyには完成した返信文だけを入れる。 - 「以下です」「作成しました」などの前置きは入れない。 - 見出し、箇条書き、解説を入れない。 - 実際のメール送信はしない。 - 送信が必要な場合は承認待ちにする。
このように、業務ごとに使いやすい返し方を設計することが、AI社員づくりの大きなポイントになる。予定確認では箇条書きがよい。メール返信では完成文だけがよい。写真台帳では次に必要な1枚だけを聞くのがよい。つまり、AIの能力を増やすのではなく、仕事ごとの“気持ちいい使い方”を設計することが重要だ。
16.定型処理のショートカット
すべてをLLMに投げる必要はない。むしろ、定型処理はコードで直接処理したほうが速くて安定する。たとえば「今日の予定見て」「明日の予定教えて」のような定型依頼は、日付解釈とGoogle Calendar読み取りをコードで直にやればよい。AIには、その結果の自然な要約だけを任せればいい。
async handleMessage(input: {
message: string;
discordUserId: string;
discordChannelId: string;
discordConversationContext?: string;
forcedAgentId?: string;
}) {
const directCalendarResponse = input.forcedAgentId
? undefined
: await handleCalendarReadRequest(input.message, this.googleCalendar);
if (directCalendarResponse) {
await this.memory.appendConversation({
id: createId("conversation"),
discordUserId: input.discordUserId,
discordChannelId: input.discordChannelId,
userMessage: input.message,
assistantMessage: directCalendarResponse.reply,
createdAt: nowIso()
});
return directCalendarResponse;
}
// ここから通常のCODEX呼び出し
}このバランス感覚はかなり重要だ。AIに任せるべきなのは、曖昧依頼の整理、確認、要約、自然文の組み立て。コードに任せるべきなのは、時刻計算、順序並べ替え、APIアクセス、状態保存のような機械的処理だ。AIを過大評価しないことが、結果的にAIを一番うまく使う近道になる。
17.人格設計――“かわいいけれど頼れる”はUIである
業務AIでも人格は大事だ。ただし、人格は仕事を邪魔してはいけない。冷たすぎると毎日使うのがつらいし、逆にキャラクターが強すぎると重要情報がぼやける。自分の秘書エージェントには、少しやわらかい口調を入れつつ、予定、期限、承認、顧客対応、安全に関わるところでは曖昧にしない、というブレーキを入れている。
最初にやわらかく受け止める。必要な情報をきれいに整理する。最後に次の行動を添える。 ただし、過度に媚びたり、仕事の情報をぼかしたりしない。かわいいけれど頼れる秘書である。
この一文があるだけで、体感はかなり変わる。業務AIの人格は、装飾ではない。毎日触るUIの一部だ。だからこそ、情緒だけでなく、仕事の精度を下げない設計にしておく必要がある。
18.個人用BOTと業務用BOTの分離
運用していると、会社の予定やタスクは業務用DISCORDへ、個人の予定やタスクは別サーバーへ流したくなる。ここで無理にひとつのBOTへ押し込むと、認証や通知先、状態保存が混ざって危ない。だから、個人用BOTと業務用BOTは強く分けたほうがよい。
DISCORD_TOKEN=業務用Botのトークン DISCORD_ALLOWED_CHANNEL_ID=業務用チャンネルID PERSONAL_DISCORD_TOKEN=個人用Botのトークン PERSONAL_DISCORD_NOTIFICATION_CHANNEL_ID=個人通知チャンネルID GOOGLE_TOKEN_PATH=data/google-token.json GOOGLE_TASK_ACCOUNT_EMAIL=work@example.com PERSONAL_GOOGLE_TOKEN_PATH=data/personal-google-token.json PERSONAL_GOOGLE_ACCOUNT_EMAIL=personal@example.com PERSONAL_GOOGLE_ALLOWED_CALENDAR_IDS=personal@example.com PERSONAL_GOOGLE_ALLOWED_TASK_LIST_NAMES=*
通知状態ファイルも分ける。業務と個人が同じ状態ファイルを使うと、未送信判定や再通知判定が混ざる。AI社員は便利だが、アカウント境界が曖昧になると一気に危険になる。だから“分けること”自体を設計原則にしておくのがよい。
19.成果物保存場所――“作る”だけでは足りない
図面、写真台帳、経費書類、顧客CSV、見積もりCSV。AI社員がファイルを作れるようになると、保存先の設計が急に重要になる。毎回違う場所に出ると、後から探せない。結局「便利そうだったけど、どこにあるか分からない」で終わる。
outputs/
camera-layouts/
drawing_xxx/
cleaned-layout.png
cleaned-layout.pptx
plan.json
photo-ledgers/
photo_ledger_xxx.xlsx
expense-reports/
expense_report_xxx.pdf
expense_report_xxx.xlsx
customer-imports/
customer_import_xxx/
customer-import.csv
missing-fields.txt成果物は、種類ごと、案件ごとに整理して置く。さらにDISCORDの成果物チャンネルへも流しておくと、ユーザーは毎回保存先を考えずに済む。AIの価値は“生成できること”だけでなく、“後から回収できること”にある。この視点を持つと、運用がかなり楽になる。
20.エラー通知――止まったことを知らせるのも社員の仕事
Google連携や外部APIを使うなら、認証切れや権限不足は必ず起きる。このときログにだけエラーが出ても、ユーザーは気づけない。だから通知エージェントには、止まったときにそれを知らせる責務も持たせる。
async function postTaskNotificationError(channel, error: unknown) {
if (!isGoogleAuthExpiredError(error) && !isGooglePermissionError(error)) return;
const now = Date.now();
if (now - lastTaskAuthErrorNoticeAt < 60 * 60 * 1000) return;
lastTaskAuthErrorNoticeAt = now;
await channel.send([
"Googleタスク通知が止まっています。",
"Google Tasksの権限を追加するため、再認証が必要です。",
"手順: `node scripts/google-auth-standalone.mjs` を実行してGoogleアカウントを許可してください。",
"原因: Google Tasks permission/auth error"
].join("\\n"));
}ここで1時間に1回までなどの抑制を入れているのも重要だ。エラー通知が毎分飛ぶと、それ自体がノイズになる。AI社員は、うまく動くときだけでなく、失敗時の伝え方まで設計すると現場で信頼されやすい。
21.新しい社員の増やし方
この構成にしておくと、新しいAI社員を増やす手順はかなりシンプルだ。基本は、社員名簿へ追加する、SKILL.mdを書く、必要ならサービス層を作る、必要ならBOT側に添付処理を足す、承認が必要な副作用なら承認タイプへ追加する、成果物があるなら保存先を切る、これだけだ。
たとえば議事録エージェントを増やすなら、会議メモや音声文字起こしから決定事項、宿題、確認事項を整理する役割を与える。あとはSKILL.mdに出力形式と禁止事項を書けば、メイン秘書はその存在を知り、必要なときに使えるようになる。
ここでの価値は、新しい機能追加が“プロンプトの大手術”ではなく、“社員の採用”になることだ。役割単位で増やせるので、設計が育ちやすい。
最初は3人でも十分。骨格を作ってから、必要な仕事ごとに社員を増やしていく。
22.最小構成――最初は3人で十分
AI社員チームと聞くと大げさに感じるかもしれないが、最初から全部作る必要はない。むしろ、最初に盛りすぎると失敗しやすい。おすすめは、メイン秘書、予定エージェント、メール返信エージェントの3人から始めることだ。
この3人だけでも、かなり便利になる。メイン秘書が依頼を受けて整理する。予定エージェントが読み取り系の仕事をする。メール返信エージェントが完成文を返す。ここに承認フローとメモリ保存が付けば、すでに小さなAI社員チームとして十分実用的だ。
その後、Google Calendarの読み取り系を安定化し、承認付きの書き込みを足し、最後にExcelやCSVなど成果物系を足していく。この順番なら、事故の少ないところから導入できる。
23.よくある失敗
ここまで作ってみて、よくある失敗はかなりはっきりしている。第一に、プロンプトへ全部入れすぎること。第二に、AIに実行までやらせようとすること。第三に、通知をまとめて送りすぎること。第四に、成果物の保存場所を決めないこと。第五に、スキル文書を抽象的に書きすぎることだ。
「丁寧に対応する」「分かりやすく返す」だけでは弱い。「一度に複数の写真を要求しない」「メール返信では完成文だけ返す」「予定作成は承認待ちにする」「タスク通知はタイトルと時間だけにする」のような具体ルールのほうがずっと効く。AIは雰囲気ではなく、境界と具体条件で安定する。
また、失敗の多くはモデル性能の不足ではなく、責務分離の甘さで起きる。AIが悪いのではなく、設計が人間の業務構造に寄っていないのだ。この視点を持つだけで、改善の方向が見えやすくなる。
24.実装で効いた設計原則
最後に、特に効いた設計原則を整理しておく。
- AIに返答ではなく業務結果を返させる。自然文だけでなく、承認待ち、作業ログ、学習候補、成果物情報を構造化する。
- 社員ごとにスキル文書を分ける。巨大プロンプトではなく、skills/<agent_id>/<skill>/SKILL.md に業務マニュアルを置く。
- 実処理をコードに寄せる。AIには判断、要約、確認、文面を担当させ、API・ファイル・状態管理はコードへ逃がす。
- 承認フローを最初から入れる。外部影響のある操作は、AIが提案し、人間が承認し、コードが実行する。
- ログと保存先を先に決める。作業室チャンネル、成果物チャンネル、outputs配下の整理は、後回しにしない。
- アカウントを混ぜない。業務用、個人用、通知用、読み取り用を環境変数レベルで分ける。
- 最初から完璧を目指さない。小さく動かし、違和感をSKILL.mdやメモリへ反映して育てる。
これらはどれも派手ではない。しかし、AIを日常業務へ置くうえでは、派手さより再現性のほうが価値が高い。
25.実際に作る順番
これから作るなら、順番もかなり大事だ。おすすめは次の通りだ。
- DISCORD BOTを作る
- メイン秘書エージェントを作る
- エージェント登録 registry.ts を作る
- JSON返答形式を決める
- parse-response.ts を作る
- メモリ保存を作る
- 予定確認エージェントを作る
- メール返信エージェントを作る
- 承認フローを作る
- 作業室チャンネルと成果物チャンネルを作る
- ExcelやCSVなどの成果物生成エージェントを足す
- 定時通知エージェントを足す
最初に作るべきは派手な自動化ではない。依頼を受ける入口、担当へ振る仕組み、JSONで返す骨格、ログが残る経路。この背骨ができると、あとは社員を増やしていくだけになる。逆に背骨がないまま業務を増やすと、どこかで必ず崩れる。
AI社員チームを使いたいなどの相談はこちら��
https://lin.ee/Nfn1iJg
26.まとめ――AIは“何でも答えるチャット”から“仕事を進めるチーム”へ変わる
CODEXでAI社員を作るときの核心は、メインエージェントとサブエージェントの分離だ。メインエージェントは受付と統合。サブエージェントは専門業務。SKILL.mdは社員ごとの業務マニュアル。実処理はTypeScriptのサービス層。外部影響のある操作は承認で止める。成果物は決まった場所へ保存する。ログはDISCORDに残す。メモリは社員別に育てる。
この形にすると、AIは“何でも答える便利な相手”から、“仕事の入口に立って、担当を選び、止めるべきところで止まり、成果物を返す実務チーム”へ変わる。一人の万能AIを作るより、小さな専門AIを複数持つ。人間はDISCORDで雑に頼む。メイン秘書が受けて、担当者へ回す。外部操作は承認を取る。成果物は残る。そして使うたびに少しずつ賢くなる。
たぶん、これからの個人や小さな会社のAI活用は、この方向へ進むと思っている。AIに答えてもらうだけではなく、自分の仕事の構造へ合わせてAI社員を作る。その社員に仕事の手順を教え、少しずつ育てる。そうするとAIは、ただの便利ツールではなく、毎日の仕事の横にいる実用的なチームメンバーになる。
付記:この構成の一番おもしろいところは、“完成品を一発で作る”感覚ではないことだ。最初は少し雑でもいい。予定の返し方が長すぎる、タスク通知がうるさい、写真台帳で一度に複数枚要求してしまう、メール返信に前置きが入る。そういう小さな違和感を、社員ごとのルールとして残していく。するとAIは、だんだん自分の仕事へ馴染んでいく。プロンプトを毎回工夫するより、業務手順書を育てる。その発想が、AIを“実務”へ持ち込むうえで非常に強い。