Add service roles and roles_order for dependency vs first-party tiers#49
Merged
Conversation
Introduce an explicit tier concept so a `.eph` file can distinguish dependency services (databases, caches: safe to start eagerly) from the first-party app (start on demand). This lets a Claude Code SessionStart hook prewarm just the dependency tier and inject its connection env without starting the app, so an agent opening a worktree finds its services already running. - `role=` tags each service with a free-form tier name. - `roles_order` orders the tiers, written as a linear `roles_order=dep,app` chain or a `[roles_order]` DAG section (`app=dep`, a bare `dep=` for a root). The two forms are mutually exclusive. - Roles mode is validated at parse time: once any service has a role, `roles_order` is required, every service must carry a listed role, every role must back at least one service, edges must reference known roles, and the graph must be acyclic. A file with no roles keeps the old behavior (declaration order, `run=` services last). - Bring-up follows the role graph topologically; teardown reverses it. The legacy "run= last" heuristic is off in roles mode. - `eph up --role R` starts a role and its dependency closure; `eph down --role R` stops a role and its dependent closure. Both are repeatable and combine with positional service names. - `eph dev` now tears down only the services it started, leaving any that were already running (a prewarmed dependency tier) up; `--clean` still bulldozes the workspace. `eph down` and `eph clean` stay absolute. - `eph check` reports each service's role and the resulting bring-up order. Covered by parser unit tests, a service ordering test, and non-Docker integration tests. Docs, the using-eph skill, and example.eph document the tiers and the SessionStart prewarm recipe (`eph up --role dep` plus `$CLAUDE_ENV_FILE`). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…r edges Fixes from a codex review pass over the roles feature: - `eph dev`: fail fast when the foreground service is already running rather than spawning a second copy and orphaning the original (its state entry would be overwritten, leaving it unstoppable). With that case rejected, the `brought_up` ownership set is just "every defined service not already running"; the foreground is always eph dev's to tear down, so the final `child.wait()` can never block on an unstopped process. - `run_all_post_start`: run hooks in start order (topological in roles mode), matching `eph up` and `run_all_pre_start`, instead of declaration order, so a dependency's post-start runs before a dependent's even when declared out of order. - `roles_order`: reject a duplicate role key in the `[roles_order]` section and a repeated role in the linear form, instead of silently overwriting an edge or desugaring to a self-cycle. - `roles_order`: keep role names uniformly free-form (drop the env-looking reclassification inside `[roles_order]`, which would have blocked an uppercase role name in DAG form while linear/`role=` accepted it). Document that top-level env vars belong outside the section. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`eph skills check` in CI compares the checked-in `.claude/skills` and `.agents/skills` copies against the skill bundled in the binary. The roles section added to the source skill left those copies drifted; regenerate them with `eph skills install --force`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Add an explicit tier concept to
.ephfiles so a stack can distinguish dependency services (databases, caches, queues: safe to start eagerly) from the first-party app (start on demand). This makes it possible to bring up one tier on its own.The motivating case: a Claude Code SessionStart hook can prewarm just the dependency tier and inject its connection env before an agent's first command, so an agent opening a worktree finds its services already running instead of discovering they are down and redoing work. The app is left alone (starting it could bind preview ports or trigger side effects).
How
role=<name>tags each service with a free-form tier name.roles_orderorders the tiers, as a linearroles_order=dep,appchain (each role depends on the previous) or a[roles_order]DAG section (app=dep, a baredep=for a root). The two forms are mutually exclusive.roles_orderis present) is validated at parse time:roles_orderrequired, every service must carry a listed role, every role must back at least one service, edges must reference known roles, and the graph must be acyclic. A file with no roles stays in legacy mode (declaration order,run=services last), so existing files are untouched.run=-last heuristic is off in roles mode.eph up --role Rstarts a role and its dependency closure;eph down --role Rstops a role and its dependent closure. Both are repeatable and combine with positional service names.eph devtears down only the services it started, leaving any already running (a prewarmed dependency tier) up;--cleanstill bulldozes the workspace. It also fails fast if the foreground app is already running (it starts and attaches to that process itself).eph down/eph cleanstay absolute.eph checkreports each service's role and the resulting bring-up order.Tests
Parser unit tests (roles, both
roles_orderforms, topological order, forward/reverse closures, every validation rule, duplicate-role rejection, free-form role names), a service-level ordering test, and non-Docker integration tests foreph checkand the--roleerror path.cargo fmt,cargo clippy --all-targets, and the unit + non-Docker integration suites pass.Docs
example.eph, theusing-ephskill, and the user guide (eph-file.md,concepts.md,command-reference.md,for-agents.md,recipes.md,README.md) document the tiers and the SessionStart prewarm recipe (eph up --role depplus$CLAUDE_ENV_FILE).Review
Reviewed with codex (gpt-5.5, high reasoning) over two rounds; all findings addressed (foreground-already-running guard, post-start hook ordering in roles mode, duplicate-role rejection, uniform free-form role names). Final verdict: approved.
🤖 Generated with Claude Code