runward

RW™ · V0.38.0

Docs · Opérer · Depuis un agent IA · Le contrat machine

Le contrat machine : check --json et wire.

Piloter runward sur des données : la sortie JSON de check, la détection wire en lecture seule, la non-interactivité durcie, les variables d'environnement, et la référence rapide pour un agent.

Étape 3 : check --json, le contrat machine

Pour un pilotage autonome, utilisez runward check --json afin que l'agent se branche sur des données, et non sur du texte coloré capté à l'écran (ADR-0030). En mode --json, chaque ligne destinée à l'humain est supprimée et la seule sortie stdout est un unique objet JSON déterministe ; le code de sortie est inchangé et reste le signal primaire.

Aucune mission trouvée, code de sortie 2

{ "runward": "<version>", "mission": null, "verdict": "no-mission", "exitCode": 2 }

Exécution normale

Ajoutez --strict pour obtenir aussi le tableau conformance :

{
  "runward": "<version>",
  "mission": "<abs path>",
  "currentGate": "<phase>",
  "adrCount": 0,
  "strict": true,
  "verdict": "clean",            // or "gaps"
  "exitCode": 0,                  // 0 clean, 1 gaps
  "gaps": { "deliverables": 0, "conformance": 0, "hooks": 0 },
  "deliverables": [ { "phase": "...", "artifact": "...", "relPath": "...", "state": "filled" } ],
  "conformance": [ { "scope": "...", "rule": "...", "problem": "..." } ]
}

La clé conformance n'est présente que sous --strict.

Ce que --strict vérifie

--strict vérifie les manifestes de conformité aux règles de manière déterministe : lignes présentes, pointeurs typés se résolvant vers un contenu réel, signatures qui correspondent, aucune dérive, sceau intact. Il vérifie la présence et la forme d'une décision tracée, jamais la qualité du code : cela reste le jugement de l'opérateur à la garde.

Au-dessus d'une garde stricte au vert, runward fait apparaître deux preuves consultatives qu'il n'exécute ni ne lit jamais :

  • la preuve comportementale (votre suite de tests)
  • le workflow de vérification (une passe contradictoire cite-contre-applique)

Les deux sont consultatives, aucune ne bloque.

Hooks

Si vous passez aussi --hooks, les commandes fournies par l'opérateur depuis runward/hooks.json s'exécutent autour de l'audit ; sous --json, leur stdout est redirigé vers stderr (fd 2) pour ne pas corrompre le contrat de l'objet JSON unique. Les hooks sont uniquement optionnels : la garde propre de runward ne les exécute jamais, si bien qu'un clone ne peut rien exécuter par surprise.

Couverture --json aujourd'hui

Note pour un agent pilote : --json est actuellement livré sur check (le chemin porteur), sur wire, et sur rules et explain. status et doctor n'exposent pas encore --json ; ADR-0030 diffère leur sortie machine comme un complément additif et non cassant. Pilotez sur check --json et son code de sortie ; lisez status/doctor en texte pour l'instant.

Étape 4 : runward wire, détection au mieux, en lecture seule

runward wire répond à une seule question : quel harnais IA exécute cette commande, afin que l'agent puisse proposer le canal de déclenchement automatique correspondant. Il est en lecture seule et n'invite jamais, si bien qu'une exécution pilotée par un agent ne se bloque jamais ; son code de sortie est toujours 0.

La détection a deux forces

Essayées dans l'ordre :

  1. Signal d'exécution : un marqueur d'environnement que le harnais injecte dans le processus enfant. Fort : il atteste cette exécution. Seuls des marqueurs vérifiés sont utilisés : CLAUDECODE=1 (Claude Code / Cowork), GEMINI_CLI=1 (Gemini CLI), CURSOR_AGENT=1 (mode agent Cursor). Le statut devient detected.
  2. Fichier de configuration : un marqueur de profil d'outil déjà présent dans le dépôt (par ex. .cursor/rules/runward.mdc, GEMINI.md, .kiro/steering/). Plus faible : un dossier .cursor/ ne prouve pas que Cursor exécute cette commande, il est donc présenté distinctement comme config-detected.

Quand ni l'un ni l'autre ne se résout

Quand ni l'un ni l'autre ne se résout, le statut est undetermined, ce n'est pas une erreur. C'est attendu pour Copilot CLI, Windsurf, Kiro, Continue, Aider, Trae, car aucun signal d'exécution fiable n'existe pour eux et il n'y a pas de convention AI_AGENT=1 transversale aux harnais. Le comportement correct est de demander à l'opérateur, en langage clair, quel outil IA il utilise, puis de câbler l'échantillon correspondant : ne jamais deviner.

Le contrat --json

runward wire --json émet la détection comme un contrat stable. La forme HarnessDetection :

{
  "runward": "<version>",
  "mission": "<abs path or null>",
  "schemaVersion": 1,
  "status": "detected",                 // detected | config-detected | undetermined
  "harness": "cursor",                  // stable id, or null
  "label": "Cursor (agent mode)",
  "family": "cursor",
  "detectedVia": "runtime-signal",      // runtime-signal | config-file | null
  "signal": "CURSOR_AGENT",             // env var name, or null
  "recommendedChannel": { "channel": "advisory-hook", "sample": null, "note": "..." },
  "candidateChannels": [
    { "channel": "pre-commit", "sample": "runward/adapters/pre-commit" },
    { "channel": "ci-required-check", "sample": "runward/adapters/github-actions.yml" }
  ],
  "wires": false,
  "operatorAction": "offer-to-wire-sample"   // or "ask-operator-which-harness"
}

wires est toujours false

wires est toujours false : l'invariant ADR-0012 rendu vérifiable par machine. candidateChannels sont les canaux durs universels présents dans chaque mission après init et valables quel que soit le harnais : le pre-commit git et un contrôle requis en CI. Là où runward livre un échantillon spécifique à un harnais, il pointe vers un adaptateur inerte (par ex. runward/adapters/claude-code-settings.json pour le hook de fin de tour Claude) ; là où il n'en livre aucun (gemini/cursor/copilot), sample vaut null avec une note pointant vers le packaging de distribution.

Non-interactivité durcie (pour qu'une exécution autonome ne se bloque jamais)

Quand une exécution est traitée comme non interactive

Un agent autonome ne doit jamais se bloquer sur une invite à laquelle il ne peut répondre. isNonInteractive() renvoie vrai quand :

  • RUNWARD_YES=1 (le drapeau --yes le positionne)
  • CI a une valeur vraie
  • l'un des flux standard n'est pas un TTY

La dernière condition est le correctif issu d'ADR-0030 : un agent dans un pty alloué, ou avec un stdin redirigé, est traité comme non interactif même sans --yes, si bien que l'assistant d'init retombe sur les défauts au lieu de se bloquer sur stdin. Toute commande autre qu'init est déjà non interactive ; wire n'invite jamais par conception.

Sortie datée déterministe

Pour une sortie datée déterministe dans les exécutions non interactives (par ex. les packs OSCAL, le sceau de preuve), RUNWARD_NOW remplace « aujourd'hui » mais est validé comme un vrai YYYY-MM-DD ; une valeur mal formée retombe sur aujourd'hui plutôt que de produire une sortie invalide au regard du schéma.

Variables d'environnement

Les drapeaux globaux positionnent des variables d'environnement ; passer la variable directement produit le même effet, ce qui est utile quand vous ne pouvez pas ajouter un drapeau (un wrapper, un script CI, un harnais qui appelle runward pour vous). Toutes sont optionnelles ; aucune ne fait entrer un modèle ou le réseau dans le chemin de décision.

Variable Effet Équivalent drapeau
RUNWARD_NOW Épingle la date de génération à un YYYY-MM-DD donné, pour des exécutions rejouables au byte près (packs OSCAL, sceau de preuve, dates des DRAFT sous characterize --mine). Une valeur mal formée retombe sur aujourd'hui, jamais une sortie invalide au regard du schéma. (aucun)
RUNWARD_YES Traite l'exécution comme non interactive : l'assistant retombe sur les défauts au lieu d'attendre une réponse. --yes
RUNWARD_DRY_RUN Prévisualise sans rien écrire ; honoré par init, check, characterize, compliance, manifest, update. --dry-run
CI Toute valeur vraie rend l'exécution non interactive (comme RUNWARD_YES). (convention CI)
NO_COLOR Désactive la couleur (standard no-color.org). --color=false
VERBOSE Imprime la trace de pile complète en cas d'erreur, pour le diagnostic. --verbose

RUNWARD_NOW est le seul de ces réglages sans drapeau équivalent : c'est le point d'injection de la date que le contrat OSCAL exige pour que deux exécutions sur le même arbre de travail soient byte-identiques. Les autres ne sont que la forme « variable » de drapeaux existants, pour les contextes où l'on ne contrôle pas la ligne de commande.

Référence rapide pour un agent en exploitation

  • runward init --yes : base neutre (AGENTS.md + .agents/skills/), aucun profil privilégié.
  • runward check --json : verdict + garde + états des livrables ; sortie 0 propre / 1 manques / 2 aucune mission.
  • runward check --strict --json : ajoute le tableau conformance ; l'autorité déterministe.
  • runward wire --json : détection + canal recommandé ; wires:false toujours ; sortie 0 toujours.
  • Sur un statut wire detected/config-detected : proposez l'échantillon recommandé ; sur undetermined : demandez à l'opérateur quel outil, puis câblez l'échantillon correspondant. N'agissez que sur approbation explicite.
  • runward explain <rule> --json, runward rules --json : texte des règles lisible par machine.
  • Vocabulaire de conformité : runward compliance <regime> assemble une preuve d'appui prête pour l'audit (OSCAL) qui alimente un programme ; l'acceptation relève de l'auditeur, jamais une revendication de certification.
← Docs