jaikit-flows
Login

● DevStudio — co-pilote IA

DevStudio transforme une description en langage naturel en code exécutable, avec validation HITL à chaque étape critique. → Ouvrir DevStudio · Guide complet

light @léger
Exécution directe, 0 confirmation, max 3 étapes.
medium (défaut)
Plan affiché, vous approuvez avant exécution.
extreme @extrême
Plan + pause HITL avant chaque étape critique.
# Démarrer
./start.sh
# Puis : http://localhost:8000/ui/studio

# Exemple de demande
"Crée une API FastAPI Todo avec SQLite, CRUD complet"
Outils disponibles (sondés au démarrage) :
fs.write ✅ human.review ✅ llm.completion ⚡ patterns.search ⚡ vibe.* ⚡
⚡ = optionnel, vérifié toutes les 90s

1 · Quickstart 3 commandes

.venv/bin/python -m scripts.init_keys
PASSWORD=ton-pw .venv/bin/python -m scripts.seed_users
./start.sh   # ou start.bat / .\start.ps1

Puis ouvre http://127.0.0.1:8000. Le 1er workflow utile sans config : kpi_dashboard_refresh (introspecte tes apps Jaikit locales).

2 · Concepts en 30 secondes

  • Workflow = un fichier YAML dans workflows/.
  • Step = une étape qui invoque un noeud avec des inputs.
  • Noeud = brique réutilisable (HTTP, LLM, Gmail, fs, …) — voir §5.
  • Run = une exécution d'un workflow, avec status / steps / outputs persistés.
  • HITL gate = pause humaine quand confidence LLM < seuil — voir §7.
  • Trigger = ce qui lance un workflow : cron, interval, date, manual, webhook.

3 · Format YAML d'un workflow

id: my_workflow                    # obligatoire — référence unique
name: Description courte
description: |
  Documentation libre (multiligne).

owner: admin                       # qui peut éditer
visibility: admin                  # admin | editor | viewer
tags: [demo, llm]

triggers:                          # 0..N triggers
  - type: cron
    expr: "0 6 * * *"              # tous les jours 06:00
    timezone: Europe/Paris

inputs:                            # paramètres au lancement manuel
  brand:
    type: str
    default: "renault"

env:                               # variables injectées dans tous les steps
  OUT_DIR: ./data/exports

steps:
  - id: fetch
    node: http.rss
    with:
      urls: ["https://${{ inputs.brand }}.com/news.rss"]

  - id: extract_each
    node: llm.extract
    foreach: ${{ steps.fetch.outputs.items }}
    parallel: 4                    # exécution concurrente bornée
    with:
      content: ${{ each.summary }}
      schema:
        type: object
        properties: { name: {type: string} }
    on_low_confidence:
      hitl_gate:
        timeout_minutes: 1440
        on_timeout: skip

on_failure:                        # exécuté si un step plante
  - id: alert
    node: notif.telegram
    with:
      message: "❌ Run ${{ run.id }} a échoué"

4 · Interpolation ${{ … }}

ExpressionDescription
${{ env.X }}variable du bloc env:
${{ inputs.X }}input runtime / défaut
${{ steps.<id>.outputs.<key> }}sortie d'un step précédent
${{ each.X }}élément courant dans un foreach
${{ run.id }} / ${{ run.workflow_id }} / ${{ run.started_at }}métadonnées du run
${{ secrets.NAME }}valeur Fernet déchiffrée à la volée
${{ X | length }}filtre — aussi: json, upper, lower
${{ steps.fetch.outputs.items[0].link }}indexation tableau / clé

5 · Catalogue des 25 noeuds

NomInputs (résumé)Outputs (résumé)
browser.scenario scenario_path, record_video, output_dir, extra status, artifacts, detail
fs.read path, encoding path, content, bytes
fs.write path, content, encoding, append path, bytes
gmail.archive message_id ok
gmail.label message_id, add_labels, remove_labels ok
gmail.list_inbox query, max messages
gmail.send to, subject, body, from_addr message_id
http.get url, headers, params, timeout_seconds, follow_redirects status, body, headers
http.post url, headers, json_body, data, timeout_seconds, follow_redirects status, body, headers
http.rss urls, timeout_seconds items, errors
jaikit.car_canary_diff items, db_path, id_field new_items, known_count
jaikit.car_publish payload, slot, api_base, db_path mode, entity_id, detail
jaikit.kpi_collect app, root app, snapshot, available, detail
jaikit.kpi_render snapshots, out_path written, bytes
jaikit.patterns_ingest url, theme, title, snippet mode, accepted, detail
jaikit.patterns_search query, tags, top_k, include_legacy hits, count, bundle_markdown, backend
jaikit.vibe_evolve action, prompt, project_path, patterns_context, contract, max_iters … status, ok, job_id, project_path, vibe_status, result …
llm.classify text, classes, model, system, temperature label, confidence, rationale, raw
llm.completion prompt, model, system, temperature, max_tokens, extra text, model, usage
llm.extract content, schema_, model, instructions, temperature data, confidence, raw
notif.ntfy message, topic, server, title, priority, tags … sent, server, topic
notif.smtp to, subject, body, host, port, username … sent
notif.telegram message, chat_id, parse_mode, disable_notification sent, message_id
search.ddg query, top_k, region hits
search.searxng query, top_k, instance_url, categories hits

6 · Exemples prêts à copier

6.1 — Télécharger un RSS et écrire un résumé

id: rss_to_file
triggers: [{type: manual}]
inputs: {}
env: {}
steps:
  - id: fetch
    node: http.rss
    with:
      urls: ["https://hnrss.org/frontpage"]
  - id: write_titles
    node: fs.write
    with:
      path: ./data/exports/titles.txt
      content: |
        {% for it in fetch.items %}
        Run ${{ run.id }} a récupéré ${{ steps.fetch.outputs.items | length }} items.

6.2 — Classifier un texte LLM avec HITL si confidence basse

id: classify_text
triggers: [{type: manual}]
inputs:
  text: {type: str, default: "Le serveur est tombé en panne ce matin"}
env: {}
steps:
  - id: classify
    node: llm.classify
    with:
      text: ${{ inputs.text }}
      classes: [critical, info, spam]
    on_low_confidence:
      hitl_gate:
        timeout_minutes: 60
        on_timeout: skip
  - id: log
    node: fs.write
    with:
      path: ./data/exports/classify.txt
      content: "label=${{ steps.classify.outputs.label }} conf=${{ steps.classify.outputs.confidence }}"

6.3 — Foreach parallèle (httpx parallèle borné)

steps:
  - id: download_many
    node: http.get
    foreach: ${{ inputs.urls }}
    parallel: 8                       # max 8 requêtes simultanées
    with:
      url: ${{ each }}
      timeout_seconds: 30

6.4 — Notification Telegram conditionnelle

steps:
  - id: alert
    node: notif.telegram
    if: ${{ steps.classify.outputs.label }}   # truthy
    with:
      message: "Label: ${{ steps.classify.outputs.label }}"

6.5 — Trigger interval (toutes les 30 min)

triggers:
  - type: interval
    every: "30m"            # s / m / h / d

7 · HITL — pattern HITL.001 (5 stratégies)

Quand un noeud LLM retourne confidence < 0.8 (seuil par défaut), le runner crée un gate et bloque ce step. L'humain ouvre /ui/hitl et choisit parmi 5 stratégies du pattern HITL.001 :

  • verify — demander à un autre LLM de re-vérifier
  • decompose — décomposer en sous-étapes
  • challenge — challenger le contrat d'extraction
  • request_help — l'humain édite manuellement le résultat (champ edits)
  • accept_limit — accepter la limite, noter l'incident
  • skip — sauter cette itération sans fail

La décision est persistée dans hitl_gates.decision_json + data/runs/<run_id>.jsonl (audit).

8 · Chat HITL (LLM assist)

Sur la page d'un gate (/ui/hitl/<id>) un panneau de chat permet de dialoguer avec un LLM faible (Groq par défaut) pour t'aider à décider. Inspiré du chat HITL de jaikit-sysmonitor + principes jaikit-patterns.

  • L'évidence du gate est passée en system prompt au LLM.
  • L'historique est stocké en data/hitl_chats/<gate_id>.jsonl.
  • Bouton ↺ Reset pour repartir d'une nouvelle conversation.
  • Si LITELLM_API_KEY non configuré : le chat marche en mode scratchpad (notes humaines sans réponse LLM).

9 · Scheduler

APScheduler démarre automatiquement avec uvicorn. Il enregistre :

  • tous les workflows YAML actifs (déplacés dans workflows/_disabled/ = pause)
  • le job système system_backup tous les jours à 03:17 UTC (rotation = 14 backups dans data/backups/)

Pour lancer un workflow à la main : bouton ▶ Run now sur sa page, endpoint POST /api/workflows/<id>/trigger (auth requise), ou CLI :

.venv/bin/python -m scripts.run_workflow <workflow_id>

10 · Credentials chiffrés (KV Fernet)

# Poser
curl -b cookies.txt -X PUT http://127.0.0.1:8000/api/credentials \
  -H 'content-type: application/json' \
  -d '{"name":"TELEGRAM_BOT_TOKEN","value":"123:abc"}'

# Lister (noms uniquement, jamais les valeurs)
curl -b cookies.txt http://127.0.0.1:8000/api/credentials

# Supprimer
curl -b cookies.txt -X DELETE http://127.0.0.1:8000/api/credentials/TELEGRAM_BOT_TOKEN

Clé maître stockée dans .env sous FLOWS_MASTER_KEY. Générée par scripts/init_keys.py. À sauvegarder hors machine — sans elle, le KV est perdu.

11 · API HTTP

MéthodeRouteAuthDescription
GET/healthsanity check
POST/api/auth/login{email, password} → cookie
POST/api/auth/logoutsessionclear cookie
GET/api/auth/meuser courant ou anon
GET/api/workflowsliste des YAML
GET/api/workflows/{id}meta + YAML brut
POST/api/workflows/{id}/triggeradmin/editorlance manuellement
GET/api/runs?limit= &workflow_id= &status=
GET/api/runs/{id}détail + steps
POST/api/runs/{id}/replayadmin/editorrejoue mêmes inputs
GET/api/hitl/pendinggates en attente
GET/api/hitl/{id}détail gate
POST/api/hitl/{id}/decidesoumet décision
GET/api/hitl/{id}/chathistorique du chat HITL
POST/api/hitl/{id}/chat{message} → réponse LLM
GET/PUT/DELETE/api/credentialsadminCRUD KV Fernet

12 · CLI

# Lancer un workflow
.venv/bin/python -m scripts.run_workflow hello_world
.venv/bin/python -m scripts.run_workflow ./workflows/foo.yaml --inputs '{"x":1}'

# Seed admin
PASSWORD=xxx EMAIL=me@x ROLE=admin .venv/bin/python -m scripts.seed_users

# Générer / regénérer les clés crypto
.venv/bin/python -m scripts.init_keys
.venv/bin/python -m scripts.init_keys --force

# Backup manuel SQLite (rotation BACKUP_KEEP, défaut 14)
.venv/bin/python -m scripts.backup

13 · Sécurité prod (checklist)

  • scripts/init_keys.py exécuté, .env en chmod 600, backup de FLOWS_MASTER_KEY.
  • Reverse proxy TLS (nginx/caddy) devant uvicorn.
  • secure=True sur le cookie session quand HTTPS actif (édite flows/core/auth.py).
  • Bind sur 127.0.0.1 + reverse proxy, jamais 0.0.0.0 direct.
  • Job system_backup vérifié après 24h (data/backups/).
  • Encore TODO : rate-limit login, CSRF forms HTML, audit log mutations credentials.

14 · Ajouter un noeud custom

# flows/nodes/mycat/mything.py
from pydantic import BaseModel
from flows.nodes.base import Node

class _In(BaseModel):
    x: int
class _Out(BaseModel):
    y: int

class MyThingNode(Node):
    name = "mycat.mything"
    InputSchema, OutputSchema = _In, _Out

    async def run(self, ctx, inp: _In) -> _Out:
        return _Out(y=inp.x * 2)

Au prochain boot, l'auto-discovery l'enregistre automatiquement (voir flows/nodes/__init__.py). Ensuite tu peux l'utiliser :

steps:
  - id: double
    node: mycat.mything
    with: { x: 21 }
# steps.double.outputs.y == 42

15 · Troubleshooting

SymptômeCauseFix
CredentialsError: FLOWS_MASTER_KEY not setclé non généréepython -m scripts.init_keys
401 sur /triggerpas connectélogin (admin/editor)
unknown node 'foo.bar'noeud absent/typolist_nodes() dans REPL
Run bloque sur HITLgate ouvert sans décision/ui/hitl ou attendre timeout
Workflow ne se relance passcheduler off (mode CLI)démarrer uvicorn
Port 8000 occupéautre procstart.sh / start.bat kill auto

16 · FAQ

Comment changer de port ?
Édite FLOWS_PORT dans .env. Les scripts start.* le lisent et killent le proc qui occupe ce port.
Comment désactiver un workflow ?
Déplace son YAML vers workflows/_disabled/ et redémarre uvicorn.
Comment voir le YAML brut d'un workflow ?
/ui/workflows/<id> affiche le contenu complet. API : GET /api/workflows/<id>.
Pourquoi pas d'Anthropic SDK ?
Règle §1 Charte Jaikit. Tout LLM passe par LiteLLM proxy (Groq par défaut, Ollama fallback) pour rester gratuit + détachable.
Combien d'utilisateurs supporté ?
RBAC posé (viewer / editor / admin / partner). MVP = 1 admin. CRUD users à activer si revente envisagée.