Operational guidance coverage
The Guides' recommendations (SHOULD), kept apart from the specification. Upstream: personalagentprotocol.org/docs/guides.
41 entries: 32 implemented, 4 partial, 5 open. Each row is one atomic statement of the upstream text, with the SDK symbols that implement it (Python; TypeScript mirrors them), the tests and shared conformance cases that show it, and its status.
| ID | Section | Level | Role | Requirement | SDK symbol | Tests | Status |
|---|---|---|---|---|---|---|---|
GUIDE-secrets-01 | Handling secrets | SHOULD | both | Keep them out of logs, telemetry, error reports, model context, conversation messages, and URLs. | PersonalAgentProtocolError | test_accounts.py::test_no_error_carries_the_account_tokentest_sessions.py::test_session_error_never_includes_remote_error_descriptiontest_conformance.py::test_python_agent_python_company_over_tlssecurity.refusals-disclose-no-credential | partial: Errors and results are credential-free; logs and traces are not yet covered by a test. |
GUIDE-secrets-02 | Handling secrets | SHOULD | both | Redact the Authorization, DPoP, and Cookie headers and credential fields before a request reaches a logger or tracer. | — | — | open: No logging or tracing hook redacts headers yet. |
GUIDE-secrets-03 | Handling secrets | SHOULD | company | Send Cache-Control: no-store on every response that carries a token or code. | CompanyService._issueCompanyService._device | test_conformance.py::test_python_agent_python_company_over_tlssecurity.token-responses-not-cached | implemented |
GUIDE-jwt-01 | Signing and verifying JWTs | SHOULD | both | Support ES256 and RS256, and sign with ES256 when you can. Use RSA keys of at least 2048 bits. | JoseSigner_algorithm | test_crypto.py::test_signs_and_verifies_only_asymmetric_pap_assertionstest_conformance.py::test_python_agent_python_company_over_tlssecurity.client-assertion-rs256-header-on-ec-key | partial: RSA key size is not checked. |
GUIDE-jwt-02 | Signing and verifying JWTs | SHOULD | both | Reject alg: none and symmetric algorithms such as HS256. | _algorithmverify_assertion | test_crypto.py::test_rejects_symmetric_and_none_algorithmstest_conformance.py::test_python_agent_python_company_over_tlssecurity.client-assertion-alg-nonesecurity.client-assertion-hs256-public-keysecurity.client-assertion-rs256-header-on-ec-keysecurity.dpop-alg-none | implemented |
GUIDE-jwt-03 | Signing and verifying JWTs | SHOULD | company | Verify assertions only with keys from the personal agent's verified jwks_uri (4.1). Ignore jku, x5u, and any key embedded in an assertion. | verify_assertion | test_conformance.py::test_python_agent_python_company_over_tlssecurity.client-assertion-embedded-key-ignoredsecurity.session-assertion-wrong-issuer | implemented |
GUIDE-jwt-04 | Signing and verifying JWTs | SHOULD | company | Check aud, exp, and iat. Allow a small clock skew, such as 30 seconds, and reject assertions that claim a long lifetime. | verify_assertion | test_conformance.py::test_python_agent_python_company_over_tlssecurity.client-assertion-bad-audiencesecurity.client-assertion-audience-arraysecurity.session-assertion-bad-audiencesecurity.session-assertion-clock-skew-toleratedsecurity.session-assertion-from-the-futuresecurity.session-assertion-expiredsecurity.session-assertion-long-lived | implemented |
GUIDE-jwt-05 | Signing and verifying JWTs | SHOULD | both | Make each jti at least 128 random bits. | _opaque_id | test_sessions.py::test_builds_short_lived_session_assertion_claimstest_conformance.py::test_python_agent_python_company_over_tlssecurity.session-assertion-replay | implemented |
GUIDE-keys-01 | Rotating keys | SHOULD | personal-agent | Give every key a kid. | JoseSignerAgentIdentity.jwks | test_identity.py::test_fetches_metadata_and_public_jwks | implemented |
GUIDE-keys-02 | Rotating keys | SHOULD | company | Companies refetch jwks_uri when they see an unknown kid, with a rate limit so a stream of bad assertions can't trigger a fetch each time. | AgentIdentityClient.fetch_jwksIdentityClientResolver.resolve | test_guidance.py::test_unknown_kid_refetches_the_overlapping_jwks_rate_limitedtest_guide_security.py::test_overlapping_jwks_verify_either_key_then_refuse_the_retired_onetest_guide_security.py::test_unknown_kids_refetch_the_set_at_most_once_a_minute | implemented |
GUIDE-fetch-01 | Fetching metadata safely | SHOULD | both | Set limits: a few redirects at most, a response size such as 100 KB, and a timeout of a few seconds. | DiscoveryClient._get_jsonAgentIdentityClient._get_jsonHttpxDiscoveryTransport.get | test_fixtures.py::test_local_fixture_casesecurity.discovery-redirect-limitsecurity.oversized-metadatasecurity.slow-metadatasecurity.metadata-not-json | implemented |
GUIDE-fetch-02 | Fetching metadata safely | SHOULD | both | Never send cookies, tokens, or other credentials on these fetches. | HttpxDiscoveryTransport.get | test_discovery.py::test_metadata_fetches_carry_no_cookie_or_credential | implemented |
GUIDE-fetch-03 | Fetching metadata safely | SHOULD | both | Reject loopback, private, and link-local addresses unless you've configured them on purpose. Check the address you actually connect to, not only the one DNS returned first. | PublicUrlSafetyPolicy.assert_safePinnedNetworkBackendHttpxDiscoveryTransport.get | test_discovery.py::test_rejects_private_discovery_addresstest_real_sockets.py::test_metadata_connects_to_the_approved_addresstest_fuzz.py::test_url_safety_approves_only_https_public_addressestest_fixtures.py::test_local_fixture_casesecurity.ssrf-loopbacksecurity.ssrf-privatesecurity.ssrf-link-local-metadatasecurity.ssrf-ipv6-loopback-and-unique-localsecurity.ssrf-ipv4-mappedsecurity.dns-rebinding-mixed-answerssecurity.dns-rebinding-after-checksecurity.public-address-allowed | implemented |
GUIDE-fetch-04 | Fetching metadata safely | SHOULD | both | Cache according to HTTP caching headers, within bounds you set, such as at least a minute and at most a day. | CacheBoundscache_lifetimeMetadataCacheDiscoveryClient._get_jsonAgentIdentityClient._cached_json | test_guidance.py::test_cache_lifetime_follows_cache_control_within_boundstest_guidance.py::test_discovery_and_oauth_metadata_are_cached_until_they_expiretest_guidance.py::test_no_store_metadata_is_fetched_every_time | implemented |
GUIDE-retry-01 | Retries | SHOULD | personal-agent | Send an Idempotency-Key on API writes that accept one. Use a random UUID (version 4), and reuse the same key when you retry the same request. | — | — | open: ApiClient sends no Idempotency-Key. |
GUIDE-retry-02 | Retries | SHOULD | personal-agent | Each retry needs a new DPoP proof, and a retried token request needs a new assertion. | DpopHttpClient.requestSessionClient._request | test_sessions.py::test_retries_once_with_a_fresh_dpop_nonce_and_assertions | implemented |
GUIDE-retry-03 | Retries | SHOULD | personal-agent | Otherwise, back off exponentially with jitter, cap the delay, and limit the number of attempts. | RetryPolicyDpopHttpClient.request | test_guidance.py::test_retry_policy_honours_retry_after_backs_off_with_jitter_and_capstest_guidance.py::test_safe_requests_retry_after_retry_after_with_fresh_proofs | implemented |
GUIDE-rate-01 | Rate limits | SHOULD | company | Limit by client_id, by user, and by session; also send the RateLimit and RateLimit-Policy headers. | — | — | open: The reference company does not rate-limit. |
GUIDE-rate-02 | Rate limits | SHOULD | personal-agent | Keep track of limits for each company and share them across your tasks and users. | RateLimitBudgetSharedRateLimitBudgetDpopHttpClient.request | test_guidance.py::test_shared_budget_paces_tasks_and_refuses_long_waitstest_guidance.py::test_writes_and_long_pauses_go_back_to_the_caller_and_are_shared | implemented |
GUIDE-json-01 | Transport and JSON | SHOULD | both | Encode JSON as UTF-8 and reject objects with duplicate member names. | loads_strict | test_models.py::test_strict_json_rejects_duplicate_memberstest_fixtures.py::test_local_fixture_casetest_fuzz.py::test_strict_json_accepts_every_unique_document_and_refuses_every_duplicatesecurity.duplicate-json-members | implemented |
GUIDE-pa-dpop-01 | DPoP keys and proofs | SHOULD | personal-agent | Generate a DPoP key pair for each user at each company (4.3). | DpopProofRequestDpopKeys | test_conformance.py::test_python_agent_python_company_over_tls | implemented |
GUIDE-pa-tokens-01 | Storing tokens | SHOULD | personal-agent | Request Bearer session tokens only for MCP (4.3). Each one is limited to one MCP server by resource. | McpBearerAuthDpopHttpClient.request | test_mcp_and_web.py::test_a_bearer_token_is_refused_everywhere_but_its_mcp_servertest_conformance.py::test_python_agent_python_company_over_tlslifecycle.mcp-bearer-for-its-url | implemented |
GUIDE-pa-tokens-02 | Storing tokens | SHOULD | personal-agent | On invalid_grant for an account token, delete it and continue signed out. | AccountTokenClient.refresh | test_accounts.py::test_invalid_grant_deletes_the_token_and_leaves_the_session_signed_outtest_conformance.py::test_python_agent_python_company_over_tls | implemented |
GUIDE-pa-signin-01 | Choosing how to sign in | SHOULD | personal-agent | Prefer direct sign-in, then device sign-in, then mediated sign-in. | — | — | open: The SDK offers all three; no helper chooses among them. |
GUIDE-pa-browser-01 | Web browsing | SHOULD | personal-agent | Use a separate browser storage context, with its own cookies and local storage, for each session. | — | — | open: The browser is a host contribution (pap.browserFormPost); isolation is not checked. |
GUIDE-pa-conv-01 | Conversations | SHOULD | personal-agent | Read events with a long wait, such as 30 seconds, or a stream, rather than many short polls (7.5). | ConversationClient.events | test_conversations.py::test_events_recover_cursor_drain_backlog_and_suppress_duplicates | implemented |
GUIDE-co-consent-01 | Consent pages | SHOULD | company | Protect the page against cross-site request forgery, and against framing by other sites with Content-Security-Policy: frame-ancestors. | CompanyService._pageCompanyService.consent_decisionCompanyService._operation_page | test_direct_and_api.py::test_the_consent_page_is_never_framed_and_bad_redirects_are_not_followedtest_guide_security.py::test_the_consent_form_is_bound_to_the_browser_that_was_shown_ittest_guide_security.py::test_the_operation_page_refuses_cross_site_and_another_browsers_form | implemented |
GUIDE-co-consent-02 | Consent pages | SHOULD | company | Accept PKCE with S256 only, and reject plain. | CompanyService._authorize | test_direct_and_api.py::test_the_consent_page_is_never_framed_and_bad_redirects_are_not_followed | implemented |
GUIDE-co-consent-03 | Consent pages | SHOULD | company | Return iss in every redirect, success or error (MUST, 4.5). | CompanyService._redirect_url | test_direct_and_api.py::test_a_denial_reaches_the_agent_as_access_denied_only | implemented |
GUIDE-co-device-01 | Device sign-in | SHOULD | company | Return slow_down to personal agents that poll faster than interval, and keep expires_in short, such as 10 minutes. | CompanyService._device_grantCompanyService._device | test_company_refusals.py::test_polling_too_fast_is_slow_down | implemented |
GUIDE-co-mediated-01 | Mediated sign-in | SHOULD | company | Return the same failed response for an unknown account and a wrong password, so responses don't reveal whether an account exists. | CompanyService._mediated | test_company_end_to_end.py::test_mediated_sign_in_with_a_one_time_code | implemented |
GUIDE-co-account-01 | Account settings | SHOULD | company | Offer a control to disconnect each one. Disconnecting revokes its account tokens and signs out its sessions (4.9). | CompanyService.disconnect | test_conformance.py::test_python_agent_python_company_over_tlslifecycle.disconnect-from-account-settings | implemented |
GUIDE-co-conv-01 | Conversations | SHOULD | company | On a stream with nothing to send, send an SSE comment every 15 to 30 seconds, so proxies don't close it (7.6). | CompanyService._stream | — | partial: The stream opens with a comment and ends after the backlog; it does not stay open with keep-alives. |
GUIDE-co-conv-02 | Conversations | SHOULD | company | When a session signs out, close its conversations that used the account, so their account details can't be read signed out (7.11). | CompanyService._owned | test_conformance.py::test_python_agent_python_company_over_tls | partial: A signed-out token gets sign_in_required on them; they are not closed. |
GUIDE-co-errors-01 | Error messages | SHOULD | company | Return the same error for "doesn't exist" and "belongs to someone else", as conversation_not_found does (7.13). | CompanyService._ownedCompanyService._operation_route | test_conformance.py::test_python_agent_python_company_over_tls | implemented |
GUIDE-ops-pa-01 | Operations: personal agents | SHOULD | personal-agent | When a new revision arrives, show the user what changed before asking them to approve it again. | ConfirmOutcome.changed | test_operations.py::test_terms_changed_returns_the_new_revision_and_what_changedtest_conformance.py::test_python_agent_python_company_over_tls | implemented |
GUIDE-ops-pa-02 | Operations: personal agents | SHOULD | personal-agent | If a term you need to check is missing from terms, ask the user instead. | covers | test_operations.py::test_a_standing_permission_that_does_not_cover_it_asks_the_user | implemented |
GUIDE-ops-pa-03 | Operations: personal agents | SHOULD | personal-agent | For a permission with a limited number of uses, reserve a use before you confirm, so two tasks running at once can't both use the last one. Release the reservation if the company rejects the confirmation. | StandingPermissionStoreOperationsClient.confirm | test_operations.py::test_a_refused_confirmation_gives_the_use_backtest_conformance.py::test_python_agent_python_company_over_tlsoperations.standing-permission-within-boundsoperations.standing-permission-concurrent-use | implemented |
GUIDE-ops-pa-04 | Operations: personal agents | SHOULD | personal-agent | After a confirm times out, read the operation or confirm again with the same revision. Wait for Retry-After between reads. | OperationsClient.confirm | test_operations.py::test_a_lost_answer_is_read_back_by_the_same_id_never_assumed_failedtest_conformance.py::test_python_agent_python_company_over_tlsoperations.confirm-timeout-recovery | implemented |
GUIDE-ops-co-01 | Operations: companies | SHOULD | company | When you call another system to perform the action, derive its idempotency key from the operation ID. | OperationExecutor | test_conformance.py::test_python_agent_python_company_over_tls | implemented |
GUIDE-ops-co-02 | Operations: companies | SHOULD | company | To spot the same action coming up in another channel, look for an open operation in the same session or account with the same terms before proposing a new one. | CompanyService._propose | test_conformance.py::test_python_agent_python_company_over_tls | implemented |
Conformance case IDs name protocol/fixtures/conformance cases; see the conformance report for their outcome in every language pairing.