get-fable / fable-tend
CLAUDE.md6 starsChanged 3 months agoMITView on GitHub
Tend is an autonomous CI maintainer for GitHub repos: it reviews PRs, triages issues, and fixes CI, powered by Claude or Codex. This repo ships the generator (uvx tend@latest) that stamps each consumer's workflow files, plus the plugins and composite actions those workflows run. No backward compatibility. When a config format or API changes, cut over completely — old formats should fail with a clear error, not silently parse. Simplicity outranks efficiency. Complexity earns its place by preventing wrong outward…
In this repository55 other files it gives its agents.
- AGENTS.mdAGENTS.md
- .codex/AGENTS.mdAGENTS.md
- docs/AGENTS.mdAGENTS.md
- skills/fable-tend/AGENTS.mdAGENTS.md
- skills/fable-wise/AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- skills/fable-tend/site/CLAUDE.mdCLAUDE.md
- skills/fable-wise/CLAUDE.mdCLAUDE.md
- public/llms.txtllms.txt
- get-fableSkill
- typesafe-aiSkill
- get-fableSkill
- get-fableSkill
- fable-architectureSkill
- fable-artifactSkill
- fable-configSkill
- fable-context-thriftSkill
- fable-councilSkill
- fable-coworkSkill
- fable-datavizSkill
- fable-delegateSkill
- fable-discoverSkill
- fable-domainSkill
- fable-ecoSkill
- fable-evalSkill
- fable-executeSkill
- fable-finish-your-turnSkill
- fable-handoffSkill
- fable-healSkill
- fable-judgeSkill
- fable-learningSkill
- fable-loopSkill
- fable-memorySkill
- fable-methodSkill
- fable-native-codeSkill
- fable-outcome-firstSkill
- fable-planSkill
- fable-prove-itSkill
- fable-recoverSkill
- fable-redteamSkill
- fable-releaseSkill
- fable-researchSkill
- fable-reviewSkill
- fable-runSkill
- fable-scope-disciplineSkill
- fable-securitySkill
- fable-simplifySkill
- fable-simulatorSkill
- fable-skill-creatorSkill
- fable-sparkSkill
- fable-tddSkill
- fable-tendSkill
- fable-verifySkill
- fable-wiseSkill
- get-fableSkill
The file
We split this CLAUDE.md into the rules it gives an agent
10 rules. About 0 are common, 7 are distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈7
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 8
No backward compatibility.
- Distinctive: line 228
When overrides are present, the generator renders the base template, parses it, merges the overrides, and re-serializes.
- Very specific: line 270
Every consumer runs the environment gate: both secrets live in the tend environment with no repo-level copies.
- Very specific: line 348
Before writing guidance outside plugins/, check whether it ends up in a consumer's repo or session.
- Distinctive: line 413
When reviewing or surveying a guidance file, ask both questions of it — does this ship, and who acts on it — and ask them of the sections a …
- Very specific: line 420
When adding to or editing files in plugins/tend-ci-runner/skills/ or .claude/skills/:
- Distinctive: line 432
No specific past-run references: Don't link GitHub Actions runs, cite session IDs, or quote durations from individual incidents.
- Distinctive: line 436
No specific past-case references: Don't cite individual past PRs, issues, or commits as supporting precedent for a rule (e.g.
- Distinctive: line 473
Keep the actions' Bash as linear installation and plumbing, not a second implementation layer.
- Distinctive: line 478
Don't build deterministic YAML steps for work that happens *inside* an agent run.
≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Development
Tend is an autonomous CI maintainer for GitHub repos: it reviews PRs, triages
issues, and fixes CI, powered by Claude or Codex. This repo ships the generator
(uvx tend@latest) that stamps each consumer's workflow files, plus the plugins
and composite actions those workflows run.
No backward compatibility. When a config format or API changes, cut over completely — old formats should fail with a clear error, not silently parse.
Simplicity outranks efficiency. Complexity earns its place by preventing wrong outward actions — what the bot posts, approves, merges, or closes — rather than by saving compute. Prefer deleting a mechanism over refining it. "What waste is worth fixing" below says which waste earns a change.
Commands
wt test # everything: uv run pytest, then worker/'s vitest
uv run pytest # the Python half alone, from the repo root
uvx tend@latest init # regenerate workflows from .config/tend.yaml
uvx tend@latest init --dry-run # preview without writing
uvx tend@latest check # verify branch protection, secrets, bot access
uv tool run pre-commit run --all-files # lint: every hook in .pre-commit-config.yamlThe repo root is a uv workspace, and pytest run from it collects every Python
test — generator/, proxy/, shared/steps/, and the install-tend scripts — so
one pytest is the whole Python run. wt test (defined in
.config/wt.toml) adds worker/'s vitest suite and
typecheck. Its arguments narrow pytest and nothing else, so a filtered run
still pays for worker/; uv run pytest -k render
is the Python half on its own.
pre-commit is not on the CI sandbox's PATH, which is why the lint command
above carries the uv tool run prefix; a narrower substitute (ruff alone,
shellcheck alone) skips ten of the thirteen hooks, including the three
repo: local guards — the bang-backtick check, the install-tend mirror sync,
and the sandbox_env reserved-set parity check.
Inside a tend session the sandbox has no DNS, so the two tests in
generator/tests/test_refresh_consumers.py fail: they drive the script
through uv run --script, which resolves its dependencies from PyPI. Nothing
else in the suite reaches the network, so a third failure is a real one.
Architecture
Four pieces:
- Plugins —
install-tend(user-facing setup) andtend-ci-runner(CI skills). Both ship from the same marketplace. Composite actions — the stable interface, pinned to an immutable release tag (
max-sixty/tend/<action-path>@X.Y.Z, the generator's own version — no floatingv1). Every action lives under a harness-named path; there is no bare-root default. The two harness runners are:max-sixty/tend/claude@X.Y.Z(Claude) — runs the officialclaudebinary headless (claude -p) inside the shared Anthropic Sandbox Runtime boundary; completion is the process exit code plus result event. Inputs inclaude/action.yaml.max-sixty/tend/codex@X.Y.Z(Codex) — installs@openai/codexand shells out tocodex exec. Skills are staged on disk and anAGENTS.mdin$CODEX_HOMEteaches Codex to resolve/tend-ci-runner:NAMEslash commands. Inputs incodex/action.yaml. Shares the cross-harness workspace, SRT lifecycle, preflight, and teardown scripts undershared/steps/.
Both harness runners resolve the bot's numeric ID at runtime, run security and rate-limit preflight, prepare an independent event checkout, run
sandbox_setup:and the complete agent turn in one SRT process tree, reap it, and upload bounded session logs. The generated workflow's checkout stays runner-owned on reviewed code for setup and local-action POST chains.max-sixty/tend/codex/refresh@X.Y.Zis the Codex support action. A generated serialized workflow runs it weekly to rotate Plus/Pro credentials and publish the full and access-only bundles. It holds no bot token and does not inspect consumer code. Inputs incodex/refresh/action.yaml.Removed:
claude-interactive, a PTY-supervised variant of the same binary that existed only to dodge the 2026-06-15 Agent SDK metering (which coveredclaude -pbut not interactive sessions). Anthropic paused that change and the default harness now runs the binary rather than the SDK, so nothing selected it. Restore from036f9c4if the metering resumes.- Generator (
uvx tend@latest init) — stamps workflow files into the consumer's.github/workflows/from.config/tend.yaml. Picks the right action ref and secret names perharness. Generation is idempotent — runninginitagain overwrites all files from the current config. When the review workflow is generated, it also merges theconcurrency.queueignore into the consumer-owned.github/actionlint.yaml(see "Concurrency and filtering"). - Config (
.config/tend.yaml) — inputs to the generator. Overrides from defaults only.harness: claude | codexselects the harness (defaultclaude). A per-workflowharness:override (and matchingmodel:) lets a consumer trial a different harness on one workflow at a time. All workflows are generated by default.enabled: falseomits a workflow on regeneration. Pausing all of tend is not config: every job that runs the agent checks theTEND_ENABLEDrepository variable in itsif:.
Generated workflows are standalone — full steps: jobs, not
workflow_call. The generator owns the entire file. Trusted runner setup
(system tools and Actions caches) is defined in setup:; dependency setup
against the event tree is defined in sandbox_setup: and runs inside SRT.
Structure
tend/
├── .claude-plugin/
│ └── marketplace.json # Claude Code marketplace — lists both plugins
├── .agents/plugins/
│ └── marketplace.json # Codex marketplace — lists both Tend plugins
├── plugins/
│ ├── install-tend/ # User-facing plugin (setup skill)
│ └── tend-ci-runner/ # CI plugin (review, triage, ci-fix, etc.)
│ ├── .codex-plugin/ # Codex plugin manifest
│ └── scripts/ # Helper scripts (survey, run listing)
├── claude/
│ └── action.yaml # Claude harness composite action (default, headless)
├── codex/
│ ├── action.yaml # Codex harness composite action
│ ├── refresh/
│ │ └── action.yaml # Serialized Plus/Pro credential refresh action
│ ├── runner.py # Codex harness commands (plugin, prompt, execution)
│ └── agents-tail.md # AGENTS.md appendix for Codex
├── shared/
│ ├── steps/ # Shared composite-action step bodies (Python; bash for the install/plumbing ones)
│ └── system-prompt.md # Harness-neutral system prompt base
├── proxy/ # Credential-injection proxy (setup_sandbox.py, addon)
├── generator/ # Python package (uvx tend@latest), uv_build backend
│ ├── src/tend/
│ │ ├── config.py # Reads .config/tend.yaml
│ │ ├── workflows.py # Generates workflow YAML
│ │ ├── checks.py # Security checks (branch protection, secrets)
│ │ └── cli.py # Click CLI (init, check)
│ └── tests/
├── site/ # Astro marketing site (tend-src.com)
├── worker/ # Cloudflare Worker — serves the 2 site data streams
├── data/ # consumers.json — Worker's input (refreshed weekly)
├── docs/
│ └── security-model.md
└── pyproject.toml # uv workspace root: dev deps + the lockfileKey details
The tend-*.yaml files in .github/workflows/ are generated by uvx tend@latest init
from .config/tend.yaml. Edit the generator or config, not the workflow files
directly.
The generator is a Python package under generator/ — uses the uv_build
backend, requires Python 3.11+. Runtime dependencies: click, jinja2,
ruamel.yaml. It is the one member of the repo's uv workspace, so the lockfile
and the dev dependencies (pytest, pytest-regtest, and the pinned mitmproxy the
proxy addon imports) live in the root pyproject.toml, and the dev environment
needs 3.12+ even though the package supports 3.11.
Consumer repos regenerate their tend-*.yaml workflows nightly (tend itself
included — it dogfoods its own workflows). Changes to the generator do not
require manual regeneration in downstream repos.
Tend's own tend-*.yaml workflows track the latest published release. They
update each night via uvx tend@latest init. Updating earlier to the latest
release (e.g., during a release commit) is fine. Never regenerate them with
the in-tree generator: the action ref is pinned to the generator's own
version (max-sixty/tend/<harness>@X.Y.Z), so an unreleased in-tree version
stamps a tag that does not exist yet, and the workflow's uses: fails to resolve.
Between a generator commit and the next release the committed workflows lag
the in-tree generator; that is expected, and the gap closes at the next
release (which tags X.Y.Z before regenerating, so the pin always resolves).
claude_version in claude/action.yaml is an exact version taken from npm's
latest dist-tag, not stable. stable lags several releases, and pinning it
wouldn't keep un-promoted code out of the job anyway: install.sh always
downloads the latest build as its bootstrap installer, and only the agent
session runs the pin.
Generator vs consumer ownership
| Aspect | Owner | Lives in |
Trigger events (on:) | Generator | generated workflow |
Filter conditions (if:) | Generator | generated workflow |
| Engagement verification (mention) | Generator | generated workflow |
| Concurrency groups | Generator | generated workflow |
| Permissions | Generator | generated workflow |
| Checkout | Generator | generated workflow |
| Composite action call | Generator | generated workflow |
| Runner setup (system tools, Actions cache) | Consumer | setup: in .config/tend.yaml |
| Event-tree setup (dependencies, generated files) | Consumer | sandbox_setup: in .config/tend.yaml |
| Bot identity, auth config | Consumer | .config/tend.yaml |
| Skills (generic) | Tend | tend-ci-runner plugin (marketplace) |
| Skills (project-specific) | Consumer | .claude/skills/ in their repo |
Workflow overrides
Consumers extend generated workflows via YAML keys that the generator
merges into the rendered YAML using RFC 7396 (JSON Merge Patch — mappings
deep-merge, scalars and lists replace, null deletes):
workflows:
review:
workflow_extra:
env:
MY_VAR: hello
jobs:
review:
timeout-minutes: 240
runs-on: ubuntu-22.04-largeYAML has a native null literal, so RFC 7396's null-deletes works
directly — e.g. drop the cron from a scheduled workflow while keeping
workflow_dispatch:
workflows:
nightly:
workflow_extra:
on:
schedule: nullWorkflow-level (workflow_extra) and job-level (jobs.<name>) overrides
are supported; step-level is not — setup: handles trusted runner steps and
sandbox_setup: handles event-workspace commands. No allowlist of override
keys; unknown job names produce a warning.
When overrides are present, the generator renders the base template, parses it, merges the overrides, and re-serializes. Output YAML formatting differs slightly from the base template (block-style lists) but is functionally identical.
Auth
Each consumer creates a GitHub bot account and a classic PAT with repo,
workflow, notifications, write:discussion, gist, and user. repo
is required on public and private repositories alike — install-tend mints
exactly that set, and the nightly scope audit (pat_scope_audit.py) reports
anything narrower as missing. The PAT and a Claude OAuth token are
stored as secrets in the repo's tend GitHub Environment, whose deployment
branch policy admits only the branches tend check confirmed the bot
cannot write — the default branch and any protected_branches that exist
and are protected. A workflow the bot pushes to any other ref is refused
them before its first step. Two things use the gist scope, both
through bot-owned secret gists: review-reviewers keeps a per-month
structured evidence store (avoids the 65 KB comment-body limit), and the
experimental memory_gist setting persists Claude Code's auto memory
across runs. The user scope lets install-tend set the bot's profile
bio (PATCH /user) so the account's authorization stance is discoverable
on the bot's user page.
The agent may use PAT-backed GitHub API and git access for any repository the
bot account can reach; this cross-repository access is intentional. Credential
isolation keeps the PAT itself out of the agent process. If the PAT is stolen
by compromising the runner or proxy, its repo scope grants full write to
every repository the bot account can reach, private ones included.
Fine-grained PATs allow per-category scoping but don't support outside
collaborators (GitHub roadmap
#601, not shipped).
Current privilege model: write + branch protection + environment gate.
The bot has write access; a merge restriction (ruleset or branch
protection) is the primary security boundary — without it the bot can merge
its own PRs — and the tend environment keeps the operational secrets out
of any run the bot can cause on its own. tend check verifies both are
configured correctly, and --fix creates either. See
docs/security-model.md for the full threat model. Alternative models
(GitHub App, triage+fork) are in TODO.md.
Every consumer runs the environment gate: both secrets live in the tend
environment with no repo-level copies. This repo's own workflows name it
too, including the hand-maintained ones the generator never touches.
Concurrency and filtering
Events pass through three layers before the bot does work:
- GHA
if:conditions — evaluated by Actions before the job starts. A false condition skips the job entirely (never enters the concurrency group, never queues). - Custom
should_runpre-checks — cheap deterministic steps that decide whether the agent boots: mention's verify job checks engagement, and notifications' check repairs repository watching, captures a paginated cutoff snapshot, and finds configured-bot PR conflicts whose current heads have not already been deferred for manual resolution. - Concurrency groups — at most one running job per group.
Concurrency groups:
| Workflow | Group key | Cancel-in-progress |
| review | workflow-PR# | no — killing a session discards a review it can still deliver; queue: max holds pending PR events within GitHub's queue limit while it folds the push in and posts |
| mention-relay | none | stateless — secretless job that re-posts review events to mention as a repository_dispatch |
| mention/verify | none | stateless |
| mention/handle | workflow-handle-issue#|PR# | no — each mention runs to completion |
| triage | workflow-issue# | yes — latest comment wins |
| notifications | tend-notifications | no — one poll drains notifications and repairs bot PRs at a time |
| ci-fix | workflow-<watched workflow>-<branch> | no — a session mid-fix may already have pushed a branch or opened a PR |
| nightly / weekly | none | cron-serialized |
| codex-auth-refresh (codex only) | tend-codex-auth-refresh | no — only this workflow may rotate the refresh-token chain, so a second refresher must never race it |
Fork guard. Workflows whose triggers can fire from a fork's own
Actions (schedule, workflow_dispatch, workflow_run, issues) carry
if: github.repository_owner == '<owner>' so a fork that's enabled
Actions but doesn't have the bot/Claude secrets no-ops cleanly. The
canonical owner is detected at init time (via gh repo view, walking
source.owner.login if the local repo is itself a fork) and pinned in
the generated workflow. tend-review uses pull_request_target (base
repo only) and tend-mention-relay, which carries tend-mention's review
events, already filters forks via head.repo.full_name ==
github.repository, so neither needs the guard.
Red branches. A red default branch fails every push that follows it, each
on its own commit, so ci-fix keys its group on the branch — a commit-keyed
group collapses nothing, since the burst is distinct commits rather than one
commit retried. The watched workflow is in the key too, so a red
publish-site isn't starved behind a stream of red ci. ci-fix's group is
job-level, not workflow-level: most workflow_run events are green runs the
job's if skips, and a skipped job never enters the group.
GHA queue depth. Review sets queue: max, so pending PR events within
GitHub's queue limit wait and a later push cannot replace ready_for_review.
GitHub accepts the key; actionlint's schema does not, so init merges the
matching ignore into .github/actionlint.yaml, which the binary reads for
every invocation.
Mention/handle and notifications keep the default one-pending-run queue; when a
third job arrives while one runs and one queues, the pending job is replaced. For mention,
mitigation lives in the skill prompts: dedup if the bot already responded to
the triggering comment; self-heal earlier comments without a bot reply (oldest
first). The workflow injects the queue-to-run time delta (seconds between event
timestamp and job start) into the prompt — over ~40 s indicates the job was
queued behind another run, making conversation drift more likely.
Notifications stay unread until a poll records an outcome, so the newest
pending run covers a replaced poll. ci-fix keeps the default too, and wants
it: while a session works a red branch, the newest unsuccessful run carries
that branch's current state, so replacing the pending run loses nothing.
Shipped guidance and tend's own
Two kinds of guidance live in this repo, split by which repo the text governs.
Shipped guidance is any text that reaches a repo other than this one. It
tells the bot what to do in a repo it maintains, and has to hold in repos
nobody here has seen. More of it than plugins/**: shared/system-prompt.md
and the two directives compose_system_prompt.py prepends, codex/agents-tail.md,
the header comment workflows.py stamps into every generated workflow, and
docs/tend.example.yaml, which a pre-commit hook mirrors into install-tend.
Before writing guidance outside plugins/, check whether it ends up in a
consumer's repo or session.
Tend's own guidance is read only here: CLAUDE.md, TODO.md,
docs/security-model.md, and .claude/skills/. It tells a session — the bot's
or yours — how to work on tend.
The test for a line of shipped text: does it hold in a repo whose maintainer disagrees with us? What tend values, what this repo will spend complexity on, how many open PRs is too many, how much runner time is worth saving — those are one maintainer's calls. Shipped, they make every consumer's repo answer to tend's; they belong here instead. Mechanics ship freely: how GitHub behaves, what the harness does, what a session must never do. A default a consumer might want differently ships as a default their overlay can change, stated without our reasoning for it.
Two naming decisions. A repo that installs tend is a consumer, the term
data/consumers.json, refresh_consumers.py, and the Worker already use — not
an adopter, which the changelog keeps only in released entries. And the second
kind of guidance isn't internal: a skill's metadata: internal: true already
means it isn't user-invocable, and every shipped skill carries it.
Bundled and overlay
Within shipped guidance, plugins/tend-ci-runner/skills/ holds the defaults
and a consumer overlays them at .claude/skills/running-tend/SKILL.md; where
the two conflict, the overlay wins. .agents/skills links to .claude/skills,
so Claude and Codex discover the same repo-local skills. Repo-specific policy,
taste, or convention (PR title formats, label names, branch routing) goes in an
overlay. Use outcomes from consumer runs to refine the defaults: a general
missing instruction or wrong default is a bundled fix, repository policy an
overlay one.
Tend's overlay here is tend's own guidance by the rule above — it doesn't ship,
and it carries the tasks and conventions for bot sessions in this repo. A whole
skill belongs there when only this repo's workflows invoke it:
.claude/skills/review-reviewers/ is one, dispatched by the hand-maintained
review-reviewers.yaml, whose prompt invokes it as /review-reviewers — a
slash command like any skill, without the plugin prefix. A tend-only skill
left in the plugin installs into every consumer, is invoked by none of them,
and collects tend's internal text next to the files they do load.
Which file
Shipped or not, a rule is loaded by one set of sessions and acted on by a subset of them. Put it at the narrowest level every actor still reads: the sessions in the gap each pay attention for text that changes nothing they do.
| File | Read by | Ships |
shared/system-prompt.md | every session, both harnesses | yes |
running-in-ci/SKILL.md | every session | yes |
a workflow's SKILL.md | that workflow's sessions | yes |
a skill's references/ | only the sessions taking that action | yes |
.claude/skills/ (the overlay, and any skill only this repo invokes) | sessions in that one repo | no |
CLAUDE.md | sessions working on the repo it sits in | no |
- A rule has one home. Another file that needs it names the section
instead of restating it — copies drift, and a partial copy drops what the
copier left out. Before adding a rule,
rga distinctive phrase from it acrossplugins/,shared/, andCLAUDE.md. - Every workflow that invokes the agent names a skill (
default_promptinconfig.py). One without a skill has nowhere to put its own rules, so they land in the every-session file instead.
When reviewing or surveying a guidance file, ask both questions of it — does this ship, and who acts on it — and ask them of the sections a change leaves in place, not only the ones it touches: a restructure that moves three sections and keeps four has reviewed three.
Authoring skills
When adding to or editing files in plugins/tend-ci-runner/skills/ or
.claude/skills/:
- Be brief. Skills are loaded into every relevant session — extra prose is overhead. Lead with the rule or recipe; cut motivation, anecdotes, and historical context unless required to apply the rule.
- The References table in
running-in-ci/SKILL.mdistend-ci-runner's one index ofreferences/files, naming each file and the action that triggers reading it — a new bundled reference gets a row there, not a list in its own skill. A repo overlay's references stay in the overlay, named where its own steps use them: every consumer reads the bundled table, and repo-specific guidance doesn't belong in it. - No specific past-run references. Don't link GitHub Actions runs, cite session IDs, or quote durations from individual incidents. They age into trivia and aren't useful when the skill is reused. State the structural rule without the run links.
- No specific past-case references. Don't cite individual past PRs, issues, or commits as supporting precedent for a rule (e.g. "[#123] tried X and was closed"). They age into trivia, accumulate as the rule is re-tested, and create read-time work. State the structural rule (what shape is accepted, what shape is not) without naming the cases that produced it.
- Date-stamp only when the value depends on time. A baseline number expected to drift can be dated; a one-shot incident citation should not be.
- Prefer recipe over narrative. A code block plus a one-sentence framing beats a multi-paragraph explanation.
- Examples over templates; open frames over closed menus. For text the agent produces (comments, PR bodies, summaries), give an example labeled as such rather than verbatim wording to paste — canned phrasing reproduced literally reads awkwardly off-situation. For choices the agent makes (when to respond, which cases to check), frame the goal and give examples rather than a list it will read as exhaustive. Reserve mandatory exact wording for fragile mechanics: dedup keys, commands, API formats.
Agent-driven vs deterministic steps
Tend's workflows invoke the agent through the harness-specific composite
action (max-sixty/tend/claude@X.Y.Z for Claude, max-sixty/tend/codex@X.Y.Z
for Codex). When adding new capability, split work along this line:
- The agent drives diagnostics and remediation. Once the action is
running, put logic into the relevant skill (or a script the skill calls —
see
plugins/tend-ci-runner/scripts/). The agent handles edge cases, interprets output, and writes clearer messages than shell. - Actions gate whether the agent runs at all. Agent invocations cost
tokens; gating them in YAML is cheap. Pre-check steps that early-exit
the job (e.g.
tend-notifications's notification and PR conflict check) save an entire agent run when there's nothing to do. A gate stays cheap only while it stays trivial: one pre-check against a frequent no-op (an empty inbox every cron tick) earns its place; a run of bespoke gates, each skipping one rare event shape, is machinery that costs more than the boots it saves.
Keep the actions' Bash as linear installation and plumbing, not a second implementation layer. Do not add branches, retries, caches, or state for rare event shapes or small runner-time savings; put substantial behavior in a skill or tested Python.
Don't build deterministic YAML steps for work that happens inside an agent run. Extend the skill instead.
What waste is worth fixing
Wasted runner time (a slow CI job, a run lost to a blip that a later tick retries) costs cents, while the retry wrapper, cache, or scheduling arithmetic that would prevent it is maintained forever: fix it only with a simple knob — a cadence value, a deleted step, a one-line condition — and otherwise leave it. Model tokens are the larger cost, dollars per session, so keep sessions, and the context each one carries, to what the work needs.
Judge a token saving by the share of total spend it removes. A change that materially cuts the total — a frequent boot that never had work to do, bulk reading carried into every deciding context — is worth the mechanism it leaves behind, so estimate that share before proposing one. A small or rare saving doesn't justify complication, however well the change reads line by line.
Judge the fix itself by the whole change, and by what it leaves behind — logic a future session must re-derive, a rule every later run loads, failure modes of its own — rather than by its line count. The same setting repeated across workflows, jobs, platforms, or call sites is a configuration scheme rather than one knob, and a mechanism compressed into one dense line is still a mechanism.
This is tend's answer, and it stays here. The bundled skills carry no view on what compute is worth — a bot session in this repo reads this file, and a session in another repo reads that repo's.
Live testing against real GitHub
For live experiments against real GitHub behavior (environments, branch
protection, workflow triggers, secret release), tend-agent/tend-integration
is a persistent public repo we own, admin via the tend-agent account
(gh auth token --user tend-agent). Its main is branch-protected with
enforce_admins: false, so the owner can push directly and reset in place.
The weekly integration test (.claude/skills/running-tend/references/integration-test.md)
also drives it, so clean up any probe artifacts (extra workflows, branches,
environments, dummy secrets) when done.
Site and worker
site/ is the Astro marketing site (tend-src.com); worker/ is the
Cloudflare Worker that serves its two data streams from data/consumers.json.
The site's dev server starts automatically per worktree via a wt post-start
hook (.config/wt.toml) on a deterministic port derived from the branch name.
Get the URL with
wt list statusline --format json | jq -r '.items[].dev_server.url'; logs land
in .git/wt/logs/. Don't run npm run dev; it duplicates the running server on
a different port.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

