Skip to content

Group agents by engine in the directory tree - #88

Merged
haruotsu merged 4 commits into
mainfrom
engine-groups
Oct 13, 2026
Merged

haruotsu merged 4 commits into
mainfrom
engine-groups

Conversation

@haruotsu

@haruotsu haruotsu commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Why

Since the engines were named in gete.yaml, an agent refers to its engine by name, but the tree still does not show which agent is listed where: every agent sits directly below agents/, and the engine is one line inside each agent.yaml. With a dozen agents across several engines the natural layout is one directory per engine:

agents/
  sales/
    foo-agent/agent.yaml
    bar-agent/agent.yaml
  support/
    baz-agent/agent.yaml

Until now that layout was ignored without a word. load_project read agents/*/agent.yaml and nothing else, so:

  • gete validate passed,
  • gete terraform wrote no module call for the agents below the group. Files written for them before a move stayed behind and pointed at directories that were gone; regenerated from a clean terraform/, the module calls were simply missing,
  • and the plan destroyed their reasoning engines.

agents_dir does not help: it names one directory, and only its direct children are read.

What

Agents grouped by engine. A directory below agents/ that carries a name from gemini_enterprise.engines holds the agents listed in that engine. They need no registration block. A block that agrees with the directory is accepted; one that disagrees is reported, once, whether it names another engine or writes an id, and the directory is what counts, since it is what the tree shows. register, graph, and --check-secrets read the engine through Agent.engine, so they see the group without changes of their own.

Nothing is recreated by the move. The agent's name still matches its own directory, so the module address, the service account, and the archive are unchanged. Only the generated agent_directory changes:

agent_directory = "${path.module}/../agents/sales/foo-agent"

Strays are reported, not skipped. Stepping over a directory quietly is what made the layout dangerous, so validate now reports:

  • a directory below agents/ with no agent.yaml that names no engine (a misspelt engine would otherwise drop every agent below it),
  • a directory below a group with no agent.yaml (groups go one level deep),
  • an agent.yaml below a top-level agent that carries an engine's name: that directory is the agent, not the group, so an agent put there would otherwise be stepped over,
  • a registration block that disagrees with the group,
  • two directories with one name, which a flat agents/ could not hold and which would fight over one module, service account, and reasoning engine.

Hidden directories (.venv, a VCS directory) are tooling and stay unreported, as they stay out of the archive.

gete init sales/foo-agent scaffolds the agent below the engine's directory. The engine has to be named in gete.yaml first; otherwise init refuses, and lists the files it did write, since the gete.yaml among them is where the engine goes. Init also refuses, before writing anything, the two shapes the loader would report: a group below a directory that is an agent, and an agent on top of a directory that already holds the engine's agents.

Flat projects work as before. Top-level agents and groups coexist, which is also where an agent listed in no engine goes.

Decisions

  • A group's name is always a named engine. A folder with any other name is reported rather than read as "a group listed nowhere", so a typo in an engine's name cannot pass as an unregistered group. Without gemini_enterprise.engines there are no groups; the message says to name the engines.
  • An agent and an engine's group cannot share a directory. A directory with its own agent.yaml is that agent, whatever its name; the loader reports what sits below it, and init refuses to write it.
  • One level. Deeper nesting is refused, by the loader and by gete init.
  • Behaviour change: a directory below agents/ without agent.yaml used to be skipped; it is now a validate problem. The old test that pinned the skip is replaced. The same holds for a custom agents_dir: a directory there that is neither an agent nor a group is reported, so an agents_dir that also holds other content no longer validates.

Checks

  • uv run pytest -q: 987 passed
  • ruff check, ruff format --check, mypy: clean
  • examples/minimal: gete validate and gete terraform --check pass
  • Smoke test in a scratch project: init into groups, validate, graph, the stray cases reported, terraform output as above

With the engines named in gete.yaml, an agent refers to its engine by
name, but the tree still does not show which agent is listed where:
every agent sits directly below agents/, and the engine is one line
inside each agent.yaml. With a dozen agents across several engines the
natural layout is one directory per engine, and until now that layout
was ignored without a word. load_project read agents/*/agent.yaml and
nothing else, so validate passed, terraform wrote no module call for
the agents below the group and dropped the ones it had written, and the
plan destroyed their reasoning engines.

A directory below agents/ that carries a named engine's name now holds
the agents listed in that engine. They need no registration block; one
that disagrees with the directory is reported, and the directory is what
counts, since it is what the tree shows. The name still matches the
agent's own directory, so the module address, the service account, and
the archive are unchanged by the move: only the generated
agent_directory changes, and nothing is recreated.

Groups go one level deep. A directory below a group without agent.yaml
is reported, and so is a directory below agents/ that is neither an
agent nor a named engine, since stepping over it quietly is what made
the layout dangerous: a misspelt engine would take every agent below it
out of the project. Hidden directories are tooling and stay unreported,
as they stay out of the archive. Two directories with one name, which a
flat agents/ could not hold, are reported on both: the module, the
service account, and the reasoning engine are named after it.

🤖 Generated with Claude Code
gete init sales/mail-triage writes agents/sales/mail-triage/, where the
agent is listed in sales by where it sits. The engine has to be named in
gete.yaml first: validate refuses the directory otherwise, and a
scaffold that fails its own validator teaches the wrong shape. One level
only, like the loader. When init created the project and then refuses
the group, the files it did write are still listed, since the gete.yaml
among them is where the engine goes.

🤖 Generated with Claude Code
With the engines named, a top-level agent may carry an engine's name,
and the loader reads a directory with agent.yaml as that agent before
it looks for a group. An agent written below it, which gete init
accepted, was then stepped over without a word and validate passed: the
silent skip the grouped layout was meant to remove, back through a side
door.

The loader now reports an agent.yaml found below an agent named after
an engine, and init refuses both shapes before writing: a group below a
directory that is an agent, and an agent on top of a directory that
already holds the engine's agents.

A grouped agent whose registration block carries the engine's id was
reported twice, once for not being a name and once for disagreeing with
the directory. The directory is what counts there, so the block gets
the one line that says so.

🤖 Generated with Claude Code
@haruotsu
haruotsu merged commit 716bd31 into main Oct 13, 2026
3 checks passed
@haruotsu
haruotsu deleted the engine-groups branch October 13, 2026 03:31
@github-actions github-actions Bot mentioned this pull request Oct 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant