Files
brain-template/agents/key-guardian.md

6.9 KiB

name, type, context_tier, status, brain
name type context_tier status brain
key-guardian protocol boot active
version type scope owner writer lifecycle read triggers export ipc
1 protocol kernel human human permanent header
boot-L0
true
receives_from sends_to zone_access signals
human
human
kernel
ESCALATE
ERROR

Agent : key-guardian

Dernière validation : 2026-03-17 Domaine : Validation Brain API Key — boot silencieux, grace period 72h


Rôle

Valide la brain_api_key (brain-compose.local.yml > instances.) au boot et écrit le feature_set dans brain-compose.local.yml. N'émet jamais d'erreur visible. N'interrompt jamais le boot. Tier free = défaut absolu silencieux.


Protocole au boot (invoqué automatiquement après L0)

1. Lire brain_api_key dans brain-compose.local.yml → instances.<name>.brain_api_key
   (brain-compose.yml garde toujours null — jamais la vraie clé dans le versionné)
   → null ou absent : tier: free implicite. Stop. Rien à écrire.

2. Clé présente → POST https://keys.tetardtek.com/validate
   Body    : { "key": "<brain_api_key>" }
   Header  : X-Server-Secret: $BRAIN_SERVEUR_SECRET
   Timeout : 3s max — le boot ne doit jamais attendre

3a. Réponse { valid: true } :
    → Écrire dans brain-compose.local.yml > instances.<name>.feature_set :
        tier: <tier>
        agents: <liste selon tier, voir ci-dessous>
        contexts: "*"
        distillation: <true si full, false sinon>
        last_validated_at: <now ISO 8601>
        expires_at: <expires_at du serveur ou null>
        grace_until: null
    → Aucun output visible au boot

3b. Réponse { valid: false } :
    → Écrire feature_set avec tier: free
    → 1 ligne discrète : "[key-guardian] Clé invalide — tier: free"

4. VPS unreachable (timeout, connexion refusée, erreur réseau) :
    → Lire last_validated_at + grace_until depuis brain-compose.local.yml
    → Si last_validated_at absent : aucune grace, tier: free silencieux
    → Si grace_until null : écrire grace_until = last_validated_at + 72h
    → Si now < grace_until : conserver le tier existant (silent)
    → Si now > grace_until : tier: free silencieux
    → Aucune erreur. Aucun blocage.

feature_set par tier

free:
  tier: free
  agents:
    - coach, scribe, debug, mentor, helloWorld, brainstorm, orchestrator
    - todo-scribe, interprete, aside, recruiter, agent-review
  contexts: "*"
  distillation: false

pro:
  tier: pro
  agents: "*"    # tous les agents fondamentaux + agents calibrés métier
  contexts: "*"
  distillation: false

full:
  tier: full
  agents: "*"
  contexts: "*"
  distillation: true   # brain-engine local autorisé

Implémentation bash

Fonctions intégrables dans brain-setup.sh ou invocables depuis helloWorld :

_key_guardian() {
  local brain_root
  brain_root=$(git rev-parse --show-toplevel 2>/dev/null) || return 0
  # La clé est dans brain-compose.local.yml (gitignored) — jamais dans brain-compose.yml
  local local_file="$brain_root/brain-compose.local.yml"

  local api_key
  api_key=$(python3 -c "
import yaml, sys
d = yaml.safe_load(open(sys.argv[1]))
instances = d.get('instances', {})
name = next(iter(instances), None)
print((instances.get(name) or {}).get('brain_api_key') or '')
" "$local_file" 2>/dev/null)

  [[ -z "$api_key" ]] && return 0   # pas de clé → free implicite, rien à faire

  local url="https://keys.tetardtek.com/validate"
  local secret="${BRAIN_SERVEUR_SECRET:-}"
  local response

  response=$(curl -sf --max-time 3 -X POST "$url" \
    -H "Content-Type: application/json" \
    -H "X-Server-Secret: $secret" \
    -d "{\"key\":\"$api_key\"}" 2>/dev/null) || {
    _key_guardian_grace "$local_file"
    return 0
  }

  local valid tier expires
  valid=$(echo "$response"  | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('valid',''))")
  tier=$(echo "$response"   | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('tier','free'))")
  expires=$(echo "$response"| python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('expires_at') or '')")

  if [[ "$valid" == "True" ]]; then
    _key_guardian_write "$local_file" "$tier" "$expires"
  else
    echo "[key-guardian] Clé invalide — tier: free" >&2
    _key_guardian_write "$local_file" "free" ""
  fi
}

_key_guardian_grace() {
  local local_file="$1"
  python3 - "$local_file" <<'PY'
import sys, yaml
from datetime import datetime, timedelta, timezone

path = sys.argv[1]
with open(path) as f:
    data = yaml.safe_load(f) or {}

inst = list((data.get("instances") or {}).values())[0]
fs   = inst.get("feature_set", {})
last = fs.get("last_validated_at")

if not last:
    pass   # jamais validé → pas de grace, reste free
elif not fs.get("grace_until"):
    fs["grace_until"] = (datetime.fromisoformat(str(last)) + timedelta(hours=72)).isoformat()
    with open(path, "w") as f:
        yaml.dump(data, f, default_flow_style=False, allow_unicode=True)
PY
}

_key_guardian_write() {
  local local_file="$1" tier="$2" expires="$3"
  python3 - "$local_file" "$tier" "$expires" <<'PY'
import sys, yaml
from datetime import datetime, timezone

path, tier, expires = sys.argv[1], sys.argv[2], sys.argv[3]

agents_map = {
    "free": ["coach","scribe","debug","mentor","helloWorld","brainstorm",
             "orchestrator","todo-scribe","interprete","aside","recruiter","agent-review"],
    "pro":  "*",
    "full": "*",
}

with open(path) as f:
    data = yaml.safe_load(f) or {}

inst = list((data.get("instances") or {}).values())[0]
inst["feature_set"] = {
    "tier":             tier,
    "agents":           agents_map.get(tier, []),
    "contexts":         "*",
    "distillation":     tier == "full",
    "last_validated_at": datetime.now(timezone.utc).isoformat(),
    "expires_at":       expires or None,
    "grace_until":      None,
}

with open(path, "w") as f:
    yaml.dump(data, f, default_flow_style=False, allow_unicode=True)
PY
}

Règles non-négociables

  • Jamais de blocage — le boot continue même si la validation échoue
  • Jamais d'exposition de la clé dans les logs (ni api_key ni secret ne sont loggués)
  • Tier free = défaut absolu si aucune clé ou erreur non récupérable
  • Grace period : 72h max depuis last_validated_at — au-delà → free silencieux
  • Output visible au boot : zéro (sauf clé invalide → 1 ligne discrète sur stderr)

Composition

Avec Pour quoi
helloWorld Invoqué step 1.5 — résultat (tier actif) transmis au BHP
pre-flight Pre-flight utilise le tier validé par key-guardian
feature-gate Key-guardian valide la clé → feature-gate applique les restrictions

Changelog

Date Changement
2026-03-17 Création — validation Brain API Key au boot, grace period 72h, tier silencieux
2026-03-18 Composition + Changelog ajoutés — review Batch C