a2edaf8694
Implements PRD prd-new-trusted-agent-forge-identity. Moves author identity and named forge configurations onto the trusted, host-only agent definition, and lets a bottle repository optionally associate a git-gate repo with one of the agent's forge aliases. - Agent-owned identity: new `author` (name/email) and `forge-accounts` (alias -> canonical Gitea /api/v1 origin + host `token_secret` ref) on the agent manifest. `author` populates the bottle's git user.name/user.email. - Remove `git-gate.user` from both agents and bottles; fail with a migration pointer to `author`. `git-gate` is no longer accepted on an agent. - Bottle git-gate repos gain optional `forge: <alias>`, resolved against the selected agent's forge-accounts at composition (fail-closed on unknown). - Fail-closed Gitea API URL validation (https, no userinfo/query/fragment, /api/v1 base, host lowercased, trailing slash normalized, host dedup). - Proxy-held credential: synthesize one inspected, token-authenticated egress route per referenced forge alias, scoped to the origin + API prefix. The token is resolved from the host env at launch into the egress proxy only — never the bottle env, prompt, gitconfig, or workspace. - Generated, non-secret forge workflow guidance appended to the agent prompt for associated repos (API base, branch-backed PR flow, AGit-ref prohibition, mutation verification); omitted when no repo declares a forge. - Agents become home-only: cwd `.bot-bottle/agents` no longer contributes, overrides, or is selectable; warned-and-ignored like cwd bottles. - Docs: README examples, PRD 0011 supersession note, manifest schema docstring. - Tests: new test_forge_identity suite; legacy git-gate.user/cwd tests updated to the agent-owned identity model. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
392 lines
17 KiB
Markdown
392 lines
17 KiB
Markdown
# PRD prd-new: Trusted agent forge identity and guidance
|
|
|
|
- **Status:** Accepted
|
|
- **Author:** didericis-claude
|
|
- **Created:** 2026-07-25
|
|
- **Issue:** #423
|
|
|
|
## Summary
|
|
|
|
Move author identity and named forge configurations into host-trusted agent
|
|
definitions. A bottle may optionally associate a git-gate repository with one
|
|
of the selected agent's forge aliases. 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 forge token to the bottle. The authenticated account is inferred from the
|
|
identity that owns that token; it is not separately declared in the manifest.
|
|
|
|
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 set of forge credentials 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 configurations
|
|
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
|
|
alias from the selected agent. Repositories without `forge` retain current
|
|
behavior and generate no forge guidance or credential route.
|
|
- **Proxy-held forge credential.** The host resolves the selected forge
|
|
alias'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 configurations | agent `forge-accounts` | maps forge aliases to API origins and host token references available to this agent; token ownership determines account identity |
|
|
| 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 alias 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/<name>.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 are **forge aliases** and follow the
|
|
manifest's existing kebab-case identifier grammar
|
|
(`[a-z][a-z0-9-]*`). Each forge entry 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's owner determines the authenticated forge account; there is no
|
|
separate account-name field. The token secret name is host configuration, not
|
|
bottle configuration. The token value is resolved only if a selected bottle
|
|
repository references the forge alias.
|
|
|
|
### Bottle manifest
|
|
|
|
Repository policy remains in `~/.bot-bottle/bottles/<name>.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.<name>.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 alias in `forge-accounts` 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 alias's 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 alias 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 alias, they share one
|
|
credential route. Unreferenced forge aliases 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:
|
|
|
|
- forge 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/<branch>` 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 forge 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.<name>.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.
|