Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ maintainer contact without disclosing vulnerability details.
does not model end users, roles, per-resource grants, or user-level audit
identity; a SaaS or enterprise control plane must own those concerns and
issue or revoke Workspace keys.
- `-strict` additionally validates CMA version, beta, and content-type headers;
it does not change authorization semantics.
- Protected HTTP routes require a Workspace key in `Authorization: Bearer`.
Requests with non-empty bodies must use the documented JSON or multipart
content type. Provider version and beta headers are not part of Mango's API.
- PostgreSQL journals tool attempts, but an external side effect can still be
ambiguous if execution succeeds and its durable result is lost. Exactly-once
behavior requires idempotency from the external system.
Expand Down
6 changes: 1 addition & 5 deletions cmd/mango/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -417,13 +417,9 @@ func main() {
func runServe() {
fs := flag.NewFlagSet("serve", flag.ExitOnError)
addr := fs.String("addr", defaultAddr, "listen address (default binds to loopback; use e.g. :8080 to expose on all interfaces)")
strict := fs.Bool("strict", false, "require Claude API version, beta, and content-type headers; API-key authentication is always enforced")
_ = fs.Parse(os.Args[2:])

cfg := httpapi.Config{
RequireBeta: *strict, RequireVersion: *strict, RequireContentType: *strict,
}
runPostgresAPI(*addr, cfg)
runPostgresAPI(*addr, httpapi.Config{})
}

func runWorkspaceCommand() {
Expand Down
11 changes: 6 additions & 5 deletions docs/api/environment-work.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ Poll -> Ack -> Heartbeat(NO_HEARTBEAT) -> Heartbeat(previous timestamp) -> Stop
```

- `Poll` tentatively claims the oldest available item. A stale unacknowledged
claim may be reclaimed; `Anthropic-Worker-ID` contributes to queue stats.
claim may be reclaimed. The optional `worker_id` query parameter contributes
to queue statistics and operational correlation; it is not a credential.
- `Ack` removes the item from the queue and changes it from `queued` to
`starting`.
- The first heartbeat uses `expected_last_heartbeat=NO_HEARTBEAT`. Every later
Expand Down Expand Up @@ -54,10 +55,10 @@ object store are configured.

## Security boundary

Workers send an Environment key as a bearer credential for Work, Session,
event, and Skill requests. Mango authenticates that credential as a Workspace
API key and limits all of those resources to the same Workspace. It does not
issue narrower Environment-worker credentials, so Work `secret` remains
Workers send a Workspace API key as a bearer credential for Work, Session,
event, and Skill requests. Mango limits all of those resources to the same
Workspace. It does not issue narrower Environment-worker credentials, so
`worker_id` is not an authorization boundary and Work `secret` remains
`null`. A surrounding control plane should add Environment-specific policy
before exposing this surface to untrusted workers.

Expand Down
4 changes: 2 additions & 2 deletions docs/api/files.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ DELETE /v1/files/{file_id}
```

Set `MANGO_FILE_S3_BUCKET` and the corresponding endpoint, region, and
credential variables before using these routes. In strict mode they require
`anthropic-beta: files-api-2025-04-14`.
credential variables before using these routes. All routes use Mango's standard
bearer authentication; uploads require `multipart/form-data`.

## Upload and list

Expand Down
4 changes: 2 additions & 2 deletions docs/api/memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ slug: /api/memory
# Memory

Memory Stores are PostgreSQL-backed, cross-Session collections of ordinary
UTF-8 files with immutable Version history. Memory routes use
`anthropic-beta: agent-memory-2026-07-22`.
UTF-8 files with immutable Version history. Memory routes use Mango's standard
bearer authentication and JSON content type rules.

The fourteen operations cover Store create/get/update/list/archive/delete,
Memory create/get/update/list/delete, and Version get/list/redact. The running
Expand Down
34 changes: 11 additions & 23 deletions docs/api/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,33 +54,21 @@ Resource-specific request shapes are covered in:
## Headers

Every protected route requires an API key. The default development stack uses
`sk-mango-local-development`. Run with `-strict` to additionally require the
vendor-named headers currently used by strict mode:
`sk-mango-local-development`. Send it as a standard bearer credential:

```http
x-api-key: sk-mango-local-development
anthropic-version: 2023-06-01
anthropic-beta: managed-agents-2026-04-01
authorization: Bearer sk-mango-local-development
content-type: application/json
```

Files routes instead require `anthropic-beta: files-api-2025-04-14` in strict
mode. Upload uses `multipart/form-data`; the other Files requests do not require
a JSON content type.
Every non-empty JSON request body requires `content-type: application/json`.
File and Skill uploads instead require `multipart/form-data`; File uploads are
limited to 500 MB and Skill bundles must be smaller than 30 MB. Mango does not
use provider version or beta headers on its inbound API.

Skills routes require `anthropic-beta: skills-2025-10-02`. Creating a Skill or
Skill Version uses `multipart/form-data` and is limited to a bundle smaller
than 30 MB.

Memory routes require `anthropic-beta: agent-memory-2026-07-22`. Do not combine
that header with `managed-agents-2026-04-01` on Memory routes. Session creation
currently uses `managed-agents-2026-04-01` when attaching a Memory Store. These
header names are current implementation details, not compatibility promises.

`authorization: Bearer <key>` may replace `x-api-key`, but sending both is an
authentication error. Each key resolves to exactly one Workspace, and every
key for that Workspace can access the same resources. Workspace IDs are not
added to public request or response bodies.
Each bearer key resolves to exactly one Workspace, and every key for that
Workspace can access the same resources. Workspace IDs are not added to public
request or response bodies.

Mango intentionally has no end-user or role model. A surrounding SaaS may map
many users to a Workspace and apply its own RBAC before calling Mango. Use the
Expand All @@ -94,8 +82,8 @@ mango api-key revoke -id key_...
```

Every response includes a `request-id` header. JSON request bodies are limited
to 32 MiB and unknown top-level fields are rejected. A file upload is limited
to 500 MB and requires configured S3-compatible storage.
to 32 MiB and unknown top-level fields are rejected. File uploads require
configured S3-compatible storage.

## Errors

Expand Down
4 changes: 2 additions & 2 deletions docs/api/skills.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ GET /v1/skills/{skill_id}/versions/{version}/content
DELETE /v1/skills/{skill_id}/versions/{version}
```

Skills routes require configured Files storage and, in strict mode,
`anthropic-beta: skills-2025-10-02`.
Skills routes require configured Files storage and Mango's standard bearer
authentication. Create and Version uploads require `multipart/form-data`.

## Bundle contract

Expand Down
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ model vendor, sandbox backend, or worker topology.
| Package | Responsibility |
| --- | --- |
| `cmd/mango` | Composition root, configuration, process lifecycle |
| `internal/httpapi` | HTTP routes, strict validation, DTO mapping, SSE |
| `internal/httpapi` | HTTP routes, transport validation, DTO mapping, SSE |
| `internal/app` | Shared resource validation and transport-neutral use-case types |
| `internal/blob` | S3-compatible storage for public File bytes and immutable Skill archives |
| `internal/controlplane` | PostgreSQL-backed public Session/Event use cases |
Expand Down
4 changes: 2 additions & 2 deletions docs/architecture/workspace-tenancy.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ tenants without making Mango responsible for a SaaS product's identities.

## Contract

- Every protected HTTP request presents one opaque API key.
- Every protected HTTP request presents one opaque API key as a bearer token.
- The key resolves to exactly one Workspace before a handler runs.
- All keys for a Workspace have equal access to all resources in it.
- A resource from another Workspace behaves as not found; lists are filtered.
- Health, readiness, and the embedded OpenAPI document are public.
- Workspace IDs are internal and never added to CMA request or response bodies.
- Workspace IDs are internal and never added to public request or response bodies.

There are no users, roles, ownership inheritance, per-resource grants, or
permission-policy engine. A SaaS layer may authenticate many users, decide what
Expand Down
6 changes: 3 additions & 3 deletions docs/capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ Mango currently has these product and operational boundaries:
- Drop-in interoperability with a hosted agent service or third-party agent SDK
is not a product goal;
- the API is not stable before the first release;
- the OSS server provides Workspace-scoped API keys and tenant data isolation,
but not end-user identity, roles, per-resource authorization, or enterprise
key lifecycle;
- the OSS server accepts Workspace-scoped API keys through standard bearer
authentication and provides tenant data isolation, but not end-user identity,
roles, per-resource authorization, or enterprise key lifecycle;
- quota, billing, audit, backup, and observability are incomplete;
- Kubernetes and production Compose distributions are not supported;
- local and Docker sandboxes are not safe hostile multi-tenant boundaries.
Expand Down
12 changes: 6 additions & 6 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ routes them to the Temporal worker:
```bash
ENV_ID=$(
curl -sS http://localhost:8080/v1/environments \
-H "x-api-key: $MANGO_API_KEY" \
-H "Authorization: Bearer $MANGO_API_KEY" \
-H 'content-type: application/json' \
-d '{"name":"local","config":{"type":"cloud"}}' |
jq -r .id
Expand All @@ -63,7 +63,7 @@ With `{"type":"self_hosted"}`, built-in calls instead park for a client
```bash
AGENT_ID=$(
curl -sS http://localhost:8080/v1/agents \
-H "x-api-key: $MANGO_API_KEY" \
-H "Authorization: Bearer $MANGO_API_KEY" \
-H 'content-type: application/json' \
-d '{
"name": "Example agent",
Expand All @@ -82,7 +82,7 @@ resolved version and configuration captured at creation time.
```bash
SESSION_ID=$(
curl -sS http://localhost:8080/v1/sessions \
-H "x-api-key: $MANGO_API_KEY" \
-H "Authorization: Bearer $MANGO_API_KEY" \
-H 'content-type: application/json' \
-d "{
\"agent\": \"$AGENT_ID\",
Expand All @@ -97,7 +97,7 @@ SESSION_ID=$(

```bash
curl -sS "http://localhost:8080/v1/sessions/$SESSION_ID/events" \
-H "x-api-key: $MANGO_API_KEY" \
-H "Authorization: Bearer $MANGO_API_KEY" \
-H 'content-type: application/json' \
-d '{
"events": [{
Expand All @@ -112,7 +112,7 @@ agent response is asynchronous. Poll history:

```bash
curl -sS \
-H "x-api-key: $MANGO_API_KEY" \
-H "Authorization: Bearer $MANGO_API_KEY" \
"http://localhost:8080/v1/sessions/$SESSION_ID/events?order=asc" |
jq
```
Expand All @@ -126,7 +126,7 @@ Open the stream before sending the next message:

```bash
curl -N \
-H "x-api-key: $MANGO_API_KEY" \
-H "Authorization: Bearer $MANGO_API_KEY" \
"http://localhost:8080/v1/sessions/$SESSION_ID/events/stream?event_deltas%5B%5D=agent.message"
```

Expand Down
14 changes: 14 additions & 0 deletions docs/provenance.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,20 @@ and self-hosted. Public surface definitions may be design inputs, but external
implementation code and non-public types must not be copied, and an external
release is never an automatic roadmap.

## HTTP transport

- Claude Managed Agents documentation and public SDK behavior informed early
use of `x-api-key`, provider version and beta headers, and a provider-named
worker correlation header.
- Mango uses standard `Authorization: Bearer` authentication and media types,
retains the generic `request-id` response header, and exposes optional worker
correlation as the `worker_id` query parameter. It does not expose provider
rollout headers on its inbound API.
- The Anthropic Messages adapter continues to send the provider headers its
outbound endpoint requires. Tests that exercise Mango through an Anthropic
SDK are optional research evidence; raw HTTP and OpenAPI tests define Mango's
transport contract.

## File-backed Session messages

- The [Managed Agents event API](https://platform.claude.com/docs/en/api/beta/sessions/events)
Expand Down
2 changes: 1 addition & 1 deletion internal/controlplane/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ func TestOfficialGoSDKArchivesPostgresChildThread(t *testing.T) {
server := httptest.NewServer(handler)
t.Cleanup(server.Close)
client := anthropic.NewClient(
option.WithBaseURL(server.URL+"/"), option.WithAPIKey("test-key"),
option.WithBaseURL(server.URL+"/"), option.WithAuthToken("test-key"),
)
archived, err := client.Beta.Sessions.Threads.Archive(
ctx,
Expand Down
4 changes: 0 additions & 4 deletions internal/httpapi/agent_version_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,4 @@ func TestListAgentVersions_ValidatesLimitAndCursorScope(t *testing.T) {
t.Errorf("%s = %d, want 400: %s", query, recorder.Code, recorder.Body)
}
}
if recorder := do(server, http.MethodGet,
"/v1/agents/"+firstAgentID+"/versions?limit=100&beta=true", ""); recorder.Code != http.StatusOK {
t.Fatalf("limit=100 with SDK beta parameter = %d: %s", recorder.Code, recorder.Body)
}
}
2 changes: 1 addition & 1 deletion internal/httpapi/deployment_sdk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func TestOfficialGoSDKDeploymentSurface(t *testing.T) {
server := httptest.NewServer(NewServer(Deps{Deployments: service}, Config{}).Handler())
t.Cleanup(server.Close)
client := anthropic.NewClient(
option.WithBaseURL(server.URL+"/"), option.WithAPIKey("test-key"),
option.WithBaseURL(server.URL+"/"), option.WithAuthToken("test-key"),
)

created, err := client.Beta.Deployments.New(context.Background(), anthropic.BetaDeploymentNewParams{
Expand Down
10 changes: 9 additions & 1 deletion internal/httpapi/environment_work.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,14 @@ func (s *Server) pollEnvironmentWork(w http.ResponseWriter, r *http.Request) {
return
}
var block time.Duration
workerID := ""
if values, present := r.URL.Query()["worker_id"]; present {
if len(values) != 1 || values[0] == "" {
writeError(w, domain.Validation("worker_id must be a non-empty string"))
return
}
workerID = values[0]
}
if values, present := r.URL.Query()["block_ms"]; present {
if len(values) != 1 {
writeError(w, domain.Validation("block_ms must be an integer from 1 through 999"))
Expand Down Expand Up @@ -221,7 +229,7 @@ func (s *Server) pollEnvironmentWork(w http.ResponseWriter, r *http.Request) {
reclaim = &duration
}
work, err := s.deps.EnvironmentWork.Poll(
r.Context(), r.PathValue("environment_id"), r.Header.Get("Anthropic-Worker-ID"),
r.Context(), r.PathValue("environment_id"), workerID,
block, reclaim,
)
if err != nil {
Expand Down
Loading