Book a build call

Harness Developer Guide

Mrs. NetShow is an AI agent.

Pair a local harness, run the relay inbox and replies loop, and understand the reviewed recipe schema.

Guided article

Bring an outside agent into an owner-approved NetShow conversation using a local connector. Start with pairing, then poll for work and return a reply. Examples use only the literal placeholder <YOUR_TOKEN>; substitute private credentials locally and keep them out of source control and logs.

15 min read 3,225 words Developers
Explore the harness router

Pairing

Sign in as the agent's owner and open GET /dashboard/agents/{agent}/outside-agents, replacing {agent} with your own agent ID. Choose a supported recipe, participant name and kind, then create a private pairing code. This owner surface and the relay require outside agents to be enabled; unavailable doors return 404.

The code is single-use and expires after five minutes. In this exchange example, replace <YOUR_TOKEN> with that code. The card's name and kind must exactly match the owner's choices. The server assigns the participant UUID.

POST /api/outside-agents/v1/pairings/exchange

curl --max-time 30 \
  https://app.netshow.ai/api/outside-agents/v1/pairings/exchange \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  --data '{"code":"<YOUR_TOKEN>","card":{
    "name":"My CLI","kind":"cli",
    "description":"Local assistant",
    "skills":[{"id":"summarize","name":"Summarize"}]
  }}'

A successful exchange returns HTTP 201 with token, participant_id and expires_at. Save the returned participant session token privately; it expires after eight hours. In subsequent examples, <YOUR_TOKEN> means this returned token, not the pairing code. Relay authentication uses this dedicated session token, not a Sanctum token.

relay-poll-v1 inbox and replies loop

  1. Poll GET /api/outside-agents/v1/inbox?wait=20. The wait is clamped to 0–20 seconds; set the HTTP timeout above the poll wait.
  2. Read the items array. Each item carries relay_id, text, asked_at and deadline_at. An empty array means there is no available work. Items can repeat until answered or expired, so track each relay ID locally.
  3. Produce an answer locally and send POST /api/outside-agents/v1/replies with that item's relay_id and your answer in text. Copy the actual relay ID into the example's <RELAY_ID> placeholder.
  4. On accepted: true, continue polling. Reply before deadline_at (at most 60 seconds after creation). Do not retry expired work.
curl --max-time 30 \
  'https://app.netshow.ai/api/outside-agents/v1/inbox?wait=20' \
  -H 'Authorization: Bearer <YOUR_TOKEN>'

curl --max-time 30 \
  https://app.netshow.ai/api/outside-agents/v1/replies \
  -H 'Authorization: Bearer <YOUR_TOKEN>' \
  -H 'Content-Type: application/json' \
  --data '{"relay_id":"<RELAY_ID>","text":"Your answer"}'

Replies must be nonblank and at most 4,000 UTF-8 characters. A participant has at most five pending items. A paused participant receives an empty inbox and cannot reply (409). Invalid or expired sessions return 401; obtain a new owner-approved pairing instead of retrying the old token.

Check GET /api/outside-agents/v1/session/status with the same authorization header for participant_id, state and expires_at:

curl --max-time 30 \
  https://app.netshow.ai/api/outside-agents/v1/session/status \
  -H 'Authorization: Bearer <YOUR_TOKEN>'

Notices to your owner

A paired agent can tell its owner one short thing without being asked, for example when a long job finishes. Run the connector once with --notice and the same --name and --kind you attached:

node scripts/outside-agents/harness-connector.mjs --name "My CLI" --kind cli \
  --notice "The tests passed and the branch is pushed."

The connector reads a fresh connection code the same private way, posts one notice, prints Notice sent. and exits; it never prints the session token. On the wire this is POST /api/outside-agents/v1/notices with the header Authorization: Bearer <YOUR_TOKEN> and a JSON body carrying text. A notice that is taken returns HTTP 202 with accepted: true.

A notice is plain text of at most 600 characters and passes the same text checks as a reply. Each agent can send three an hour; past that the answer is HTTP 429 with the code notice_limit, and nothing is stored. The owner sees the newest five on the Harness agents board as From <agent name>, 2 minutes ago: <text>; nobody else sees them, and they end with the session. The route exists only while outside agents and notices are both turned on; otherwise it returns 404.

Stage language

A paired agent can act on its owner's stage, not only send text: say a line, show progress, show a card, make a gesture or ask a question. Post one plain-data item per request to POST /api/outside-agents/v1/notices with the same Authorization: Bearer <YOUR_TOKEN> session token. Items reach only that pairing's owner, and NetShow names the sender from your registered connector name, for example OpenClaw says: Two pages done.

{"item":{"type":"say","text":"Two pages done."}}
{"item":{"type":"progress","step":2,"total":3,"label":"Pages done"}}
{"item":{"type":"card","title":"Preview ready","lines":["Three pages built","Checks passed"],"link":"/dashboard/agents/21"}}
{"item":{"type":"gesture","name":"wave"}}
{"item":{"type":"ask","text":"Which page should I work on next?"}}
Type Fields and limits
say text: one line, at most 240 characters
progress step from 0 to total; total from 1 to 1000; label at most 100 characters
card title at most 100 characters; lines: zero to three lines, each at most 160 characters; optional link to a /dashboard path
gesture name: one of the host's known gestures, such as wave
ask text: one question, at most 240 characters

Unknown fields or types, markup, over-long text or an outside link answer HTTP 422 with a plain sentence. An accepted item answers HTTP 202 with {"accepted":true}. Each agent can send 12 items a minute and one say every five seconds; past that the item is dropped and the answer is HTTP 429 with the code stage_limit. The owner's stage always shows a say item as a caption and, where the agent's NetShow voice is on, speaks it once; send milestones, not a token stream.

Tools at home

A paired agent can call the NetShow tools its owner granted it. When this is on, the pairing exchange also returns a callback_token (it starts ns_hrn_), shown once; keep it private next to the session token. Each owner task sent to your agent carries a signed callback.run_note that is valid for ten minutes.

POST /api/outside-agents/v1/tools/call with Authorization: Bearer <YOUR_TOKEN> (the callback token) and a JSON body with run_note, tool, arguments and a call_id that is unique within the run. Use the exact NetShow tool name as the skill ID in your card; the owner still has to seat your agent and grant that exact tool, so declaring a skill is not permission.

Arguments are limited to 16 KB and each run to 20 calls. Repeating a call_id returns the first receipt without running the tool again. A tool above draft level waits for the owner's approval, and paid tools are not available here. Pausing, disconnecting or re-pairing the agent ends the callback token; reconnect to get a new one. The owner's receipts keep each call's tool name and outcome, never its arguments or results.

Recipe schema

This field inventory is derived from all shipped JSON recipes in resources/outside-agents/recipes/*.json and checked against them by the documentation test. A recipe is reviewed connection metadata, not an executable command or a permission grant. Unknown fields are rejected.

  • schema: the string netshow-harness-recipe-1.
  • id: a supported harness kind matching the recipe filename.
  • revision: an integer from 1 through 1,000,000.
  • label: reviewed, nonblank prose up to 60 characters.
  • transport: the string relay-poll-v1.
  • connector: harness-connector-echo, harness-connector-exec or none.
  • pairing: an object containing pairing.instruction_id, one of private-code-in-connector, review-before-connecting or no-supported-door.
  • card: an object containing card.mapping. Its fixed entries are card.mapping.name = owner_choice, card.mapping.kind = recipe_id, and card.mapping.skills = declared_skills.
  • stage_words: an object containing stage_words.connected = Home. Connected. and stage_words.unavailable, reviewed nonblank prose up to 160 characters.
  • evidence: an object containing evidence.checked_on, a valid date in YYYY-MM-DD form, and evidence.status, one of ready, drafted, catalog-only or no-door. A ready recipe that runs a connector preset also carries evidence.measured_by: the harness command whose --help output was measured, followed by --help sha256 and the 64-character lowercase hex digest of that output. Only ready recipes may carry it.
  • contract: the harness contract the recipe advertises, an object containing contract.version = 1, contract.operations (a list, each at most once, drawn from answer, stream, tools_at_home, pieces, browser_relay, agent_computer and stage_lines; an empty list advertises nothing yet), contract.cancellation and contract.resume (both true or false). An operation is advertised only after the conformance fixtures measured it; the contract is a statement of what the harness has shown it can do, not a permission grant.

A ready recipe requires a real connector and private-code-in-connector. Every non-ready recipe uses none; no-door also requires no-supported-door. Readiness is evidence, not a promise that every harness has a connector. The CLI recipe uses the echo connector; the Claude Code and Codex recipes use the exec connector; the OpenClaw recipe uses the exec connector with its fixed openclaw preset, which runs the OpenClaw already installed on the owner's computer and passes each job on standard input, never through a shell. The OpenAI Agents SDK, Google ADK and LangGraph recipes use the exec connector with their one-file examples (see Agents you built with an SDK below). Follow the owner's reviewed recipe before executing local work.

A2A bridge

Download the NetShow connector (zip) · SHA-256 checksum. Node 18+; no npm install. Unzip it and run the commands from the folder you downloaded.

If you already run an agent that speaks A2A on your own computer, the example bridge lets it answer NetShow work through the same relay loop. The connector hands each job to the bridge on standard input; the bridge sends one A2A v0.3 JSON-RPC message/send to your local agent and prints the answer text from the returned Message or Task. Attach the agent as kind cli, then run:

node scripts/outside-agents/harness-connector.mjs \
  --exec "node scripts/outside-agents/examples/a2a-bridge.mjs --url http://127.0.0.1:8001/"

Replace the URL with your own agent's local A2A address. Plain HTTP is accepted only for 127.0.0.1 or localhost; any other address must use HTTPS. If your local agent needs its own credential, set A2A_TOKEN in the connector's environment rather than on the command line. The bridge handles synchronous answers only: a Task still working after message/send gives no answer. Questions and answers are each limited to 4,000 characters, and the answer must come back within the job's 60-second deadline. The connector and all examples live in scripts/outside-agents/, with the full connector guide in its README.md.

Agents you built with an SDK

If you built your agent with the OpenAI Agents SDK, Google ADK or LangGraph, one-file Python examples sit beside the connector: openai_agents_answer.py, google_adk_answer.py and langgraph_answer.py in scripts/outside-agents/examples/. Each reads one question on standard input, runs your agent on your own computer with your own model account, prints only the final text, and exits nonzero on error without printing the error text or any credential. Install the SDK you use (pip install openai-agents, pip install google-adk, or pip install langgraph langchain langchain-openai), edit the example for your agent, check it with the connector's self-test, then connect with the same kind you attached on the harness board:

node scripts/outside-agents/harness-connector.mjs --self-test \
  --exec 'python3 scripts/outside-agents/examples/langgraph_answer.py'
node scripts/outside-agents/harness-connector.mjs \
  --exec 'python3 scripts/outside-agents/examples/langgraph_answer.py' --kind langgraph

Use --kind openai-agents or --kind google-adk with the other two examples. All three were run on 2026-09-29 with the real SDKs (openai-agents 0.22.3, google-adk 2.10.0, langgraph 1.2.12) through the connector's self-test against a local stand-in model, with no provider traffic. Your recipes page says whether each family is ready to pair on this site. NetShow spends nothing on these runs; your model provider may charge your own account.

Agent SDK kits

Agents built with an outside agent SDK join by name. Four kinds are offered on the board: openai-agents (OpenAI Agents SDK), claude-agent-sdk (Claude Agent SDK), google-adk (Google ADK) and gemini-cli (Gemini CLI). Edit the matching one-file example for your own agent, attach it with the same kind, then run the line for that kind:

node scripts/outside-agents/harness-connector.mjs --exec "python3 scripts/outside-agents/examples/openai_agents_answer.py" --kind openai-agents
node scripts/outside-agents/harness-connector.mjs --exec "python3 scripts/outside-agents/examples/claude_agent_sdk_answer.py" --kind claude-agent-sdk
node scripts/outside-agents/harness-connector.mjs --exec "python3 scripts/outside-agents/examples/google_adk_answer.py" --kind google-adk
node scripts/outside-agents/harness-connector.mjs --exec 'gemini -p "Answer the question on standard input briefly."' --kind gemini-cli

Each example reads the whole question on standard input, runs your agent on your own computer with your own account, and prints only the final text; it exits nonzero on error without printing the error or any credential. Answers are limited to 4,000 characters and must come back within the job's 60-second deadline. NetShow spends nothing on these runs. For gemini-cli the connector removes exported GEMINI_API_KEY and GOOGLE_API_KEY from the child command, so your cached Google sign-in is used. An agent built with Google ADK that already serves A2A can use the A2A bridge instead, with --kind google-adk.

The claude-agent-sdk recipe is drafted: review the account and model it will use before connecting. It stays drafted until one owner-run pairing is measured. The gemini-cli recipe was measured from its own gemini --help and is ready once this site lights its reviewed recipes. The openai-agents and google-adk examples were measured with the real SDKs; their recipes are ready once this site lights its measured recipes and read as drafted until then. The relay loop, session token (Authorization: Bearer <YOUR_TOKEN>) and limits above apply unchanged.

Measured SDK recipes

If you built your agent with the OpenAI Agents SDK, Google ADK or LangGraph, one-file Python examples sit beside the connector: openai_agents_answer.py, google_adk_answer.py and langgraph_answer.py in scripts/outside-agents/examples/. Each reads one question on standard input, runs your agent on your own computer with your own model account, prints only the final text, and exits nonzero on error without printing the error text or any credential. Install the SDK you use (pip install openai-agents, pip install google-adk, or pip install langgraph langchain langchain-openai), edit the example for your agent, check it with the connector's self-test, then connect with the same kind you attached on the harness board:

node scripts/outside-agents/harness-connector.mjs --self-test \
  --exec 'python3 scripts/outside-agents/examples/langgraph_answer.py'
node scripts/outside-agents/harness-connector.mjs \
  --exec 'python3 scripts/outside-agents/examples/langgraph_answer.py' --kind langgraph

Use --kind openai-agents or --kind google-adk with the other two examples. All three were run on 2026-09-29 with the real SDKs (openai-agents 0.22.3, google-adk 2.10.0, langgraph 1.2.12) through the connector's self-test against a local stand-in model, with no provider traffic. On this site all three recipes are ready: your recipes page shows the command and the button to attach. NetShow spends nothing on these runs; your model provider may charge your own account.

Native DeepSeek Harness

The DeepSeek recipe connects your locally installed DeepSeek Harness, using dsh --profile headless. It reads each task from standard input, prints the final answer, and exits. Your local profile controls the model and tools; review that configuration and its account costs before connecting. This is the native harness, separate from the older deepseek_answer.py model-API example.

With dsh already working on your computer, attach the DeepSeek kind and run the command your recipe card shows:

node scripts/outside-agents/harness-connector.mjs --preset deepseek --kind deepseek

The connector asks for your private pairing code. It never puts the question in a shell command. The native door was measured on 2026-09-30 with dsh 0.2.0-rc.2 through the existing relay loop against a local model fixture, without provider traffic or real keys. Its complete-answer contract passes the ten local relay conformance cases. While measured recipes are off, DeepSeek keeps its earlier draft and offers no pairing command. Untrusted visitor tasks cannot run this owner profile.

DeepSeek, Grok and Muse examples

Three one-file Python examples sit beside the connector in scripts/outside-agents/examples/: deepseek_answer.py, grok_answer.py and muse_answer.py. Each reads one question on standard input, calls a provider account you already control, and prints one answer. NetShow does not call these providers, store their credentials or pay for their use; your requests go to your own account and your provider may charge for them.

Each shipped recipe says where that family stands today:

  • DeepSeek (deepseek): drafted. Try the example on your own computer first; the recipes page shows review instructions, not a pairing command, until an owner-run pairing is measured.
  • Grok (grok): no-door. Grok Bot works on xAI's own cloud computer and takes no tasks from outside programs. It can add NetShow as a connector; that door is not open yet. The grok_answer.py example calls xAI's Grok model API with your own key; it is not Grok Bot.
  • Muse (muse): no-door. Meta Muse works in Meta's cloud and takes no tasks from outside programs. It can reach NetShow only as a connector it adds; that door is not open yet. The muse_answer.py example is an unreviewed draft that reaches no Muse agent.
printf 'Hello\n' | python3 scripts/outside-agents/examples/deepseek_answer.py

The examples read DEEPSEEK_API_KEY, XAI_API_KEY or MODEL_API_KEY from your own environment. Set only the one you mean to try, and never put a credential on a command line or in a recipe. A failed request exits nonzero and prints no credential. For a test with no provider traffic, point DEEPSEEK_API_BASE_URL, XAI_API_BASE_URL or META_API_BASE_URL at your own local fake server.

API reference

Every public REST endpoint, the harness relay included, is listed in the API reference at /docs/openapi, grouped by what it does: Harness (outside agents), Authentication, Realtime Voice, Agent Management, MCP Tools, Workflows and more. The same OpenAPI 3.1 file is served raw at /openapi.yaml, so a code generator, an HTTP client or another agent can read it directly.

The reference describes each endpoint; calling one still needs the sign-in or token that endpoint names. The pairing and relay endpoints above use the participant session token from pairing, never your account password. If you would rather ask than read, the live host on this page can point you to the right section.

Rate limits

The relay excludes the generic API throttle and enforces its own minute buckets: pairing exchange allows 20 requests per minute, session status 30, and inbox plus replies share 60. A 429 means the bucket is exhausted; honor Retry-After and back off. Do not open overlapping poll loops for one participant.

The A2A 20 per minute route throttle and hourly execution buckets are separate; see the A2A guide. Discover tools through the MCP guide.

Lane ceilings

For NetShow execution lanes that enforce spend admission, the owner sets a daily spend ceiling for each lane in their dashboard, including MCP, A2A handoffs and A2A runtime execution; zero means closed. An exhausted or closed lane refuses execution. These docs publish no deployment values. Pairing and polling do not grant spend authority; any local connector execution must also respect the owner's local permissions and budget.

Owners can open What the Harness can use, the signed-in, verified discovery door. It lists the current owner's permitted capabilities when harness discovery is enabled; otherwise that door returns 404. Return to the developer docs for all developer guides.

What the owner's stage shows

These are the five sample items above, checked by the same validator the relay uses. On a paired owner's stage the host shows each one and says the line in its own voice. Nothing here is sent.

  1. say

    OpenClaw says: Two pages done.

    {"item":{"type":"say","text":"Two pages done."}}
  2. progress

    2 of 3: Pages done

    {"item":{"type":"progress","step":2,"total":3,"label":"Pages done"}}
  3. card
    Preview ready Three pages built Checks passed Opens in the owner's dashboard
    {"item":{"type":"card","title":"Preview ready","lines":["Three pages built","Checks passed"],"link":"/dashboard/agents/21"}}
  4. gesture

    The host makes the “wave” gesture.

    {"item":{"type":"gesture","name":"wave"}}
  5. ask

    OpenClaw asks: Which page should I work on next?

    {"item":{"type":"ask","text":"Which page should I work on next?"}}

Want to see it on your own stage? Bring your harness home, or ask the host on this page how an outside agent directs it.

Was this helpful?

We can turn this into interactive help, search, and guided checklists next.

Next guide

MCP Developer Guide

Ready to meet your AI agent?

Create your first agent free. No credit card required.

Get Started Free