runshiftrunshift

docs / amp

AMP

Agent Message Protocol

powered by runshift

v1.0

Your agent sends a signal. runshift governs it. Your code doesn't change.

March 2026 // runshift.ai/amp // draft

The problem with autonomous agents

AI agents can send emails, publish content, move money, and modify production systems. Most agent frameworks give you no approval layer. If an agent decides to act, it acts.

AMP exists so every consequential action passes through an operator gate before it executes. One signal. One decision. Full audit trail.

Pull requests for AI agents.

When you pay for something online, your checkout doesn't talk directly to your bank. It talks to Stripe. Stripe validates the transaction, applies rules, and returns approved or declined. Your checkout never touches the payment logic.

AMP works the same way. Your agent doesn't execute consequential actions directly. It sends a signal to runshift. runshift validates it against operator rules and returns approved, rejected, or pending. Your agent never touches the gate logic.

Stripe is in the payment path. runshift is in the agent execution path. Neither controls your code.

You have 3–4 agents running. You can only watch one at a time. You review outputs manually before acting on them. You context-switch between them because there's no single surface that shows you all of them at once.

You're already operating like runshift exists. AMP is the infrastructure for what you're already doing.

control layer

Relay

The operator's interface to every running agent

oversight layer

Trust Gates

Human approval before every consequential action

reporting contract

AMP

The standard surface agents report into

the flow

How a signal moves

Signal

Your agent POSTs an AMP payload to runshift

Gate

runshift holds it and notifies the operator

Resolution

approved | rejected | pending returns to your agent

integration patterns

Two ways to gate — both v1, both first-class

pattern 1

Complete → Signal

Agent runs to completion, then POSTs the AMP payload. Gate fires on the outcome. Operator reviews and approves or rejects.

Best for agents where the output needs review before it's acted on — drafts, reports, summaries.

pattern 2

Signal → Gate → Continue

Agent routes its model call through the runshift proxy. Gate fires before the action executes. Agent receives 202 + gate_id, then waits for webhook callback.

Best for agents where the action itself is consequential — send, publish, delete, transfer.

pattern-1-flow.txt
your agent runs
  → task finishes
  → POST /amp/signal with AMP payload
  → gate fires if gate_required: true
  → operator approves/rejects
  → your agent receives resolution
pattern-2-flow.txt
your agent runs
  → routes model call through runshift proxy
  → runshift evaluates against operator rules
  → gate fires if conditions met
  → returns 202 + gate_id to your agent
  → operator approves/rejects
  → runshift POSTs resolution to your webhook
  → your agent continues or halts
pattern-2-proxy.py
import anthropic

# One line change — route through runshift proxy
client = anthropic.Anthropic(
    api_key="...",
    base_url="https://api.runshift.ai/v1/proxy"
)

# Your existing code works unchanged
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    messages=[{"role": "user", "content": "..."}]
)

the contract

The AMP payload

When an agent completes a run — approved or not — it sends a single structured payload to relay. relay validates it, writes the audit trail, fires the trust gate if required, and records the outcome. No SDK required. One JSON object, one endpoint.

POSTrunshift.ai/amp/signal
Authorization: Bearer rs-amp-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

API keys are scoped to workspace and agent. Get yours from the runshift dashboard.

amp-payload.json
{
  // identity
  "amp_version":     "1.0",
  "agent_id":        "resume-tailor",
  "run_id":          "run_01jt4k...",
  "project_id":      "job-search",

  // execution
  "status":          "completed",
  "summary":         "Rewrote resume for Senior PM role at Stripe",
  "proposed_action": "Send to applicant",
  "artifacts":       [],
  "model":           "claude-sonnet-4-6",
  "input_tokens":    1842,
  "output_tokens":   614,
  "cost_usd":        0.007532,
  "started_at":      "2026-03-06T22:10:38Z",
  "completed_at":    "2026-03-06T22:10:46Z",
  "metadata": {
    "candidate_id":  "123",
    "source":        "manual relay prompt"
  },

  // governance
  "gate_required":   true,
  "webhook_url":     null
}

summary vs proposed_action. summary is what the agent already did — a record. proposed_action is what happens next if the operator approves — a proposal. relay shows both in the gate slide.

gate_required. Deterministic trigger. Your agent sets it. Operator rules in runshift confirm it. Both must be true for a gate to fire — agent declaration + operator authorization.

schema reference

Field definitions

fieldtyperequireddescription
amp_versionstringrequiredProtocol version. Always '1.0' for this release. Used for schema migration and backwards compatibility.
agent_idstringrequiredUnique identifier. Matches the agent registered in the runshift roster. Use kebab-case.
run_idstringrequiredUnique execution ID. relay ignores duplicate payloads with the same run_id.
project_idstringoptionalMaps to the runshift deck (e.g. 'job-search'). Scopes events to the right operator view.
statusenumrequiredcompleted | failed | interrupted. If status is failed, include metadata.error_code and metadata.error_message when available.
summarystringrequiredWhat the agent did. Shown in audit trail. Write for an operator, not a developer.
proposed_actionstringoptionalRequired if gate_required is true. What the operator is approving. Distinct from summary — this is what happens next.
artifactsarrayoptionalStructured outputs. Displayed in gate slide for operator review.
modelstringrequiredThe model used. AMP is model-agnostic — Claude, GPT, Gemini, Llama, or anything.
input_tokensintegerrequiredTokens consumed as input.
output_tokensintegerrequiredTokens generated.
cost_usdfloatrequiredAgent-calculated cost in USD. relay accepts agent-reported cost in v1 and may independently verify when model pricing metadata is available.
gate_requiredbooleanrequiredDeterministic trigger. Your agent sets it. Operator rules in runshift confirm it. Both must be true for a gate to fire.
started_atISO 8601requiredWhen the agent started.
completed_atISO 8601requiredWhen the agent finished.
metadataobjectoptionalArbitrary key-value context. Use for domain-specific data — prevents one-off field creep.
webhook_urlstringoptionalWhere runshift POSTs the resolution for Pattern 2 integrations. Register once in the dashboard.

integration

Ways to connect an agent

Pick the path that matches how your agent is built. All paths produce the same result: your agent appears in the runshift roster, relay can see it, and the operator has full control.

recommended

One line of code

Add the AMP reporter to your existing agent. One fetch call at the end of your function. No dependencies, no config.

coming soon

Proxy route

Change base_url in your existing Anthropic client. Register a webhook URL in the dashboard. Gate fires mid-execution. Zero agent refactor.

coming soon

@runshift/agent SDK

Wrap your agent function. Token counting, cost calculation, and error reporting handled automatically.

coming soon

Webhook endpoint

Point any automation tool — Make, Zapier, n8n — at your AMP endpoint.

path 1 — complete → signal

One line. Your agent is live.

At the end of your agent function, post the AMP payload to relay. That's the entire integration.

your_agent.py
import requests, time, uuid

def resume_tailor(job_description: str, resume: str) -> str:
    """Your existing agent — unchanged."""
    start = time.time()

    # ... your agent logic ...
    result = rewrite_resume(job_description, resume)
    input_tokens = 1842
    output_tokens = 614

    # ── AMP: one call, agent is live ──
    requests.post("https://runshift.ai/amp/signal",
        headers={"Authorization": "Bearer rs-amp-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"},
        json={
        "amp_version":     "1.0",
        "agent_id":        "resume-tailor",
        "run_id":          str(uuid.uuid4()),
        "project_id":      "job-search",
        "status":          "completed",
        "summary":         f"Rewrote resume for {job_description[:60]}",
        "proposed_action": "Send tailored resume to applicant",
        "artifacts":       [{"type": "text", "content": result}],
        "model":           "claude-sonnet-4-6",
        "input_tokens":    input_tokens,
        "output_tokens":   output_tokens,
        "cost_usd":        calculate_cost("claude-sonnet-4-6", input_tokens, output_tokens),
        "gate_required":   True,
        "started_at":      format_iso(start),
        "completed_at":    format_iso(time.time()),
        "metadata":        {"source": "manual relay prompt"},
    })

    return result

summary vs proposed_action. summary is what the agent already did — a record. proposed_action is what happens next if the operator approves — a proposal. relay shows both in the gate slide.

path 2 — sdk

@runshift/agent SDK

SDK coming soon.

what happens next

What relay does with the payload

The payload alone is not the product. What relay does after ingestion is the product.

01

Validates the payload — fields, agent_id registration, amp_version, duplicate run_id rejection

02

Writes to the audit trail — immutable, before gate fires

03

Fires the trust gate — if gate_required, shows operator summary + proposed_action + artifacts

04

Records the outcome — execution record, live counter updates

05

Notifies the agent — returns approved | rejected | pending

relay-response.json
// gate_required: true → pending until operator decides
{
  "status":  "pending",
  "gate_id": "gate_01jt...",
  "message": "Awaiting operator approval"
}

// operator approved
{
  "status":  "approved",
  "gate_id": "gate_01jt...",
  "message": "Gate approved by operator"
}

// operator denied
{
  "status":  "rejected",
  "gate_id": "gate_01jt...",
  "message": "Gate rejected by operator"
}
webhook-callback.json
// Pattern 2: runshift POSTs this to your webhook_url on resolution
{
  "gate_id":     "gate_01jt...",
  "status":      "approved",
  "run_id":      "run_01jt4k...",
  "agent_id":    "resume-tailor",
  "resolved_at": "2026-03-06T22:11:14Z",
  "resolved_by": "operator"
}

protocol boundaries

What AMP v1 does and does not do

runshift does

Read the signal payload

Apply operator-defined gate rules

Return approved / rejected / pending

Surface payload to operator for review

Log every gate decision immutably

Notify via Slack

runshift does not

Modify your agent's logic

Change the payload content

Retry your agent

Initiate an agent run (v1.1 — registration payload)

Stop or pause a running agent (v2.0 — heartbeat)

Access anything outside the signal

Start, stop, and manage agents from the runshift dashboard — this is a product capability, not an AMP protocol concern. See the roadmap for when agent lifecycle management arrives in the protocol.

cost calculation

Calculating cost_usd

In v1, agents calculate their own cost using known model pricing. runshift maintains a pricing constants file agents can import.

amp-pricing.ts
export const MODEL_PRICING: Record<string, { input: number; output: number }> = {
  // Anthropic (per 1M tokens)
  'claude-sonnet-4-6':           { input: 3.00,  output: 15.00 },
  'claude-haiku-4-5':            { input: 0.80,  output: 4.00  },
  'claude-opus-4-6':             { input: 15.00, output: 75.00 },

  // OpenAI (per 1M tokens)
  'gpt-4o':                      { input: 2.50,  output: 10.00 },
  'gpt-4o-mini':                 { input: 0.15,  output: 0.60  },
  'o3':                          { input: 10.00, output: 40.00 },

  // Google (per 1M tokens)
  'gemini-2.5-pro':              { input: 1.25,  output: 10.00 },
  'gemini-2.5-flash':            { input: 0.15,  output: 0.60  },
}

export function calculateCost(
  model: string,
  inputTokens: number,
  outputTokens: number,
): number {
  const pricing = MODEL_PRICING[model]
  if (!pricing) return 0
  return (inputTokens / 1_000_000) * pricing.input
       + (outputTokens / 1_000_000) * pricing.output
}

v1 trust model. relay accepts agent-reported cost and may independently verify when model pricing metadata is available.

roadmap

What AMP becomes

AMP v1 ships with two first-class patterns: completion reporting and proxy-based gating. The protocol is designed to extend — future versions add message types without breaking existing integrations.

v1.0

Completion + proxy patterns

Agent reports on completion (Pattern 1) or routes through the runshift proxy for mid-execution gates (Pattern 2). What you're reading now.

completionproxy
v1.1

Registration payload

Agent registers identity, capabilities, and trust level with relay on startup.

completionproxyregistration
v2.0

Gate request + heartbeat

Agents request gates mid-execution. Cost verification layer added.

completionproxyregistrationgate_requestheartbeatfailure
v3.0

Policy + routing layer

relay becomes the policy surface. Trust levels, governance, routing.

completionproxyregistrationgate_requestheartbeatfailurepolicyroutingaction_receipt