AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
翻译准备中
wicked-crew is the control plane for governed agent delivery — the harness for your agent harnesses. Intent in; governed, verified work out: your own installed CLIs (Claude Code, Codex, Copilot, openc
翻译准备中
开发工具TypeScript · JavaScriptGitHub @mikeparcewski在线运营者未确认
试用过了吗? 点一下就好,无需登录。
The control room for governed agent delivery — drive, gate, and audit the work; the human keeps authority over anything irreversible.
翻译准备中
取自仓库 README
正在加载完整 README。 以下为已保存的摘录。
bash npm install -g wicked-crew # or run it directly: npx wicked-crew serve wicked-crew serve bash launch a governed run over the API… curl -X POST http://127.0.0.1:7701/api/v1/runs \ -H 'content-type: application/json' \ -d '{"problem": "Add input validation to the signup form", "workflow": "feature"}' …or without the console entirely wicked-crew start --problem "Fix the flaky retry test" --workflow bug查看完整 README ↗
_ _ _
__ _(_) ___| | _____ __| | ___ _ __ _____ __
\ \ /\ / / |/ __| |/ / _ \/ _` |_____ / __| '__/ _ \ \ /\ / /
\ V V /| | (__| < __/ (_| |_____| (__| | | __/\ V V /
\_/\_/ |_|\___|_|\_\___|\__,_| \___|_| \___| \_/\_/
Intent in. Gated work out. crew runs the jobs studio starts, and refuses to call them done without proof.
Your coding agents do the work. A different agent checks it against your rules. You get the evidence. In the wicked platform crew is the control plane: studio is where you direct the work, garden supplies the skills, estate holds the record, and crew runs each job between them.
feature, bug,
migration, mcp-server) are not delivered until QE acceptance reads an attributed PASS. A gate
says what it checked, one failed check stops the job, and "done" is re-derived from evidence,
never asserted. Jobs are durable workflows defined as data, crash-resumable, and exportable as
evidence.Under the hood it's a daemon (wicked-crew serve) plus CLI and the bundled studio console. The
engine underneath is wicked-core. (In the API and the code a job is a run, and an agent is a
seat.)
One machine today. Loopback only, in-process engine, local bus. Agents run in containers on this machine, and model traffic leaves through the credential broker to the providers you signed in. The execution seam is built for a remote runner, but remote/distributed execution is not shipped: crew runs on a single host. No cluster, no horizontal scale.
Requires Node.js ≥ 22 and a container runtime (Rancher Desktop, Docker, Podman, Colima or OrbStack):
governed agents run each CLI's headless mode in a hardened seat container (crew builds the seat images
on first use), and you sign each agent in once from studio's Agents tab, in a login container.
Seat images ship for Claude Code, Codex, GitHub Copilot, OpenCode and Pi. Antigravity has no seat
image, and a local model or custom binary needs your own image (WICKED_SEAT_IMAGE_<CLI>, offline
seats only). WICKED_SEAT_FLOOR=host keeps the labelled interim host floor instead; there, only
claude and codex are given governed work.
npm install -g wicked-crew # or run it directly: npx wicked-crew serve
wicked-crew serve
Or use the family installer — npx wicked-installer
installs/updates the whole wicked-* family, crew included.
crew needs wicked-garden ≥ 12.40.0. Garden ships the skills every governed worker follows,
including the base skill wicked-garden-governed-worker. crew does not vendor it, so install it once:
npx wicked-installer install wicked-garden # or install the wicked-garden plugin in Claude Code
crew finds garden in Claude Code's plugin cache (<config dir>/plugins/cache/wicked-garden/…) or in
the installer's copy (<config dir>/plugins/wicked-garden), where <config dir> is
$CLAUDE_CONFIG_DIR or ~/.claude. To use another garden root on purpose, point
WICKED_CREW_SKILLS_SOURCE at it. Without garden, /health reports the missing base skill as an
error. Every launch and every Register & onboard is refused with that reason, and nothing is
registered. A garden older than 12.40.0 is never used. At first boot, /health carries a
skills.garden error that names the version found, where it was found, and the version required.
Launches and onboarding answer 422 garden_required until a newer garden is installed and the
daemon restarts. A refresh (POST /api/v1/skills/refresh-baseline) from an older garden is refused
with the same words. Skills a daemon already published are kept as published until the next
refresh and publish.
serve starts the daemon on http://127.0.0.1:7701 (--port / CREW_PORT override) and serves
the bundled wicked-studio console same-origin — open the URL to launch and steer runs, answer
human gates, and browse projects and evidence. Durable state lives in ~/.wicked-crew/ (--db
overrides). Headless works too:
# launch a governed run over the API…
curl -X POST http://127.0.0.1:7701/api/v1/runs \
-H 'content-type: application/json' \
-d '{"problem": "Add input validation to the signup form", "workflow": "feature"}'
# …or without the console entirely
wicked-crew start --problem "Fix the flaky retry test" --workflow bug
With only one CLI signed in, a governed run cannot be evaluated or judged by a seat other than the
one that built it. Under the default (full) assurance the engine refuses a review on its builder's
seat and holds a gate whose judge has no distinct seat, so the run waits for a second CLI; the launch
answer says so (assuranceNotice). To proceed with one CLI, opt in explicitly:
"reducedAssurance": true on POST /runs, or wicked-crew start --reduced-assurance. The creator's
seat then evaluates its own work, and every receipt says reduced. crew never turns it on for you.
QE acceptance. A run that makes application changes (feature, bug, migration,
mcp-server) requires real functional QE acceptance: garden's three-agent wicked-garden-qe accept
pipeline, whose verdict lands in the QE ledger stamped with the run. Delivery is refused (409 qe_acceptance_required) unless the ledger holds a PASS attributed to the run. The engine decides at
the run's QE phase, from the run's actual diff, whether it is required or waived. A waiver needs
the impact score in its lowest band on every dimension (reach, complexity, novelty). The decision,
with its reason, is on the run, every gate receipt and the delivery. To skip it, say so with a
reason: "skipQeAcceptance": {"reason": "…"} on POST /runs, or
wicked-crew start --skip-qe-acceptance "<reason>". It is labelled "QE acceptance skipped by
operator: " everywhere. To require it even when the score would waive it, send
"forceQeAcceptance": true (--force-qe-acceptance). crew never sends either for you.
A daemon started by hand dies with the machine. Register it as a per-user login service instead:
npm install -g wicked-crew # the service needs a durable install, not the npx cache
wicked-crew serve --install-service # add the serve options you want, e.g. --port 7702
wicked-crew status # the daemon's runs once it is up
wicked-crew serve --uninstall-service # stop and remove it
~/Library/LaunchAgents/com.wickedagile.wicked-crew.plist (RunAtLoad,
KeepAlive), loaded into your login session. Restart it with
launchctl kickstart -k gui/$(id -u)/com.wickedagile.wicked-crew.~/.config/systemd/user/wicked-crew.service (Restart=on-failure).
Run loginctl enable-linger $USER so it starts at boot without a login session.--install-service prints a Task Scheduler recipe.The service manager starts the daemon with an almost empty environment, so the install captures
PATH, HOME, CLAUDE_CONFIG_DIR, CODEX_HOME, CREW_PORT, GH_ACCOUNT and every WICKED_*
variable from the shell that runs it. Run the install again after you change them.
CLAUDE_CONFIG_DIR matters: the daemon fences its workers away from a non-default Claude config
directory only when it knows that directory. Tokens (GH_TOKEN and any variable whose name carries
TOKEN, SECRET, PASS, KEY, CREDENTIAL, BEARER, COOKIE or SESSION) are never written into the unit file. The install names them, and the
deliver phase then pushes with GH_ACCOUNT's own gh token (read from gh's keyring per command, whatever account gh has active), or with gh's login when GH_ACCOUNT is unset. The daemon logs to
<state home>/daemon-stdout.log. The install refuses while a hand-started daemon holds the port.
wicked-crew status with no daemon answering exits 1 with one line: start it with
wicked-crew serve (or --install-service), or, when the service is installed, read its log.
The CLI surface is wicked-crew serve|start|resume|gate|status|mcp|governance — mcp runs a
stdio MCP server (crew-as-a-tool for coding agents), gate answers a pending human gate from the
terminal, governance replay <outbox> drains governance events the engine could not store.
Governance evidence (conformance claims, phase transitions, rule lifecycle) lands in a store
beside the core db — <core db>.governance/governance.db (--governance-db /
WICKED_CREW_GOVERNANCE_DB override; an inherited WICKED_ESTATE_DB is honoured — the emit seam stores
to SQLite only, so a URL-form WICKED_ESTATE_DB such as postgres://… in your shell refuses boot until
--governance-db names a SQLite file). Events the
engine cannot store dead-letter to <core db>.governance/emit-outbox.ndjson by default — under the
state home, not your home directory (an explicit WICKED_APPS_EMIT_DEADLETTER is honoured);
GET /api/v1/diagnostics → governance counts them and raises a
governance.deadletter finding, and wicked-crew governance replay <outbox> puts them back.
The interactive document bridge (Vibe / Demo documents) is spawned on demand as
npx wicked-interactive@^0.14.1 (0.14.1 has the demo import crew's demo runs use and the theme-source upload
studio's Learn from a brand uses; 0.12.0 confined export asset reads to the document's own and approved
roots; earlier floors added the recorder browser preflight and the typed RecorderError frames crew relays); WICKED_INTERACTIVE_SPEC overrides the
range for an operator or a test rig (a semver range only — ^0.14.1, 0.14.1, >=0.14.1 <1.0.0; tags, paths and pkg@range
spellings are refused and the default is used), reported on the boot line, with a warning when the
override's floor is below the release crew needs.
Spec-driven development promised rigor and delivered ceremony. wicked-crew goes for the outcome without the theater — call it intent-driven: you state intent, and the orchestrator drives a governed workflow to a verified result, harnessing the coding agents (and subscriptions) you already pay for instead of replacing them.
wicked-core — validates and drives them.)One run token moves through the workflow's phases with a gate between every one — e.g. for feature:
clarify → design → build → adversarial-review → test → review
│gate │gate │gate │gate │gate │gate
By default every phase's worker follows garden's role-keyed base skill (wicked-garden-governed-worker;
settings or the workflow definition can override or disable it), read from an immutable skills
snapshot generation handed to each worker spawn, so control is consistent instead of an ad-hoc
prompt every run. A phase runs a task-specific skill only when its definition names one (skill_ref): the
built-in domain-extraction and mcp-server workflows do; feature, bug and migration rely on
the base skill. Phases, gates, roles, and any phase skill are all data on the workflow definition.
wicked-core, DES-EXEC-001 rev0.4)Built + live-verified in the
wicked-coreengine: a test-strategy skill authors a grounded deterministic check (provision-validator), a human/council approves it (approve-validator, distinct content-hash pin), and the gate re-verifies that pinned script against the worktree (deny-dominates, no LLM at gate time) alongside an independent agent judge that can reject but never lone-approve. The security controls (approval gate, fail-closed parse, denylist) survived a 14-finding adversarial review. Caveat: the shipped feature/bug/migration workflows pin only the built-in evidence floor (e2e7af1db9e48454— "the run left a change in its worktree"; it proves a change exists, not that the change is correct) on their code and verification phases (featurebuild+adversarial-review+test, bugfix+verify, migrationexecute+verify); every other phase shipsvalidator_pin: nulland engages the full gate only once an operator authors, approves, and pins a task-specific validator into the def.
The deterministic gate check is not a generic precanned assertion. A test-strategy agent authors a grounded validation script for that specific phase/task — stored as the phase's evidence evaluator, versioned and approved; when the spec changes the script is regenerated and re-approved, so validation always tracks the spec. The evaluator is a complementary pair:
(As built, the agent validator runs under a distinct council seat — identity-distinct from both
the deterministic-validator author and the work author, by resolved binary. When the roster has no
such seat the judge is skipped, never self-graded: the gate then decides on its deterministic floor
alone and the verdict says so (agentVerdict: skipped, judgeDistinct: false; studio renders it as
"no distinct judge reviewed this verdict (floor only)"). An adversarial review caught an early
self-grade hole here — the judge excluded the wrong author — since fixed and dispatch-proven.)
Combined so that Approve requires the deterministic piece to PASS; the agent piece can REJECT but is never the sole approver — a model may fail a gate, never solely approve one. The converse also holds: with the judge skipped, a deterministic PASS approves on its own, so a floor-only approval is only as strong as that floor.
Trust model, named honestly: diverse-seat agent consensus, on a deterministic structural floor, with human escalation above a threshold. A green run means "diverse seats + the escalation policy agreed," not "proven."
Distinct from the per-phase gates above — which govern the RUN — the acceptance gate governs
delivery: when a run's contract requires QE acceptance (see QE acceptance above), delivery is
refused (409 qe_acceptance_required) until the ledger holds a PASS attributed to the run (crew
0.9.0). It can be waived by the run's own lowest-band score, skipped with a reason, or turned off
per daemon in Settings, and each of those is labelled. The read itself is computed on GET. It answers a different question: does the QE evidence ledger accept the work this run did to its repo? This is
the machine gate absorbed from the retired wicked-testing product, and it lives on the daemon:
GET /api/v1/runs/:id/acceptance (?qeRun=<id> pins the read to one QE run's newest verdict)
A workflow declares its acceptance requirement through its phases: any phase carrying
verified_evidence: true requires "done" to be re-derived from evidence. Crew resolves that
requirement from the repo's QE evidence ledger — a wicked-ledger
store at <repo>/.wicked-qe/ written by wicked-garden's QE skills (legacy <repo>/.wicked-testing/
ledgers are still read; WICKED_QE_LEDGER_DIR overrides) — deny-dominates throughout:
PASS → satisfied. Nothing else is.FAIL, CONDITIONAL, PARTIAL, INCONCLUSIVE, a missing ledger, a missing verdict, an
unreadable ledger → each denies with its own named reason. No evidence is never a pass, and a
conditional approval's conditions are unmet work — the assessment stays a deny until a clean
PASS is recorded. A run can therefore read completed (even delivered) while its acceptance
answer is a deny; read both before you merge.The route always answers 200 for a known run: "no ledger", "no verdict", and "FAIL" are real answers
about the gate, not errors in the request. It sits beside GET /runs/:id/evidence deliberately —
evidence is what the run recorded about itself; acceptance is what the QE pipeline recorded
about the repo the run worked on. Two systems of record, two routes. The route reads the ledger on
demand; it needs no bus.
The same acceptance body carries a conformance section — the governance half of "is it done?",
so a wiki-rule violation appears on the page humans actually look at instead of only on the
standalone /governance/claims wire:
wicked-agent/<runId>/…), each conform: obligation parsed back to the conformance rule it
cites (severity, rule id, statement — the ruleset the output had to meet). Denials name their
rules; advisory boundary-read denies are counted separately and never deny the run.enforcement — what the run's durable event log says about whether governance was actually
in force: enforced | unenforced | ungoverned | unverifiable.guardrailed — the one deny-dominates headline. True ONLY when the claims wire was readable,
no non-advisory denial stands, AND enforcement was positively verified. An unenforced,
ungoverned, or unverifiable run is never reported guardrailed.Coverage boundary, stated plainly: in a seat container the floor bounds every seat's effects
(only the worktree is mounted; the model gateway is its only egress). Deterministic per-tool-call
input governance is claude's gate hook; other seats get no per-tool-call check yet (the container
floor's pause-and-ask will replace it; not built), and on the interim host floor a governed unit on
such a seat is refused. A governed unit on any other seat is reported unenforced and gets
phase-boundary coverage only: the in-process output gate and the deliverable floor evaluate the
unit's output after the fact, advisory relative to mid-flight tool-call blocking. "Guardrailed"
is a verified claim about governed units with per-tool-call checks, not a blanket property of every
run. (The earlier ACP-era admission is in the CHANGELOG.)
Components publish and subscribe; they don't call each other. Standard event types (wicked.*) mean you
can attach sidecars — audit, extra processing, skill provisioning/refresh — to any workflow without
touching it.
Built: the
wicked-coreengine has a Rust↔wicked-busbridge (src/bus.rs) — it emits/polls the real wicked-bus SQLite log and turnswicked.crew.run.requestedinto a launch (cross-language round-trip verified, at-least-once with retry). The reducer + skill-provisioner sidecars (poll-loops with idempotent, correlation-aware handlers) run on it (wicked-bus/examples/crew-sidecars, smoke-verified). Wiring the bridge into the studio's browser feed is a follow-up.
Crew drives the coding-agent CLIs you install, under your own accounts and subscriptions.
It is your responsibility to confirm that driving a given CLI programmatically is
permitted by its terms of service. For Antigravity (agy) the answer depends on
which credential the seat runs under, not on the fact that it is driven programmatically:
| Credential | Notes |
|---|---|
| Consumer Google account sign-in (OAuth) | The restricted path — do not run a governed seat this way |
Gemini API key (GEMINI_API_KEY) | Terms differ by whether the project is billing-enabled; a free-tier and a paid-tier key are the same env var |
| Vertex AI (GCP project) | Enterprise terms; the clean path for programmatic use |
Crew cannot detect which tier a key belongs to — a free-tier and a paid-tier
GEMINI_API_KEY are indistinguishable at the seat. Configuring the seat's credential is
an assertion you are making, not a fact crew verifies. Review Google's current terms
before enabling that seat, and remove it from your roster if in doubt. The engine works
identically with any subset of the roster.
Since the #98 carve, the studio SPA lives in its own repo —
wicked-studio, the coder-facing skin of the
experience plane. Crew keeps the control-plane function: the daemon, the /api/v1 REST surface,
the /ws CoreEvent stream, and the wicked-core engine underneath. The two share exactly one
thing: the published wire contract (packages/crew-api-types, npm: wicked-crew-api-types),
which studio consumes as a normal dependency. No source coupling in either direction.
Crew still ships a default skin. packages/crew declares wicked-studio as a devDependency
whose package carries only its built dist/; the release build copies that artifact into the
daemon's serving tree, so npx wicked-crew serve keeps the one-command local UX.
Real work spans repos. A change in an engine lands in the daemon that embeds it and the console that renders it, and an agent that can only see one of them answers "not found" about the other two — confidently, because a per-repo graph has no way to know it was asked about something outside itself.
Attach repos to a project as crew.repo members and build one co-located wicked-estate graph
over all of them:
curl -X POST localhost:7701/api/v1/projects/$PID/members \
-H 'content-type: application/json' -d '{"kind":"crew.repo","ref":"wicked-studio"}'
curl -X POST localhost:7701/api/v1/projects/$PID/graph/refresh # indexes each member, once
| route | answers |
|---|---|
GET /api/v1/projects/:id/graph | what the graph holds, and for anything missing: why, and the remedy |
GET /api/v1/projects/:id/graph/search?name= | symbol resolution across every member repo |
GET /api/v1/projects/:id/graph/blast-radius?name= | dependents across every member repo |
POST /api/v1/projects/:id/graph/refresh | (re)index members — never happens implicitly at launch |
Every hit is attributed to the repo it came from, and results span languages: one query for
register over studio + crew + core returns TypeScript and Rust hits together, counted per repo.
Co-located is not linked. Each repo is indexed under its own label and edges do not resolve
across repos — studio → wicked-crew-api-types → crew does not traverse. What you get is per-repo
results gathered into one answer with the repo named on every hit, which is genuinely useful
("who calls record, anywhere in this project") and is not a cross-repo dependency trace. Every
response says so in a linkage: "co-located" field and a note, so a consumer cannot mistake one
for the other.
Runs bind to it, and say when they don't. A run filed into a project is handed the project graph instead of its own repo's, and the decision is recorded either way — "this run sees the project" and "this run sees one repo, because X" are both facts about what the run could observe, and the second is the one you need when a worker reports a sibling repo does not exist:
run 909db7fb: bound to the project graph as 'wicked-studio'.
run 0df7fb33: repo 'wicked-garden' is not a member of this project, so the project graph does
not describe it; this run uses the repo's own code graph. Attach it on the project's
Repositories tab and refresh the graph to widen future runs.
A refresh is wicked-estate index per member, bounded at ten minutes each, so it is never
implicit: a launch that silently indexed N repos would block the response for as long as the
slowest one takes. A missing or stale graph degrades the run to the per-repo graph and says so.
Requires wicked-estate ≥ 0.14.6 (the release that added --repo co-location) and wicked-core-ts ≥ 0.7.1.
Both are capability-probed before anything is indexed: an older estate accepts --repo, ignores
it, and exits 0, which would silently produce a database holding only the last repo indexed.
| mode | command | what you get |
|---|---|---|
| default (dev) | npm run -w packages/crew build | plain tsc — the daemon only. Without a bundle at dist/studio it serves headless API + WS and logs that it is doing so. |
| release-shaped | npm run -w packages/crew build:with-studio (or npm run build:with-studio at the root) | tsc + scripts/bundle-studio.mjs, which copies the installed wicked-studio package's dist/ → packages/crew/dist/studio/ — the daemon then serves the SPA same-origin, exactly what the published npm tarball ships (files: ["dist"]). |
CI and the release workflow run build:with-studio, so npm consumers still get the studio UI
inside wicked-crew — the carve changes where the UI lives and the dependency direction
(control plane ships a dist artifact), not the shipped experience.
To work on the studio, clone wicked-studio and
run its dev server against a local daemon (vite on :4200 → VITE_API_HOST=127.0.0.1:7701,
CORS-allowed for any loopback origin). Its e2e/studio_standalone_test.py is the scripted proof
of the standalone mode, driven against this repo's built daemon.
Shared types live in packages/crew-api-types — a types-only (zero-runtime) workspace package
defining every /api/v1 + /ws wire shape, published to npm as wicked-crew-api-types and
versioned independently of the daemon. The daemon's route layer imports the workspace copy and
studio pins the published contract; packages/crew/tests/wire-contract.test.ts fails the
typecheck if the daemon's responses ever stop satisfying it.
.product/ — requirements, design, and evidence:
REQ-001-application-overview.md, DES-001-technical-design.mdDES-CAMPAIGN-001-parallel-scheduler.md, DES-STUDIO-*, DES-TERMINAL-001, DES-RELEASE-001wicked-core — the single-writer runtime that owns the
workflow-as-data model, data-driven planning, skills-driven invocation, and the gate ladder. See
wicked-core/.product/DES-EXEC-001-event-driven-workflow-execution.md for the engine design (the two
laws, the gate model, the skills seam).site/ — the wicked-crew site (wc.wickedagile.com).Active build. Built + verified in wicked-core (each layer adversarially reviewed): workflows-as-data
→ data-driven planning → per-phase gate + role/artifact-passing → skills-driven invocation → the
dual-validator gate (author→approve→pin→re-verify) → the validator vault → the Rust↔wicked-bus bridge →
the napi bridge (wicked-core-ts,
a crate inside the wicked-core repo, published to npm — this daemon depends on ^0.7.1 and embeds the
engine through it). Remaining: an OS sandbox around validator execution (today's containment is a
denylist-screened parse + minimal env — defense-in-depth, not a hermetic sandbox), and (honestly) the
shipped workflows pin only the built-in evidence floor on their verification phases — the full
authored-validator gate engages per phase only once an operator pins one. See wicked-core/README.md
for the per-layer status.