CtrlK
BlogDocsLog inGet started
Tessl Logo

jbaruch/coding-policy

General-purpose coding policy for Baruch's AI agents

76

Quality

95%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Low

Low-risk findings worth noting

Overview
Quality
Evals
Security
Files

config.pyskills/herdr-teamlead/teamlead/

"""Agent configuration: which agents exist and how each reports its usage.

The config is operator-owned. teamlead never writes it -- the operator copies
`config.example.json` into place, so a machine that has not been set up fails
with an actionable message instead of silently inventing agents.
"""

import json
import math
import os
from pathlib import Path

from .errors import ConfigError
from .herdr import SLASH_DELIVERIES, SLASH_DELIVERY_PASTE

CONFIG_SCHEMA_VERSION = 1

REQUIRED_AGENT_FIELDS = ("name", "kind", "usage_prompt", "usage_marker", "usage_read_source", "clear_prompt")

VALID_READ_SOURCES = frozenset({"visible", "recent", "recent-unwrapped", "detection"})

#: Optional per-agent fields that default to an empty list of strings.
OPTIONAL_LIST_FIELDS = (
    "close_keys",
    "idle_markers",
    "working_markers",
    "dialog_next_tab_keys",
    "recover_keys",
    "composer_placeholders",
)

#: Default when an agent does not name one. `paste` is the older path and the
#: one that works for claude and codex; grok needs `type`.
DEFAULT_SLASH_DELIVERY = SLASH_DELIVERY_PASTE

#: Dim composer text is never somebody's typing: it is a placeholder or a
#: ghost-text suggestion. On for every kind, because reading dim hint text as
#: occupied is what killed an idle Codex.
DEFAULT_COMPOSER_IGNORE_DIM = True

#: Enters sent with a typed slash command. Codex opens an autocomplete popup
#: on `/` where the first Enter only accepts the completion, so it needs 2.
DEFAULT_SLASH_ENTER_COUNT = 1
MAX_SLASH_ENTER_COUNT = 5


class Agent:
    """One configured agent. A plain value object, never mutated after load."""

    __slots__ = REQUIRED_AGENT_FIELDS + OPTIONAL_LIST_FIELDS + (
        "slash_delivery",
        "composer_glyph",
        "composer_ignore_dim",
        "slash_enter_count",
        "model_label",
    )

    def __init__(self, name, kind, usage_prompt, usage_marker, usage_read_source, clear_prompt, close_keys=(), idle_markers=(), working_markers=(), dialog_next_tab_keys=(), recover_keys=(), composer_placeholders=(), slash_delivery=DEFAULT_SLASH_DELIVERY, composer_glyph="", composer_ignore_dim=DEFAULT_COMPOSER_IGNORE_DIM, slash_enter_count=DEFAULT_SLASH_ENTER_COUNT, model_label=""):
        self.name = name
        self.kind = kind
        self.usage_prompt = usage_prompt
        self.usage_marker = usage_marker
        self.usage_read_source = usage_read_source
        self.clear_prompt = clear_prompt
        self.close_keys = tuple(close_keys)
        # Footer signatures the idle probe matches when herdr's title-derived
        # state says `working`. See teamlead/probe.py.
        self.idle_markers = tuple(idle_markers)
        self.working_markers = tuple(working_markers)
        # Keys that move a usage dialog to its next tab, when the report is
        # behind one teamlead did not land on.
        self.dialog_next_tab_keys = tuple(dialog_next_tab_keys)
        # Keys that clear a stuck composer. Sent EXACTLY once, never in a
        # loop, and only for text teamlead can account for. Codex ships with
        # NONE: ctrl+c on an idle Codex exits the process.
        self.recover_keys = tuple(recover_keys)
        # Hint text a runtime draws in an EMPTY composer, matched exactly
        # after trimming. Codex draws "Ask Codex to do anything"; reading that
        # as typed text is what sent ctrl+c into an idle Codex and killed it.
        self.composer_placeholders = tuple(composer_placeholders)
        # Shown on the worker's pane after a dispatch, so the sidebar says
        # which model is doing the work. Cosmetic and optional: empty means the
        # label carries the role alone.
        self.model_label = model_label
        # "paste" (agent prompt) or "type" (pane send-text plus Enter).
        self.slash_delivery = slash_delivery
        # The prompt glyph that marks the composer row, so teamlead can see
        # whether a slash command was consumed. Empty disables the check.
        self.composer_glyph = composer_glyph
        # Treat dim / grey composer text as empty. Claude Code pre-fills a
        # ghost-text suggestion nobody typed; a runtime without ghost text
        # leaves this off so every character still counts.
        self.composer_ignore_dim = composer_ignore_dim
        # How many Enters a typed slash command needs to submit. Codex's
        # autocomplete popup swallows the first one.
        self.slash_enter_count = slash_enter_count

    def __repr__(self):
        return "Agent(name={!r}, kind={!r})".format(self.name, self.kind)

    def __eq__(self, other):
        if not isinstance(other, Agent):
            return NotImplemented
        return self.as_dict() == other.as_dict()

    def as_dict(self):
        record = {field: getattr(self, field) for field in REQUIRED_AGENT_FIELDS}
        for field in OPTIONAL_LIST_FIELDS:
            record[field] = list(getattr(self, field))
        record["slash_delivery"] = self.slash_delivery
        record["composer_glyph"] = self.composer_glyph
        record["composer_ignore_dim"] = self.composer_ignore_dim
        record["slash_enter_count"] = self.slash_enter_count
        return record


def default_config_path():
    """`$XDG_CONFIG_HOME/teamlead/config.json`, falling back to `~/.config`."""
    base = os.environ.get("XDG_CONFIG_HOME")
    root = Path(base) if base else Path.home() / ".config"
    return root / "teamlead" / "config.json"


def _missing_config_error(path):
    return ConfigError(
        "Agent config not found at {} - copy the shipped example into place "
        "with `mkdir -p {} && cp config.example.json {}`, then edit the agent "
        "names to match `herdr agent list`.".format(path, path.parent, path),
        {"path": str(path)},
    )


def parse_config(payload, source="<memory>"):
    """Validate an already-decoded config mapping and return a list of Agents.

    Pure apart from the `source` string used in error messages, so tests build
    configs inline instead of writing files.
    """
    if not isinstance(payload, dict):
        raise ConfigError(
            "Config at {} must be a JSON object with `schema_version` and "
            "`agents` - see config.example.json.".format(source),
            {"source": source},
        )

    version = payload.get("schema_version")
    if version != CONFIG_SCHEMA_VERSION:
        raise ConfigError(
            "Config at {} has schema_version {!r}; this build reads version {}. "
            "Update the file against config.example.json.".format(
                source, version, CONFIG_SCHEMA_VERSION
            ),
            {"source": source, "found": version, "expected": CONFIG_SCHEMA_VERSION},
        )

    raw_agents = payload.get("agents")
    if not isinstance(raw_agents, list) or not raw_agents:
        raise ConfigError(
            "Config at {} has no `agents` array - add at least one agent entry "
            "shaped like the ones in config.example.json.".format(source),
            {"source": source},
        )

    agents = []
    seen = set()
    for index, entry in enumerate(raw_agents):
        if not isinstance(entry, dict):
            raise ConfigError(
                "Config at {}: agents[{}] is not a JSON object - each agent is "
                "an object with the fields {}.".format(
                    source, index, ", ".join(REQUIRED_AGENT_FIELDS)
                ),
                {"source": source, "index": index},
            )
        missing = [field for field in REQUIRED_AGENT_FIELDS if not entry.get(field)]
        if missing:
            raise ConfigError(
                "Config at {}: agents[{}] is missing {} - add {} to that entry.".format(
                    source, index, ", ".join(missing), " and ".join(missing)
                ),
                {"source": source, "index": index, "missing": missing},
            )
        read_source = entry["usage_read_source"]
        if read_source not in VALID_READ_SOURCES:
            raise ConfigError(
                "Config at {}: agents[{}] has usage_read_source {!r}; herdr "
                "accepts {}.".format(
                    source, index, read_source, ", ".join(sorted(VALID_READ_SOURCES))
                ),
                {"source": source, "index": index, "usage_read_source": read_source},
            )
        name = entry["name"]
        if name in seen:
            raise ConfigError(
                "Config at {}: agent name {!r} appears twice - herdr agent "
                "names are unique among live agents.".format(source, name),
                {"source": source, "name": name},
            )
        seen.add(name)

        lists = {}
        for field in OPTIONAL_LIST_FIELDS:
            value = entry.get(field, [])
            if not isinstance(value, list) or not all(isinstance(item, str) for item in value):
                raise ConfigError(
                    "Config at {}: agents[{}].{} must be an array of strings - "
                    "close_keys holds herdr key names such as [\"esc\"], "
                    "idle_markers and working_markers hold literal footer text "
                    "the idle probe looks for.".format(source, index, field),
                    {"source": source, "index": index, "field": field},
                )
            lists[field] = value

        delivery = entry.get("slash_delivery", DEFAULT_SLASH_DELIVERY)
        if delivery not in SLASH_DELIVERIES:
            raise ConfigError(
                "Config at {}: agents[{}].slash_delivery is {!r}; use {}. "
                "\"paste\" sends the command with `herdr agent prompt`; "
                "\"type\" types it into the pane and presses Enter. Which one "
                "an agent needs depends on its TUI.".format(
                    source, index, delivery, " or ".join(repr(v) for v in SLASH_DELIVERIES)
                ),
                {"source": source, "index": index, "slash_delivery": delivery},
            )
        glyph = entry.get("composer_glyph", "")
        model_label = entry.get("model_label", "")
        if not isinstance(model_label, str):
            raise ConfigError(
                "Config at {}: agents[{}].model_label is {!r}; use a string "
                "such as \"gpt-5.6\", or leave it out.".format(
                    source, index, model_label
                ),
                {"source": source, "index": index, "model_label": model_label},
            )
        if not isinstance(glyph, str):
            raise ConfigError(
                "Config at {}: agents[{}].composer_glyph must be a string - the "
                "prompt glyph that starts the composer row, e.g. \"\u203a \" "
                "for codex. Omit it to skip the consumed-command check.".format(
                    source, index
                ),
                {"source": source, "index": index},
            )
        ignore_dim = entry.get("composer_ignore_dim", DEFAULT_COMPOSER_IGNORE_DIM)
        if not isinstance(ignore_dim, bool):
            raise ConfigError(
                "Config at {}: agents[{}].composer_ignore_dim must be true or "
                "false - true (the default) treats dim composer text as empty, "
                "which is what Claude Code's ghost-text suggestion and Codex's "
                "\"Ask Codex to do anything\" placeholder both need.".format(
                    source, index
                ),
                {"source": source, "index": index},
            )
        enters = entry.get("slash_enter_count", DEFAULT_SLASH_ENTER_COUNT)
        if (
            isinstance(enters, bool)
            or not isinstance(enters, int)
            or not 1 <= enters <= MAX_SLASH_ENTER_COUNT
        ):
            raise ConfigError(
                "Config at {}: agents[{}].slash_enter_count is {!r}; use an "
                "integer from 1 to {}. Codex needs 2 because its autocomplete "
                "popup swallows the first Enter.".format(
                    source, index, enters, MAX_SLASH_ENTER_COUNT
                ),
                {"source": source, "index": index, "slash_enter_count": enters},
            )
        agents.append(
            Agent(
                name=name,
                kind=entry["kind"],
                usage_prompt=entry["usage_prompt"],
                usage_marker=entry["usage_marker"],
                usage_read_source=read_source,
                clear_prompt=entry["clear_prompt"],
                slash_delivery=delivery,
                composer_glyph=glyph,
                composer_ignore_dim=ignore_dim,
                model_label=model_label,
                slash_enter_count=enters,
                **lists
            )
        )
    return agents


def _read_config(path):
    """Decode the config JSON at `path`, or raise an actionable ConfigError.

    Split out from `load_config` so the two readers of this one file -- the
    agent list and the `role_costs` weights -- share every read and decode
    error message rather than growing a second, drifting copy.
    """
    try:
        text = path.read_text(encoding="utf-8")
    except FileNotFoundError:
        raise _missing_config_error(path) from None
    except PermissionError as exc:
        raise ConfigError(
            "Agent config at {} is not readable: {} - fix its permissions "
            "with `chmod u+r {}`.".format(path, exc.strerror, path),
            {"path": str(path)},
        ) from None
    except IsADirectoryError:
        raise ConfigError(
            "Agent config path {} is a directory - point --config at the "
            "config.json file itself.".format(path),
            {"path": str(path)},
        ) from None

    try:
        return json.loads(text)
    except json.JSONDecodeError as exc:
        raise ConfigError(
            "Agent config at {} is not valid JSON ({} at line {} column {}) - "
            "validate it with `python3 -m json.tool {}`.".format(
                path, exc.msg, exc.lineno, exc.colno, path
            ),
            {"path": str(path)},
        ) from None


def load_config(path):
    """Read and validate the config file at `path`."""
    path = Path(path)
    return parse_config(_read_config(path), source=str(path))


def parse_role_costs(payload, source="<memory>"):
    """Validate the optional `role_costs` override map, `{}` when it is absent.

    Each value is what one round in that seat is expected to burn out of an
    agent's remaining headroom percentage; the planner merges the map over its
    own defaults, one role at a time. Validation lives here rather than in the
    planner so the message names the file the operator has to edit.
    """
    if not isinstance(payload, dict):
        raise ConfigError(
            "Config at {} must be a JSON object with `schema_version` and "
            "`agents` - see config.example.json.".format(source),
            {"source": source},
        )
    costs = payload.get("role_costs")
    if costs is None:
        return {}
    if not isinstance(costs, dict):
        raise ConfigError(
            "Config at {}: `role_costs` is a JSON {}, not an object mapping a "
            "role to its cost weight - write it as "
            "{{\"developer\": 12, \"tester\": 10, \"reviewer\": 5}}, or remove "
            "it to take the planner's defaults.".format(
                source, type(costs).__name__
            ),
            {"source": source, "role_costs_type": type(costs).__name__},
        )
    validated = {}
    for role, value in costs.items():
        # bool is a subclass of int, and `true` is not a one-point round.
        if (
            isinstance(value, bool)
            or not isinstance(value, (int, float))
            or math.isnan(float(value))
            or math.isinf(float(value))
            or value < 0
        ):
            raise ConfigError(
                "Config at {}: role_costs[{!r}] is {!r}; use a non-negative "
                "number of headroom points, e.g. 12. A weight only has to "
                "order the seats against each other.".format(source, role, value),
                {"source": source, "role": role, "value": value},
            )
        validated[role] = float(value)
    return validated


def load_role_costs(path):
    """`role_costs` from the config at `path`; `{}` when there is no config.

    `plan` contacts no agent and needs no roster, so it runs on a machine that
    has never been set up. A missing config there means "no overrides", not a
    failed plan -- but a config that EXISTS and is malformed still fails
    loudly, exactly as it does for `measure`.
    """
    path = Path(path)
    if not path.exists():
        return {}
    return parse_role_costs(_read_config(path), source=str(path))


def select_agents(agents, names):
    """Return the subset of `agents` named in `names`, in config order.

    An empty/None `names` selects every agent. An unknown name is an error,
    never a silent no-op.
    """
    if not names:
        return list(agents)
    by_name = {agent.name: agent for agent in agents}
    unknown = [name for name in names if name not in by_name]
    if unknown:
        raise ConfigError(
            "No configured agent named {} - configured agents are {}. "
            "Check the name against `herdr agent list`.".format(
                ", ".join(repr(name) for name in unknown),
                ", ".join(sorted(by_name)),
            ),
            {"unknown": unknown, "configured": sorted(by_name)},
        )
    wanted = set(names)
    return [agent for agent in agents if agent.name in wanted]

skills

README.md

tile.json