runward

RW™ · V0.22.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, 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.

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