0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AIエージェントのトークン制限を回避する — 繰り返し投稿作業をスクリプトに委譲した話

0
Posted at

AIエージェントを使い続けていると、セッションが長くなるにつれてコンテキストが圧迫されていく感覚があります。
1回の会話で大量のファイルを読み込み、判断し、コードを書かせると、それだけでトークンをかなり消費します。

私はある分野のアプリを題材にした技術記事を数十本まとめて書き上げ、Qiitaに1日1本ずつ投稿するスケジュールを組んでいました。
最初は「毎朝エージェントを起動して次の記事を投稿させればいい」と考えていたのですが、そのやり方には構造的な問題があることに途中で気づきました。

この記事では「繰り返し作業をスクリプトに委譲する」という設計でトークン制限の外に出た経緯を書きます。

なぜエージェントに投稿させてはいけないのか

毎朝エージェントを起動して「次の記事を投稿して」と指示する運用を想像してみます。
エージェントはまず記事ファイル一覧を読み込み、どの記事が未投稿かを状態ファイルで確認し、対象ファイルを読み込んでAPIを呼び出し、状態ファイルを更新して終わります。
この一連の作業は、じつはほぼ毎回同じ手順です。

問題は、その「毎回同じ手順」のためにエージェントを起動するたびにコンテキストが積み上がっていく点です。
ファイルの読み込み、エラーハンドリングの確認、状態の整合性チェック——こうした判断材料をセッションごとに渡し続けると、長期の連用では制限に近づきます。
さらに、エージェントはそのセッション中に「次に何をすべきか」を考えながら動くため、単純なAPIコール1本にしては重すぎる処理になります。

最初は投稿作業そのものもエージェントに頼もうとしていました。しかし「これは毎日同じことをやらせているだけだ」と気づいたのは、3日ほどその方針で動いてみてからでした。
定型作業を毎回エージェントに持ち込むのは、電卓に「昨日と同じ計算をして」と毎朝頼むようなものです。設計として非効率です。

数十本の記事を毎日投稿する状況

Android・Kotlin・Jetpack Composeを中心とした実装記録を数十本書き上げ、各記事は独立したMarkdownファイルとして管理しています。
投稿スケジュールは「1日1本」で、継続的に露出させたほうが流入が安定するという判断です。

手動では毎朝ファイルを確認してAPIを叩いて状態を記録する作業が発生します。
エージェントに頼もうとしたのですが、前述の通り「毎日同じ手順でエージェントを起動し続ける」ことの非効率さにぶつかりました。

「スクリプトを作ること」だけをエージェントに任せる

解決策はシンプルです。エージェントへの依頼を「スクリプトを1回作ること」に絞りました。
スクリプトが完成したらエージェントはループの外に出て、以降の繰り返し実行はスクリプトとタスクスケジューラに委ねます。

この設計の核心は、作業の性質による切り分けです。

  • エージェント向き: 設計判断、実装、一度きりのコード生成
  • スクリプト向き: 毎回同じ手順の繰り返し、スケジューリング、状態管理

エージェントに「考えさせる必要のある仕事」と「決まった手順を実行するだけの仕事」を混在させると、エージェントが本来得意なことに集中できなくなります。

変更前後のフローを図示するとこうなります。

【変更前】
エージェント起動 → 記事ファイル読み込み → API呼び出し → 状態更新 → (翌日繰り返し)
↑ 毎日トークン消費

【変更後】
エージェント起動 → スクリプト作成(1回のみ)→ エージェント終了
                タスクスケジューラ → スクリプト自動実行(毎朝、エージェント不在)

エージェントが関与するのは最初の1回だけです。その後は人間もエージェントも何もしなくてよくなります。

実装: Qiita REST API + Node.js + Windowsタスクスケジューラ

構成は4ファイルです。post-next.mjs がエントリポイントで、parse-article.mjs がMarkdownのパースを担当します。
投稿済み番号は articles/.qiita-state.json に永続化し、register-task.ps1 でWindowsタスクスケジューラに登録します。

API呼び出し部

post-next.mjs のQiita REST API呼び出し部分は次の通りです。

const res = await fetch("https://qiita.com/api/v2/items", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${token}`,
  },
  body: JSON.stringify({
    title: article.title,
    body,
    tags,
    private: true, // 限定公開(下書き相当)
    tweet: false,
  }),
});

private: true にしている理由は、自動投稿した記事をそのまま公開せず、一度目視確認してから手動で公開に切り替えるためです。
Qiitaでは「限定公開」状態でもAPIでタグを付与できるので、note-posterのように下書き後に手動でタグを付ける手間が不要です。

状態管理

投稿が成功すると state.posted 配列に番号・ID・URLが追記されます。
ファイル書き込みは .tmp ファイルに書いてから rename する原子的書き込みで、プロセス中断によるファイル破損を防いでいます。
エラー時は lastError フィールドに記録され、次回実行時に同じ記事を再試行します。

タスクスケジューラ登録

register-task.ps1 の核心部分です。

$action = New-ScheduledTaskAction `
    -Execute "powershell.exe" `
    -Argument "-NoProfile -WindowStyle Hidden -Command `"& '$nodePath' post-next.mjs *> '$logFile'`"" `
    -WorkingDirectory $workDir

$trigger = New-ScheduledTaskTrigger -Daily -At ([datetime]::Today.AddHours(8))

$settings = New-ScheduledTaskSettingsSet `
    -StartWhenAvailable `
    -DontStopIfGoingOnBatteries `
    -AllowStartIfOnBatteries `
    -ExecutionTimeLimit (New-TimeSpan -Minutes 10)

-StartWhenAvailable を指定すると、8:00にPCが起動していなかった場合でも、起動後に「追いかけ実行」してくれます。
ラップトップで運用しているため、必ずしも朝8時にPCが動いているとは限りません。このオプションがないと投稿が抜け落ちます。

動き始めてから気づいたこと

スクリプトを仕込んでしばらくすると、複数本の記事が自動的に限定公開されていました。
ある朝PCを開いたら、何もしていないのに次の記事がQiita上に限定公開状態で上がっていました。予想はしていたはずなのに、思ったより気持ちよかったです。

エージェントは投稿フローに一切関与していません。
スクリプトが状態を自律的に管理し、毎朝静かに1本ずつ処理を進めています。

この経験から得た切り分け原則をまとめると次のようになります。

仕事の種類 適した手段
設計・実装・判断が必要な作業 AIエージェント
毎回同じ手順の繰り返し スクリプト + スケジューラ
状態を持つ長期実行 スクリプト(状態ファイルで管理)

AIエージェントは「考える道具」であって「定期実行ランナー」ではありません。
この切り分けを意識するだけで、トークンの使い方が大きく変わります。

「エージェントに繰り返し作業をさせようとしている」人の検索に引っかかる場所にこの記事を置いておきます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?