
Construire mon propre assistant personnel d’IA : une chronique, partie 2
la première partie de mon parcours de construction Fernãomon agent IA personnel. Il est maintenant temps de continuer l’histoire et de plonger dans la deuxième partie !
Dans cet article, je vais vous présenter les dernières améliorations apportées à Fernãoen affinant les fonctionnalités existantes et en ajoutant de nouvelles fonctionnalités à l’agent. Commençons par ce qui a changé.
Vous vous souvenez de la fonction qui récupérait le calendrier via ICS (le format de calendrier universel) et extrayait mes tâches de calendrier ?
Cette fonction était un gâchis et reflétait une mauvaise décision architecturale. Les calendriers ICS ne prennent pas en charge le filtrage natif, ce qui signifie que chaque demande nécessitait d’extraire tous les événements du calendrier et de les filtrer ensuite. En pratique, Fernão je téléchargeais l’intégralité de mon emploi du temps juste pour en extraire quelques réunions pertinentes.
C’était comme ramener à la maison une bibliothèque entière juste pour rechercher une seule phrase dans un livre.
Plus j’essayais d’optimiser la fonction, moins j’arrivais à quelque chose, car j’avais besoin d’une nouvelle solution système. J’avais besoin de changer la façon dont le système obtenait le calendrier et je ne contournerais JAMAIS ce goulot d’étranglement avec ICS.
J’ai donc creusé plus profondément et découvert que Google fournit un accès au calendrier via une API prenant en charge le filtrage natif. Fernão ne récupère désormais que les événements dont il a réellement besoin. Cela a amélioré la génération du planning, qui est passée de près de cinq minutes à une vingtaine de secondes.
Avec ce nouveau pipeline en place, j’ai également refactorisé la logique environnante. L’ensemble de la fonction est désormais nettement plus propre et plus rapide. Nous disposons désormais d’une belle façon de récupérer les événements du calendrier via l’API :
def get_events_for_date(target_date=None, use_api=True):
"""
Fetches events for a specific date from Google Calendar.
Tries API first (if use_api=True), falls back to ICS if API fails.
Args:
target_date: datetime.date object for the target day. If None, uses today.
use_api: If True, try Google Calendar API first. If False, use ICS only.
Returns:
List of event dictionaries.
"""
if use_api and GCAL_API_AVAILABLE:
print("[GCal] Attempting to use Google Calendar API...")
try:
events = get_events_for_date_api(target_date)
if events is not None:
print(f"[GCal] Successfully fetched {len(events)} events via API")
return events
else:
print("[GCal] API returned None. Falling back to ICS...")
except Exception as e:
print(f"[GCal] API failed with error: {e}")
print("[GCal] Falling back to ICS...")
# Fallback to ICS
print("[GCal] Using ICS feed method...")
return get_events_for_date_ics(target_date)
.. et voici notre récupération API :
def get_events_for_date_api(target_date=None):
"""
Fetches events for a specific date from Google Calendar using the Calendar API.
Args:
target_date: datetime.date object for the target day. If None, uses today.
Returns:
List of event dictionaries, or None if API call fails.
"""
service = get_calendar_service()
if not service:
return None
day_start, day_end, target_date, tz_name, local = _get_local_time_range(target_date)
print(f"\n[GCal API] Fetching events for {target_date.strftime('%Y-%m-%d')}")
print(f" Timezone: {tz_name}")
# Get calendar IDs from environment, or use primary
calendar_ids_str = os.getenv('GCAL_CALENDAR_IDS', '')
if calendar_ids_str:
calendar_ids = [cid.strip() for cid in calendar_ids_str.split(',')]
else:
calendar_ids = ['primary']
all_events = []
# Fetch from each calendar
for calendar_id in calendar_ids:
try:
print(f"[GCal API] Fetching from calendar: {calendar_id}")
# Call the Calendar API with timeoutlobally
old_timeout = socket.getdefaulttimeout()
socket.setdefaulttimeout(10)
try:
events_result = service.events().list(
calendarId=calendar_id,
timeMin=day_start.isoformat(),
timeMax=day_end.isoformat(),
singleEvents=True,
orderBy='startTime'
).execute()
finally:
socket.setdefaulttimeout(old_timeout)
events = events_result.get('items', [])
print(f"[GCal API] Found {len(events)} event(s) in {calendar_id}")
# Parse events
for event in events:
# Get start time
start = event['start'].get('dateTime', event['start'].get('date'))
end = event['end'].get('dateTime', event['end'].get('date'))
# Parse datetime
if 'T' in start: # DateTime
start_dt = datetime.fromisoformat(start.replace('Z', '+00:00'))
end_dt = datetime.fromisoformat(end.replace('Z', '+00:00'))
# Convert to local timezone
start_local = start_dt.astimezone(local)
end_local = end_dt.astimezone(local)
start_str = start_local.strftime("%H:%M")
end_str = end_local.strftime("%H:%M")
else: # All-day event
start_str = "00:00"
end_str = "23:59"
all_events.append({
"title": event.get('summary', 'Untitled Event'),
"start": start_str,
"end": end_str,
"location": event.get('location', ''),
"description": event.get('description', '')
})
except Exception as e:
print(f"[GCal API] Error fetching from {calendar_id}: {e}")
continue
# Sort by start time
all_events.sort(key=lambda x: x["start"])
print(f"[GCal API] Total events: {len(all_events)}")
return all_events
Au-delà des améliorations du back-end, j’ai également ajouté de nouvelles fonctionnalités à l’assistant.
Dans la vue Calendrier, je peux désormais marquer les tâches comme terminées. Ce qui est cool, c’est que quand je le fais, ils sont automatiquement synchronisés avec Application Microsoft To-Do.


Merveilleux!
Cela me fait penser qu’au fil du temps, beaucoup d’entre nous finiront par construire nos propres « systèmes d’exploitation personnels », en assemblant des flux de travail à partir de nos outils préférés, en intégrant ce dont nous avons besoin et en remplaçant les composants chaque fois que de meilleures options apparaissent. Si les fonctionnalités deviennent faciles à reproduire, la fidélité à des plateformes spécifiques s’affaiblira.
Cela soulève également une question intéressante : les entreprises finiront-elles par tenter de verrouiller leurs systèmes en restreignant les API et les intégrations externes ? Peut-être. Mais il est peu probable que s’isoler soit efficace à long terme. Si les utilisateurs ne peuvent pas connecter les outils à leurs propres flux de travail, ils se déplaceront simplement ailleurs (… et s’agira-t-il d’un comportement réservé aux utilisateurs expérimentés ?)
Avec cette nouvelle fonctionnalité dans Schedule Maker, j’ai ensuite implémenté une autre fonctionnalité pour Fernãosuggéré par l’un des lecteurs.
Présentation : le disjoncteur de tâches.

Le Disjoncteur de tâches suit un flux de travail simple :
- Commencez par une tâche générique et volumineuse dans Microsoft à faire;
- Ajoutez du contexte sur la façon dont la tâche doit être décomposée ;
- Fernão décompose la tâche et construit un plan ;
- Les tâches résultantes sont enregistrées dans To-Do avec les dates d’échéance, puis apparaissent plus tard dans l’assistant quotidien ;

Voici comment le Task Breaker apparaît dans la barre latérale :

Et voici l’avant actuel (encore approximatif) du Disjoncteur de tâches:

Prenons un exemple réel. L’une des tâches les plus importantes figurant sur ma liste de tâches est « Documentation du projet sur DareData.» Ce n’est pas une petite tâche opérationnelle, c’est un projet structurant.
L’objectif est de consolider et de formaliser les connaissances internes de l’entreprise dans Notion, en s’assurant que les principales questions sur l’entreprise trouvent une réponse claire dans notre Centre de connaissances. Cela signifie examiner chaque département, identifier les lacunes, créer ou affiner des pages, structurer correctement les informations et attribuer une propriété claire à chaque section.
En pratique, cela nécessite des décisions en matière d’audit, de rédaction et de gouvernance. Ce n’est pas quelque chose que l’on « fait » en une seule séance et je souhaite terminer cela dans un délai de trois semaines. En réalité, je peux y consacrer entre 30 minutes et une heure par jour.
C’est exactement le genre de tâche qui bénéficie de la décomposition, je vais donc utiliser quelques informations contextuelles dans le Task Breaker et transformer cette giga-tâche en un plan d’action :
I’m currently building our Knowledge Hub in Notion. Here’s the current structure of Notion and Departments:
# DareData Hub
## Handbook for Network Members
[DareData Network Member Handbook ]
## Teams
### Sales & Partnerships
### Marketing (Brand and Gen-OS)
### Finance
### Delivery
[Tech Specialists]
[Principals]
[Account Managers]
I’ll need to:
- Go Through Every department and create the pages that make sense (search the web for more context on what DareData is if you need ideas of the pages I need to create on every department or use your knowledge on what definitely needs to be documented in a 130 people B2B company).
- Make sure that every page has an Owner
- Revisit the Suggestions recorded by my team in the suggestions, I can probably pick one or two suggestions every day
- Add some departments that are not in there: Admin, Core Members, Finance, Product
I have around 30 minutes to 1 hour to work every day and want to complete this project by 7 March 2026.
Après avoir cliqué sur « panne », Fernão va commencer à forger :

Merveilleux x2 !

Nous pouvons maintenant examiner les tâches et le plan générés et les soumettre à mon Microsoft To-Do. Je dois également attribuer à quelle liste sur Microsoft To-Do ils seront attribués :

Cool! Voyons à quoi cela ressemble dans l’application Microsoft :

Parfait.
En testant cela, j’ai réalisé que je souhaitais ajuster deux choses.
- D’abord, Fernão est censé être un chroniqueur médiéval, pas un guerrier fantastique, je vais donc modifier la conception du forgeage. Ses vêtements doivent paraître médiévaux.
- Deuxièmement, sur une note plus pratique, j’ai besoin d’un « Soumettre tout » Bouton. Soumettre les tâches une par une est fastidieux, surtout lorsqu’il s’agit de décomposer des projets plus importants.
Arrêtons une autre tâche après avoir apporté ces modifications :

Fernão se forge désormais dans un véritable gilet médiéval :

Et maintenant, nous pouvons aussi utiliser le gentil « Soumettre tout » bouton:

Je vais probablement supprimer l’emoji. Ce genre de « émojiification» est très typique du code généré par LLM dans le front-end, et cela m’énerve vraiment.
Quoi qu’il en soit, créer cette nouvelle fonctionnalité pour Fernão était vraiment satisfaisant. Si vous avez des idées de fonctionnalités supplémentaires, je suis ouvert aux suggestions !
Vous trouverez ci-dessous l’invite actuelle que j’utilise pour la répartition des tâches. Je continuerai à l’affiner au fur et à mesure que j’expérimenterai et observerai ses performances dans des cas d’utilisation réels.
name: task_breakdown
description: Break down a task into 20-minute actionable subtasks
max_tokens: 4096
variables:
- task_name
- task_context
- current_date
template: |
You are a productivity expert helping break down complex tasks into manageable 20-minute chunks.
**CURRENT DATE:** {current_date}
**TASK TO BREAK DOWN:**
{task_name}
**CONTEXT PROVIDED BY USER:**
{task_context}
**YOUR JOB:**
Break this task into specific, actionable subtasks that can each be completed in approximately 20 minutes.
**RULES:**
1. Each subtask should be concrete and actionable (starts with a verb)
2. Each subtask should take ~20 minutes (can be 15-25 min, but aim for 20)
3. Subtasks should follow a logical order
4. Be specific - avoid vague tasks like “work on X”
5. If the task is already small enough, you can create 1-3 subtasks
6. If it’s large, create > 5 subtasks
7. Consider the context provided - use it to make subtasks relevant and specific
8. **SCHEDULING:** Based on the user’s context (e.g., “every other day”, “weekends only”), suggest a specific Due Date for each task starting from the Current Date.
**OUTPUT FORMAT:**
Return ONLY a markdown list of subtasks in this format:
- {task_name} - [Subtask description] (20 min) [Due: YYYY-MM-DD]
Example:
- {task_name} - Create project repository (20 min) [Due: 2026-02-13]
- {task_name} - Configure CI/CD pipeline (20 min) [Due: 2026-02-15]
Do NOT include any other text or explanations. Just the list.
En parallèle, je travaille déjà sur plusieurs nouveaux modules :
- UN Analyseur de dividendes pour projeter les revenus de mes actions et ETF
- UN Assistante de rédaction créer un plan éditorial pour mes écrits.
- UN Réductions module pour vérifier les promotions pertinentes lorsque je planifie un achat
- UN Organisateur de guitare pour structurer et planifier des séances de pratique chaque fois que je prends ma guitare
Restez à l’écoute pour les prochains modules et espérons que cela vous inspirera également pour vos projets personnels !



