docs(prd): assign PRD number 0082
prd-number-check / require-numbered-prds (pull_request) Successful in 13s
tracker-policy-pr / check-pr (pull_request) Successful in 11s
test / integration-docker (pull_request) Successful in 1m5s
test / image-input-builds (pull_request) Successful in 1m22s
test / unit (pull_request) Failing after 10m22s
lint / lint (push) Failing after 10m24s
test / coverage (pull_request) Has been skipped

CI (prd-number-check) rejects unnumbered prd-new-*.md on merge to main.
Rename docs/prds/prd-new-trusted-agent-forge-identity.md to
0082-trusted-agent-forge-identity.md (0081 is claimed by #517/#519) and
update the in-repo 'PRD prd-new-trusted-agent-forge-identity' citations to
'PRD 0082'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 02:07:20 +00:00
committed by didericis
parent 91f6cbabdc
commit ccfb935b19
19 changed files with 31 additions and 31 deletions
+1 -1
View File
@@ -196,7 +196,7 @@ bot-bottle start <agent> # builds the image on first run, drops you into claud
## Manifest
Bottles and agents are Markdown files with YAML frontmatter under `~/.bot-bottle/`. The Markdown body is the system prompt. Both bottles and agents are **home-only**: they live under `~/.bot-bottle/bottles/` and `~/.bot-bottle/agents/`. A `<repo>/.bot-bottle/agents/<name>.md` shipped by a workspace is ignored with a warning — since an agent may select a host identity and forge secret, checked-out content must not define one (PRD prd-new-trusted-agent-forge-identity). Keep repo-specific behavioral instructions in `AGENTS.md` instead.
Bottles and agents are Markdown files with YAML frontmatter under `~/.bot-bottle/`. The Markdown body is the system prompt. Both bottles and agents are **home-only**: they live under `~/.bot-bottle/bottles/` and `~/.bot-bottle/agents/`. A `<repo>/.bot-bottle/agents/<name>.md` shipped by a workspace is ignored with a warning — since an agent may select a host identity and forge secret, checked-out content must not define one (PRD 0082). Keep repo-specific behavioral instructions in `AGENTS.md` instead.
Identity is **agent-owned**: the author name/email and named forge accounts live on the agent, not under `git-gate` (which now carries only Git transport policy). A bottle repo may optionally name one of the selected agent's forge aliases via `forge:`.
+2 -2
View File
@@ -76,7 +76,7 @@ def prepare_agent_state_dir(slug: str, manifest: Manifest) -> tuple[Path, Path]:
For repositories associated with a forge, appends generated, non-secret
provider-specific workflow guidance to the prompt (PRD
prd-new-trusted-agent-forge-identity). The guidance carries neither the
0082). The guidance carries neither the
token value nor its `token_secret` name."""
agent = manifest.agent
agent_dir = agent_state_dir(slug)
@@ -102,7 +102,7 @@ def prepare_egress(
) -> EgressPlan:
"""Build the egress plan, adding a scoped, proxy-held Gitea API route for
each forge alias referenced by a selected git-gate repo (PRD
prd-new-trusted-agent-forge-identity). The token is resolved from the host
0082). The token is resolved from the host
env at launch and never enters the bottle."""
egress_dir = egress_state_dir(slug)
egress_dir.mkdir(parents=True, exist_ok=True)
+1 -1
View File
@@ -383,7 +383,7 @@ def _peek_agent_bottle(manifest: ManifestIndex, agent_name: str) -> str:
from ...manifest.loader import scan_agent_names
from ...yaml_subset import YamlSubsetError, parse_frontmatter
# Agents are home-only (PRD prd-new-trusted-agent-forge-identity).
# Agents are home-only (PRD 0082).
home_agents = scan_agent_names(manifest.home_md / "agents")
path = home_agents.get(agent_name)
if path is None:
+1 -1
View File
@@ -124,7 +124,7 @@ def egress_forge_routes(
) -> tuple[EgressRoute, ...]:
"""Synthesize one inspected, token-authenticated egress route per distinct
forge alias referenced by a selected git-gate repo (PRD
prd-new-trusted-agent-forge-identity).
0082).
The route is scoped to the canonical forge origin (host) and API prefix
(`/api/v1`): the proxy injects the Gitea `token` scheme using the value of
+1 -1
View File
@@ -1,7 +1,7 @@
"""Manifest dataclasses (PRD 0011 layout).
Reads the per-file manifest tree (home-only
PRD prd-new-trusted-agent-forge-identity):
PRD 0082):
$HOME/.bot-bottle/bottles/<name>.md one bottle per file
$HOME/.bot-bottle/agents/<name>.md agents
+2 -2
View File
@@ -119,7 +119,7 @@ class ManifestAgent:
bottle: str = ""
skills: tuple[str, ...] = ()
prompt: str = ""
# Agent-owned identity (PRD prd-new-trusted-agent-forge-identity).
# Agent-owned identity (PRD 0082).
# `author` populates the bottle's git user.name/user.email;
# `forge_accounts` maps a forge alias to a canonical Gitea API origin and
# a host token reference. Both live only on the agent — never under
@@ -138,7 +138,7 @@ class ManifestAgent:
if "git-gate" in d:
raise ManifestError(
f"agent '{name}' has a 'git-gate' block, which is no longer "
f"accepted on an agent (PRD prd-new-trusted-agent-forge-identity). "
f"accepted on an agent (PRD 0082). "
f"Move git-gate.user name/email into the 'author' block; "
f"git-gate.repos stays on the bottle."
)
+1 -1
View File
@@ -109,7 +109,7 @@ class ManifestBottle:
# `git_user` is now an internal resolved carrier populated from the
# selected agent's `author` at composition time — it is never parsed
# from the bottle manifest (PRD prd-new-trusted-agent-forge-identity).
# from the bottle manifest (PRD 0082).
git: tuple[ManifestGitEntry, ...] = ()
git_user = ManifestGitUser()
git_raw = d.get("git-gate")
+1 -1
View File
@@ -1,4 +1,4 @@
"""Agent-owned author identity and forge accounts (PRD prd-new-trusted-agent-forge-identity).
"""Agent-owned author identity and forge accounts (PRD 0082).
`ManifestAuthor` is the agent's git author identity (name/email) — it moved off
`git-gate.user` (PRD 0027 / ADR 0002) and onto the trusted agent definition.
+3 -3
View File
@@ -117,7 +117,7 @@ class ManifestGitEntry:
UpstreamHost: str = ""
UpstreamPort: str = ""
UpstreamPath: str = ""
# Optional forge alias (PRD prd-new-trusted-agent-forge-identity). When
# Optional forge alias (PRD 0082). When
# set, it must match a `forge-accounts` alias on the selected agent; the
# composition enables a scoped proxy-held API credential and forge
# workflow guidance for this repo. Empty = no forge association.
@@ -309,12 +309,12 @@ def parse_git_gate_config(
raw: object,
) -> tuple[ManifestGitEntry, ...]:
"""Parse `git-gate` on a bottle. Only `repos` is accepted; `git-gate.user`
moved to the agent's `author` block (PRD prd-new-trusted-agent-forge-identity)."""
moved to the agent's `author` block (PRD 0082)."""
d = as_json_object(raw, f"bottle '{bottle_name}' git-gate")
if "user" in d:
raise ManifestError(
f"bottle '{bottle_name}' git-gate.user is no longer supported "
f"(PRD prd-new-trusted-agent-forge-identity). Move name/email into "
f"(PRD 0082). Move name/email into "
f"the selected home agent's 'author' block:\n"
f" author:\n name: <name>\n email: <email>\n"
f"Identity is agent-owned; git-gate now carries only transport "
+6 -6
View File
@@ -52,7 +52,7 @@ def _warn_ignored_cwd_dir(cwd_dir: Path, kind: str, home_path: str) -> None:
warn(
f"ignoring {kind[:-1]} file(s) under {stale}: {names}. "
f"{kind.capitalize()} can only live under {home_path} "
f"(PRD prd-new-trusted-agent-forge-identity). Move them or delete."
f"(PRD 0082). Move them or delete."
)
@@ -62,7 +62,7 @@ def _compose_manifest(
raw_bottle: "ManifestBottle",
) -> "Manifest":
"""Build the single-value Manifest from the selected agent and its
effective bottle (PRD prd-new-trusted-agent-forge-identity):
effective bottle (PRD 0082):
- the agent's `author` populates the bottle's git user.name/user.email;
- each git-gate repo's `forge` alias is resolved against the agent's
@@ -192,7 +192,7 @@ class ManifestIndex:
$HOME/.bot-bottle/agents/<name>.md agents (home-only)
Both agents and bottles are home-only
(PRD prd-new-trusted-agent-forge-identity): a `bottles/` or `agents/`
(PRD 0082): a `bottles/` or `agents/`
subdir under $CWD is logged as a warning and ignored the filesystem
layout IS the trust boundary, since an agent may now select a host
identity and forge secret.
@@ -248,7 +248,7 @@ class ManifestIndex:
if cwd_dir is not None:
_warn_ignored_cwd_dir(cwd_dir, "bottles", "$HOME/.bot-bottle/bottles/")
# Agents became home-only in
# PRD prd-new-trusted-agent-forge-identity: a cwd agent file that
# PRD 0082: a cwd agent file that
# once shadowed a home agent could select a host identity/secret,
# so it is now ignored with a migration pointer.
_warn_ignored_cwd_dir(cwd_dir, "agents", "$HOME/.bot-bottle/agents/")
@@ -295,7 +295,7 @@ class ManifestIndex:
filenames without reading their content. In eager mode (from
from_json_obj) it returns the pre-parsed agents' names.
Agents are home-only (PRD prd-new-trusted-agent-forge-identity): cwd
Agents are home-only (PRD 0082): cwd
agent files never contribute names."""
if self.home_md is not None:
return sorted(scan_agent_names(self.home_md / "agents").keys())
@@ -351,7 +351,7 @@ class ManifestIndex:
"""Lazy path (resolve/from_md_dirs): read and parse the agent file and
its bottle chain from disk for the first time here."""
assert self.home_md is not None # guaranteed by load_for_agent dispatch
# Agents are home-only (PRD prd-new-trusted-agent-forge-identity):
# Agents are home-only (PRD 0082):
# a cwd agent file must not select a host identity or forge secret.
home_agents = scan_agent_names(self.home_md / "agents")
+1 -1
View File
@@ -23,7 +23,7 @@ BOTTLE_KEYS = frozenset(
)
AGENT_KEYS_REQUIRED: frozenset[str] = frozenset()
# `author` / `forge-accounts` are agent-owned identity (PRD
# prd-new-trusted-agent-forge-identity). `git-gate` is no longer accepted on an
# 0082). `git-gate` is no longer accepted on an
# agent: `git-gate.user` moved to `author`, and `git-gate.repos` is bottle-only.
AGENT_KEYS_OPTIONAL = frozenset({"bottle", "skills", "author", "forge-accounts"})
+1 -1
View File
@@ -4,7 +4,7 @@
- **Author:** didericis
- **Created:** 2026-05-24
> **Superseded in part by PRD prd-new-trusted-agent-forge-identity.**
> **Superseded in part by PRD 0082.**
> The `$CWD/.bot-bottle/agents/<name>.md` discovery/override path described
> below is removed: agents are now **home-only**, like bottles. Once an agent
> definition can select a host identity (`author`) and a host forge secret
@@ -1,4 +1,4 @@
# PRD prd-new: Trusted agent forge identity and guidance
# PRD 0082: Trusted agent forge identity and guidance
- **Status:** Accepted
- **Author:** didericis-claude
+1 -1
View File
@@ -1,5 +1,5 @@
"""Unit: trusted agent forge identity & guidance
(PRD prd-new-trusted-agent-forge-identity).
(PRD 0082).
Covers the net-new surface: agent `author`, agent `forge-accounts` (Gitea API
URL validation + host token reference), the repoforge association resolved at
+1 -1
View File
@@ -1,5 +1,5 @@
"""Unit: agent-owned identity via `author` (PRD
prd-new-trusted-agent-forge-identity).
0082).
Identity is agent-only now: an agent file declares an `author` block
(name + email, both required) and at `ManifestIndex.load_for_agent()`
+2 -2
View File
@@ -132,7 +132,7 @@ class TestExtendsEnvMerge(unittest.TestCase):
class TestExtendsGitMerge(unittest.TestCase):
"""git-gate.repos merges by name, with same-name child entries
merging field-by-field (child wins). Bottles no longer carry a user
identity (PRD prd-new-trusted-agent-forge-identity), so only repos
identity (PRD 0082), so only repos
merging is meaningful across extends chains."""
_GIT_ENTRY_A = {"url": "ssh://git@host-a/a.git", "key": {"provider": "static", "path": "/dev/null"}}
@@ -339,7 +339,7 @@ class TestExtendsEgressMerge(unittest.TestCase):
class TestExtendsNoBottleUserIdentity(unittest.TestCase):
"""Bottles no longer carry a user identity (PRD
prd-new-trusted-agent-forge-identity): identity moved to the agent's
0082): identity moved to the agent's
`author` block. `git-gate.user` on a bottle is rejected outright, and
`git_user` is always empty across an extends chain."""
+1 -1
View File
@@ -1,5 +1,5 @@
"""Unit: agent `author` identity -> bottle.git_user (PRD
prd-new-trusted-agent-forge-identity).
0082).
Identity moved off `git-gate.user` (bottle) onto the trusted agent's
`author` block. At `load_for_agent` the agent's author populates the
+2 -2
View File
@@ -72,7 +72,7 @@ class _LazyCase(unittest.TestCase):
class TestAllAgentNamesLazy(_LazyCase):
def test_cwd_agents_ignored_home_only(self) -> None:
# Agents are home-only (PRD prd-new-trusted-agent-forge-identity):
# Agents are home-only (PRD 0082):
# a cwd agents/ dir is warned-and-ignored, so only the home agent
# appears in all_agent_names.
_write(self.home_cb / "bottles" / "dev.md", _BOTTLE_DEV)
@@ -100,7 +100,7 @@ class TestRequireAgentLazy(_LazyCase):
self.resolve().require_agent("alpha") # no raise
def test_cwd_only_agent_not_selectable(self) -> None:
# Agents are home-only (PRD prd-new-trusted-agent-forge-identity):
# Agents are home-only (PRD 0082):
# a cwd-only agent file is never selectable, so require_agent raises.
_write(self.home_cb / "agents" / "alpha.md", _AGENT)
_write(self.cwd_cb / "agents" / "beta.md", _AGENT)
+2 -2
View File
@@ -111,7 +111,7 @@ class TestAgentFileParses(_ResolveCase):
class TestCwdAgentIgnoredHomeWins(_ResolveCase):
"""SC #3 (revised, PRD prd-new-trusted-agent-forge-identity): agents
"""SC #3 (revised, PRD 0082): agents
are home-only. A cwd agent file with the same name as a home agent no
longer wins it is warned-and-ignored, so the HOME agent's prompt is
used and the home bottle stays intact."""
@@ -141,7 +141,7 @@ class TestCwdAgentIgnoredHomeWins(_ResolveCase):
class TestCwdBottlesIgnored(_ResolveCase):
"""SC #4: a bottles/ dir under $CWD is ignored (with a warn).
The home bottle still wins. Under
PRD prd-new-trusted-agent-forge-identity a cwd agents/ dir is also
PRD 0082 a cwd agents/ dir is also
ignored, so $CWD contributes nothing the filesystem layout is the
trust boundary."""