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 PATHConnect 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 --stdioserve --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 terminalAmong 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 --watchThe 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 ← zshGiving 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:4bThe 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 --stdioServe capabilities to an MCP client.
ivyx serve --httpThis 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 approveThe 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 statusWhether serve is running, what it serves, where policy came from.
ivyx initWrite .punica/policy.yaml and .punica/serve.yaml, with a commented recipe you uncomment.
Policy
ivyx policy lintSchema, unknown ids, unreachable rules.
ivyx policy explainRun the gate against a call. Nothing executes.
ivyx capability listEverything 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 listRuns recorded here, newest first, groupable by the sitting they ran in.
ivyx runs showEvery gated call in one run, with who approved what and through which door.
ivyx runs tailGate decisions as they happen.
Proof and host
ivyx evidence verifySignature and hashes of an evidence package. No key needed.
ivyx extension checkWhich installed extensions can actually start without a screen.
Flows and generation
ivyx flow runRun 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 scheduleWrite .punica/schedules.yaml and print the launchd or crontab entry. The operating system is the timer; nothing runs from the file alone.
ivyx flow exportA folder holding the agent, its nodes, the policy, a start.sh and a Dockerfile, with no key and no secret inside.
ivyx node generateA node from a sentence. It asks once, says what a yes lets happen, and records who answered.
ivyx agent generateAn agent from a sentence, the same way.
Model
ivyx modelWhat this project's runs may reach, and what this machine holds.
ivyx model useBind a local core. Every capability that calls a model reaches it.
ivyx model pullFetch one first, with its progress.
ivyx model noneBind 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.