How Amp plugins work

Amp plugin lifecycle

How Amp plugins work

Follow one ordinary plugin from an unavailable capability to a running relationship carried by messages.

If you use coding agents, you probably meet plugins through the capability they add. A command appears. A tool becomes available. A new mode changes how the agent approaches a task. The useful part is visible; the machinery that made it possible usually is not.

Decorative observer / rights-safe stand-in
Semantic architectureUNRESOLVED
MAINAmp processCoordinator
RPC
CHILDPlugin processNot active
CAPABILITYUnavailable

Semantic event

Unresolved signal

The capability is not available. The observer rests outside the architecture and does not originate the unresolved state.

Observer responseResting offset state

The observer is an original decorative stand-in, not Puck, Amp, a plugin, or an action source. Puck artwork rights and attribution remain unresolved.

The observer above is an original decorative stand-in, not official Puck art. It echoes semantic events only after the architecture changes, and it retires once the first process model stabilizes.

If you have never thought about that machinery, it helps to follow one plugin from the moment its capability is absent to the moment Amp can use it. An ordinary plugin, in this article, is code that contributes capabilities such as commands, tools, skills, events, modes, or interface actions. The plugin is not itself any one of those things. It has to start, establish communication with Amp, and report what it provides before the capability can appear.

The lifecycle is small enough to hold in your head. It also corrects a tempting first picture: Amp does not simply absorb a plugin's code and continue as though the code had always been part of the application. The two sides establish a working relationship, one explicit step at a time.

01 | What are we following?

A capability that is not there yet

Imagine opening Amp before the plugin has loaded. The capability we care about does not exist from Amp's point of view. The plugin's files may be present, and Amp may have decided to load them, but a file on disk is not yet a working command or tool. There is still a lifecycle to complete.

This distinction is useful throughout the article. The plugin is the running participant. Commands, tools, skills, event handlers, and modes are things it can register. An interface action is something it can ask Amp to perform. None of those should be treated as proof that the plugin is already active.

Files are not availability

A plugin module may exist locally while the dashed target slot remains unavailable to Amp. Limited static metadata discovery does not replace the live registration and readiness protocol.

Starting from absence gives us a simple test for every step that follows: has Amp learned enough to make the capability available yet? At this point, the answer is no. Before Amp can learn anything from the plugin, it needs somewhere to run it.

The official Amp Plugins guide describes the capability surfaces plugins can provide. The live process and readiness sequence shown here comes from the versioned bundle analysis described near the end.

02 | Where does it run?

A process of its own

For each ordinary plugin, Amp starts an isolated Bun child process. Here, isolated means the plugin runs in a separate operating-system process from the main Amp process. It is not a claim that the process is a security sandbox; process separation alone does not establish those properties.

The main Amp process coordinates the session and presents its interface. The plugin process is the child in which this one plugin runs. There is one such child for each ordinary plugin, placing each plugin on the other side of a process boundary from the main application.

Amp creates that runtime by re-executing its own binary with BUN_BE_BUN=1. To re-execute a binary is to start that program again as a new process. In this mode, the child behaves as a Bun runtime for the plugin code. This does not produce a second visible Amp application, and the child is not a separate plugin server. It is a local process with a specific job.

Separate process, separate responsibility

Amp re-executes its binary with BUN_BE_BUN=1. The result is one local Bun-mode child for this ordinary plugin, not another visible Amp application.

We now have a more accurate picture than "Amp loads some code." There are two processes with different responsibilities. That creates an immediate constraint: they cannot rely on ordinary in-process function calls. They need a channel across the boundary.

03 | How do they communicate?

Two streams and a small protocol

Amp communicates with the child through its standard input and standard output, usually shortened to stdin and stdout. These streams carry messages between the main process and plugin runtime.

The messages use newline-delimited JSON RPC. JSON gives each message a structured shape. Newline-delimited means one complete message ends at a newline. RPC, or remote procedure call, means one side can ask the other to perform an operation and receive a result. "Remote" describes a call crossing the local process boundary here; it does not imply a network server.

Think of the stream as a roll of paper where each line contains one complete form. The newline tells the reader where one form ends. The real runtime exchanges JSON rather than paper, but the analogy explains how both sides recognize messages on a continuous stream.

A local protocol, not a network hop

One newline ends one JSON message. Requests, responses, and runtime events can cross the same two process streams without shared memory.

The important change is not the punctuation. Communication is explicit. Requests leave one process, cross the boundary, and are handled by the other. Responses and runtime events use the same deliberate channel.

04 | What runs first?

The startup handshake

The child begins by requesting client.info. The plugin runtime asks for information about the Amp client it is joining. The response contents are outside this article's scope, but the order matters: the child asks before it imports and runs the plugin.

After receiving context, the child imports the plugin module. Import makes the module available to the runtime, but it does not make the plugin ready. The child then invokes the plugin's default export with amp.

The amp value is the interface the plugin receives for interacting with Amp. It gives the plugin a defined way to participate in the relationship without reaching into the main process directly.

The startup order

  1. The child requests client.info.
  2. Amp returns context.
  3. The child imports the module.
  4. It invokes default export(amp).
  5. Registration changes follow.
  6. runtime.ready follows the changes.

Compressing this to "Amp runs the plugin" would hide a useful boundary. A successful import says only that the module was imported. Even after invocation, Amp has not necessarily collected a finished set of capabilities.

05 | How does Amp learn what changed?

Registration becomes readiness

As the plugin runs, it can register capabilities. A registration is the plugin declaring something it contributes: a command, tool, skill, event, or mode. The child emits changes as registrations are made, but a stream of changes does not mean startup is complete.

The distinct completion signal is runtime.ready. It marks the end of the runtime's startup sequence. It does not carry every command and tool inside one ready package. After receiving readiness, Amp fetches commands, tools, skills, events, and modes from the child.

Keep five states separate: the plugin code exists; its process runs and invokes the entry function; the plugin registers capabilities; the runtime declares itself ready; then Amp fetches those registrations and makes the capabilities available. Each state depends on the one before it, but none can stand in for the next.

Causal checkpoint

runtime.ready carries no capability tokens. Amp fetches five ordered groups after readiness, then makes the capability available.

  1. Changes. Registrations accumulate in the child.
  2. Ready. The gate opens after runtime.ready.
  3. Fetch. Amp requests commands, tools, skills, events, and modes.
  4. Available. Results enter Amp's rack.

This is why module import cannot stand in for readiness. A module may import before its entry function finishes. Registrations may change while startup is underway. runtime.ready gives the relationship an explicit boundary, and Amp's subsequent fetch turns pending registrations into capabilities the main process knows about.

The official execute-mode documentation describes plugin loading and its readiness timeout at the product boundary. The exact changes, ready, fetch, and availability order shown here was verified from the normalized CLI bundle.

06 | Who renders plugin UI?

When the plugin asks Amp to draw

A plugin running in a child process does not render the Amp interface inside that child. Its UI APIs work as reverse RPC calls to the main Amp process. Reverse RPC is not a different transport; it names the direction of the request.

Suppose the plugin needs Amp to show an interface element. The plugin requests that action through its API. The request travels from child to main process, and Amp renders the interface. The plugin owns the request; Amp owns the presentation.

Causal checkpoint

The panel appears only inside Amp. Reverse RPC names request direction, not another transport.

Ownership stays split

The plugin owns the request. Amp handles it and owns the presentation. This model does not require an intermediary or an unverified rendering framework.

The child is not a tiny copy of the application with a private interface waiting to be revealed. It is a runtime that can ask the main process to perform supported UI actions. We do not need to name Amp's rendering framework to understand that division of responsibility.

The official Plugin API reference documents the supported UI-facing API surface. The reverse request direction and process ownership shown here were verified from the versioned CLI bundle.

07 | Which failures retry?

Ready is a real boundary

A plugin that fails before runtime.ready has not completed the startup contract. Amp may have started the child, answered client.info, or reached module import, but the relationship never crossed readiness. The capability never reaches the same available state as a successful load.

That differs from an active plugin crashing after it became ready. In the active-crash case, Amp can make three automatic restarts after the initial run, for up to four launches total. Restarts use backoff, meaning they are separated rather than made in an uncontrolled loop. No exact delay should be inferred.

Causal checkpoint

Only the active-crash branch retries. Three restarts follow the initial launch, allowing up to four launches total.

Before ready

The capability never becomes available and this path has no automatic restart continuation.

After active

Amp withdraws the capability, then may perform restart 1, restart 2, and restart 3. Success is not guaranteed.

"The plugin failed" is too broad to explain the lifecycle. A load failure means readiness never happened. An active crash interrupts a relationship that already produced capabilities, so Amp withdraws them through its own registry cleanup and may enter the bounded retry path.

08 | How does it stop?

Ending the relationship cleanly

Dispose names a deliberate closing phase. Terminate means ending the child process. They are related, but they are not synonyms.

Amp first withdraws the plugin's capabilities from its active registries. If the child is still running, reached readiness, and is not terminally failed, Amp asks it to dispose. The child runs registered callbacks and replies. Callback failures are logged and ignored, so cleanup remains best-effort.

Amp waits for the reply, child exit, or a three-second timeout. If the child remains alive, Amp sends SIGINT, waits up to another three seconds, and uses SIGKILL only if it is still alive. An unexpected crash does not run dispose callbacks. A never-ready, terminally failed, or already-exited child does not receive the dispose request.

Causal checkpoint

Amp withdraws capabilities before disposal. The request is conditional, cleanup is best-effort, and SIGKILL is not implied for every shutdown.

  1. 1. Withdraw.

    Amp removes capabilities from active registries.

  2. 2. Dispose if eligible.

    The live, ready child runs callbacks and acknowledges.

  3. 3. Wait.

    Amp waits for acknowledgement, exit, or 3 seconds.

  4. 4. Escalate if alive.

    SIGINT, another wait up to 3 seconds, then conditional SIGKILL.

Once the child ends, the active relationship is over. This sequence does not guarantee that in-flight work completed, every resource was released, or every callback succeeded.

The official Plugin API reference documents that onDispose runs on unload, reload, and graceful shutdown, is bounded to about three seconds, and does not run after a crash or SIGKILL. The exact parent-side request and termination sequence shown here was verified from the versioned CLI bundle.

09 | Where does this model apply?

Similar names, different systems

First separate how an extension is distributed from where it executes. Global plugins differ from ordinary plugins in distribution and scope, but "global" does not mean "server-side." They still run in the user's environment, on the client/orb side. Distribution, scope, and execution location are separate properties.

Declarative server-only agents and tools are different. They form a distinct system and should not be described as ordinary plugins with another option. The child-process lifecycle in this article should not be copied onto them. Their implementation details are outside this explanation.

Causal checkpoint

Global does not mean server-side. Distribution, scope, and execution location remain separate dimensions.

Extension type, scope, and execution side
ExtensionScopeExecution
Ordinary pluginProject or machine-local in this comparisonClient/orb side
Global pluginPersonal or workspaceClient/orb side
Declarative server-only agent/toolSeparate declaration systemServer side

This final comparison tests the mental model rather than expanding it into a product taxonomy. For an ordinary plugin, we can ask where it runs, how it exchanges messages, when it is ready, who renders its UI requests, and what happens at failure or shutdown. Other extension mechanisms need their own answers.

The official Global Plugins and Skills guide defines global behavior through availability and scope. The Plugin API reference documents declarative server-only agents and tools. Those sources support the comparison boundary, not every internal runtime detail.

Sources and method

What is public, and what was verified

Official documentation establishes the supported product surfaces. A versioned bundle analysis establishes the internal causal sequence described in this article.

Official documentation

Versioned internal verification

Process, transport, handshake, reverse-RPC direction, restart behavior, Amp-owned registry withdrawal, and the exact termination sequence were checked against stable strings and structural relationships in the normalized Amp CLI bundle 0.0.1787888254-g2bce74, build 2026-08-28.

Public documentation does not state all of those internals. The bundle analysis avoids relying on minified local names and makes no claim beyond that version.