Keep the local loop cheap.
devloop is a config-driven tool for running multi-process systems
locally.
Most local setups become expensive to change:
- restarting everything
- losing state
- waiting for rebuilds
- coordinating multiple services
devloop keeps everything alive so you can change one thing at a
time.
Working on a blog post with a public preview:
- change Rust -> the server rebuilds and restarts
- the browser reconnects and reloads
cloudflaredrestarts -> new public URL- the current page path is combined with the new tunnel URL
- the final URL is printed, ready to paste into LinkedIn validator
One change -> everything updates -> copy and paste once.
Without devloop:
- restarting the server does not automatically coordinate the rest of the loop
- you still need to manage CSS rebuilds separately
- the tunnel keeps the old URL unless you restart
cloudflaredyourself - you rebuild the full public URL by hand every time
The pieces exist.
They just don’t know about each other.
Install the latest published main branch directly from GitHub:
cargo install --git https://github.com/pasunboneleve/devloop.gitTagged releases are also published automatically on GitHub with prebuilt release archives for Linux x86_64 and macOS Apple Silicon. Each supported platform publishes its release asset independently, so a failure on one platform does not block the other asset from being attached to the GitHub release.
Supported prebuilt release targets:
x86_64-unknown-linux-gnuaarch64-apple-darwin
For local development from a checkout:
cargo install --path .Run devloop in a repository with a devloop.toml config:
devloop runThe tool will:
- start declared processes
- watch configured paths
- execute workflows on change
Built-in reference docs are also available from the CLI:
devloop docs config
devloop docs behavior
devloop docs securityThe tool has three layers:
-
Engine Watches files, supervises processes, executes workflows, and stores session state.
-
Repository config Declares watch groups, named processes, workflow steps, and hook commands.
-
Repository hooks Small commands that answer project-specific questions such as "what is the current post slug?" or "what public URL should be printed now?"
Internally, devloop is being refactored toward a pure core plus an
imperative shell: workflow orchestration is planned as explicit
state/effect transitions, runtime scheduling and process-supervision
decisions are planned the same way, and replaceable adapters interpret
those effects at the edges.
The session state file is owned by devloop while it is running.
External edits to that file are not merged back into the live session;
restart the supervisor if you need to seed a different initial state.
Used as the primary local development workflow for
gcp-rust-blog-public.
The generic example config lives at:
The real client config lives in the client repository itself:
gcp-rust-blog-public/devloop.toml
It models a blog workflow as configuration:
rustchanges restart the server, wait for health, refresh the current post slug, restart the tunnel, and publish the current post URLcontentchanges refresh the current post slug, restart the tunnel, and republish the current post URL- CSS is handled by a long-running Tailwind watch process started by the startup workflow
The example expects repo-owned helper scripts:
./scripts/build-css.sh./scripts/current-post-slug.sh
At the same time, the tunnel itself is described as a managed process:
cloudflaredis started directly by the engine- stdout is scanned with regex rules
- the matched tunnel URL is written into session state
- readiness waits for the state key to be populated
- restart policy keeps the process alive if it exits
- inherited process output is source-labeled without wrapper scripts
When you need to identify which managed process emitted a line in mixed output, inherited process lines include the executable first and the configured process name second. The label is color-coded per process, and the body style is configurable:
[process.tunnel]
command = ["cloudflared", "tunnel", "--url", "http://127.0.0.1:18080"]
output = { inherit = true, body_style = "plain", rules = [{ state_key = "tunnel_url", extract = "url_token" }] }That renders inherited lines with the executable and process name, for
example [cloudflared tunnel] ..., using ANSI color when stdout is a
terminal and NO_COLOR is not set.
For the runtime behavior reference, see
docs/behavior.md.
For the full configuration reference, see
docs/configuration.md.
For the external-event trust model and push-versus-polling tradeoffs,
see docs/security.md.
The client config can then compose derived values with write_state
steps, for example:
step = { action = "write_state", key = "current_post_url", value = "{{tunnel_url}}/posts/{{current_post_slug}}"}Workflows can also emit rendered log lines:
step = { action = "log", message = "current post url: {{current_post_url}}"}For high-visibility output in a mixed process log, use the boxed style:
step = { action = "log", message = "current post url: {{current_post_url}}", style = "boxed"}Repeated setup can be factored into helper workflows and reused with
run_workflow, for example a publish_post_url workflow that waits for
the tunnel and then writes the derived URL.
Hooks can also be observed on the runtime tick when external state changes are not represented by file edits. For example:
[hook.current_post_slug]
command = ["./scripts/current-post-slug.sh"]
capture = "text"
state_key = "current_post_slug"
observe = { workflow = "publish_post_url", interval_ms = 1000 }That lets a helper hook refresh session state from something like a development server endpoint, and rerun the follow-up workflow only when the state actually changes.
For more precise local event flows, devloop can also accept
capability-scoped pushed events over a localhost HTTP server. A trusted
client process can post a value to a configured event, devloop
updates the mapped session-state key, and then runs the mapped workflow
if the value changed. This is the preferred model for things like
browser-path updates, while observed hooks remain a simpler fallback.
Real working configs should live in the client repository, not under
devloop/examples/. The example here is intentionally generic.
Quality gates:
cargo fmt
cargo test
cargo clippy --all-targets --all-features -- -D warningsGit hook setup:
git config core.hooksPath .githooksThat enables the versioned pre-commit hook in .githooks/pre-commit,
which runs cargo fmt before each commit.
Task tracking:
bd ready
bd show <issue>
bd update <issue> --status in_progress
bd close <issue>