Automatisation de bureau gouvernée

adk-computer-use est une couche de gouvernance au-dessus du serveur d’automatisation de bureau computer-use-mcp. Elle n’effectue aucune action elle-même : elle ordonne l’observation, l’approbation, la mutation et la vérification sous forme de graphe déterministe, et vérifie ce que renvoie le runtime externe.

Liaison des réponses

Le runtime externe fait autorité, mais ses réponses sont vérifiées localement avant d’entrer dans l’état du graphe. Les vérifications s’exécutent dans le graphe de référence pour chaque implémentation ComputerUseRuntime, et l’adaptateur MCP les répète à sa frontière d’appel direct. La désérialisation typée prouve la forme, pas la provenance — ControlLease, TargetReservation et ExecutionReceipt n’ont pas de constructeur imposant des invariants, donc un objet bien formé appartenant à une autre session se parse correctement.

Chaque réponse est rattachée à la requête qui l’a produite :

RéponseVérifié par rapport à l'enveloppe
ControlLeasesession_id, principal_id, agent_id, execution_mode, état actif, non expiré expires_at, budget restant (actions_used < action_budget), et cible dans boundaries
TargetReservationsession_id, principal_id, agent_id, execution_group_id, intent_id == action_id, état actif, non expiré expires_at, et portée cible exacte de l'application/de la fenêtre
ExecutionReceiptsession_id, action_id et action_digest par rapport à args_digest de l'enveloppe

Une divergence produit ComputerUseError::IdentityMismatch en nommant le champ, et la réponse est rejetée plutôt que stockée.

use adk_computer_use::runtime::binding::validate_lease;

// Refuses a lease that is well-formed but belongs to another session.
validate_lease(&lease, &envelope)?;

Note : le digest est la liaison la plus forte disponible. args_digest est ce qui a reçu l’approbation, donc un reçu portant un digest différent décrit un travail différent même lorsque tous les identifiants correspondent.

Un champ optionnel qui revient absent est traité comme une sous-spécification, et non comme une contradiction ; un champ qui est présent et différent constitue une divergence.

Révalidation au moment de l’exécution

L’aperçu n’est pas une autorité de mutation. Le graphe de référence révalide toutes les entrées de mutation dans le nœud execute juste avant d’appeler execute_action :

EntréeVérification immédiate
ActionEnvelopehorodatages RFC 3339, fenêtre de validité positive, et now < expires_at
ControlLeaseidentité, mode, état actif, budget restant, expiration et limites cibles
TargetReservationidentité, intention d’action, état actif, expiration et portée cible exacte
Approvalroute, résumé d’action, résumé de politique et exactement une source d’autorité (grantId ou approbation détenue à l’exécution)

La vérification de l’enveloppe s’exécute aussi au retour de l’aperçu, de sorte qu’un aperçu déjà expiré n’atteint jamais la réservation ni l’approbation. Elle s’exécute à nouveau lors de l’exécution, car une interruption d’approbation, une réservation de cible ou l’acquisition d’un bail peuvent consommer la fenêtre de validité restante.

ComputerUseMcpRuntime conserve l’enveloppe exacte renvoyée par preview_action. Un appel direct à execute_action est rejeté si un champ de l’enveloppe change après l’aperçu, même lorsque l’ID de l’action correspond toujours.

Pour la reprise à partir d’un point de contrôle, le graphe stocke l’aperçu d’exécution dans un canal d’état en ajout uniquement. L’approbation et l’exécution exigent que ce canal contienne exactement un aperçu correspondant à l’aperçu actif. L’entrée de reprise peut ajouter de l’état mais ne peut pas remplacer la valeur mise en point de contrôle : une tentative de remplacement crée une deuxième entrée et échoue avant la réservation ou la mutation.

Nettoyage de la réservation

Une fois une réservation acceptée, chaque chemin terminal tente de la libérer :

  • échecs de l’acquisition de bail, de l’autorisation, de la validation, de l’exécution, du reçu et de la vérification ;
  • vérification réussie ; et
  • échecs de validation ou de sérialisation de la réservation lorsque le runtime a renvoyé une réservation.

L’échec principal reste principal lorsque le nettoyage réussit. Si le nettoyage échoue aussi, le graphe renvoie une erreur déterministe contenant les deux échecs. Un échec de nettoyage après une vérification par ailleurs réussie est renvoyé comme une erreur plutôt que masqué.

La vérification n’est pas l’engagement

ComputerUseRuntime::verify renvoie un VerificationOutcome, pas un booléen :

RésultatSignificationis_verified()is_committed()status()
VerifiedLa postcondition déclarée a été observée comme satisfaitetruetruecompleted
CommittedUnverifiedLe runtime a exécuté l’action ; aucune preuve de postconditionfalsetruecommitted_unverified
FailedNon validé, ou les preuves contredisent la postconditionfalsefalseverification_failed

Le nœud verify du graphe écrit verified, committed et un result.verificationDetail expliquant tout ce qui est inférieur à Verified.

Important : un reçu engagé est une reconnaissance que l’action a été acceptée et exécutée. Ce n’est pas une preuve que l’effet attendu s’est produit. verify renvoyait auparavant receipt.status == Committed, qui signalait qu’une action engagée mais inefficace était terminée — à partir d’un nœud intitulé « verify ».

Ce qui compte comme preuve

Pour une postcondition déclarant un digest (valueDigest, contentDigest), la vérification nécessite un verification.observedDigest sur le résultat du reçu qui correspond à celui-ci. Pour une postcondition déclarant uniquement l’existence, une verification.satisfied: true explicite est requise.

Preuve de réceptionRésultat
verification.satisfied: falseFailed — une observation négative explicite
observedDigest correspond au digest attenduVerified
observedDigest diffèreFailed
Aucun objet verificationCommittedUnverified
verification présent, aucun observedDigest, digest attenduCommittedUnverified

L’absence de preuve n’est jamais considérée comme une preuve de succès. Si computer-use-mcp vérifie avant d’émettre un reçu, c’est son contrat ; cet adaptateur ne le présume pas.

Limites du bail

Un bail est refusé sauf si toutes les conditions suivantes sont remplies :

VérificationRejeté lorsque
Expirationexpires_at est dans le passé, ou ne peut pas être analysé comme RFC 3339
Budget restantactions_used >= action_budget
Limite de la cibletarget.app_id de l'enveloppe est absent d'un boundaries.app_ids non vide
Limite de fenêtretarget.window_id de l'enveloppe est absent d'un boundaries.window_ids non vide

Des bornes vides signifient « non délimité » et autorisent toute cible — l’absence de restriction n’est pas une restriction à rien.

Important : la première version de ce validateur vérifiait action_budget == 0, qui est le budget total, donc un bail avec action_budget: 1, actions_used: 1 passait tout en n’autorisant rien. Elle ne lisait jamais non plus expires_at ni boundaries, de sorte qu’un bail expiré et un bail limité à une application différente étaient tous deux acceptés. Une expiration impossible à analyser est rejetée plutôt qu’ignorée : un bail dont la validité ne peut pas être établie n’est pas valide.