Delegation & permission relay
You can send a prompt from your CLI to a different CLI on the same machine. Glider runs that CLI without a console. If the CLI stops for a permission decision, Glider sends the question to you as a usual reply. Glider does not refuse the permission automatically.
The CLIs you can send a task to
Three CLIs are supported today. Glider finds the CLIs that are on your machine when you
push Rescan on the Vendors page of the dashboard. The equivalent request
is POST /api/vendors/discover. A CLI that Glider does not find is not a flag
that you can use.
| Flag | CLI | Program | Default template | Templates |
|---|---|---|---|---|
/claude |
Claude Code | claude |
headless — the answer comes back to you | default, resume, interactive |
/cursor-agent |
Cursor Agent | cursor-agent |
headless — the answer comes back to you | default, resume, interactive |
/agy |
Antigravity | agy |
interactive — a window opens, and no text comes back | default, headless, resume, interactive |
Each CLI operates in two directions. It can send a task with a flag, and it can do a task that a different CLI sends to it. Glider does not stop you from sending a task to the CLI that you use. But this gives you no advantage, and it adds the start time of a second process.
The number three is not a limit in the design. Glider keeps the list of
CLIs as data in configs/vendor_candidates.yaml. The shared Go code does not
contain a condition on a vendor name. Behaviour that is different for one CLI goes into an
adapter for that CLI. To add a fourth CLI, you write one YAML entry and one adapter, and
you change no shared code.
How to add a fourth vendor gives the full list of files.
The flag
fix the failing test in internal/foo /claude refactor this function /cursor-agent summarize recent commits /agy
vendors.ParseDelegateCommand looks for a /vendor-name flag at
the end of the message. The flag must not be at the start, and it must
not be in the middle. DelegateHandler takes the message before the usual
routing. On the gateway, the Messages route takes it. Therefore the flag does not change
a usual request. Add a :template suffix to select a different command
template. For example, fix the auth bug /agy:headless. Each vendor has an
interactive template in configs/vendor_candidates.yaml. That
template does not run the CLI without a console and does not send the text back. It opens
the native session of the vendor in a new console window, with your task as the first
message. You then control that session. No text comes back to this chat.
Why the flag must be at the end. A live test showed the cause. The client
of Claude Code reads an unknown /word at the start as a local command. It then
shows "Unknown command: /agy" and does not send the message. Therefore a flag at
the start never comes to Glider from that CLI. The parser cannot correct this. A flag at
the end prevents the problem for each CLI, not only for Claude Code. It also keeps the
full text before the flag as the prompt. An earlier parser looked for the flag at each
position, and it deleted the text before the first flag.
Why Glider cannot stop the run and ask you
The supported vendors do not stop for a permission decision when they operate
without a console (-p mode):
| Vendor | What the vendor does with a permission |
|---|---|
| cursor-agent | It does not stop. It records the refusal in the stream-json output, and the model continues. |
| claude (Claude Code) | It does not stop. The refused tool gets an error immediately, and the last event contains a list of the refusals. |
| agy (Antigravity) | It always fails. Its stderr says that this mode cannot ask a question. |
Therefore Glider uses a resume loop. Glider runs the CLI without a console, finds the refusal in the recorded output, and asks you. After your answer, Glider continues the session of that CLI with the new permission. The run is complete before Glider asks the question, but the loop is quick.
Sequence
User (in claude, say) DelegateHandler vendors.RunWithOptions agy subprocess | | | | | "fix the bug /agy" | | | |---------------------------->| ParseDelegateCommand | | | |------------------------->| exec agy -p --add-dir | | | |----------------------->| | | | | denies a tool | | |<-----------------------| | |<-------------------------| DetectDenials | | | RegisterPendingResume(token) | |<----------------------------| reply: "needs permission… abc123 /agy:allow" | | | | | | "abc123 /agy:allow" | | | |---------------------------->| TakePendingResume(token) | | | | GrantResumePermission (vendor-specific) | | |------------------------->| exec agy -p --add-dir | | | |----------------------->| | | | edits a file | | | |<-----------------------| | |<-------------------------| ExtractEditViews | |<----------------------------| reply: result + diff block |
How Glider gives the permission
Your approval must change a condition before Glider runs the CLI again. If it does not, the new run stops at the same permission. Each CLI has a different mechanism, and there are two types:
| Vendor | What your approval does |
|---|---|
| claude | Glider adds --allowedTools <refused tools> to the resume command.
Glider knows the tool name only after the refusal. Therefore the name cannot be in a
fixed template, and Glider makes the argument from the refusal. |
| cursor-agent | Glider adds nothing. --resume [chatId] --trust is sufficient. This CLI
has no flag for one tool. The only other flag is --force or
--yolo, which permits each tool. Glider does not use these flags for
you. |
| agy | Glider writes a limited rule into the settings.json of agy. If the
workspace has its own config, Glider writes to that file also. Glider then puts the
original bytes back into the two files. The changelog of agy shows that this is the
correct mechanism for a permission in this mode. It is not an
unofficial method. |
Because of this difference, the interface has two methods and not one.
ExtraResumeArgs gives the command-line arguments.
GrantResumePermission makes a change outside the command line. Each
vendor uses the method that its CLI supports. The shared code calls the two methods and
does not know which one operates.
Two other methods for agy were tested first, and they failed. The
--dangerously-skip-permissions flag was more simple, and it was the same as
the other vendors. But in a live test the model gave an explanation of the flag and
did not obey it. The --continue and --conversation <id>
flags also failed, because agy continued a different conversation.
The VendorAdapter interface contains each difference between the vendors. The
methods are DetectDenials, ExtractSessionID,
GrantResumePermission, ExtraResumeArgs,
WrapResumePrompt and ExtractEditViews. The shared code never
makes a decision from a vendor name.
How Glider finds the workspace
The protocols that Glider intercepts do not contain a file path. Therefore a delegate call cannot find the project directory of the origin CLI from the request. Glider tried to read the path from the memory of the origin process (the PEB). This method failed: on a usual installation, Windows Defender protects the memory of a process, and it makes the read incomplete.
Glider uses the PID of the origin process instead. It gets the PID from the TCP
connection, and it does not read the memory. The PID is the key to a small registry of
directories. For a new PID, Glider asks you one time. Reply with
<path> /workspace. This uses the same flag position as above. Glider
keeps the directory while that process operates. Glider asks each new session, also when you
set a default workspace on the Vendors page of the dashboard. That default
goes in the question, and you send it back. Glider does not use it as a silent answer,
because it cannot see which project a new CLI is in.
The context for the delegate
A delegate CLI received only two items: the prompt text and a work directory. It did not
know the full task, and it did not know which CLI sent the task. Now each delegate run
gets its own directory at ~/.glider/delegates/<id>. That directory holds
one context file. Glider gives the file to the CLI with a flag that the vendor supports.
The CLI reads the file when the session starts, and the file does not use the prompt
space.
| Vendor | How the context comes to the CLI |
|---|---|
| claude | --append-system-prompt-file=<file>. Glider gives the full path, and the name of the file is not important. |
| cursor-agent | --add-dir=<dir>. The CLI reads an AGENTS.md file in that directory. |
| agy | --add-dir=<dir>. This is the same method, and it also uses AGENTS.md. |
These flags are usual template arguments in configs/vendor_candidates.yaml.
They use {{context_file}} and {{context_dir}}. The flag for each
vendor is data. The shared code does not make a decision from the vendor name. If a vendor
has no context setting, Glider runs it with no context flag. Glider removes the flag.
The context file contains the task, the name of the CLI that sent the task, and the workspace. It also contains the recent instructions from the session that sent the task. The oldest instruction is first, and each one has a mark that shows it is background data.
Glider does not write in your files. Glider never changes the
CLAUDE.md file or the AGENTS.md file of your project. An earlier
design wrote to these files. That design had to lock them, put the original bytes back,
and correct them after a run stopped incorrectly. One directory for each delegate removes
these problems. The directory belongs to one run, and Glider deletes it when the run ends.
How Glider shows the result
Glider adds the edits of a delegate to the reply as a diff. This is the same for each
vendor. VendorAdapter.ExtractEditViews reads the edit format of the vendor and
makes a standard EditViews structure. Claude gives patch hunks, cursor-agent
gives a before-and-after string, and agy replaces a range or writes a full file. Then
FormatEditSummary shows the standard structure in one format.
Clean or raw
A headless run writes a full transcript to its output. Glider does not give you that
transcript by default. The setting is on the Vendors page of the dashboard,
and POST /api/vendors/response-detail also writes it.
| Value | What you receive |
|---|---|
clean | The final answer only. DelegateRenderer removes the frames of the protocol. This is the default, and an unknown value becomes this value. |
raw | The recorded output, with no change. Use it when a delegate gives a strange answer and you must see what the CLI truly wrote. |
The value stays on disk, in the vendor registry. Therefore it continues after a restart. Refer to NGL for how a renderer finds the answer in the output of a vendor.
What Glider records about a vendor
GET /api/vendors gives the registry. Discovery writes it, and the dashboard
changes it.
| Field | Content |
|---|---|
name · binary · path | The name that a flag uses, the name of the program, and where discovery found it. |
version | What probe_args gave back. |
printFlag | The flag for a run with no console. |
templates | The command shapes. A :name after the flag selects one. |
contextFile | The file that this CLI reads when a session starts: CLAUDE.md for claude, AGENTS.md for the others. Glider writes the pack of the delegate there, and puts the original content back after the run. An empty value stops the sharing of context for that vendor. |
enabled · discoveredAt | Whether Glider uses it, and when it found it. |
The registry also holds defaultWorkspace and responseDetail.
Discovery never changes those two, therefore a rescan does not remove your settings.
An interactive template opens a window
A template with mode: interactive does not go through any of the above.
LaunchInteractive opens a new, visible console with the native session of that
CLI, and your task is its first message. Glider records nothing from that window and relays
nothing back. You control it.
POST /api/vendors/{name}/launch-interactive does the same thing from the
dashboard.
The known limits
- agy can ask a question after you give the permission. The agent is careful with a destructive task, and this is not a defect. But the loop is not always one cycle.
- There is no true interactive relay (Path B). To stop a run in the middle and ask you, Glider needs Windows ConPTY code and a recorded prompt format for each vendor. This code does not exist. Do not confuse this with the
:interactivetemplate above. That template gives the task to a new native session, and it does not relay a prompt. - There is no cost control. Only the flag itself controls a delegate call. That call can use a different subscription or a different API key.
- Delegation is slow, and Glider is not the primary cause. A delegate CLI does its full start procedure for each call. It starts the process, does the authorization, gets the list of models, and then does the inference. There is no session to use again. A test with Glider out of the path showed that a small
cursor-agenttask needs approximately 20 seconds. Calculate this time for work with many steps. Glider does not apply its own time limit. The call ends when the CLI of the vendor completes the task, when that CLI reaches its own time limit, or when you stop the request.
Send the task without a flag
Each example on this page has a flag that you type. Your CLI can also write that flag itself. Put rules in the context file of your project, and the CLI applies them. Automatic rules gives a template and the conditions that operate correctly. This needs no new function in Glider, because the flag is only text at the end of a message.
The full design is in planning/permission_relay_design.md. The adapter interface is in planning/ngl_and_adapters.md.