A Strands Agents agent that works in the background and surfaces only when there's a real human decision — then stops, waits, and resumes to do exactly what was approved. Nothing more.
Most agent demos optimize for autonomy: let it run, let it act, review the aftermath. This project inverts that. The agent runs a bounded job, gathers evidence, and when it reaches a decision that belongs to a human, it emits a typed decision request and halts. A human approves or rejects. The agent resumes from its persisted session, executes only the approved action, and returns a report that ties the outcome back to the decision that authorized it.
- Receive a bounded job (task packet with explicit authority).
- Collect evidence and prepare a proposed action.
- Emit a typed human decision request — question, options, evidence, consequences.
- Stop. No execution before approval, no "ask forgiveness" path.
- Resume from the same persisted run after the human approves or rejects.
- Execute only the approved action.
- Produce a typed report connecting the decision to the outcome.
The decision request is not a confirmation dialog. It is a structured artifact: what the agent wanted to do, why, what evidence supported it, what it was forbidden from doing, and what would have happened on the other branch. Every stop has a reason. Every resume references the decision that unlocked it.
Because that's the only question that matters when an agent surfaces from the background. Not "what did it do?" — you can read a log for that. The interesting moment in any agentic system is the transfer of judgment: when does the machine decide, and when does the human? This project takes a hard line: authority is explicit, declared up front in the task packet, and never silently transferred.
Being built for the Agents for Humans hackathon (AWS / Strands Agents SDK), September 2026. The core loop is proven: a real Strands agent on Bedrock stops at a typed decision request, resumes on a recorded human decision, and every artifact validates against the vendored HACP schemas. See docs/roadmap.md and docs/spike-log.md for evidence. The full build epic — every milestone from the governed design debate through the AgentCore deployment — is on the public who-decides project board.
Live demo: https://who-decides.88mphserver.com — public watch mode is read-only; sign in as the operator (passcode in the hackathon submission) to run the demo end to end. Deployed per deploy/PROVISION.md.
nvm use # Node 22, matching CI and the runtime image
npm ciThe decision console is deterministic and needs no model credentials. It starts in public, read-only watch mode; running the demo requires operator sign-in.
-
Choose a long operator passcode and generate its SHA-256 hash (the command prints the hash plus the exact line to paste):
npm run hash -- 'your-long-operator-passcode'
-
Add the following to
.env.localin the repository root, replacing the placeholder with the generated 64-character hash. This file is gitignored; store the hash here, not the passcode itself. Next.js loads it automatically.WD_OPERATOR_PASSCODE_HASH=<paste-the-generated-hash>
-
Start the console:
npm run console
-
Open http://localhost:3100, expand Operator sign-in, enter the original passcode, and select Sign in. Run the demo then appears. Use
localhostfor local browser access; the session cookie isSecure. Restart the console after changing.env.local. Without a valid hash, sign-in fails closed and the console stays read-only.
# Tests (all offline; CI runs the same plus the contention proof)
npx tsc --noEmit
npm run test:auth # operator sessions, mutation guards, login rate limit
npm run test:console # engine: branches, idempotency, crash recovery, reset
npm run test:artifacts # schema validation incl. tamper-rejection
npm run test:consumption # consume-once claims, races, expiry, replay
npm run test:live-loop # the live script's decision flow (synthetic runtime)
npm run scenario # end-to-end artifact spine, deterministicThe console stores runs and consumption receipts in SQLite by default
(zero-config local demo). Setting WD_STORE=postgres swaps in the Postgres
adapters for the hosted demo — engine logic is unchanged:
WD_STORE=postgres
WD_PG_URL=postgres://user:password@host:5432/database # or DATABASE_URL
# Discrete fallbacks: WD_PG_HOST / WD_PG_PORT / WD_PG_USER / WD_PG_PASSWORD / WD_PG_DATABASEBoth stores live in the same database; the adapter creates its tables
idempotently on first use (initialize). The Postgres suites mirror the
SQLite contract — claims and contention semantics included — and run against
any disposable database, skipping green when unset:
WD_TEST_PG_URL=postgres://postgres@127.0.0.1:55440/wd_test npm run test:pgWD_PROVIDER=bedrock AWS_PROFILE=who-decides npm run live-loopOne real agent run end to end: invocation A stops at exactly one
HUMAN_DECISION_REQUIRED interrupt, a scripted decision is claimed exactly
once (the claim gates execution), invocation B resumes from the session and
executes only the approved branch as a dry-run. Artifacts land in
.tmp/live-run/<tag>/. Set WD_LIVE_TAG (filename-safe slug) to name a run;
rerunning a consumed tag stops typed. An OpenAI-compatible escape hatch exists
via WD_PROVIDER=openai-compatible with WD_BASE_URL/WD_MODEL/WD_API_KEY.
Pull requests are reviewed by Codex under the
ACK lane under
.agent-control/lanes/pr-review-loop.yaml (see
docs/ack-startup-guide.md). Normal feature PRs
target dev; promotion to main is a separate human-mediated PR. Merge
commits only, never squash. Qodo remains disabled.
The build is a human-gated multi-agent loop: implementation sessions do the work, independent bot and agent reviewers attack each PR, and the human owner triages, dispositions, and merges. Everything below is sourced from docs/spike-log.md — the day-by-day evidence record; claims without a recorded measurement are marked TODO rather than guessed.
Models (roles). Bedrock global.anthropic.claude-sonnet-4-6
(us-east-1) is the documented default agent model, adopted after a
measured commit-or-pivot gate (spike-log Day 2b). An OpenAI-compatible
endpoint (gpt-4o, smoke-tested Day 1) remains the escape hatch via
WD_PROVIDER=openai-compatible. The decision console itself calls no
model — it replays a fixture deterministically.
Prices (only what was measured). The Day 2b gate recorded 7,206 input / 2,152 output tokens ≈ $0.054 per full run at the then-listed $3/$15 per-million-token rates, against a $5 ceiling. The Day 4 end-to-end pass (both invocations, 5.6 s) was "in line with" that measurement; no exact figure was recorded for it. AgentCore Runtime was surveyed (Day 7) at $0.0895/vCPU-hour + $0.00945/GB-hour with a 128 MB floor. TODO: later passes recorded no per-run cost measurements, and total spend is not tracked in this repository.
Review claims (what actually converged). Not every PR converged cleanly, and the log says so. Convergence examples with receipts: PR #1 (console) — 11 bot findings triaged, 8 patched in the PR. PR #4 (live loop) — eleven review rounds across Codex/Qodo passes, including two reviews wrongly called clean before the real findings landed; the loop produced the claim-first restructure, durable crash recovery, and the no-takeover reservation posture. PR #13 (public demo) — 13 findings across three passes. PR #15 (Postgres adapters) — two Codex rounds plus an independent second-family pass, verified 88/88 SQLite + 49/49 Postgres. AC-1 (agent service) — three review rounds. PR #10 merged only by explicit owner override after repeated rounds kept surfacing real findings — the ceiling on review is the human's, by design.
Deployed-agent evidence. The AgentCore packaging repair (Node 22
ARM64 container, native SQLite) is validated by local gates — unit
suites, build, agentcore validate, and a container smoke test — and by
the strict live gate. npm run test:agentcore-live (see
deploy/PROVISION.md and
agentcore/DEPLOY-CHECKLIST.md) ran
against the deployed container runtime on 2026-09-09 and passed 6/6,
0 skipped, 26.8 s — a real A → human decision → B cycle through the
production dispatcher and ConsoleEngine, plus duplicate/conflict/
rejection discipline on typed dispatches (spike-log Day 10).
The decision console (npm run console, port 3100) is a single-operator
demo with public watch mode:
- State is publicly readable. Starting runs, submitting decisions, resetting
the console, and probing duplicate resumes require a server-side operator
session issued after passcode sign-in. Sessions expire after 12 hours;
the browser carries a
Secure,HttpOnly,SameSite=Strictcookie. - Authenticated console decisions record the originating operator session in
the HACP human-decision artifact. This is shared-passcode authentication,
not individual user accounts. Engine/CLI callers without an authenticated
channel retain explicit
demo-unauthenticated-local-consoleattribution. - Public hosting requires HTTPS and the reverse proxy setup in
deploy/PROVISION.md.
npm run consoleis the local development server; the hosted deployment uses a production build. - Preparation and verification are fixture simulations. Task-packet initial approval hashes are illustrative, not signature verification. Runtime machine authentication proves the relay credential, not an independently verified human act; the console retains the originating operator-session decision.
- Live mode uses both
WD_AGENTCORE_ENDPOINTandWD_MACHINE_TOKEN. Live runs advance only on confirmed AgentCore responses; failures stay visible. Unset both variables and restart for deterministic mode. See the deployment checklist. - Live mode keeps two artifact spines: the console persists its own
decision → consumption-receipt → effect-receipt chain (whose
successorInvocationIdis the console's reserved claim successor), while the AgentCore runtime persists its own spine with its own invocation IDs. The spines are joined by the run tag embedded in the runtime's decision ID and by the full dispatch envelopes the console stores per invocation — not by sharing one invocation ID space. - If a live resume is confirmed remotely but the console dies before
finalizing, resubmitting the same decision repairs the run idempotently
(no redispatch). A terminal claim rejection parks the run as
blocked, which reset may archive; audit artifacts are retained either way. - The prepared effect is always a dry-run: the exact payload is recorded and shown, and no external mutation is performed in any branch.
- Resetting the console archives the current run; completed run records and artifacts stay in the local database for audit.
This repository is a new project, but it builds on disclosed prior work by the same author and on open-source libraries. See docs/disclosure.md for the full list, notably:
- HACP — Human-Approved Coordination Protocol: pre-existing conceptual work. Its machine-readable schemas (Apache-2.0) are incorporated here with attribution and drive the typed artifacts in this demo.
- Strands Agents SDK (Apache-2.0, AWS).
Apache-2.0 — see LICENSE.