---
title: "Execution"
description: "Who may call a command and what the runtime does with the call, from the exec envelope through the fail-closed pipeline and wrap argv to the result."
canonical: https://harnesscontextprotocol.io/docs/runtimes/execution
---

# Execution

> Who may call a command and what the runtime does with the call, from the exec envelope through the fail-closed pipeline and wrap argv to the result.

Every Invoker reaches a command through one exec envelope and one fail-closed
pipeline, which ends in a result an agent can branch on. Build your runtime's
exec path against this page; [Specification: Execute](/docs/specification/execute)
holds the normative text.

## Invoker

An Invoker is whoever runs a command, and HCP treats all four the same:

| Invoker | Typical face | Example |
|---------|--------------|---------|
| Human | CLI | `ledger transfer 2.80 --to demo-transit --confirm --json` at a terminal |
| Agent | CLI or JSON | A tool loop calling `hcpx exec` or `Hcp.exec()` |
| Product API | JSON | A backend posting `{ pack_id, verb_key, args }` |
| UI | JSON | A button that fires the same envelope |

Invokers never redefine Policy or Binding
([L11](/docs/introduction/design-principles)). An Invoker carries a
[Principal](/docs/concepts/binding#principal) and adds no auth, confirm logic or
policy of its own, because with two policies on one command the weaker one wins.
Nor does its type pick the Binding mode: an agent acting for a human uses `user`
Binding for that human's mail, an operator job uses `mothership` Binding for
fleet wraps, and "it's an agent, give it the service token" is how elevation
bugs start.

```text
Agent harness ─┐
Product API   ─┼─► same command ─► Policy + grants ─► Binding mint ─► Adapter
UI gesture    ─┤
Human CLI     ─┘
```

Routing agent calls through bash and UI calls through HTTP is a runtime detail,
as long as both reach the same command, Policy row and Binding under a
Principal. An agent Invoker runs unattended, so it stays non-interactive (no
prompts, `-y` on installers), asks for `--json`, reads availability before
planning, surfaces writes as approvals and passes `confirm: true` only after
agreement, and loads a Skill's lead first and the full text on demand. A UI Invoker fires the JSON envelope, shows
the confirm gate as an approval dialog and never pre-sets `confirm: true`, takes
badges and "credential stale" banners from the runtime's Sense sink without
owning mint, and never embeds credentials in the client.

## The envelope

```json
{
  "pack_id": "ledger",
  "verb_key": "transfer",
  "args": { "amount": 10, "confirm": true }
}
```

| Field | Meaning |
|-------|---------|
| `pack_id` | The pack. `surface_id` is accepted as a legacy alias |
| `verb_key` | The command name |
| `args` | Command arguments. `confirm: true` or `apply: true` satisfies a confirm gate. `mode: "mock"` requests explicit mock Binding |

The CLI face (`ledger transfer 2.80 --to demo-transit --confirm --json`) is the
same command, and
`hcpx exec ledger transfer --json -- amount=2.80 payee=demo-transit confirm=true`
is how the CLI sends the envelope.

## The pipeline

The steps run in order, and nothing runs after a denial.

| # | Step | Denies with |
|---|------|-------------|
| 1 | Resolve the pack, its activation on this session and the command's Policy row, which a linted pack always has | `unknown pack <id>`, `pack <id> not activated`, `no policy for <verb_key>` |
| 2 | Authenticate the Principal and take the tenant from the session. Land `brain` needs a project in scope, and land `local` binds the sandbox cwd | `privacy.land brain requires project_id` |
| 3 | Assert the capability: `verb.exec.read` for `operation: read`, `verb.exec.write` for `operation: write`, `pack.connect` for a `connect` command, with grants scoped by `pack_id` and `verb_keys` | `denied: missing verb.exec.write for ledger.transfer` |
| 4 | Enforce confirm: a Policy row with `confirm: true` needs `confirm: true` or `apply: true` in the envelope | `confirm required` |
| 5 | Mint Binding from the hub for this Principal, pack and family (`user`, `local`, `mothership`). `public` skips the step and `mock` must be explicit in the envelope | The mint failure, never a fallback to an environment token |
| 6 | Run the Adapter in the sandbox: a wrap renders argv, checks the allow-list and spawns, and a composite calls in-process | `sandbox: bin "x" not allowlisted` (exit 127) |
| 7 | Return a structured result and audit writes with the [attribution tuple](/docs/concepts/access#audit) | None |

## Wrap rendering

For `kind: wrap`, the runtime builds argv from the command's `cli[]`:

```text
cli[]          ["ledger", "transfer", "{{amount}}", "--to", "{{payee}}", "--memo", "{{memo}}", "--confirm"]
args           { amount: "2.80", payee: "demo-transit", confirm: true }
argv           ["transfer", "2.80", "--to", "demo-transit", "--confirm", "--json"]
spawn          <resolved wrap.bin> transfer 2.80 --to demo-transit --confirm --json   (cwd = pack dir)
```

- The first `cli[]` token is the bin name and is dropped, since `wrap.bin` is
  what spawns; a relative `wrap.bin` resolves against the pack directory.
- `{{name}}` renders as `String(args.name)` and `--flag={{name}}` as
  `--flag=<value>`. A missing value renders empty and drops out with the flag in
  front of it (`--memo` above).
- Without `cli[]`, `wrap.argv_template` renders with `{{verb}}` set to the
  command tail (`setup.apply` becomes `apply`).
- The JSON flag (`--json` unless `json_flag` says otherwise) is appended, and
  the arguments go to the process as an array, with no shell.
- The working directory follows `wrap.workspace.mode`: the pack directory by
  default, the caller's project for `project`, or a private directory for
  `workspace` and `ephemeral` ([Where the CLI runs](/docs/packs/manifest#where-the-cli-runs)).
- `wrap.agent_env` is merged into the child environment, and `HCP_SANDBOX` is
  set to `sandbox` or `native-local`.

## Result envelope

```json
{
  "ok": true,
  "pack_id": "transit",
  "verb_key": "plan",
  "result": { "ok": true, "verb": "plan", "from": "A", "to": "B", "optionId": "opt-1" },
  "stdout": "…",
  "stderr": ""
}
```

| Field | Meaning |
|-------|---------|
| `ok` | `true` on exit 0 and no denial |
| `result` | Parsed JSON stdout if it parses, else the raw string |
| `error` | Present when `ok` is false: a denial reason, `confirm required`, stderr, or `exit N` |
| `stdout` / `stderr` | Raw streams (wrap only) |

A denial comes back as a value, never a thrown error, so agents can branch on
it. [Errors](/docs/reference/errors) has the full list.

## Composite and other adapters

`composite` returns an in-process result. `openapi` and `sense_only` are not
executable in the reference-local runtime, and a proprietary runtime implements
them behind the same `exec` signature. The bootstrap pack `hcp-cli` is composite:
its read commands (`hcp.doctor`, `hcp.list`, `hcp.availability`, `hcp.skill`)
return directly, and commands that change the store answer with
`deferred_to_cli: true` and a hint, because the CLI process owns the durable
store.

## Related

- [Command](/docs/concepts/command) · [Adapter](/docs/concepts/adapter)
- [Sandbox](/docs/runtimes/sandbox)

## Sitemap

- [Documentation](https://harnesscontextprotocol.io/docs)
- [Packs](https://harnesscontextprotocol.io/packs)
- [Glossary](https://harnesscontextprotocol.io/docs/reference/glossary)
- [llms.txt](https://harnesscontextprotocol.io/llms.txt)
- [llms-full.txt](https://harnesscontextprotocol.io/llms-full.txt)
- [OpenAPI](https://harnesscontextprotocol.io/openapi.json)
- [MCP](https://harnesscontextprotocol.io/mcp)
