feat: instance manifest, tool profiles, metrics, and budget enforcement
Land HARNESS-WP-0001 T01/T02/T04/T05: extend ADR-005 schedule.yml with harness fields, named tool-profile registry, ADR-004 metrics writes, and BudgetTracker wiring. CLI gains validate/profiles; task-file path kept.
This commit is contained in:
parent
16f5c29c08
commit
4144eba160
17 changed files with 1387 additions and 60 deletions
78
agent_harness/profiles.py
Normal file
78
agent_harness/profiles.py
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
"""Named tool-profile registry.
|
||||
|
||||
Instances declare a profile by name in the instance manifest; the harness
|
||||
resolves and enforces the allow-list. Instances never enumerate tools.
|
||||
Unknown profile names refuse to run.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
class UnknownToolProfileError(ValueError):
|
||||
"""Raised when a manifest references a profile that is not registered."""
|
||||
|
||||
|
||||
# Claude Code --allowedTools strings. No push, no network, no arbitrary shell.
|
||||
_GREEN_COMMIT_TOOLS = (
|
||||
"Read,Write,Edit,Glob,Grep,"
|
||||
"Bash(git add:*),Bash(git commit:*),Bash(git status),"
|
||||
"Bash(git log:*),Bash(git diff:*),Bash(date:*),Bash(ls:*)"
|
||||
)
|
||||
|
||||
# Blue-lane mail triage session: same session tools as green-commit-only.
|
||||
# Credentialed IMAP scan is a deterministic pre-step outside the session
|
||||
# (see mailscan.py); the session only reads reports and updates queues.
|
||||
_BLUE_MAIL_TRIAGE_TOOLS = _GREEN_COMMIT_TOOLS
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ToolProfile:
|
||||
"""A named hard allow-list for agentic sessions."""
|
||||
|
||||
name: str
|
||||
description: str
|
||||
allowed_tools: str
|
||||
lane: str # green | blue — advisory; enforcement is the allow-list
|
||||
|
||||
|
||||
PROFILES: dict[str, ToolProfile] = {
|
||||
"green-commit-only": ToolProfile(
|
||||
name="green-commit-only",
|
||||
description=(
|
||||
"Green-lane local commit: read/edit tools plus git add/commit/"
|
||||
"status/log/diff. No push, no network, no arbitrary shell."
|
||||
),
|
||||
allowed_tools=_GREEN_COMMIT_TOOLS,
|
||||
lane="green",
|
||||
),
|
||||
"blue-mail-triage": ToolProfile(
|
||||
name="blue-mail-triage",
|
||||
description=(
|
||||
"Blue-lane mail triage session after a credentialed deterministic "
|
||||
"scan: same local commit tools as green-commit-only. Credentials "
|
||||
"and network stay outside the agentic session."
|
||||
),
|
||||
allowed_tools=_BLUE_MAIL_TRIAGE_TOOLS,
|
||||
lane="blue",
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def get_profile(name: str) -> ToolProfile:
|
||||
"""Resolve a profile by name. Raises UnknownToolProfileError if missing."""
|
||||
key = (name or "").strip()
|
||||
if not key:
|
||||
raise UnknownToolProfileError("tool_profile name is empty")
|
||||
profile = PROFILES.get(key)
|
||||
if profile is None:
|
||||
known = ", ".join(sorted(PROFILES))
|
||||
raise UnknownToolProfileError(
|
||||
f"unknown tool_profile '{key}' (known: {known})"
|
||||
)
|
||||
return profile
|
||||
|
||||
|
||||
def list_profiles() -> list[ToolProfile]:
|
||||
return [PROFILES[k] for k in sorted(PROFILES)]
|
||||
Loading…
Add table
Add a link
Reference in a new issue