How to Build an AI Agent
How to Build an AI Agent
s11

Agents autonomes

Collaboration

Scan Board, Claim Tasks

499 LOC14 outilsTask board polling + timeout-based self-governance
Teammates scan the board and claim tasks themselves; no need for the lead to assign each one

s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > [ s11 ] s12

"Les co-écutants scannent le tableau et claiment les tâches eux-mêmes" -- pas besoin que le lead assigne chacun.

Problème

Dans s09-s10, les co-écutants ne travaillent que quand explicitement dit. Le lead doit générer chacun avec un prompt spécifique. 10 tâches non réclamées sur le tableau ? Le lead assigne chacune manuellement. Ça ne scale pas.

Vraie autonomie : les co-écutants scannent eux-mêmes le tableau de tâches, claiment les tâches non réclamées, travaillent dessus, puis cherchent plus.

Une subtilité : après la compression de contexte (s06), l'agent pourrait oublier qui il est. La ré-injection d'identité corrige ça.

Solution

Cycle de vie du co-équiper avec cycle idle:

+-------+
| spawn |
+---+---+
    |
    v
+-------+   tool_use     +-------+
| WORK  | <------------- |  LLM  |
+-------+                +-------+
    |
    | stop_reason != tool_use (or idle tool called)
    v
+--------+
|  IDLE  |  poll every 5s for up to 60s
+---+----+
    |
    +---> check inbox --> message? ----------> WORK
    |
    +---> scan .tasks/ --> unclaimed? -------> claim -> WORK
    |
    +---> 60s timeout ----------------------> SHUTDOWN

Ré-injection d'identité après compression:
  if len(messages) <= 3:
    messages.insert(0, identity_block)

Comment Ça Marche

  1. La boucle du co-équiper a deux phases : WORK et IDLE. Quand le LLM arrête d'appeler des outils (ou appelle idle), le co-équiper entre en IDLE.
def _loop(self, name, role, prompt):
    while True:
        # -- WORK PHASE --
        messages = [{"role": "user", "content": prompt}]
        for _ in range(50):
            response = client.messages.create(...)
            if response.stop_reason != "tool_use":
                break
            # execute tools...
            if idle_requested:
                break

        # -- IDLE PHASE --
        self._set_status(name, "idle")
        resume = self._idle_poll(name, messages)
        if not resume:
            self._set_status(name, "shutdown")
            return
        self._set_status(name, "working")
  1. La phase idle poll la boite aux lettres et le tableau de tâches en boucle.
def _idle_poll(self, name, messages):
    for _ in range(IDLE_TIMEOUT // POLL_INTERVAL):  # 60s / 5s = 12
        time.sleep(POLL_INTERVAL)
        inbox = BUS.read_inbox(name)
        if inbox:
            messages.append({"role": "user",
                "content": f"<inbox>{inbox}</inbox>"})
            return True
        unclaimed = scan_unclaimed_tasks()
        if unclaimed:
            claim_task(unclaimed[0]["id"], name)
            messages.append({"role": "user",
                "content": f"<auto-claimed>Task #{unclaimed[0]['id']}: "
                           f"{unclaimed[0]['subject']}</auto-claimed>"})
            return True
    return False  # timeout -> shutdown
  1. Scan du tableau de tâches : trouver les tâches pending, sans owner, non bloquées.
def scan_unclaimed_tasks() -> list:
    unclaimed = []
    for f in sorted(TASKS_DIR.glob("task_*.json")):
        task = json.loads(f.read_text())
        if (task.get("status") == "pending"
                and not task.get("owner")
                and not task.get("blockedBy")):
            unclaimed.append(task)
    return unclaimed
  1. Ré-injection d'identité : quand le contexte est trop court (compression arrivée), insérer un block d'identité.
if len(messages) <= 3:
    messages.insert(0, {"role": "user",
        "content": f"<identity>You are '{name}', role: {role}, "
                   f"team: {team_name}. Continue your work.</identity>"})
    messages.insert(1, {"role": "assistant",
        "content": f"I am {name}. Continuing."})

Qu'est-ce qui a Changé par Rapport à s10

ComposantAvant (s10)Après (s11)
Outils1214 (+idle, +claim_task)
AutonomieDirigé par le leadAuto-organisé
Phase idleNonePoll inbox + tableau de tâches
Claim tâcheManuel seulementAuto-claim tâches non réclamées
IdentitéPrompt système+ ré-injection après compression
TimeoutNone60s idle -> arrêt automatique

Essayer

python agents/s11_autonomous_agents.py
  1. Créer 3 tâches sur le tableau, puis générer alice et bob. Les regarder auto-claimer.
  2. Générer un co-équiper codeur et le laisser trouver du travail depuis le tableau de tâches
  3. Créer des tâches avec des dépendances. Regarder les co-écutants respecter l'ordre bloqué.
  4. Tapez /tasks pour voir le tableau de tâches avec les owners
  5. Tapez /team pour surveiller qui travaille vs idle