# PRD prd-new: Trusted agent forge identity and guidance - **Status:** Draft - **Author:** didericis-claude - **Created:** 2026-07-25 - **Issue:** #423 ## Summary Move author identity and named forge accounts into host-trusted agent definitions. A bottle may optionally associate a git-gate repository with one of the selected agent's forge accounts. When associated, bot-bottle gives the agent non-secret, provider-specific system guidance for that repository and routes authenticated forge API calls through the egress proxy without exposing the account token to the bottle. Repository-local agent and bottle definitions are no longer discovered. Once agent definitions can select a host forge credential, allowing checked-out repository content to define or override an agent would let untrusted workspace content select host identities and secrets. This PRD is deliberately limited to identity ownership, manifest trust, forge API access, and generated guidance. Per-activation signing, signature enforcement, commit attribution, and the commit audit model move to a follow-up PRD. Successor to: - **PRD 0011 (per-file manifests)** — allowed repository-local agent files to override home agents. This PRD removes that trust path: agents and bottles are loaded only from the host-owned `~/.bot-bottle` tree. - **PRD 0027 (agent git identity, #94)** / **ADR 0002** — put name/email in `git-gate.user` while keeping them claimed rather than vouched. This PRD moves those agent properties out of git-gate and into the trusted agent definition. - **PRD 0048 (deploy-key provisioning, #169)** — remains the Git push capability. A forge actor token is a separate API credential and never replaces a deploy key. ## Problem ### Forge workflow context is missing The agent prompt does not know which forge backs a git-gate repository, which API base URL to use, or that authenticated requests must go through the egress proxy. Bespoke prompt text has drifted between agents. Missing guidance has already caused incorrect PR behavior, including attempts to use Gitea AGit review refs instead of a normal branch-backed pull request. ### Identity is owned by the wrong layer `git-gate.user` puts an agent property on a repository transport component. An author identity and forge actor should follow the agent across bottles and repositories. Git-gate should own Git transport policy, not decide who the agent is. ### Repository-local agents become a credential-selection path Today `$CWD/.bot-bottle/agents/*.md` can define new agents and override home-resident agents. If an agent definition may reference an operator-provided forge token, a malicious repository could select a host credential merely by being the current workspace. Repository-local bottles are already ignored; agents need the same host-only boundary. ## Goals / Success Criteria - **Agent-owned identity.** Author name/email and named forge accounts live on the agent definition, not under `git-gate`. - **Trusted definitions only.** Agent and bottle files are discovered only under `~/.bot-bottle/{agents,bottles}`. Repository-local definitions never contribute names, defaults, or overrides. - **Optional repository association.** A bottle repository may name one forge account from the selected agent. Repositories without `forge` retain current behavior and generate no forge guidance or actor credential route. - **Proxy-held forge credential.** The host resolves the selected forge account's token reference and gives the value only to the egress proxy. The bottle receives neither the token nor a credential file containing it. - **Forge-aware system guidance.** For associated repositories, bot-bottle generates provider-specific instructions describing the API base URL, repository/account association, proxy-authenticated access, and correct PR workflow. - **No secret prompt material.** Neither token values nor token environment-variable names appear in the prompt or bottle environment. - **Fail closed.** Unknown account references, unsupported/non-HTTPS URLs, missing host secrets, and misplaced agent/bottle fields fail before creating the bottle. - **Push capability unchanged.** Git transport remains PRD 0048 deploy keys. The forge actor token is only for API actions such as opening, reviewing, and commenting on pull requests. ## Non-goals - **Commit signing or signature enforcement.** No signing key is minted and git-gate does not verify commit signatures in this PRD. - **Commit attribution or audit tables.** There is no trustworthy commit observation event in this slice. A follow-up signing PRD owns activation keys, control-plane verification, and any configured-author-versus-claimed-author audit model. - **Cryptographically vouched author identity.** `author` configures Git and identifies the agent actor, but commit author/committer fields remain claims under ADR 0002. - **Forge account or token minting.** The operator creates the agent-specific account/token out of band. Bot-bottle references an existing host secret; it does not create, rotate, or revoke that credential. - **Forge-side commit attribution surfaces.** No commit status, signing-key registration, or "Verified" badge. - **Provider-generic arbitrary prompts.** Gitea is the first supported forge. A future provider adds typed validation and generated guidance in code rather than accepting repository-supplied prompt text. ## Design ### Ownership model The resolved bottled agent is an agent definition composed with a bottle: | Part | Source | Role | |------|--------|------| | Author identity | agent `author` | configures Git name/email and identifies the agent's claimed author | | Forge accounts | agent `forge-accounts` | names API origins and host token references available to this agent | | Git repository | bottle `git-gate.repos` | configures Git transport, host verification, and push capability | | Repository/forge association | bottle repo `forge` | optionally selects an agent forge account for guidance and API access | This boundary keeps identity on the agent, capability/policy on the bottle, and transport enforcement in git-gate. ### Agent manifest Agent identity and forge accounts live only in `~/.bot-bottle/agents/.md`: ```yaml --- author: name: didericis-claude email: eric+claude@dideric.is forge-accounts: didericis-gitea: auth: type: token token_secret: GITEA_CLAUDE_TOKEN url: https://gitea.dideric.is/api/v1 --- ``` `author` contains: - `name`: non-empty string; - `email`: non-empty string passing the existing Git identity validation. The resolved values populate `user.name` and `user.email`. Existing `git-gate.user` fields fail with migration guidance to move the values into the selected home agent's `author` block. There is no compatibility period where a bottle identity silently overrides the agent identity. `forge-accounts` is a map whose keys follow the manifest's existing kebab-case identifier grammar (`[a-z][a-z0-9-]*`). Each account contains: - `url`: a canonical HTTPS Gitea API base URL; - `auth.type`: `token` in this slice; - `auth.token_secret`: the name of a host environment variable containing the operator-provided, agent-specific API token. The token secret name is host configuration, not bottle configuration. The token value is resolved only if a selected bottle repository references the account. ### Bottle manifest Repository policy remains in `~/.bot-bottle/bottles/.md`: ```yaml --- git-gate: repos: bot-bottle: url: ssh://git@100.78.141.42:30009/didericis/bot-bottle.git provisioned_key: provider: gitea token_env: GITEA_DEPLOY_TOKEN host_key: "ssh-ed25519 AAAA..." forge: didericis-gitea --- ``` `git-gate.repos..forge` is optional: - When absent, the repository behaves exactly as it does today. Bot-bottle does not resolve a forge actor token and does not add forge-specific instructions for that repository. - When present, it must match a `forge-accounts` entry on the selected agent. The resolved association enables a scoped proxy credential route and adds the repository/forge relationship to generated system guidance. `provisioned_key.token_env` remains the deploy-key administration credential from PRD 0048. It is separate from `forge-accounts.*.auth.token_secret`: the former provisions Git push capability, while the latter performs API actions as the agent. `author` and `forge-accounts` are agent-only. `git-gate.repos`, including `forge`, is bottle-only. Validation errors point to the correct file and trust domain rather than ignoring misplaced keys. ### Definition trust and discovery Only the host-owned manifest tree is authoritative: - Agents: `~/.bot-bottle/agents/*.md` - Bottles: `~/.bot-bottle/bottles/*.md` `$CWD/.bot-bottle/agents/*.md` no longer contributes new agents and no longer overrides a home agent. `$CWD/.bot-bottle/bottles/*.md` remains unusable. If either repository-local directory contains manifest files, bot-bottle emits a warning that they are ignored and points to the corresponding home path. Every discovery and resolution surface uses the same home-only index: - agent enumeration and selectors; - `require_agent`; - lazy `load_for_agent`; - default-agent/default-bottle resolution; - dashboard and headless launch paths. There must be no alternate direct-path load that can still select a workspace definition. Workspace instructions remain repository content (for example `AGENTS.md`), but runtime policy, host secret references, and actor identity do not. Programmatic in-memory manifests remain available for tests and trusted internal composition; they are not a filesystem discovery path. ### Forge URL validation The host parses and canonicalizes each referenced forge account URL before creating any runtime resources: - scheme must be `https`; - userinfo, query, and fragment are forbidden; - hostname must be present; - the path must be a supported Gitea API base (initially `/api/v1`, with normalization of a trailing slash); - visually different inputs that canonicalize to the same origin/prefix are deduplicated; - unsupported providers or path shapes fail closed. The provider is determined by typed support in bot-bottle, not by prompt text from a repository. Adding another provider requires a validator, auth scheme, and guidance renderer. ### Proxy credential provisioning For each distinct forge account referenced by at least one selected bottle repository, the host: 1. Resolves `auth.token_secret` from the host environment and rejects a missing or empty value. 2. Copies the token value only into the egress proxy's credential environment. 3. Adds an inspected route scoped to the canonical forge origin and API prefix. 4. Configures the provider authentication scheme (`token` for Gitea) so the proxy injects authentication. The bottle makes an unauthenticated request to the configured HTTPS API URL. The token is not copied into the bottle, `.gitconfig`, generated prompt, workspace, or process environment visible to the agent. If several repositories reference the same forge account, they share one credential route. Unreferenced forge accounts resolve no secret and create no route. ### Generated system guidance Bot-bottle appends a generated, non-secret section to its existing system prompt file. It is derived from validated typed fields, not copied Markdown from a repository. For each associated repository, Gitea guidance includes: - account alias (for example `didericis-gitea`); - API base URL (for example `https://gitea.dideric.is/api/v1`); - the git-gate repository name tied to that forge; - the instruction to call the configured HTTPS API through the proxy without reading, printing, or manually attaching an authorization token; - the distinction that Git pushes still use the git-gate remote; - the requirement to create/update a normal `refs/heads/` and open a branch-backed pull request through the API; - the prohibition on pushing `refs/for/*`, `refs/draft/*`, or `refs/for-review/*`; - the instruction to use the API for reviews/comments and verify returned object state before claiming completion. The prompt contains neither the token value nor its `token_secret` name. Repositories without `forge` are omitted from this section. If no selected repository has a forge association, no forge guidance section is generated. ### Failure and lifecycle behavior Forge configuration is validated before bottle creation. A bad association or credential must not leave a partially-created bottle or proxy. The operator-owned token is not minted, rotated, or revoked by bot-bottle. On teardown, stopping the egress proxy discards the activation's in-memory/runtime copy. Later activations resolve the current host secret again. Logs may include the account alias, canonical API origin, and repository name. They must never contain the token value. Errors for missing secrets name the configuration field and host environment variable, but do not print any value. ## Migration This change is intentionally breaking at the manifest trust boundary: 1. Move each home bottle/agent `git-gate.user.name` and `.email` into the corresponding home agent's `author`. 2. Add agent-specific `forge-accounts` only to home agent definitions. 3. Add optional `forge` associations to home bottle repository entries. 4. Move any `$CWD/.bot-bottle/agents/*.md` that should remain selectable into `~/.bot-bottle/agents/`. Repository copies are ignored thereafter. 5. Keep repository-specific behavioral instructions in `AGENTS.md` or another workspace instruction file; do not put runtime identity or secret references there. Errors and warnings link to this migration rather than silently changing which identity or definition is active. ## Follow-up: signed commits and attribution A separate PRD will consume the trusted agent `author` introduced here and own: - per-activation signing key minting and sidecar isolation; - commit-time signing through a forwarded signing capability; - git-gate rejection of unsigned/wrong-key new commits; - independent control-plane object-ID recomputation and signature verification; - activation and attributed-commit audit tables; - storage of configured agent author separately from each commit's unenforced claimed author/committer; - post-teardown verification and key-retention policy. That PRD must not reintroduce identity under git-gate or expand repository-local manifest trust. ## Implementation chunks 1. **This PRD.** Establish the identity, forge, and filesystem trust boundary. 2. **Home-only definitions.** Remove cwd agents from discovery, override, enumeration, lazy loading, defaults, and selectors. Warn on ignored cwd agent/bottle files and provide migration guidance. 3. **Manifest schema.** Add agent-only `author` and `forge-accounts`; remove `git-gate.user`; add optional bottle-only `git-gate.repos..forge`. Validate account names, composition references, field placement, and Gitea API URLs. 4. **Proxy provisioning.** Lazily resolve only referenced token secrets and create scoped authenticated Gitea API routes without putting credentials in the bottle. 5. **Prompt generation.** Render typed Gitea/repository workflow guidance into the existing bot-bottle prompt path for associated repositories only. 6. **Docs and migration.** Update README examples, PRD 0011-facing discovery documentation, agent/bottle schema docs, and error guidance. ## Testing strategy - **Trust boundary:** cwd agent files are ignored with a warning, cannot override a home agent, are absent from enumeration/selectors/defaults, and cannot be loaded by name. Cwd bottle behavior remains home-only. - **Agent schema:** `author` and `forge-accounts` parse; malformed identities, non-kebab account names, unknown fields, invalid auth types, and misplaced git-gate fields fail clearly. - **Bottle schema:** `forge` is optional; absent associations preserve current behavior; present associations resolve against the selected agent; unknown or misplaced associations fail before launch. - **URL validation:** HTTPS Gitea API bases pass and canonicalize; HTTP, userinfo, query, fragment, missing host, unsupported paths, and unsupported providers fail closed. - **Secret handling:** only referenced accounts resolve environment secrets; missing/empty secrets fail before runtime creation; token values are absent from bottle env, prompt, generated config, logs, and workspace; token secret names are absent from the bottle and prompt. - **Proxy behavior:** associated API requests receive proxy-injected Gitea authentication scoped to the configured origin/prefix; unrelated hosts and paths receive no credential. - **Prompt behavior:** guidance names account/API/repository associations, branch-backed PR workflow, prohibited AGit refs, and mutation verification; repositories without `forge` are omitted; no associations means no section. - **Migration:** legacy `git-gate.user` fails with an `author` migration pointer; ignored cwd definitions warn with the target home path. ## Open questions - None.