Skip to content

Add service roles and roles_order for dependency vs first-party tiers#49

Merged
jssblck merged 3 commits into
mainfrom
jssblck/amazing-chandrasekhar-281948
Jul 1, 2026
Merged

Add service roles and roles_order for dependency vs first-party tiers#49
jssblck merged 3 commits into
mainfrom
jssblck/amazing-chandrasekhar-281948

Conversation

@jssblck

@jssblck jssblck commented Jul 1, 2026

Copy link
Copy Markdown
Member

What

Add an explicit tier concept to .eph files 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_order orders the tiers, as a linear roles_order=dep,app chain (each role depends on the previous) or a [roles_order] DAG section (app=dep, a bare dep= for a root). The two forms are mutually exclusive.
  • Roles mode (any service has a role, or roles_order is present) is validated at parse time: roles_order 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 stays in legacy mode (declaration order, run= services last), so existing files are untouched.
  • 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 tears down only the services it started, leaving any already running (a prewarmed dependency tier) up; --clean still 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 clean stay absolute.
  • eph check reports each service's role and the resulting bring-up order.

Tests

Parser unit tests (roles, both roles_order forms, 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 for eph check and the --role error path. cargo fmt, cargo clippy --all-targets, and the unit + non-Docker integration suites pass.

Docs

example.eph, the using-eph skill, 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 dep plus $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

jssblck and others added 3 commits July 1, 2026 01:26
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>
@jssblck jssblck merged commit b22d26d into main Jul 1, 2026
19 checks passed
@jssblck jssblck deleted the jssblck/amazing-chandrasekhar-281948 branch July 1, 2026 08:47
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