Exclusivobeta
Run Contract <mv-run-contract>
Um contrato de execução para tarefas longas de IA que vira o dashboard ao vivo da mesma tarefa: um card, duas vidas. Antes: objetivo, janela de tempo desenhada como uma faixa em um eixo temporal, teto de gastos ajustável, critérios de conclusão, limite da delegação (permitido ✓ / não permitido ⊘) e perguntas em aberto a resolver antes do lançamento; você assina segurando o botão enquanto uma assinatura manuscrita é traçada sob o seu dedo, e ela se desfaz se você soltar antes da hora. Depois: o card se transforma no lugar (FLIP), um marcador "now" avança pela faixa e a estica em caso de estouro, o teto vira um pavio aceso que pausa para pedir aprovação ("Allow +$5" / "Stop"), e os checkpoints aparecem como nós, com resumos legíveis das decisões tomadas e "Resume from here".
| Categoria | Feedback |
|---|---|
| Tipo | Web Component (<mv-run-contract>) |
| Status | beta |
| Kit | IA que você pode verificar |
| Keywords | exclusive, ai, agent, long-running, slow-ai, contract, consent, budget, cost-cap, eta, checkpoint, breadcrumbs, progress, operator, dashboard, hold-to-confirm, signature, human-in-the-loop |
When to use
- A long AI task needs agreed terms before launch: objective, time window, spending cap and allowed actions
- The same card should become a live dashboard with elapsed time, budget burn, pauses and checkpoints
- A run must pause for approval when it hits its budget and allow resuming from a checkpoint
Avoid when
- The task is short and a simple progress indicator is enough → use Progress instead
- Many agents run in parallel and need a shared overview → use Agent Lenses instead
- A plain hold-to-confirm action is needed without contract terms → use Hold Button instead
Instalação
node scripts/add.mjs run-contract --out ./src/marvelousAgente de IA com o servidor MCP do Marvelous UI: install_components({ slugs: ["run-contract"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
Arquivos copiados (dependências incluídas): tokens/tokens.css, core/base.css, core/canvas.js, core/dom.js, core/element.js, core/motion.js, core/observe.js, components/run-contract/run-contract.js, components/run-contract/run-contract.css.
Uso
Início rápido, a menor marcação que funciona:
<mv-run-contract eta="25-40" budget="12">
<p slot="objective">Compare 20 product sheets on price, shipping and reviews.</p>
<ul slot="done"><li>A comparison table across 18 criteria</li></ul>
<ul slot="allow"><li>Browse public websites</li></ul>
<ul slot="deny"><li>Send emails</li></ul>
</mv-run-contract>Marcação de referência, para usar como ponto de partida e personalizar com atributos, data-* e variáveis CSS:
<div id="rc-demo" style="width:min(100%,42rem);margin-inline:auto">
<style>
#rc-demo .rc-bar { display:flex; align-items:center; justify-content:space-between; gap:.75rem 1rem; flex-wrap:wrap; margin-top:.875rem }
#rc-demo .rc-hint { margin:0; color:var(--mv-fg-muted); font-size:.75rem }
</style>
<mv-run-contract id="rc-watch" eta="25-40" budget="12" budget-max="30" extend="5" speed="60">
<p slot="objective">Competitive watch: 20 product sheets compared on price, shipping and customer reviews.</p>
<ul slot="done">
<li>20 product sheets collected from dated public sources</li>
<li>Comparison table across 18 criteria, prices normalized with tax</li>
<li>One-page summary with the 3 biggest gaps</li>
</ul>
<ul slot="allow">
<li>Read your files in the “Market watch” folder</li>
<li>Browse public websites</li>
<li>Create a results spreadsheet</li>
</ul>
<ul slot="deny">
<li>Send emails</li>
<li>Create accounts or pay for access</li>
</ul>
<p slot="question" data-name="market" data-options="United States, International" data-answer="United States">Which market should we compare?</p>
<p slot="question" data-name="shipping" data-options="Yes (delivered price), No (list price)">Should prices include shipping?</p>
</mv-run-contract>
<div class="rc-bar">
<p class="rc-hint">Hold <kbd class="mv-kbd">Space</kbd> or the button to sign · accelerated demo: 1 s = 1 min</p>
<button class="mv-button" data-variant="outline" data-size="sm" type="button" id="rc-replay">Replay demo</button>
</div>
<script type="module">
const rc = document.getElementById("rc-watch");
await customElements.whenDefined("mv-run-contract");
// Scripted research agent: breadcrumbs are decisions, not logs.
const PLAN = [
{ at: 4, label: "Scope set", summary: "20 products kept out of 26 candidates, 6 out-of-range dropped", detail: "Exclusion rule: discontinued products and quote-only listings." },
{ at: 11, label: "Sources vetted", summary: "14 sources kept, 3 dropped (paywalled)", detail: "Subscription comparison sites are outside the delegation: no paid access was created." },
{ at: 20, label: "Sheets extracted", summary: "20 sheets collected, 2 incomplete ones retried", detail: "Two sheets missing shipping costs were completed from the retailers’ terms of sale." },
{ at: 29, label: "Prices normalized", summary: "All prices converted to delivered price with tax, 18 criteria aligned", detail: "Assumption: standard ground shipping within the contiguous United States." },
{ at: 38, label: "Summary drafted", summary: "3 major gaps found, table ready for review", detail: "The draft summary is in “Market watch / 2026-09”." },
];
const END = 43;
const cost = (m) => (m <= 33 ? m * 0.366 : 12.08 + (m - 33) * 0.2);
let timer = 0;
let next = 0;
let offset = 0; // minutes replayed after a "resume from"
let extra = 0; // money already spent before that point
const tick = () => {
if (rc.state !== "running") return;
const m = rc.elapsed;
const work = m - offset;
while (next < PLAN.length && work >= PLAN[next].at) rc.checkpoint({ ...PLAN[next++], at: m });
const done = Math.min(20, Math.floor((work / END) * 20));
rc.progress({ spent: extra + cost(work), percent: Math.min(0.99, work / END), note: `${done} of 20 sheet${done === 1 ? "" : "s"} compared` });
if (work >= END) {
clearInterval(timer);
rc.complete({ summary: "20 sheets compared across 18 criteria. Report ready: 3 major price gaps, 2 stock-outs to watch." });
}
};
const run = () => { clearInterval(timer); timer = setInterval(tick, 200); };
rc.addEventListener("mv-start", () => { next = offset = extra = 0; run(); });
rc.addEventListener("mv-resume-from", (e) => {
// The agent restarts from the chosen checkpoint: later breadcrumbs are redone from now on.
const cp = e.detail.checkpoint;
const i = PLAN.findIndex((p) => p.label === cp.label);
const workAt = i >= 0 ? PLAN[i].at : 0;
next = i + 1;
offset = rc.elapsed - workAt;
extra = rc.spent - cost(workAt);
run();
});
rc.addEventListener("mv-budget", (e) => console.info("mv-budget", e.detail.type, e.detail.spent.toFixed(2), "/", e.detail.cap));
document.getElementById("rc-replay").addEventListener("click", () => {
clearInterval(timer);
rc.reset();
});
</script>
</div>API
Attributes
| Name | Tipo | Default | Description |
|---|---|---|---|
eta | string | Time window in minutes, e.g. "25-40" (read on mount when the terms property is not used). | |
budget | number | 10 | Initial spending cap. |
budget-max | number | cap × 2.5 | Upper bound of the cap slider. |
extend | number | 5 | Amount offered by "Allow +…" when the cap is reached. |
currency | string | USD | ISO 4217 currency (en-US formatting). |
speed | number | 1 | Simulated minutes per real minute for the internal clock (60: one second = one minute, handy for demos). Irrelevant when the host passes elapsed to progress(). |
hold | number (ms) | 1200 | How long the button must be held to sign. |
warn | number (0-1) | 0.8 | Budget warning threshold (fraction of the cap). |
data-state / data-phase | reflected | Current state (contract | running | paused | complete | failed | stopped) and phase (contract | run), for host styling. |
Properties
| Name | Tipo | Description |
|---|---|---|
terms | { objective, eta: { min, max }, budget: { cap, max, min, step, extend, currency, editable }, done: string[], allow: string[], deny: string[], questions: { id, label, options?, answer? }[] } | Contract terms (replaces the markup). Reading returns a copy, including a cap the user may have changed. Can be set before define(). |
state | string | Current state (read-only). |
answers | { [id]: string } | Answers to the clarification questions (read-only). |
elapsed | number (min) | Running time elapsed, pauses excluded (read-only). |
spent / checkpoints | number / object[] | Current spend and a copy of the checkpoints (read-only). |
Methods
| Name | Description |
|---|---|
sign() | Signs like a completed hold: refused (false) while a question is open; emits mv-sign (cancelable), then calls start(). |
start() | Turns the contract into the tracker and starts the clock (no signature when called directly). |
checkpoint({ label, summary, detail?, at? }) | Adds a checkpoint: a knot on the axis plus a conceptual breadcrumb. at in minutes (default: now). |
progress({ spent?, percent?, note?, elapsed? }) | Updates spend, progress (0-1 or 0-100), a short note ("9 of 20 sheets") and, optionally, the real elapsed time. Crossing the warning threshold or the cap triggers the warning or the pause. |
pause(reason) / resume() | Host-driven pause (clock stopped) and resume. |
extendBudget(amount?) | Raises the cap (default: extend) and resumes if the cap had paused the run. |
complete(result) / fail(error) / stop(reason?) | Ends the run: success (result.summary), failure (error.message, can resume from the last checkpoint) or stop. |
reset() | Back to the unsigned contract with the original terms and answers (reverse morph). |
Events
| Name | Description |
|---|---|
mv-sign | Contract signed, cancelable. detail: { terms, answers, signedAt }. |
mv-start | Tracker started. detail: { terms, answers, signedAt }. |
mv-checkpoint | Checkpoint added. detail: { index, label, summary, detail, at, spent }. |
mv-budget | Budget threshold. detail: { type: "warning" | "cap" | "extend", threshold?, amount?, spent, cap }. On "cap" the run is already paused: the host must suspend the agent. |
mv-pause / mv-resume / mv-stop | Requested by the user (or by the cap for mv-resume, source: "budget"): the host must act on the agent. Method calls do not emit these events. |
mv-resume-from | "Resume from here": detail { index, checkpoint }, cancelable. Otherwise later checkpoints are marked discarded and the run restarts. |
mv-complete | Done. detail: { result, elapsed, spent, checkpoints }. |
Content structure
| Name | Description |
|---|---|
objective | Element whose text is the objective (one sentence). |
done | <ul>/<ol> of done criteria (or one element per criterion). |
allow / deny | <ul> of actions the agent may / may not take. |
question | A question to settle before signing; data-options="A, B" for choices (otherwise a text field), data-name for the id, data-answer for a default answer. |
CSS classes
| Name | Description |
|---|---|
mv-run-contract-card | The generated card (an article labelled by the objective). |
mv-run-contract-axis / -band / -over / -now | Time axis: estimate band, hatched extension on overrun, "now" marker. |
mv-run-contract-fuse / -ember | Budget fuse (role=meter) and its ember. |
mv-run-contract-sign / -ink | Hold-to-sign field and the handwritten stroke. |
mv-run-contract-crumbs / -crumb | Checkpoint list (conceptual breadcrumbs). |
mv-run-contract-alert | State panel: paused for approval, done, failed, stopped. |
CSS variables
| Name | Default | Description |
|---|---|---|
--mv-run-contract-accent | var(--mv-accent) | Action color (slider, latest checkpoint, primary button). |
--mv-run-contract-band | var(--mv-accent) | Tint of the estimate band. |
--mv-run-contract-ember | warning / danger mix | Ember and spark color (artistic effect). |
--mv-run-contract-ink-color | var(--mv-accent-fg) | Signature ink. |
--mv-run-contract-bg | var(--mv-surface) | Card background. |
Accessibility
Terms are a real description list (dt/dd); allowed and not-allowed actions are two labelled lists where every line has its icon and the group title carries the label (never color alone). Questions are fieldset/legend groups of native radios. The sign button is held with a pointer or Space/Enter; aria-describedby explains the hold and, while a question is open, why it is blocked (aria-disabled: it stays focusable and says why). A plain click never signs. While running, the budget is a role=meter and progress a role=progressbar, both with plain-language aria-valuetext; the time text gives elapsed time, remaining estimate and overrun (icon + label); checkpoints are a list of aria-expanded buttons revealing details and "Resume from here" (the axis knots are decorative duplicates). A polite live region announces signing, checkpoints, budget thresholds, time overrun, pauses, completion and failure. After signing, focus moves to the Pause button. Reduced motion: no animated morph, no sparks or flicker, markers step instead of gliding; the signature stays because it is the hold gauge. A single frameLoop is suspended offscreen, in hidden tabs and when idle; nothing keeps running once the element is removed.