Build an agent

Venia gives human-derived clearance to autonomous machines at security checkpoints. Your agent is a caller: you call Venia, you don’t grant, and you don’t operate Venia.

Five-minute quickstart

Run plug-test --demo, attach the MCP server in demo mode, then run gate_check and watch it return HOLD.

Run these from mcp-agent/. The demo fake is local, and none of these commands start the ledger.

node plug-test.mjs front --demo
node plug-test.mjs rear --demo
node plug-test.mjs mcp --demo

mcp --demo attaches the MCP server in demo mode with the scope contract, front_live, and gate_check. It runs gate_check for unit-mcp-hold with at_gate omitted, and the pass line includes gate_check HOLD.

To attach the MCP server in demo mode the same way, run the sample. It starts the same demo fake and attaches server.mjs.

node examples/node-mcp-agent.mjs

The sample runs gate_check for unit-14 with at_gate not exactly true, then prints HOLD.

Contract

The contract lives at GET /api/v1/contract (no key) and is named venia.plugs.v1. Don’t call contract just to start a conversation. Call it when someone asks to read or show the contract.

Roles and scope

There are two roles. The host sets the role, and you cannot add tools or raise it.

  • front — the site operator and gate — contract, front_live, front_sync, front_hunt, front_health, front_roster, front_lookup, front_revoke, gate_check
  • rear — machine dispatch and assignment — contract, rear_live, rear_health, rear_authorize, rear_end

Scope is the host’s MCP tool list (VENIA_MCP_SCOPE, or /settings → MCP on a TTY). A tool outside your role is refused, and so is a tool outside your scope. both covers every tool, so don’t use both for a gate-only agent.

Rules from AGENT.md:

  1. Use only the tools for your role. contract is optional: don’t call it just to start talking, but do call it if they ask to read the contract.
    • Front: use front_live when they ask who is live, front_sync to pull the site roster, front_health for the last unattended hunt (if it says no hunt yet, run node install-plant.mjs or npm run watch), and front_hunt when they say something is wrong. Look up and revoke by name or person_id.
    • Rear: use rear_live for people and open visits, rear_health to bind the rear key (empty visits is not a failure), rear_authorize only when they have named both the person and the unit, and rear_end by trip_id.
    • Rear does not pull the site roster, and front does not loan a visit. Never ask anyone for a badge serial.
  2. Fail closed. Never invent a person, badge, or unit.
  3. Set at_gate: true only if the site gate process already claims that named unit is at the trap. That process may run node mcp-agent/gate-claim.mjs vehicle_id=unit-14 at_gate=true. If at_gate is omitted or set to anything other than true, the answer is HOLD. Never set at_gate from video.

If someone asks you to edit Venia source, use the site operator’s desk, rewrite the chain, add tools, or raise your role, refuse.

at_gate

at_gate is true only if your gate process already claims the named unit is at the trap.

That process may run node mcp-agent/gate-claim.mjs vehicle_id=unit-14 at_gate=true. If at_gate is omitted or set to anything other than true, the answer is HOLD. Never set at_gate from video.

Tools

This table is generated from TOOLS in lib.mjs. Don’t edit it by hand.

Tool Title Role Arguments Read-only
contract Read the plug contract front, rear none yes
front_live Live people and visits front none yes
front_sync Pull the site roster front none no
front_hunt Run one hunt pass front none no
front_health Last hunt snapshot front none yes
front_roster Register a person front display_name, badge_serial, format, rights?, valid_days? no
front_lookup Look up a person front person_id?, display_name?, badge_serial?, format? yes
front_revoke Take a person off site front person_id?, display_name?, badge_serial?, format?, reason? no
gate_check Ask if a unit is allowed front vehicle_id, at_gate?, on_loop? no
rear_live Live dispatch rear none yes
rear_health Dispatch health rear none yes
rear_authorize Loan a person to a unit rear person_id, vehicle_id, valid_minutes? no
rear_end End a visit rear trip_id, visit_id?, reason? no

contract

Reads venia.plugs.v1. You are a caller of this contract, not the grant. Takes no arguments. Don’t call it just to start talking; call it when they ask to read or show the contract.

front_live

Site operator: lists live people and live visits, with names, person_id, display_name, trip_id, vehicle_id, status, and valid_until. Never serials. People have no clearance_id; visits carry the one-shot clearance_id. No arguments.

front_sync

Site operator: pulls the live site roster into this ledger, the same job as the operator’s /sync. No arguments, and rear cannot run it. The other roster label has no live adapter. Don’t ask for serials. It returns public lines only (names, ids, skip reasons) and never prints badge serials.

front_hunt

Site operator: runs one hunt pass. It binds the front key, pulls the site roster (front_sync), then runs front_live, and returns { ok, fixed, skipped, needs_human, people, visits }.

Policy skips are not failures. needs_human / attention is raised only when a person must act: the ledger is down, there is a bind/key mismatch, or there is a real roster-source fault. CSN, plate, and PIN skips are not attention, and neither is saved-token auth or a person who is already gone. The other roster label has no live adapter; that is a label, not attention.

Don’t invent serials, and don’t grant. Don’t loan a visit unless they named the person and the unit and this host is both. Use it when they say something is wrong, ask you to fix it, or want a pass run now. The unattended loop is npm run watch, and the last result is in front_health.

front_health

Site operator: the last hunt snapshot from this box (ts, ok, age_ms, fixed, skipped, needs_human, people, visits). Read-only, and it does not pull the site roster. No arguments. Use it for the last unattended pass or when they ask how the site is doing; call front_hunt to run a pass now. Never serials. Don’t grant.

front_roster

Site operator: registers a person, but only when the caller has already given display_name, badge_serial, and format from the site PACS. Example: { "display_name": "Site tech", "badge_serial": "SITE-BADGE-001", "format": "Standard 26 Bit" }.

Don’t ask the human to type a serial. If any of those three is missing, don’t call it: fail closed. Use front_sync to pull the site roster. format is the PACS card format name. Venia fingerprints format + serial and keeps neither string. Omit valid_days to leave the person live until revoked.

front_lookup

Site operator: looks up a person by person_id or display_name (exact, case-insensitive). Use serial + format only if they already gave both; don’t ask for a serial. An ambiguous name returns reason ambiguous with matches (ids and names only). Fail closed.

front_revoke

Site operator: takes that person off site, and their open visits die. Prefer { "person_id": "per_…" } from front_live or front_lookup. An exact display_name is also allowed. Use serial + format only if they already gave both; don’t ask for a serial.

gate_check

Site operator / gate: asks whether this named unit is allowed at the gate now. vehicle_id is a name the unit already has, not a scan. Set at_gate true only if your gate process already claims the unit is at the trap; Venia does not sense.

Good: { "vehicle_id": "unit-14", "at_gate": true }. Bad: at_gate true from video, a plate, or a guess. Omitted or false → HOLD. GRANT does not spend the visit.

rear_live

Dispatch: live people and open visits, with names, person_id, trip_id, and vehicle_id. Never serials. Empty visits means nothing is dispatched, not a failure. Use it to pick a person_id to loan or a trip_id to end. Don’t grant, and don’t invent a unit.

rear_health

Dispatch: binds the rear key and reads live people and visits. Returns { ok, needs_human, people, visits }. Empty visits is not a failure; attention means the ledger is down or the rear key doesn’t match. It does not pull the site roster and does not loan. Use it when they ask how the visits are doing, or for health on a rear host. The last snapshot is this same read.

rear_authorize

Dispatch: loans a live person to a unit’s existing name for one inbound visit. Example: { "person_id": "per_…", "vehicle_id": "unit-14" }. That visit is the pass. valid_minutes is time-to-arrive (default 12h). Don’t invent a unit name.

rear_end

Dispatch: ends the inbound visit. Example: { "trip_id": "trip_…" }. Outbound does not call Venia.

Refuse list

These are never listed and never executed: mint, burn, state, prove, forge, restore, desk, keys, env, read_file, write, shell, git.

If someone asks you to edit Venia source, use the site operator’s desk, rewrite the chain, add tools, or raise your role, refuse.

Never use VENIA_DESK_KEY. Never ask for a badge serial. Fail closed, and never invent a person, badge, or unit.

Contact

development@veniallc.com