> ## Documentation Index
> Fetch the complete documentation index at: https://docs.metaphi.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Twin traces

> Turn an agent trace into a synthetic twin: every person, private organisation, account and secret replaced with a consistent synthetic value, the structure and format kept.

Kennet makes a synthetic twin of an agent trace for you to keep, share or train on. Each real value is replaced at every place it occurs with one consistent synthetic value of the same kind, so the twin still parses, references still resolve, and a name that appears forty times is the same new name forty times.

One trace file in, one twinned file out, in the same format. Nothing else comes back: no table of what became what, no report.

Run it from the `metaphi` command line, or from your own agent (Claude Code, Codex, Cursor, Hum, or any MCP client).

## From the command line

Install the CLI and make an organization key once:

```sh theme={"dark"}
brew install metaphi-labs/tap/metaphi
metaphi login
metaphi keys create prod --save
```

Then twin a trace:

```sh theme={"dark"}
metaphi twin trace.json        # writes trace.twin.json
```

The twin is written to `<name>.twin<ext>` beside the input, or where `-o` says. From a server, set `METAPHI_API_KEY` to an organization key and add `--json` to print `{"output"}` on stdout.

| Flag | What it does |
| - | - |
| `-o, --output` | Where the twin goes. Default: `<name>.twin<ext>` |
| `--key <64 hex>` | Keys the synthetic values. The same key gives one person the same synthetic value in every trace of a corpus. Default: a fresh key per trace |
| `--confidential <org>` | Treat an organisation as confidential although public, such as your client; it is replaced with everything that names it. Repeatable |

## From your agent

### 1. Connect

Add the server to your agent. In Claude Code:

```
claude mcp add --transport http kennet https://kennet.metaphi.ai/mcp/
```

Any MCP client takes the same URL: `https://kennet.metaphi.ai/mcp/`.

### 2. Sign in

Authenticate the server from your agent (in Claude Code: `/mcp`, choose `kennet`, then Authenticate). Your browser opens on `app.metaphi.ai`. Sign in with Google or GitHub.

The first time, your account has no access yet: choose **Request access**. You get an email when it is approved; then authenticate again and choose **Allow**. Access is the `kennet:twin` scope.

Your agent now lists five tools: `upload_trace`, `twin_trace`, `pull_trace`, `get_task` and `cancel_task`.

### 3. Twin a trace

Ask your agent in plain words, from the directory that holds the file:

```
Twin trace.json with kennet and save the result here.
```

It makes three calls. They are shown so you know what happens.

```json theme={"dark"}
upload_trace {"filename": "trace.json"}
```

Returns a `curl` command and an `upload_id`. Your agent runs the command in your shell, which uploads the file. The URL answers for 10 minutes. A trace is JSON, JSON Lines or text.

```json theme={"dark"}
twin_trace {"upload_id": "<upload id>"}
```

Returns a task. Poll `get_task` until it ends; a trace takes a few minutes. The result carries `output_path`, or the reason the run failed.

```json theme={"dark"}
pull_trace {"trace_path": "<output_path>"}
```

Returns a `curl` command that downloads the twin into your working directory.

`twin_trace` takes the same options as the command line: `key` (64 hex characters) and `confidential` (a list of organisations).

```json theme={"dark"}
twin_trace {"upload_id": "<upload id>", "key": "<64 hex characters>",
            "confidential": ["Acme Holdings"]}
```

An upload takes one run. To run a trace again, upload it again.

## What is replaced

| Kind | What happens |
| - | - |
| People | Always replaced: names, emails, phone numbers, addresses, handles |
| Private organisations | Replaced, with everything that names them |
| Accounts and identifiers | Replaced with a value of the same shape |
| Secrets | Replaced: keys, tokens, passwords |
| Listed public companies | Kept, with their brands, subsidiaries and tickers, unless you name them confidential |

After the replacement a second reader, shown only the twin, looks for anything still real, and one repair round follows.

Keep the key if you will twin more traces of the same corpus later. Kennet does not keep it.

## Credits

Each completed trace draws your organization's prepaid credit. A run that fails costs nothing. When the balance reaches zero, new runs are refused until credit is added (`metaphi credits`).

## What Kennet keeps

* Your upload is deleted when its run ends, whether it completed or failed. An upload never run is deleted after a day.
* The twin stays under your account for 7 days; nobody else's account reaches it.
* The table of what became what is neither returned nor stored.

## Errors

Every error carries a stable `code`. Switch on the `code`, never on the message text: messages may be reworded, but a code is never renamed or reused.

A refused call comes back flagged `isError`. Its text is the message, and its structured content is the error object:

```json theme={"dark"}
{"error": {"type": "billing_error", "code": "out_of_credit",
           "message": "This organization's prepaid balance is used up; add credits to continue. Nothing was started.",
           "retryable": false, "request_id": "req_5f1c0a9e2b7d4c3a8e6f1b20"}}
```

A run that fails comes back through `get_task` as `{run_id, status: "failed", error}`. The `error` is the same object, with the `run_id` added.

`retryable` says whether the same call, made again unchanged later, may succeed. For a run error, it means a fresh upload of the same trace may succeed. Quote the `request_id` when you contact support.

| code | type | retryable | meaning |
| - | - | - | - |
| `out_of_credit` | `billing_error` | no | The organization's prepaid balance is used up. |
| `no_organization` | `auth_error` | no | The sign-in is not billed to an organization; use an organization API key. |
| `no_caller` | `auth_error` | no | The call carries no signed-in caller. |
| `caller_not_allowed` | `auth_error` | no | This sign-in cannot own trace files. |
| `balance_unavailable` | `api_error` | yes | The balance could not be checked; nothing was started. |
| `rate_limited_concurrent` | `rate_limit_error` | yes | The organization has the most runs going at once. |
| `rate_limited_daily` | `rate_limit_error` | yes | The organization has started the most runs a day takes. |
| `invalid_key` | `invalid_request_error` | no | key is not 64 hex characters. |
| `invalid_confidential` | `invalid_request_error` | no | confidential names too many, or too long, names. |
| `invalid_filename` | `invalid_request_error` | no | The file name is not one the upload takes. |
| `invalid_upload_id` | `invalid_request_error` | no | upload\_id is not one upload\_trace returned. |
| `upload_not_found` | `invalid_request_error` | no | Nothing was uploaded under upload\_id; run the upload command first. |
| `upload_already_run` | `invalid_request_error` | no | The upload has had its one run; upload the trace again. |
| `invalid_output_path` | `invalid_request_error` | no | trace\_path is not an output\_path a twin task returned. |
| `output_not_found` | `invalid_request_error` | no | Nothing is at that output\_path: it expired or never existed. |
| `task_not_found` | `invalid_request_error` | no | No such task is held for the caller. |
| `unavailable` | `api_error` | yes | The request could not be carried out; nothing was started or charged. |
| `upload_gone` | `run_error` | yes | The upload expired before the run read it; upload it again. |
| `not_text` | `run_error` | no | The trace is not UTF-8 text or JSON. |
| `too_large` | `run_error` | no | The trace has more distinct text than one run reads; split it. |
| `session_failed` | `run_error` | yes | A session did not complete; nothing was written or charged. |
| `budget_exceeded` | `run_error` | yes | The run reached its spending limit; nothing was written or charged. |
| `run_failed` | `run_error` | yes | The run failed after its sessions; nothing was written or charged. |
| `twin_unclean` | `run_error` | no | The twin did not pass its check; nothing was written or charged. |
| `run_unavailable` | `api_error` | yes | The run could not be carried out; nothing was written or charged. |

The command line raises one class per `type` (`BillingError`, `RateLimitError`, and so on) and prints `error: <message> (code: <code>, request: <request_id>)`. With `--json`, it also prints the error object on stdout.

## When something goes wrong

| You see | Do this |
| - | - |
| "does not have access" on the sign-in page | Choose **Request access** and wait for the email |
| The sign-in is not billed to an organization | Runs are billed to an organization: have its owner add you (`metaphi members add`), or run `metaphi twin` with the organization's key |
| The prepaid balance is used up | Add credit, then run again |
| The upload URL has expired | Call `upload_trace` again; a URL answers for 10 minutes |
| The task ends `failed` | Read the reason in the result, then upload the trace again and rerun it |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.