Metadata-Version: 2.5
Name: open-agent-guide
Version: 0.6.0
Summary: Open Agent Guide submission client and CLI
Project-URL: Documentation, https://www.openagentguide.com/developers/
Project-URL: Homepage, https://www.openagentguide.com
Author-email: "Goshawk Solutions, LLC" <support@openagentguide.com>
License-Expression: MIT
License-File: LICENSE
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.12
Requires-Dist: httpx<0.29,>=0.28.1
Requires-Dist: typer<1,>=0.20
Description-Content-Type: text/markdown

# Open Agent Guide client

`oag` is the command-line client for
[Open Agent Guide](https://www.openagentguide.com). It validates catalog
proposals, submits them, and watches them. Catalog proposals are immutable. The
same `open-agent-guide` package also ships `oag_client`, a typed Python library.
The library runs the same workflows in process. It does not start a subprocess.
It does not need a Django install.

- Distribution `open-agent-guide` on PyPI. Import `oag_client`. CLI `oag`.
- Documentation: <https://www.openagentguide.com/developers/>
- MIT licensed.

## Install

The client supports macOS and Linux on Python 3.12, 3.13, or 3.14.

```sh
pip install open-agent-guide        # library + oag CLI
uv tool install open-agent-guide    # isolated CLI install
```

To upgrade, run `pip install --upgrade open-agent-guide` or
`uv tool upgrade open-agent-guide`. To use the library in a project, run
`uv add open-agent-guide`. The client does not support Windows yet. The client
does not support standalone executables yet.

## Tutorial: getting started

This tutorial shows one first run from start to end. You set your token, then
you validate, submit, and watch a batch.

Set your bearer token. Generate a token in your account settings.

```sh
export OAG_TOKEN=...
```

Validate the batch. Validation calls the server and reports its field-path
errors.

```sh
oag validate batch.json --output json
```

Submit the batch. Write a submit journal with `--journal`. The journal lets you
resume the batch and reconcile it later.

```sh
oag submit batch.json --journal batch.journal.json --output json
```

Watch the submissions. Write a watch journal with `--journal`. Set an overall
timeout. The client polls each unresolved submission until it reaches a terminal
state.

```sh
oag watch --file ids.txt --journal watch.journal.json --timeout 900 --output jsonl
```

`OAG_BASE_URL` defaults to `https://www.openagentguide.com`. Point it at a
staging origin to test. `OAG_BASE_URL` cannot contain credentials. It cannot
contain a path, a query, or a fragment. Bearer tokens never appear in transport
diagnostics. The client does not follow redirects.

## How-to guides

### Submit a batch and watch it

1. Validate the batch: `oag validate batch.json --output json`.
2. Submit the batch with a journal:
   `oag submit batch.json --journal batch.journal.json --output json`.
3. Watch the submissions with a journal and a timeout:
   `oag watch --file ids.txt --journal watch.journal.json --timeout 900 --output jsonl`.

Use one pacing directory for concurrent validate, submit, and watch commands.

### Explain a rejection

Run `status` or `watch` with `--output explain` to read why a submission needs
work.

```sh
oag status 00000000-0000-0000-0000-000000000001 --output explain
```

`--output explain` (`-o explain`) drops the full record dump. It prints a
compact, line-based explanation of each result. The explanation shows the
lifecycle state, the next-action guidance, the terminal resolution, and any
successor IDs. For each failed or errored check, it shows the headline summary.
Under each summary, it shows every unsupported claim with its verdict, its
specific reason, and its citation URLs. It ends each check with its decision
basis. The view keeps the specific claim-level reason that a repair depends on
in view. It does not show only the broad diagnostic category. The view reads
only the fields that the read API already returns to you. These fields are
`guidance`, `resolution`, and the scrubbed `check_feedback` projection. The view
makes no extra server call. The `json` and `jsonl` contracts stay unchanged. The
journal format stays unchanged.

### Retire a submission

`retire` withdraws or supersedes one of your open submissions.

- To withdraw the proposal, pass no `--successor` option.
- To supersede the proposal, pass one or more `--successor UUID` options. Each
  option names an explicit newer replacement.
- To record an optional reason, pass `--reason TEXT`.

```sh
oag retire 00000000-0000-0000-0000-000000000001 --force --reason "stale"
oag retire 00000000-0000-0000-0000-000000000001 --force --successor UUID --output json
```

`retire` requires `--force`. Without `--force`, it refuses, sends nothing, and
exits 2. A success reports the server's disposition (`withdrawn` or
`superseded`) and exits 0. The command does not poll afterward. Use `status` or
`watch` to follow the retired submission and its successors. See the retire
entry under [Reference](#retire) for the full behavior and the server rules.

### Recover after a timeout and reconcile a journal

A watch timeout does not mean a submission failed. Resume its journal. Inspect
the lifecycle and the terminal evidence.

Reuse the original submit journal and idempotency keys after an uncertain
create. Never start a duplicate batch to beat a deadline.

A pacing timeout reports the lane, the reserved wait, the remaining budget, and
the requests that the client sent. Submit journals keep this diagnostic with the
original idempotency key. Watch adds `deadline_error` to unresolved records and
saves it in the watch journal. Watch keeps earlier read errors and submission
snapshots. Resume the same journal with a larger budget after you investigate
shared load. Never clear the pacing file to skip a cooldown. Never delete the
journal to retry uncertain submissions.

Some older entries have no keys. The client cannot replay them safely. Inspect
your submissions on the server across all statuses. Compare the complete payload
and the submitter. Once you positively identify an ID, associate it with the
input row:

```sh
oag reconcile batch.json --journal batch.journal.json --row 2 --id UUID
```

This checks that the ID is readable. You verify the row-to-ID match. It does not
create or edit a server submission. If no match turns up, absence is not proof
of failure. Reconcile with an administrator before you create a new batch and
journal. A server `409 idempotency_key_reused` also needs inspection. Do not
mint a fresh key for the uncertain write. The effective callback URL must stay
the same for a retry. A change to a token's default callback can cause that
conflict.

For a definitively failed row, correct the payload. Submit a new batch with a
new journal. Existing submissions are immutable. To supersede a stalled open
create, inspect the existing submission. Then set `"force": true` in a new
payload and submit with a new journal. The client never adds `force`. The client
never edits an existing proposal. Keep prior journals for audit.

### Research a product's coverage gaps

Run `oag coverage PRODUCT --output json` for one compact report of a product's
coverage and its unknown scored fields. The default text output adds evidence
guidance. The equivalent reads are
`GET /api/v1/catalog/products/{uuid-or-slug}/coverage/` and the MCP tool
`get_product_coverage`. Read each gap's shared semantics, its schema reference,
and its absence page class before you draft. Keep unsupported values unknown. A
confirmed absence can improve coverage without improving the AI-native score.
Use submission validation to inspect projected changes after you collect
evidence.

### Share pacing across workers

The `oag` CLI shares reservations and Retry-After cooldowns across processes on
one host. The processes must use the same pacing directory, the same API origin,
and the same token. The population benchmark and the pi submitter opt into the
same coordination. Python `Client` instances pace in process unless you pass
`shared_pacer(base_url, token)`.

The default directory is `oag-client-pace` under the system temp directory. Set
`OAG_PACING_DIR` to choose another directory. Cooperating processes must share
that directory, that base URL, and that token. The filename hashes the origin
and a one-way token fingerprint. The file stores timestamps. The file never
stores the raw token. A crash releases the file lock but keeps its recorded
reservations. Separate directories, hosts, origins, or tokens do not share
state. Server quotas apply account-wide across all of an account's tokens, so
coordinate those workers yourself. Shared pacing cuts collisions. It does not
guarantee that requests dodge throttling.

A transient 429 extends the shared cooldown to the longest value that any
cooperating process reports. Workers recheck the cooldown after they wait for a
reserved slot. The client honors numeric and HTTP-date Retry-After values within
the workflow deadline, across reads and mutations together. Workers outside the
shared state must pause every worker on the exhausted `X-RateLimit-Scope` for at
least `Retry-After` seconds, with jitter, before they resume. The scopes are
`global_auth` for reads, and `submission` and `submission_burst` for submission
mutations. The open-submission-cap 429 returns immediately without a retry.

### Use trusted bulk read pacing

Default read spacing is 0.55 seconds. A maintainer can grant your account the
`submissions.bulk_submit_catalogsubmission` permission. With that permission,
set `OAG_READ_SPACING=0.11` for the CLI, or pass `read_spacing=0.11` to
`shared_pacer(base_url, token, ...)`. That targets about 545 reads per minute
under the server's default 600-per-minute trusted read quota
(`BULK_AUTHENTICATED_READ_RATE`). Use the same spacing, the same token, and the
same `OAG_PACING_DIR` for coordinated workers on one host. Keep status reads
batched through `watch`. Separate tokens for one account share the quota but
need caller-managed coordination. Restore 0.55 seconds after a revocation. The
server checks permission on each request. Clients keep honoring shared
Retry-After cooldowns. Create and validate spacing stays 3.5 seconds. Review,
merge, close, and retire writes keep a separate 0.55-second minimum even with
faster reads. Custom `Pacer.reserve` implementations must accept
`global_mutation=False`.

## Reference

### Command synopsis

```sh
oag --version
oag validate batch.json --output json
cat batch.json | oag validate - --output jsonl
oag submit batch.json --journal batch.journal.json --output json
oag status 00000000-0000-0000-0000-000000000001 --output json
oag status 00000000-0000-0000-0000-000000000001 --output explain
oag watch --file ids.txt --journal watch.journal.json --timeout 900 --output jsonl
oag watch 00000000-0000-0000-0000-000000000001 --journal watch-one.json
oag retire 00000000-0000-0000-0000-000000000001 --force --reason "stale"
oag retire 00000000-0000-0000-0000-000000000001 --force --successor UUID --output json
oag get /api/v1/catalog/products/?page_size=50
oag get https://www.openagentguide.com/api/v1/catalog/products/?page=2
```

`oag --version` (`-V`) prints the installed version and exits 0.

#### validate and submit

`validate` and `submit` take one JSON object or an array. The input comes from a
file or from `-` for stdin. Nested relation envelopes and explicit nulls pass
through unchanged. Omitted keys stay omitted. Validation calls the server and
returns its field-path errors. It does not probe external citation URLs. The
batch preflight script still covers that separate check.

#### status and watch

`status` fetches one submission with all its checks and reviews pages. `watch`
polls unresolved IDs in batches of up to 100
(`ids=...&page_size=100&fields=id,status,review_ready,check_feedback,guidance`).
Pending records hold that compact snapshot. When an ID goes terminal, watch
fetches its full detail and every checks and reviews page. Watch keeps claim
verdicts, rationales, citations, and resolution reasons.

Only lifecycle `merged` counts as `accepted`. An ordinary `closed` is
`rejected`. Closed proposals with a retirement disposition report `withdrawn` or
`superseded`. Replacement IDs appear in `resolution.closed.successor_ids`. A
check error on an open submission stays `pending`. A failed detail read or
evidence read becomes `failed-to-read`, even after a terminal lifecycle was
seen. Watch retries unresolved records in later rounds. Watch stops revisiting a
terminal record only after it captures the record's evidence. The journal keeps
those terminal snapshots across restarts. Start a fresh journal for a fresh view
of later evidence. Missing batch IDs stay `failed-to-read`. The server must
support the `ids` filter.

#### retire

`retire` withdraws or supersedes one of your open submissions. With no
`--successor` option it withdraws the proposal. One or more `--successor UUID`
options supersede it with those explicit newer replacements. `--reason TEXT`
records an optional reason. `retire` makes one request. It never retries. It
never runs on its own. `--force` is required. Without `--force`, `retire`
refuses, sends nothing, and exits 2. The tool never withdraws or supersedes on
your behalf. The server enforces author-or-curator permission. The server
enforces newer-only successors. A success reports the server's disposition
(`withdrawn` or `superseded`) and exits 0. A 403 or a validation error exits 3.
The command does not poll afterward. Use `status` or `watch` to follow the
retired submission and its successors.

#### get

`get PATH` reads one catalog or submissions read-API path through the shared
read pacing lane and prints the JSON body. `PATH` is a relative read-API path or
a same-origin absolute URL. You can pass a pagination `next` link straight back
without stripping its origin. The client accepts an absolute `PATH` only when
its scheme and host match `OAG_BASE_URL` with no userinfo. A foreign origin,
userinfo, an unsupported scheme, or a path outside the catalog or submissions
read API exits 2 without a request. Catalog reads work anonymously. The
submissions field contract needs `OAG_TOKEN`.

#### Reading guidance

`status` and `watch` request `guidance` and keep its `next_action` code. For
direct API reads, request `guidance` alongside `status`, `resolution`, and
`check_feedback`. The codes:

- `wait`: keep watching.
- `prepare_correction`: prepare the repair now, but wait for terminal closure
  before you submit it.
- `revise_and_resubmit`: inspect the rejection, correct the proposal, then
  validate against the current catalog.
- `none`: read the terminal resolution, including any successor links.

Guidance never reserves an identity. Guidance never bypasses create conflicts.
If guidance is absent, fall back to lifecycle status. Never infer rejection from
a check error. Preserve your submit and watch journals.

### Output formats

Every command takes `--output text|json|jsonl|explain`. JSON holds `version`,
`records`, and `summary`. JSONL emits one `type: record` line per result, then a
`type: summary` line. Output lands at completion, including watch cancellation.
Watch also emits changed next-action guidance to stderr as it runs. Text prints
the full record with indented evidence. Then text lists any check feedback and
absence-attestation guidance, one scannable line each. You see why a row needs
work without digging through the JSON. Diagnostics go to stderr. No command
prompts.

A failed read keeps the server's structured error. Every `failed-to-read` record
carries a `failure` object with a `kind`. The `kind` is `http`, `deadline`,
`transport`, or `client`. An `http` failure also carries `http_status`, plus the
server's `code`, `detail`, and validation `errors` when present. The
human-readable `error` string stays too. `coverage` wraps the same object in a
`{"version": 1, "error": {...}}` envelope on failure and exits 3. Its successful
report is unchanged.

### Exit codes

| Exit | Meaning                                                              |
| ---- | -------------------------------------------------------------------- |
| 0    | Validation/submission succeeded, or all watched records are terminal |
| 2    | Invalid arguments, configuration, input, or journal                  |
| 3    | At least one failed/ambiguous batch row or failed-to-read record     |
| 4    | Pending records remain at the deadline or in one-shot status         |
| 5    | All records were read, but `--require-all-merged` found a rejection  |
| 130  | Cancelled by Ctrl-C                                                  |

Failures outrank pending records. Pending records outrank
`--require-all-merged`. By default a fully read rejection exits 0, because
watching finished. Pass `--require-all-merged` to status or watch to require
acceptance. Summaries always separate accepted, rejected, pending, and
failed-to-read. Batch summaries also count valid, created, failed, and ambiguous
rows.

### Pacing and deadlines

`--timeout` is an overall seconds budget. It is 300 for validate and submit, 60
for status, and 900 for watch. It covers pacing waits, requests, retries, and
cooldowns. Watch pauses 15 seconds between rounds. Ctrl-C ends watch with a
final summary and exit 130. Its journal can resume. A hard kill cannot emit a
summary.

The client spaces reads 0.55 seconds apart and mutations 3.5 seconds apart.
Validation POSTs use the mutation pace. Requests have a 45-second budget, capped
by the remaining workflow deadline. Validation and keyed creates retry transport
failures, 5xx, and transient 429s, up to five attempts. Unkeyed writes retry
only transient 429s. Polling makes one attempt per round.

Future mutation reservations leave the read schedule unchanged. Create and
validate use the mutation lane. Globally throttled writes such as review, merge,
close, and retire share the read lane. If a peer extends Retry-After during a
wait, the worker reserves a fresh slot to keep spacing after the cooldown.
Cancelled or expired reservations stay on their lane until their slots pass.
Restart preserves them.

### Sizing batches

Size batches against the combined workload. Fifty validation POSTs followed by
fifty create POSTs need about 350 seconds of mutation spacing alone, before
network time, retries, cooldowns, or other workers' reservations. Those are
separate command budgets. Raise `--timeout` per command for its share of the
work. Leave extra time for watch to capture terminal evidence. More concurrent
processes do not raise shared throughput. Prefer smaller batches, fewer
simultaneous validators and submitters, and one watcher per account that holds
all unresolved IDs. Use one pacing directory for concurrent validate, submit,
and watch commands.

### Environment variables

- `OAG_TOKEN`: the bearer token. Generate one in your account settings. Catalog
  reads work without it. The submissions field contract needs it.
- `OAG_BASE_URL`: the API origin. It defaults to
  `https://www.openagentguide.com`. It cannot contain credentials, a path, a
  query, or a fragment.
- `OAG_PACING_DIR`: the pacing directory. It defaults to `oag-client-pace` under
  the system temp directory. Cooperating processes must share it.
- `OAG_READ_SPACING`: the read spacing in seconds. The default is 0.55. Set it
  to 0.11 with the `submissions.bulk_submit_catalogsubmission` permission.

### Python API

```python
from oag_client import Client, shared_pacer
from oag_client.transport import HTTPTransport
from oag_client.workflows import payloads, validate

base_url = "https://www.openagentguide.com"
# Omit `pacer` for in-process pacing; pass `shared_pacer` to coordinate with
# other processes on this host that share the token.
client = Client(HTTPTransport(token), base_url, pacer=shared_pacer(base_url, token))
records = validate(client, payloads(json_text), deadline=client.clock.monotonic() + 60)
```

`submit`, `status`, `watch`, `summary`, and `exit_code` also come from
`oag_client.workflows`. `Journal` lives in `oag_client.journal`. The `Clock` and
`Transport` protocols make testing deterministic. A custom transport must accept
the optional `idempotency_key` keyword and forward it as `Idempotency-Key` on
create requests. The HTTP transport is synchronous, so async callers should use
a worker thread. Application code supplies authorization. Pacing is in process
by default. Pass `shared_pacer(base_url, token)` (or any `Pacer`) to coordinate
processes that share a directory, an origin, and a token on one host. Coordinate
other tokens or hosts yourself, because server quotas cover the whole account.

## Explanation

### How journals and idempotency keys keep submissions safe to retry

Always reuse the same submit journal for a batch. The journal binds the ordered
input hash, the base URL, and the token fingerprint. Changed inputs,
environments, or tokens fail instead of replaying. The journal never stores
tokens. The journal holds server evidence and may hold private submission data,
so keep it private.

Each new row gets a random UUID `Idempotency-Key`. The client flushes the key to
the journal before the first POST. Every attempt and restart reuses that key and
the same input. A confirmed `201` records the original submission ID as
`created`, even if the server already merged or closed it. The client skips
confirmed and definitively failed rows on restart. Keyed ambiguous or pending
rows retry within the command's deadline. Exhausted transient failures stay
unresolved.

Journal writes are atomic and flushed to disk. A lock prevents simultaneous
writers. After a hard kill, confirm that no writer remains before you delete the
`.lock` file. Never delete a journal to recover from a timeout. Never change a
journal's keys to recover from a timeout. A deadline or cancellation after an
uncertain attempt preserves its key.

The client uses semantic versioning. JSON output stays version 1. New and saved
journals use version 2. Version-1 journals stay readable, but unkeyed uncertain
rows need manual reconciliation. Older clients reject version-2 journals. An
upgrade alone never sends requests and never rewrites journals. Running `submit`
resumes keyed uncertain rows. Pair the client with a server that supports the
create `Idempotency-Key` contract.

### How shared pacing and deadlines work

The client spaces reads and mutations to stay under the server's quotas. The
`oag` CLI shares reservations and Retry-After cooldowns across processes on one
host when the processes use the same pacing directory, API origin, and token.
The filename hashes the origin and a one-way token fingerprint, so separate
directories, hosts, origins, or tokens do not share state. The file stores
timestamps, never the raw token. A crash releases the file lock but keeps its
recorded reservations, so a restart preserves them.

A transient 429 extends the shared cooldown to the longest value that any
cooperating process reports. The client honors numeric and HTTP-date Retry-After
values within the workflow deadline, across reads and mutations together.

Shared pacing cuts collisions. It does not guarantee that requests dodge
throttling. Server quotas apply account-wide across all of an account's tokens,
so coordinate separate tokens and separate hosts yourself. More concurrent
processes do not raise shared throughput. The open-submission-cap 429 returns
immediately without a retry.

A watch timeout does not mean a submission failed. Resume the journal and
inspect the lifecycle and the terminal evidence. The journal and the idempotency
keys make an uncertain create safe to retry without a duplicate.
