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
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:
@@ -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:`.
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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."
|
||||
)
|
||||
|
||||
@@ -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,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.
|
||||
|
||||
@@ -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 "
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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"})
|
||||
|
||||
|
||||
@@ -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
-1
@@ -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,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 repo→forge association resolved at
|
||||
|
||||
@@ -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()`
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user