Metadata-Version: 2.5
Name: open-agent-guide
Version: 0.5.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` 0.3.1 requests guidance automatically. For direct API reads, request
`guidance` alongside `status`, `resolution`, and `check_feedback` when reading
your submissions. `guidance.next_action=wait` means keep watching;
`prepare_correction` means prepare the repair now but wait for terminal closure
before submitting it. `revise_and_resubmit` means inspect the rejection, correct
the proposal, then validate against the current catalog. `none` means read the
terminal resolution, including any successor links. Guidance never reserves an
identity or bypasses create conflicts. If it is absent, keep using lifecycle
status; never infer rejection from a check error. Preserve submit/watch
journals.

`oag` validates, submits, and watches immutable catalog proposals. The Python
library runs the same workflows without a subprocess or Django installation.

## Install and upgrade

Supported platforms are macOS and Linux with Python 3.12, 3.13, or 3.14. Obtain
the versioned wheel from your administrator, then install it:

```sh
uv tool install --force ./open_agent_guide-*.whl
# Upgrade explicitly when given a newer wheel:
uv tool install --force ./open_agent_guide-*.whl
```

For library use, `uv add ./open_agent_guide-*.whl`. Maintainers build with
`uv build --package open-agent-guide` and can install from
`./packages/oag-client`. CI smoke-tests clean wheel installation on both
supported operating systems. The package is MIT-licensed. Public PyPI
publication as `open-agent-guide` is planned; until then contact
<support@openagentguide.com> for distribution. Windows and standalone
executables are not supported yet.

Package versions follow semantic versioning. JSON output remains version 1; new
and saved journals use version 2. Version-1 journals remain readable, but
unkeyed uncertain rows require manual reconciliation. Older clients reject
version-2 journals. Upgrading alone never sends requests or rewrites journals;
explicitly running `submit` resumes keyed uncertain rows. Use oag-client 0.2.0
or newer with a server that supports the create `Idempotency-Key` contract.

## Commands

Export `OAG_TOKEN` with your bearer token. `OAG_BASE_URL` defaults to
`https://www.openagentguide.com`; set it to your staging origin for testing. The
base URL cannot contain credentials, a path, query, or fragment. Bearer tokens
never appear in transport diagnostics. Redirects are not followed.

```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 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
```

`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, so a pagination `next` link can be passed straight
back without stripping its origin. An absolute `PATH` is accepted 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`.

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

`validate` and `submit` accept one JSON object or an array, from a file or `-`
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. Validation does not probe external citation URLs; the
existing batch preflight script remains available for that separate check.

Every command accepts `--output text|json|jsonl`. JSON contains `version`,
`records`, and `summary`; JSONL emits one `type: record` line per result
followed by a `type: summary` line. Output is emitted at completion, including
watch cancellation. Version 0.3.1 emits changed next-action guidance to stderr
during watch. Text includes the complete record with indented evidence, then
lists any check feedback and absence-attestation guidance as one scannable line
each so a reader sees why a row needs work without searching the JSON.
Diagnostics go to stderr. No command prompts.

`status` fetches one submission with all checks and reviews pages. In 0.3.1,
`watch` polls unresolved IDs in batches of up to 100 using
`ids=...&page_size=100&fields=id,status,review_ready,check_feedback,guidance`.
Pending records contain this compact submission snapshot. When an ID becomes
terminal, watch fetches its full detail and all checks and reviews pages,
preserving claim verdicts, rationales, citations, and resolution reasons.
Missing batch IDs remain `failed-to-read`. The server must support the `ids`
filter. Only lifecycle `merged` means `accepted`; ordinary `closed` means
`rejected`. Closed proposals with retirement dispositions report `withdrawn` or
`superseded`, with replacement IDs in `resolution.closed.successor_ids`. A check
error on an open submission remains `pending`. A failed detail or evidence
request means `failed-to-read`, including when a terminal lifecycle has been
seen. Watch retries unresolved records in later rounds and stops revisiting
terminal records only after capturing their evidence. The journal preserves
those terminal snapshots on restart. Use a fresh journal for a fresh view of
later evidence.

`retire` withdraws or supersedes one of your open submissions through the
server's retirement operation. With no `--successor` it withdraws the proposal;
one or more `--successor UUID` options supersede it with those explicit newer
replacement IDs, and `--reason TEXT` records an optional reason. The command
makes one request and never retries or runs automatically. `--force` is
required: without it `retire` refuses, makes no request, and exits 2, so the
tool never withdraws or supersedes on your behalf. The server still enforces
author-or-curator permission and newer-only successors. A successful retire
reports the server's terminal disposition, `withdrawn` or `superseded`, and
exits 0; a 403 or validation error exits 3. The command does not poll afterward;
use `status`/`watch` to observe the retired submission and its successors.

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

## Pacing and workflow deadlines

`--timeout` is an overall seconds budget, default 300 for validate/submit, 60
for status, and 900 for watch. It includes 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 be resumed. Hard kills cannot emit a
summary.

The client spaces reads by 0.55 seconds and mutations by 3.5 seconds. Validation
POSTs use the mutation pace too. The `oag` CLI shares reservations and
Retry-After cooldowns across processes on one host that use the same pacing
directory, API origin, and token. The maintained population benchmark and pi
submitter also opt into this coordination. Python `Client` instances use local
pacing unless you pass `shared_pacer(base_url, token)`.

The default directory is `oag-client-pace` under the system temp directory;
`OAG_PACING_DIR` selects another directory. All cooperating processes must reach
the same directory and use the same base URL and token. The filename hashes the
origin and a one-way token fingerprint; the file stores timestamps, never the
raw token. A process crash releases the file lock, but its recorded reservations
remain. Separate directories, hosts, origins, or tokens do not share this state.
Server quotas apply account-wide across all of an account's tokens, so
coordinate those workers yourself. Shared pacing reduces collisions; it does not
guarantee that requests avoid throttling.

A transient 429 extends the shared cooldown, keeping the longest cooldown
reported by any cooperating process. Workers check it again after waiting for a
reserved slot. Numeric and HTTP-date Retry-After values are honored within the
workflow deadline. This cooldown applies to reads and mutations together. For
workers outside that shared state, pause every worker using the exhausted
`X-RateLimit-Scope` for at least `Retry-After` seconds and add jitter before
resuming. The scopes are `global_auth` for reads, and `submission` and
`submission_burst` for submission mutations. The open-submission-cap 429 returns
immediately without retrying.

Size batches against the combined workload. Fifty validation POSTs followed by
fifty create POSTs require about 350 seconds of mutation spacing alone, before
network time, retries, cooldowns, or other workers' reservations. Those are
separate command budgets: increase `--timeout` for each command based on its
share of the work, and allow additional time for watch to capture terminal
evidence. More concurrent processes do not increase shared throughput. Prefer
smaller batches, fewer simultaneous validators/submitters, and one watcher per
account with all unresolved IDs. Use the same pacing directory for concurrent
CLI validation, submission, and watch commands.

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 preserve spacing after the cooldown.
Cancelled or expired reservations remain on their lane until their slots pass;
restart preserves those reservations and cooldowns. Watch budgets also need time
for server processing, poll intervals, and terminal evidence reads across all
watchers. Shared pacing neither bounds outstanding server work nor eliminates
deadline failures. A watch timeout does not mean a submission failed: resume its
journal and inspect lifecycle and terminal evidence. Reuse the original submit
journal and idempotency keys after an uncertain create; never start a duplicate
batch to escape a deadline.

Requests have a 45-second total budget capped by the remaining workflow
deadline. Validation and keyed creates retry transport failures, 5xx, and
transient 429 responses, at most five attempts. Unkeyed writes retry only
transient 429 responses. Exhausted HTTP failures retain `http_status` alongside
the response. Polling makes one attempt per round. Keep terminal evidence in the
journal and remove those IDs from future polls.

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

| 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 take precedence over pending records, which take precedence over
`--require-all-merged`. By default, a fully read rejection exits 0: watching has
completed. Use `--require-all-merged` on status/watch to require acceptance.
Summaries always distinguish accepted, rejected, pending, and failed-to-read;
batch summaries also count valid, created, failed, and ambiguous rows as
present.

## Resume and reconcile

Always reuse the same submit journal for the same batch. It binds the ordered
input hash, base URL, and token fingerprint; changed inputs, environments, or
tokens fail rather than replay. Tokens themselves are never stored. The journal
contains server evidence and may contain private submission data. Keep it
private.

Each new row gets a random UUID `Idempotency-Key`, flushed to the journal before
its 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 has already merged or closed it. Confirmed and definitively failed rows
are skipped on restart. Keyed ambiguous or pending rows are retried within the
command's deadline. Exhausted transient failures remain unresolved.

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

Older entries without keys cannot be replayed safely. Inspect your submissions
on the server, including all statuses, and compare the complete payload and
submitter. After positively identifying 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; the operator verifies the row-to-ID match.
It does not create or edit a server submission. If no match can be found,
absence is not proof of failure. Reconcile with an administrator before creating
a new batch and journal. A server `409 idempotency_key_reused` also requires
inspection, not a fresh key for the uncertain write. The effective callback URL
must remain unchanged for a retry; changing a token's default callback can cause
that conflict.

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

## 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` are also available from
`oag_client.workflows`; `Journal` lives in `oag_client.journal`. `Clock` and
`Transport` protocols allow deterministic testing. Custom transports must accept
the optional `idempotency_key` keyword and forward it as `Idempotency-Key` for
create requests. The HTTP transport is a synchronous entry point; 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 using the same directory, origin, and token on one host.
Coordinate other tokens or hosts yourself because server quotas cover the whole
account.

### Trusted bulk read pacing

The default read spacing is 0.55 seconds. If a maintainer has granted your
account `submissions.bulk_submit_catalogsubmission`, set `OAG_READ_SPACING=0.11`
for the CLI, or pass `read_spacing=0.11` to
`shared_pacer(base_url, token, ...)`. This targets about 545 reads/minute under
the server's default 600/minute trusted read quota. The server setting is
`BULK_AUTHENTICATED_READ_RATE`.

Use the same spacing, token, and `OAG_PACING_DIR` for coordinated workers on one
host. Keep status reads batched through `watch`; more quota doesn't require
polling each submission separately. Separate tokens for one account share the
server quota but need caller-managed pacing coordination. After permission
revocation, restore 0.55 seconds. The server checks permission on each request
and clients continue to honor shared Retry-After cooldowns.

Create/validate spacing stays at 3.5 seconds. Review, merge, close, and retire
writes keep a separate 0.55-second minimum interval even with faster reads.
Custom `Pacer.reserve` implementations must accept `global_mutation=False`.

## Research an existing product's gaps

Use `oag coverage PRODUCT --output json` for one compact report of the product's
coverage and unknown scored fields. The default text output includes 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, schema reference, and
absence page class before drafting. Keep unsupported values unknown. A confirmed
absence can improve coverage without improving the AI-native score. Use
submission validation to inspect projected changes after collecting evidence.
