Included with IVYX Studio

IVYX in your terminal

The same gateway your workspace runs on, with no window open.

Every capability call in IVYX passes a policy gate, and until now that gate was only reachable with Studio in front of you. ivyx is the same gate on a command line: an MCP client, a CI job and a script all meet the rule that lives in your repository, and a call the gate stops for a person can be answered here rather than only refused.

$ ivyx init  # write .punica/policy.yaml and .punica/serve.yaml$ ivyx capability list  # what is installed, and what this project serves$ ivyx serve --stdio  # speak MCP to any client$ ivyx approve --watch  # answer the calls policy stops for a person$ ivyx runs tail  # gate decisions as they happen

The commands in the order you would run them. ivyx --help is the reference that ships with the binary.

Set it up once

Two commands stand between a fresh install and an agent calling your workspace from another window.

Getting it

It ships inside IVYX Studio on macOS and Linux, and one command in the palette puts it on your PATH. That command copies the CLI into your home directory rather than linking into the app, so updating Studio does not break it, and the launcher runs the app's own runtime, so the machine does not need a Node of its own.

$Install 'ivyx' command in PATH

Connect it to an MCP client

serve prints what it exposes and where the policy came from, and then goes quiet so the stream belongs to the protocol.

$claude mcp add ivyx -- ivyx serve --stdio

serve --stdio speaks MCP over standard input and output, and serve --http serves the project's agents over REST with a page to run them. --print-config prints the connection line for Claude Code, Claude Desktop or Cursor, or the raw JSON if your client wants a config file.

A project says what it exposes in .punica/serve.yaml, so a capability nobody listed is refused rather than served.

When the gate stops for a person

A capability that declares high risk, any write an agent makes, or anything a rule marks require, does not run until somebody says yes. This host could only refuse such a call. Now the question is filed with the call, and there are three doors it can be asked through: the console, the client, or nobody.

The console

ivyx approve --watch draws a card and reads one keystroke. While that console is open the server holds the call instead of refusing it, so nobody has to carry a message back to the agent. It holds for sixty seconds, and only while the console is answering its heartbeat and its process is still alive.

The client

The question can also go to whoever is driving the MCP client, mid-call, over the protocol's own elicitation, with no second terminal at all. A client that offers no such channel falls back to the console. High risk and chained approvals are never asked this way, because the answer frame names no decider and the trail shows the difference. An accept arriving faster than a person could have read the request is treated as no answer.

Nobody

--approve deny asks nobody and refuses, naming the id. It is the one mode that files no question for somebody to come back to, which is what a pipeline should pin.

By default every door that is provably there is tried: the client, then a watching console, then the refusal this server always sent. --approve narrows that. terminal is the console alone, client is the client alone, deny is neither.

$ivyx serve --stdio --approve terminal

Among the commands, only the card grants. ivyx approve r-7f3a91 lists, inspects and declines with --deny, but it cannot say yes: a grant should mean a card was drawn and a key was pressed. An approval covers one capability and the arguments it was asked with, and the approved retry shares the refused call's trace, so runs show reads the whole thing in order, refused then granted then allowed.

$ivyx approve --watch

The trail records which door answered, the account that answered, and the command names above the answering process. An answer through the client grants but names no decider, and the record says so in those words. An agent approving from its own tool shell is in that chain and a person in their own terminal is not. It is written down as a fact and judged by nothing.

granted   fs.writeFile     by dana    2026-09-10T08:01:53Z
          answered from: node ← zsh ← claude ← zsh

Giving a project a model

Some of the capabilities installed with IVYX call a language model to do their work. They all reach it through one seam, so no capability learns which door answered, and nothing is bound by default, because a daemon holding five models is not a decision about which one a project uses.

A local core

ivyx model lists what this machine holds, ivyx model pull fetches one, and ivyx model use binds it in .punica/model.yaml, beside your policy, because what a repository's runs may reach is a property of the repository. It is plain HTTP to a daemon on the same machine, so nothing leaves it. ivyx model none binds nothing, on purpose.

$ivyx model use qwen3:4b

The calling model

With nothing bound and a model on the other end of serve --stdio, the call comes back carrying the instruction, the response format and the schema the answer has to match. The model writes the completion, submits it, and calls the tool again. The host was never missing a model so much as one completion, and its caller is one.

With neither door open, a capability that needs a model is refused in one sentence naming both exits. A refusal that names an exit nobody can take is worse than a plain no.

Made for CI

policy explain runs the gate and executes nothing, so a rule can be asserted in a pipeline before anything is allowed to happen. It answers with what the gate will do rather than what the catalogue advertises, which is not always the same thing.

$ivyx policy explain mlops.model.promote --args '{"alias":"production"}'

--json is on every command, so a refusal is a failing step rather than a line in a log. An unexpected error is always exit 1 and never 2, so a crashed step cannot read as a refusal, and serve --approve deny pins a pipeline to asking nobody.

Exit codes

0
The call is allowed.
2
Refused, or the input was invalid.
1
Something went wrong.

Twenty-two commands

One sentence each, and ivyx --help is the reference that stays current.

Serve

  • ivyx serve --stdio

    Serve capabilities to an MCP client.

  • ivyx serve --http

    This project's agents as a web service: a REST route per agent and a page to run them, with sources that start a run on their own: a poll, a webhook, a watched folder, the service's own events, a mailbox over IMAP IDLE, a schedule, or a Redis or NATS subscription.

  • ivyx approve

    The calls waiting for a person. --watch answers them as they arrive, --deny declines one, and --listen is the child Studio runs beside each agent window so the card lands there.

  • ivyx status

    Whether serve is running, what it serves, where policy came from.

  • ivyx init

    Write .punica/policy.yaml and .punica/serve.yaml, with a commented recipe you uncomment.

Policy

  • ivyx policy lint

    Schema, unknown ids, unreachable rules.

  • ivyx policy explain

    Run the gate against a call. Nothing executes.

  • ivyx capability list

    Everything installed, or only what is served. --search matches an id, a title or a description, and an exec mark names the ids this host cannot finish.

Record

  • ivyx runs list

    Runs recorded here, newest first, groupable by the sitting they ran in.

  • ivyx runs show

    Every gated call in one run, with who approved what and through which door.

  • ivyx runs tail

    Gate decisions as they happen.

Proof and host

  • ivyx evidence verify

    Signature and hashes of an evidence package. No key needed.

  • ivyx extension check

    Which installed extensions can actually start without a screen.

Flows and generation

  • ivyx flow run

    Run an agent or a flow by id. A node is Python, so the run is handed to the open Studio window; the CLI governs it and records it.

  • ivyx flow schedule

    Write .punica/schedules.yaml and print the launchd or crontab entry. The operating system is the timer; nothing runs from the file alone.

  • ivyx flow export

    A folder holding the agent, its nodes, the policy, a start.sh and a Dockerfile, with no key and no secret inside.

  • ivyx node generate

    A node from a sentence. It asks once, says what a yes lets happen, and records who answered.

  • ivyx agent generate

    An agent from a sentence, the same way.

Model

  • ivyx model

    What this project's runs may reach, and what this machine holds.

  • ivyx model use

    Bind a local core. Every capability that calls a model reaches it.

  • ivyx model pull

    Fetch one first, with its progress.

  • ivyx model none

    Bind nothing, and say so.

Six things it does not do

It does not carry its own policy

The rule comes from the project, found by walking up from your directory the way git finds a repository. A second policy file would be a second definition of the policy. init writes only the files that are missing, so running it in a project that already has a policy leaves that policy alone.

It does not execute

It installs the filesystem and the workspace and none of the providers a window has, so it reads, judges and records and never spawns a process. It reports the MCP servers a project declares in .punica/mcp.yaml and starts none of them, and capability list marks every id whose declaration includes process.exec. A call that needs one is refused in a sentence that says where to run it. The mark reads a declaration, so it is only as honest as the declaration is.

It does not decide for a person

A call that needs a person is asked rather than answered here: the card is drawn, one keystroke settles it, and with nobody there it is refused and the record says it needed a person rather than that it failed. What counts as needing one is the gate’s answer, not the catalogue’s: an approval a capability declares is enforced, and one the catalogue synthesized for display is not.

It does not prove a person pressed the key

The record names the account that answered, the door the answer came through, and the command names above the answering process, so an agent approving from its own shell is visible in the chain. That is written down as a fact and judged by nothing. Prevention is not the claim: the terminal door is a control wherever the agent cannot reach that shell, a CI runner or a server or another user’s session, and a speed bump wherever it can. Where the difference matters, decide in Studio.

It does not hold a signing key

There is no account and no cloud, and the CLI signs nothing. Studio’s key is sealed by the OS keychain inside its own process and is not readable from here, which status reports in those words rather than as “no key”. A package built through ivyx therefore comes out unsigned, says so on its own front page, and evidence verify refuses it. Verifying a signed one still works here, because a signature carries the public half a verifier needs.

It does not run where it cannot enforce

It probes the substrate for the guards it depends on rather than comparing version numbers, and on an older one it stops and names what is missing. It also reads the capability budget back after applying it and refuses to serve if it did not take effect, because a server that believes it serves four capabilities while serving five hundred is worse than one that does not start. runs tail marks a record as seen only after it has read it, so a file caught mid-write is retried rather than skipped.