Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions BREAKTHROUGHS.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,12 @@ This separates long-lived root trust from short-lived operational signing author

It does not establish legal identity, employment, organizational approval, regulatory authority, certification, independent validation, or official ĀRU authorization. Those remain separate external claims.

## 18. A host-owned approval can be claimed once before an agent tool runs

The proposed-action pilot has an optional `consumeApproval` callback. After exact digest approval and policy recheck, a host can atomically claim a short-lived grant before invoking the tool. A reused or expired grant blocks, and a policy revocation during an asynchronous claim is checked again before dispatch.

Run `node pilots/action-boundary/one-shot-grant-demo.mjs`: the first call invokes one simulated callback; a second call reusing the same grant is blocked with no additional callback. The [visual replay lab](https://aruintelligence.github.io/aml-core/action-lab.html?scenario=replay) shows the same project-authored scenario. The local store works in one Node process only; it is not a durable cross-worker replay defense, authenticated human approval, proof of delivery, or independent validation.

## The frontier

The next technical frontiers are:
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@

**Explore the interface:** [Live lab](https://aruintelligence.github.io/aml-core/) · [View Meaning™ inspector](https://aruintelligence.github.io/aml-core/view-meaning.html) · [API contract](protocol/aml-http.openapi.yaml)

**Explore the next boundary:** [Try the Action Boundary Lab](https://aruintelligence.github.io/aml-core/action-lab.html?view=action-path-1) — change an agent's intended tool call and see an exact policy decision, digest, stale simulated approval, and uncertain outcome. [Explore the visual 13-case challenge](https://aruintelligence.github.io/aml-core/action-challenge.html), [read its adapter contract](publications/ACTION_BOUNDARY_CHALLENGE.md), or [read the pilot](pilots/action-boundary/README.md) and its [six published vectors](pilots/action-boundary/vectors.json). The browser rehearsal and repository self-test are project-authored, with no live tool access.
**Explore the next boundary:** [Try the Action Boundary Lab](https://aruintelligence.github.io/aml-core/action-lab.html?view=action-path-1) — change an agent's intended tool call and see an exact policy decision, digest, stale simulated approval, and uncertain outcome. [Try a one-use grant replay](https://aruintelligence.github.io/aml-core/action-lab.html?scenario=replay): a second attempt with the same local grant blocks before a callback. [Explore the visual 13-case challenge](https://aruintelligence.github.io/aml-core/action-challenge.html), [read its adapter contract](publications/ACTION_BOUNDARY_CHALLENGE.md), or [read the pilot](pilots/action-boundary/README.md) and its [six published vectors](pilots/action-boundary/vectors.json). The browser rehearsal and repository self-test are project-authored, with no live tool access.

[![Illustrated local action boundary: an agent proposes sending to an outside destination; the host policy blocks the call before dispatch.](docs/action-boundary-story.svg)](https://aruintelligence.github.io/aml-core/action-lab.html?view=action-path-1)

**Wire one agent tool:** [Follow the function-tool quickstart](docs/AGENT_TOOL_QUICKSTART.md) to wrap a host-owned callback with exact policy matching, approval binding, and a dispatch receipt. Its runnable local demo simulates execution; the host must supply real authorization and tool mapping.

[![One local approval grant permits one simulated callback. Reusing it blocks before a second callback.](docs/one-shot-approval.svg)](https://aruintelligence.github.io/aml-core/action-lab.html?scenario=replay)

## Prove the core claim in 60 seconds

Open this exact state: [**attention 5 / restoration 1 → SUPPRESS**](https://aruintelligence.github.io/aml-core/proof.html?attention=5&restoration=1&lang=en).
Expand Down
26 changes: 26 additions & 0 deletions docs/AGENT_TOOL_QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,32 @@ const execute = createGuardedToolExecute({

`requestApproval` must return `{ approved: true, proposal_sha256 }` for the exact frozen proposal, or refuse. The callback may be omitted for a policy rule that allows an action without approval. If an approval-required rule has no callback, dispatch blocks. The adapter passes the framework's second `execute` argument as `context` to the host callbacks, and calls the tool with the frozen canonical arguments. A host mapping error throws before any tool invocation.

## Rehearse a one-use grant

Run `node pilots/action-boundary/one-shot-grant-demo.mjs` or open the [replay scenario](https://aruintelligence.github.io/aml-core/action-lab.html?scenario=replay). The first invocation uses a local grant; the second attempt with the same grant blocks before the callback. The example sends no message and authenticates no person.

To opt in for a host function tool, keep the grant store and approval issuer **inside the host**:

```js
import { createLocalOneShotGrants } from "./pilots/action-boundary/one-shot-grants.mjs";

const grants = createLocalOneShotGrants(); // One Node process, demonstration only.
const guarded = createGuardedToolExecute({
tool: "send_message", effect: "send", purpose: "Send a team update",
resourceFor: args => args.to, policy,
requestApproval: async (input, context) => {
if (!await hostAuthenticateAndApprove(input.proposal, input.proposal_sha256, context)) {
return { approved: false };
}
return grants.issue(input.proposal_sha256); // New ID, short expiry.
},
consumeApproval: grants.consume,
execute: async (args, context) => sendThroughMyService(args.to, args.body, context)
});
```

`hostAuthenticateAndApprove` and `sendThroughMyService` are host functions you must implement; the browser and CLI examples only simulate their outcome. The `consumeApproval` hook runs after the proposal digest and rule match, before the callback. It must make an atomic one-use claim bound to the digest and return `true` only once. A false claim blocks with `approval_reused_or_expired`; an error blocks with `approval_consume_error`. A policy change during an asynchronous claim still blocks. The local Map store does not survive restarts or coordinate multiple workers. Use a durable atomic store and your own authenticated approval, time policy, audit, and idempotency controls in a real deployment. An uncertain callback result still consumes the grant and must not be retried automatically.

For the [OpenAI Agents SDK JavaScript function-tool API](https://openai.github.io/openai-agents-js/guides/tools/), install `@openai/agents` and `zod` in your agent app, then attach the `execute` function above:

```js
Expand Down
5 changes: 4 additions & 1 deletion docs/action-lab-core.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,14 @@ export async function planPreview(proposal, policy, subtle) {
}
}

export function rehearse(plan, approvedDigest, uncertain = false) {
export function rehearse(plan, approvedDigest, uncertain = false, grantConsumed = false) {
if (plan.decision === "deny") return { outcome: "blocked", reason: plan.reason };
if (plan.decision === "requires_approval" && approvedDigest !== plan.proposal_sha256) {
return { outcome: "blocked", reason: "approval_missing_or_mismatched" };
}
if (plan.decision === "requires_approval" && grantConsumed) {
return { outcome: "blocked", reason: "approval_reused_or_expired" };
}
return uncertain ? { outcome: "unknown", reason: "simulated_dispatch_exception" } :
{ outcome: "would_dispatch", reason: "simulation_only" };
}
4 changes: 3 additions & 1 deletion docs/action-lab-story.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Presentation mapping for the local Action Boundary Lab. It uses the plan
// and rehearsal result; it does not make a second policy or dispatch decision.
export function actionPathStory(proposal, plan, approvedDigest, outcome) {
export function actionPathStory(proposal, plan, approvedDigest, outcome, grantConsumed = false) {
const digest = plan?.proposal_sha256;
const match = Boolean(digest && approvedDigest && digest === approvedDigest);
const stale = Boolean(approvedDigest && !match);
Expand All @@ -17,6 +17,8 @@ export function actionPathStory(proposal, plan, approvedDigest, outcome) {
{ value: "DENY", detail: `Host rule: ${readable(plan?.reason)}`, tone: "blocked" };
const approvalCard = stale ?
{ value: "STALE", detail: "Prior simulated digest no longer matches", tone: "blocked" } :
match && grantConsumed ?
{ value: "SPENT", detail: "The one-use grant was already claimed", tone: "blocked" } :
match ?
{ value: "DIGEST MATCH", detail: `Simulated approval · ${digest.slice(0, 12)}…`, tone: "allowed" } :
plan?.decision === "allow" ?
Expand Down
7 changes: 4 additions & 3 deletions docs/action-lab.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="theme-color" content="#080c17">
<meta name="description" content="Rehearse an AI agent tool call against an exact host policy. Change one argument, inspect its digest, and see a simulated approval become stale.">
<meta name="description" content="Rehearse an AI agent tool call against an exact host policy. Change an argument, inspect its digest, and test a simulated one-use grant against replay.">
<title>Action Boundary Lab · ĀML™</title>
<style>
:root{color-scheme:dark;--bg:#080c17;--panel:#111b2d;--line:#304058;--ink:#f4f7ff;--muted:#b7c5d8;--cyan:#78e8f5;--gold:#f5d27a;--red:#ffa0ac;--green:#9af4bb}
Expand All @@ -26,14 +26,15 @@
<header>
<div class="eyebrow">PROPOSE · POLICY · APPROVAL · RECEIPT</div>
<h1>What exactly did the <span>agent ask to do?</span></h1>
<p class="lede">Edit a proposed tool call. ĀML's reference contract hashes its exact meaning and asks a host-owned policy for an exact match. Change the destination or body after simulated approval to see why the approval no longer matches.</p>
<p class="lede">Edit a proposed tool call. ĀML's reference contract hashes its exact meaning and asks a host-owned policy for an exact match. Try <strong>Replay one-use grant</strong>: approve once, rehearse dispatch twice, and watch the second attempt block.</p>
<p class="notice"><strong>Browser rehearsal only.</strong> No tool runs, no message is sent, and the button does not obtain a person's consent. The visible policy is a fixed local fixture.</p>
</header>
<div id="presets" class="presets" role="group" aria-label="Choose a scenario">
<button type="button" data-case="send" aria-pressed="true">01 · Send needs approval</button>
<button type="button" data-case="changed" aria-pressed="false">02 · Different destination</button>
<button type="button" data-case="read" aria-pressed="false">03 · Allowed read</button>
<button type="button" data-case="timeout" aria-pressed="false">04 · Uncertain result</button>
<button type="button" data-case="replay" aria-pressed="false">05 · Replay one-use grant</button>
</div>
<section class="path-visual" aria-label="Live action decision path">
<div class="path-heading"><strong>THE ACTION PATH</strong><span>Changes with the local scenario and your edits</span></div>
Expand Down Expand Up @@ -74,7 +75,7 @@ <h2 id="result-title">02 / Inspect the boundary</h2>
</main>
<div class="steps" aria-label="What this proves">
<div class="step"><b>01 · Exact match</b><p>An undeclared destination or effect is denied by the local fixture.</p></div>
<div class="step"><b>02 · Approval binding</b><p>The simulated approval stores one proposal digest. Editing arguments invalidates that match.</p></div>
<div class="step"><b>02 · Approval binding and replay</b><p>Editing arguments invalidates the digest. In the replay scenario, a spent local grant blocks the second attempt.</p></div>
<div class="step"><b>03 · Honest outcome</b><p>A simulated callback error is unknown. It cannot prove an external action happened or did not happen.</p></div>
</div>
<footer class="foot">Project-authored reference experiment · <a href="https://github.com/aruintelligence/aml-core/blob/main/pilots/action-boundary/README.md">Read the contract and claim limits</a> · <a href="https://github.com/aruintelligence/aml-core/blob/main/docs/AGENT_TOOL_QUICKSTART.md">Wrap one agent tool</a> · <a href="https://github.com/aruintelligence/aml-core/blob/main/publications/ACTION_BOUNDARY_CHALLENGE.md">Run the outside implementation challenge</a> · <a href="https://github.com/aruintelligence/aml-core/issues/88">Independent verification call</a><br>Local browser inputs are not uploaded by this lab. SHA-256 is a change detector, not proof of trusted identity, policy authority, human approval, or delivery. ĀML™ · ĀRU Intelligence Inc.™</footer>
Expand Down
17 changes: 14 additions & 3 deletions docs/action-lab.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@ const cases = {
read: { tool: "fetch_record", effect: "read", resource: "record:42",
purpose: "Inspect record", arguments: { id: 42 } },
timeout: { tool: "send_message", effect: "send", resource: "team@example.test",
purpose: "Send a status update", arguments: { subject: "Update", body: "The build passed." } },
replay: { tool: "send_message", effect: "send", resource: "team@example.test",
purpose: "Send a status update", arguments: { subject: "Update", body: "The build passed." } }
};
const $ = id => document.getElementById(id);
const form = $("proposal");
let scenario = "send";
let approvedDigest = null;
let grantConsumed = false;
let outcome = null;
let latest = null;
let generation = 0;
Expand Down Expand Up @@ -59,12 +62,14 @@ async function render() {
"Reason: " + plan.reason.replaceAll("_", " ");
$("digest").textContent = plan.proposal_sha256 || "No valid proposal digest";
$("approve").disabled = plan.decision !== "requires_approval";
$("approval").textContent = approved ? "Simulated approval matches this exact digest." :
$("approval").textContent = approved && grantConsumed ?
"Simulated one-use grant spent. Re-approve to try again." :
approved ? "Simulated approval matches this exact digest." :
approvedDigest ? "Previous simulated approval is stale for this proposal." :
"No simulated approval recorded.";
$("outcome").textContent = outcome ? outcome.outcome.toUpperCase().replaceAll("_", " ") : "NOT ATTEMPTED";
$("outcome").className = "badge " + (outcome?.outcome || "");
const story = actionPathStory(proposal, plan, approvedDigest, outcome);
const story = actionPathStory(proposal, plan, approvedDigest, outcome, grantConsumed);
for (const key of ["proposal", "policy", "approval", "receipt"]) {
const card = $("path-" + key);
card.dataset.tone = story[key].tone;
Expand All @@ -73,6 +78,7 @@ async function render() {
}
const report = { protocol: "aml-action-lab-report/1", provenance: "project-authored browser simulation",
proposal: proposal || null, plan, simulated_approval_matches: approved,
simulated_one_use_grant_spent: scenario === "replay" ? grantConsumed : null,
simulation: outcome, canonical_proposal_json: plan.canonical_json,
limits: "No live tool, authenticated approver, external delivery, or execution proof." };
$("report").textContent = JSON.stringify(report, null, 2);
Expand All @@ -81,6 +87,7 @@ async function render() {
function selectCase(name) {
scenario = name;
approvedDigest = null;
grantConsumed = false;
outcome = null;
const selected = cases[name];
for (const key of ["tool", "effect", "resource", "purpose"]) input(key).value = selected[key];
Expand All @@ -100,13 +107,17 @@ $("approve").addEventListener("click", async () => {
await render();
if (latest.plan.decision === "requires_approval") {
approvedDigest = latest.plan.proposal_sha256;
grantConsumed = false;
outcome = null;
await render();
}
});
$("dispatch").addEventListener("click", async () => {
await render();
outcome = rehearse(latest.plan, approvedDigest, scenario === "timeout");
const oneUse = scenario === "replay";
const wasConsumed = oneUse && grantConsumed;
outcome = rehearse(latest.plan, approvedDigest, scenario === "timeout", wasConsumed);
if (oneUse && !wasConsumed && outcome.outcome === "would_dispatch") grantConsumed = true;
await render();
});
$("copy").addEventListener("click", async () => {
Expand Down
41 changes: 41 additions & 0 deletions docs/one-shot-approval.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading