ampacp

package module
v0.0.0-...-69050fd Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 23, 2026 License: MIT Imports: 30 Imported by: 0

README

acp-go-amp

acp-go-amp exposes the Amp CLI through the Agent Client Protocol. It creates a native thread with amp threads new, then runs one threads continue process per prompt using stream-json input and output.

Continue the conversation after closing the adapter:

amp threads continue NATIVE_SESSION_ID

New, load, and resume responses and session-list entries expose the current native ID as _meta.amp.nativeSessionId. Use it for native CLI continuation. ACP requests continue to use the stable ACP sessionId. The store's configuration record saves both IDs with the matching native history.

Run and embed

go install github.com/savid/acp-go-amp/cmd/acp-go-amp@latest
acp-go-amp [-path amp] [-seed-file rel=host]... [-debug]

Verified against Amp 0.0.1789432613-gd97f0d; requires native Amp authentication. Run amp login separately. Native configuration and auth resolve from the inherited environment. -version prints the adapter version; OTEL_* variables configure telemetry exporters. -home and -model refuse nonempty values. -scratch-dir accepts a scratch parent. -path selects the executable, -seed-file seeds a relative native configuration file, and -debug enables stderr diagnostics.

err := ampacp.Serve(ctx, os.Stdin, os.Stdout,
    ampacp.WithSessionStore(store),
)
Process option Meaning
WithExecutablePath Select the native executable.
WithHome Refuse nonempty values; native home selection uses the inherited environment.
WithEnv Overlay the inherited environment.
WithScratchDir Parent for temporary lifecycle bridge files.
WithSeedFiles Seed native configuration files without overwriting unmanaged files.
WithDefaultModel, WithConfiguredModels Refuse nonempty values; native Amp selects models through modes.
WithSessionStore Select the durability store.
WithConcurrencyLimits Configurable concurrency.
WithImageLimits, WithInputHandoffRoot Set image byte limits and the root for image handoffs.
WithLogger Supply the structured logger.
WithTracerProvider, WithMeterProvider, WithTextMapPropagator Configure OpenTelemetry providers and context propagation.
WithAgentName, WithAgentTitle, WithAgentVersion Set the identity advertised at initialize.

Sessions

Pass _meta.amp.options on new, load, or resume, or use WithSessionAmpOptions.

Field Meaning
mode Native built-in or plugin mode, forwarded unchanged
env Environment overlay for every command belonging to this session
extraPathDirs Absolute directories prepended to the session PATH in order

The only session config option is mode, a select over low, medium, high, and ultra plus the accepted value when it is outside that menu. No model selector is advertised, and configId: "model" is refused.

The environment merges the adapter process, WithEnv, then session env. Only ACP_GO_AMP_INTERNAL_* markers are dropped. Executable resolution uses the base PATH before applying the session paths. Seed files are relative to the native settings directory and never overwrite an unmanaged file.

The adapter installs a unique temporary plugin in Amp's system plugin directory under XDG_CONFIG_HOME, or ~/.config when unset. It is inert in other launches and removed after its process is reaped. Other plugins and the inherited native configuration remain active. The plugin directory must be writable.

model, outputSchema, nonempty mcpServers, and unknown owned fields are refused. Mode configuration applies to the next prompt; native agent_mode updates the accepted value. Amp keeps its native tool permission behavior. There is no ACP permission or elicitation bridge, model catalog, or slash command catalog. Slash-prefixed text is ordinary prompt input.

Images use bounded inline base64 or validated file handoffs. Native inline tool images are validated; remote image URLs become resource links without fetching. The current native input ceiling is 5,138,022 bytes per image and 8,000 pixels per dimension. Native Amp enforces the dimension limit.

_meta.amp.rawEvent.enabled enables _amp/rawEvent. Image payloads are removed from this diagnostic channel. Optional lifecycle negotiation opens a fresh stream for each prompt process and reports acceptance and terminal state.

Persistence

session/new runs amp threads new, creating an empty remote thread before the first prompt. --stream-json-input delivers prompts to that thread.

SessionStoreFormat is amp-thread-json-v1. Each generation contains the raw native thread export and a config sidecar with cwd, additional directories, ACP and native session IDs, service origin, accepted mode, environment, ordered paths, update time, and historic message usage. The process exits and is reaped before export and atomic store publication. The mirror is durable before terminal lifecycle state and the prompt response; a turn the store never received ends its lifecycle incarnation without a terminal idle. A generation captured by a failed commit is published by the next successful commit, including the one at close. A failed close commit fails the close and still releases the session.

A temporary native plugin observes agent.end, the native thread state, and message IDs and contents. Each prompt first attaches to the existing thread without input and refuses to submit while remote work is active. Cancellation calls the native thread's cancel API and waits for acknowledgement and a settled thread before stopping the local process. A disconnected process is reattached without input to discover whether the remote work actually stopped.

Amp can finish a streamed turn before its export includes all completed messages. The adapter compares the raw export with the observed quiet thread, retrying with increasing delays under a 30-second deadline. Identical stale exports do not count as completion. Terminal lifecycle state and the prompt response wait for the verified export and atomic store commit. If reconciliation fails, the previous mirror survives and no terminal idle is emitted.

Load attaches to the remote thread without submitting a prompt, verifies a current export against its native state and every shared mirror message, adopts newer messages, and replays history. Resume performs the same validation without replay. A confirmed missing remote thread is recovered into a new private thread through Amp's authenticated internal import API. Recovery attaches without input, verifies exported message identities, order, roles, content, and completion state, then commits the replacement native ID and history under the same ACP session ID. Historic usage remains in the configuration record when native import omits it.

Shorter, conflicting, or inaccessible remote history fails restore. Authentication and network failures never authorize replacement. Failed verification or store publication preserves the previous committed generation and deletes the private destination thread recovery created and never bound; a process killed mid-recovery can still leave one behind. No other remote state is ever deleted.

The importer is an internal Amp API and may change independently of CLI commands. Unsupported informational messages or a conversion that changes history fail recovery. Native timestamps and some metadata may change. Cross-account recovery and remote attachment access have not been verified.

Amp compacts a thread on its own when the context window fills, inserting an informational summary message before the prompt that triggered it. A turn that compacts settles normally: the mirror keeps the summary, verification compares the conversation around it, and later prompts, loads, and resumes read past it. A compacted thread cannot be recovered after native deletion: the importer rejects summary messages, so restore fails and the backup stays intact.

Close joins the current prompt process. Delete tombstones the adapter store first, then closes the session. Both leave the native remote thread intact. The default store is in memory; supply a durable store for adapter restarts.

Development

make test
make audit
make test-integration-smoke
ACP_GO_AMP_MODE=medium make test-integration-live

Unit tests use a native subprocess fixture and need no credentials. Smoke tests exercise native creation/export/restore without model calls and skip when the native binary is missing; ACP_GO_AMP_HARNESS_PATH overrides which binary they resolve. Live tests spend model tokens and test ACP → native CLI → ACP continuation, fresh local state, PATH changes, cancellation, and deletion. Integration tests link the native secret store into temporary XDG directories so credential refresh updates the original store.

Documentation

Overview

Package ampacp exposes Amp as an Agent Client Protocol agent.

Serve translates ACP requests into native Amp commands. Each prompt owns one stream-json child process with the inherited environment and session overlays and a temporary lifecycle plugin. Native end receipts and quiet thread state gate export verification and durable terminal publication. ACP session identity stays stable while the native binding is stored beside history.

WithSessionStore supplies durable native exports and configuration records. Load reconciles the remote thread and replays history; resume omits replay. Confirmed missing threads recover through native import into a new thread; verified history and the replacement binding commit together. Close preserves native state.

Hosts supply telemetry through WithTracerProvider and WithMeterProvider; the package does not configure global providers.

Index

Examples

Constants

View Source
const (
	// RawEventMethod is the notification carrying one raw Amp event when a
	// session opted in through _meta.amp.rawEvent.enabled.
	RawEventMethod = "_amp/rawEvent"
	// SessionStoreFormat identifies the store layout this package writes: raw
	// Amp thread exports under the main subpath plus the adapter's session
	// record under the config subpath.
	SessionStoreFormat = "amp-thread-json-v1"
)

Variables

This section is empty.

Functions

func Serve

func Serve(ctx context.Context, input io.Reader, output io.Writer, opts ...Option) (returnErr error)

Serve runs an ACP agent over the provided streams. It blocks until the context is cancelled or the peer closes the connection, then closes the agent.

Example (Initialize)

ExampleServe_initialize embeds the agent over a pair of pipes, the same wiring a host uses for stdio, and reads the capabilities the handshake advertises.

package main

import (
	"bufio"
	"context"
	"encoding/json"
	"fmt"
	"io"

	ampacp "github.com/savid/acp-go-amp"
)

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	clientToAgentReader, clientToAgentWriter := io.Pipe()
	agentToClientReader, agentToClientWriter := io.Pipe()

	done := make(chan error, 1)

	go func() {
		done <- ampacp.Serve(ctx, clientToAgentReader, agentToClientWriter)
	}()

	_, _ = fmt.Fprintln(clientToAgentWriter,
		`{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":1}}`)

	line, _ := bufio.NewReader(agentToClientReader).ReadString('\n')

	cancel()
	_ = clientToAgentWriter.Close()
	<-done

	var response struct {
		Result struct {
			AuthMethods       []any `json:"authMethods"`
			AgentCapabilities struct {
				LoadSession         bool           `json:"loadSession"`
				SessionCapabilities map[string]any `json:"sessionCapabilities"`
			} `json:"agentCapabilities"`
		} `json:"result"`
	}

	_ = json.Unmarshal([]byte(line), &response)

	fmt.Println(len(response.Result.AuthMethods))
	fmt.Println(response.Result.AgentCapabilities.LoadSession)
	fmt.Println(len(response.Result.AgentCapabilities.SessionCapabilities))
}
Output:
0
true
5

func SetModelRequest

func SetModelRequest(sessionID acp.SessionId, model string) acp.SetSessionConfigOptionRequest

SetModelRequest constructs a model selector update, which Amp refuses: it advertises no model config option.

func ValidateAmpSessionMeta

func ValidateAmpSessionMeta(meta map[string]any) error

ValidateAmpSessionMeta runs the owned-namespace parsing of a session lifecycle request's _meta without an Agent and returns the same refusal.

func WithSessionAmpOptions

func WithSessionAmpOptions(options AmpOptions) wire.SessionRequestOption

WithSessionAmpOptions merges Amp-specific options into _meta.amp.options.

func WithSessionRawEvents

func WithSessionRawEvents(enabled bool) wire.SessionRequestOption

WithSessionRawEvents toggles raw Amp event emission for the session.

Types

type Agent

type Agent struct {
	// contains filtered or unexported fields
}

Agent exposes the Amp coding agent through ACP.

func NewAgent

func NewAgent(opts ...Option) *Agent

NewAgent creates an ACP agent for the Amp coding agent CLI. Construction never fails; a refused option is reported by Initialize and every session-establishing method as amp_invalid_options.

func (*Agent) Authenticate

func (a *Agent) Authenticate(_ context.Context, params acp.AuthenticateRequest) (acp.AuthenticateResponse, error)

Authenticate exists because the SDK interface requires it. The harness authenticates itself in its own home, outside ACP.

func (*Agent) Cancel

func (a *Agent) Cancel(ctx context.Context, params acp.CancelNotification) (err error)

Cancel interrupts the session's in-flight turn. It is wire-silent on an unknown session or with no turn in flight.

func (*Agent) Close

func (a *Agent) Close() error

Close runs the shutdown ladder for every session and refuses every later request.

func (*Agent) CloseSession

func (a *Agent) CloseSession(ctx context.Context, params acp.CloseSessionRequest) (resp acp.CloseSessionResponse, err error)

CloseSession runs the shutdown ladder for one session.

func (*Agent) HandleExtensionMethod

func (a *Agent) HandleExtensionMethod(_ context.Context, method string, params json.RawMessage) (any, error)

HandleExtensionMethod answers every extension method with method-not-found. The only extension surface is the outbound RawEventMethod notification.

func (*Agent) Initialize

func (a *Agent) Initialize(ctx context.Context, params acp.InitializeRequest) (resp acp.InitializeResponse, err error)

Initialize implements ACP initialize.

func (*Agent) ListSessions

func (a *Agent) ListSessions(ctx context.Context, params acp.ListSessionsRequest) (resp acp.ListSessionsResponse, err error)

ListSessions lists live sessions and stored sessions, newest first.

func (*Agent) LoadSession

func (a *Agent) LoadSession(ctx context.Context, params acp.LoadSessionRequest) (resp acp.LoadSessionResponse, err error)

LoadSession restores a session and replays its history.

func (*Agent) Logout

func (a *Agent) Logout(_ context.Context, params acp.LogoutRequest) (acp.LogoutResponse, error)

Logout exists because the SDK interface requires it.

func (*Agent) NewSession

func (a *Agent) NewSession(ctx context.Context, params acp.NewSessionRequest) (resp acp.NewSessionResponse, err error)

NewSession creates a native Amp thread.

func (*Agent) Prompt

func (a *Agent) Prompt(ctx context.Context, params acp.PromptRequest) (resp acp.PromptResponse, err error)

Prompt sends one turn to Amp and streams updates until it settles.

func (*Agent) ResumeSession

func (a *Agent) ResumeSession(ctx context.Context, params acp.ResumeSessionRequest) (resp acp.ResumeSessionResponse, err error)

ResumeSession restores a session without replaying its history.

func (*Agent) SetSessionConfigOption

func (a *Agent) SetSessionConfigOption(ctx context.Context, params acp.SetSessionConfigOptionRequest) (resp acp.SetSessionConfigOptionResponse, err error)

SetSessionConfigOption applies one select value.

func (*Agent) SetSessionMode

SetSessionMode exists because the SDK interface requires it. Native modes are config options, never ACP session modes.

func (*Agent) UnstableDeleteSession

func (a *Agent) UnstableDeleteSession(ctx context.Context, params acp.UnstableDeleteSessionRequest) (resp acp.UnstableDeleteSessionResponse, err error)

UnstableDeleteSession tombstones the session first, then closes any live session with the same id. The native thread remains on Amp's service.

type AmpOption

type AmpOption func(*AmpOptions)

AmpOption configures AmpOptions values.

func WithAmpEnv

func WithAmpEnv(env map[string]string) AmpOption

WithAmpEnv configures the session environment overlay.

func WithAmpExtraPathDirs

func WithAmpExtraPathDirs(dirs ...string) AmpOption

WithAmpExtraPathDirs configures the directories prepended to the session PATH.

func WithAmpMode

func WithAmpMode(mode string) AmpOption

WithAmpMode selects a native built-in or plugin mode.

func WithAmpModel

func WithAmpModel(model string) AmpOption

WithAmpModel sets the model field, which Amp refuses at session start: the native CLI selects models through its modes.

type AmpOptions

type AmpOptions struct {
	// Mode selects a native built-in or plugin mode.
	Mode string `json:"mode,omitempty"`
	// Model is unsupported by Amp; use Mode.
	Model string `json:"model,omitempty"`
	// Env overlays the session's amp process environment.
	Env map[string]string `json:"env,omitempty"`
	// ExtraPathDirs are absolute directories prepended, in order, to the PATH
	// of this session's amp process.
	ExtraPathDirs []string `json:"extraPathDirs,omitempty"`
}

AmpOptions is the per-session options struct carried at _meta.amp.options.

func NewAmpOptions

func NewAmpOptions(opts ...AmpOption) AmpOptions

NewAmpOptions constructs AmpOptions from functional options.

Example
package main

import (
	"fmt"

	ampacp "github.com/savid/acp-go-amp"
)

func main() {
	options := ampacp.NewAmpOptions(ampacp.WithAmpMode("medium"))
	fmt.Println(options.Mode)
}
Output:
medium

func (AmpOptions) Meta

func (options AmpOptions) Meta() map[string]any

Meta returns exactly {"amp": {"options": {...}}} with the selected fields.

type ConcurrencyLimits

type ConcurrencyLimits struct {
	MaxActiveSessions        int
	MaxConcurrentClientCalls int
}

ConcurrencyLimits controls per-agent backpressure. Zero fields use defaults.

type ImageLimits

type ImageLimits struct {
	MaxInputBytesPerImage     int64
	MaxInputBytesPerPrompt    int64
	MaxOutputBytesPerImage    int64
	MaxOutputBytesPerToolCall int64
}

ImageLimits bounds decoded image bytes. A zero field disables that policy limit; the frame clamp still applies.

type Option

type Option func(*Options)

Option configures the amp ACP agent.

func WithAgentName

func WithAgentName(name string) Option

WithAgentName sets the protocol identifier advertised during ACP initialize.

func WithAgentTitle

func WithAgentTitle(title string) Option

WithAgentTitle sets the human-readable agent name advertised during ACP initialize.

func WithAgentVersion

func WithAgentVersion(version string) Option

WithAgentVersion sets the agent version advertised during ACP initialize.

func WithConcurrencyLimits

func WithConcurrencyLimits(limits ConcurrencyLimits) Option

WithConcurrencyLimits sets process-local backpressure limits.

func WithConfiguredModels

func WithConfiguredModels(ids []string) Option

WithConfiguredModels is unsupported by Amp.

func WithDefaultModel

func WithDefaultModel(model string) Option

WithDefaultModel is unsupported by Amp.

func WithEnv

func WithEnv(env map[string]string) Option

WithEnv sets the static agent-scoped environment overlay applied to every amp process after the inherited environment and before the session env.

func WithExecutablePath

func WithExecutablePath(path string) Option

WithExecutablePath selects the amp executable.

func WithHome

func WithHome(path string) Option

WithHome is unsupported by Amp. A nonempty path fails initialization.

func WithImageLimits

func WithImageLimits(limits ImageLimits) Option

WithImageLimits bounds decoded image bytes. A zero field disables that policy limit; a negative field fails construction.

func WithInputHandoffRoot

func WithInputHandoffRoot(dir string) Option

WithInputHandoffRoot sets the absolute directory under which handoff-form prompt images are read. The adapter never writes there.

func WithLogger

func WithLogger(logger *slog.Logger) Option

WithLogger configures structured diagnostic logging.

func WithMeterProvider

func WithMeterProvider(provider metric.MeterProvider) Option

WithMeterProvider configures the OpenTelemetry meter provider.

func WithScratchDir

func WithScratchDir(dir string) Option

WithScratchDir sets the parent directory for ephemeral adapter state.

func WithSeedFiles

func WithSeedFiles(files map[string]string) Option

WithSeedFiles registers files written into amp's config root before each launch. Keys are paths relative to that root; values are the contents.

func WithSessionStore

func WithSessionStore(store acpcore.SessionStore) Option

WithSessionStore configures the session store.

func WithTextMapPropagator

func WithTextMapPropagator(propagator propagation.TextMapPropagator) Option

WithTextMapPropagator configures trace-context extraction from ACP _meta.

func WithTracerProvider

func WithTracerProvider(provider trace.TracerProvider) Option

WithTracerProvider configures the OpenTelemetry tracer provider.

type Options

type Options struct {
	// AgentName is the protocol identifier advertised during ACP initialize.
	AgentName string
	// AgentTitle is the human-readable agent name advertised during ACP initialize.
	AgentTitle string
	// AgentVersion is the agent version advertised during ACP initialize.
	AgentVersion string

	// ExecutablePath selects the amp executable. A bare name is searched on the
	// base PATH; a path containing a separator is used as given. Empty means
	// "amp".
	ExecutablePath string
	// Home is unsupported; Amp resolves its native files from the inherited environment.
	Home string
	// ScratchDir is the parent directory for ephemeral adapter state. Empty
	// means the system temp directory.
	ScratchDir string
	// InputHandoffRoot is the absolute directory under which handoff-form
	// prompt images are read. Empty rejects the handoff form.
	InputHandoffRoot string
	// DefaultModel is unsupported; use the native mode option.
	DefaultModel string
	// ConfiguredModels is unsupported because Amp has no model catalog.
	ConfiguredModels []string
	// Env is the static agent-scoped overlay on the inherited process
	// environment every amp process runs with.
	Env map[string]string

	// Logger receives structured diagnostic logs. If nil, the default logger is used.
	Logger *slog.Logger
	// TracerProvider records adapter spans. If nil, tracing is a no-op.
	TracerProvider trace.TracerProvider
	// MeterProvider records adapter metrics. If nil, metrics are no-ops.
	MeterProvider metric.MeterProvider
	// TextMapPropagator extracts trace context from ACP _meta. If nil, W3C
	// trace context plus baggage propagation is used.
	TextMapPropagator propagation.TextMapPropagator

	// SessionStore is the durability boundary for session rows. Nil installs a
	// fresh in-memory store.
	SessionStore acpcore.SessionStore
	// ConcurrencyLimits controls process-local backpressure.
	ConcurrencyLimits ConcurrencyLimits
	// SeedFiles maps paths relative to amp's config root to file contents
	// written there before each launch.
	SeedFiles map[string]string
	// ImageLimits bounds decoded image bytes on prompt input and emitted
	// output. Every field defaults to 6 MiB when the option is omitted.
	ImageLimits ImageLimits
	// contains filtered or unexported fields
}

Options configures the ACP agent process and the native Amp prompt processes.

Directories

Path Synopsis
cmd
acp-go-amp command
Package integration holds the tests that run against an installed Amp.
Package integration holds the tests that run against an installed Amp.
internal
amp
Package amp implements the native Amp command and stream-json boundary.
Package amp implements the native Amp command and stream-json boundary.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL