Imported from rschoonheim/lp (
AGENTS.md). Install upstream withnpx skills add rschoonheim/lp. Copyright stays with the author.
LP - Local Pipeline: Agent Guide
Project structure
cmd/server/ # Server entrypoint and YAML configuration wiring
internal/hooks/ # Generic, reusable event hook registry (Go generics)
internal/listener/ # Trigger monitoring and event production
internal/logging/ # Categorized user-feedback log collection
internal/observability/ # Structured logging and metrics collection
internal/pipeline/ # YAML-defined pipelines with per-run logs and observability
internal/runners/ # Core pipeline execution engine (Pool, Pipeline, Step, Hooks)
cmd/server/main.go— CLI entrypoint; reads a YAML config file path fromos.Args[1]cmd/server/configuration.go—Configurationstruct with YAML tags; converts torunners.Configcmd/server/events.go—HookEntryYAML model andRegisterConfiguredHookswiringcmd/server/listeners.go—ListenerFactoryregistry andRegisterConfiguredListenerswiring; built-in factories forfile_watcherandwebhookcmd/server/pipelines.go—LoadPipelinesFromDirectoriesscans dirs for pipeline YAML files;ReloadPipelinesFromDirectorieshot-swaps definitionscmd/server/server.go—serverstruct orchestrates lifecycle: wires hooks, listeners, pool, store, REST API, WebSocket hub, hot-reload polling, and signal handlingcmd/server/api.go— REST API (/api/status,/api/history,/api/pipelines,/api/pipelines/{name}/runs,/api/listeners); serves embedded dashboard at/cmd/server/ws.go—wsHubpure-stdlib WebSocket server; broadcastswsEventJSON to connected clients on hook eventscmd/server/cli.go—clithread-safe ANSI-colored CLI output writer; globaloutsingletoncmd/server/embed.go—//go:embed dashboard.htmlfor the web dashboardcmd/server/dashboard.html— Single-file HTML dashboard served at/internal/hooks/hooks.go—Registry[T]generic thread-safe observer (pure registry, no observability); seeinternal/hooks/README.mdinternal/listener/listener.go—Listenerinterface (Name,Start,Stop); seeinternal/listener/README.mdinternal/listener/event.go—Eventtype produced by listeners (Listener, Trigger, Pipeline, Payload)internal/listener/manager.go—Managerruns listeners concurrently, merges events into a single channelinternal/listener/hooks.go— Listener lifecycle hooks with observability trackersinternal/listener/file_watcher.go—FileWatcherpolls a directory for file changes matching a glob patterninternal/listener/webhook.go—Webhookstarts an HTTP server; fires events onPOST /{event_name}internal/logging/ledger.go—Ledgerappend-only log store with category/level/source queries; seeinternal/logging/README.mdinternal/logging/scoped.go—Scopedconvenience writer bound to a category and sourceinternal/observability/logger.go—Loggercomponent-scoped structured JSON logger (log/slog); seeinternal/observability/README.mdinternal/observability/tracker.go—HookTrackeremit counting and panic recovery for hook registriesinternal/observability/collector.go—CollectoraggregatesStatsand custom countersinternal/pipeline/pipeline.go—Pipeline(UUID, name, YAML-defined[]StepDef); seeinternal/pipeline/README.mdinternal/pipeline/step.go—StepDef(YAML step definition) andStepResult(execution output)internal/pipeline/run.go—Run(UUID, status, per-runLedger+Collector,[]StepResult)internal/pipeline/store.go—Storethread-safe in-memory registry of pipelines and runsinternal/pipeline/id.go— UUID v4 generation (no external deps)internal/runners/pool.go— Concurrency-limitedPoolthat runs pipelines via a semaphore channel;EventSinkchannel enables pipeline-to-pipeline chaining viarunners.Eventinternal/runners/pipeline.go—Pipeline(name + ordered[]Step)internal/runners/step.go—Step.Executeruns an OS command viaos/execinternal/runners/hooks.go— Runner-specificHooksstruct composed ofhooks.Registry[T]fieldsinternal/runners/config.go—Configstruct (Timeout,MaxConcurrent)
Build and run
go build -o lp-server ./cmd/server
./lp-server <configuration.yaml>
Dependencies
- Go 1.26 (
go.mod) gopkg.in/yaml.v3— YAML config parsing
Conventions
Function comment style
Each function should have a comment block that describes its purpose, parameters, and return value. The comment block should be placed immediately above the function definition. It should have the following structure:
// functionName - Description of the function's purpose.
Exported vs unexported
- Public registration methods are exported (e.g.,
OnPipelineStarted); internal emit helpers are unexported (e.g.,emitPipelineStarted). Follow this pattern when adding new hook events.
Error wrapping
Wrap errors with fmt.Errorf and %w to preserve the error chain. Include identifying context (pipeline name, step index). See pool.go:run.
Hook lifecycle events
Six hook events exist: pipeline_started, pipeline_completed, pipeline_failed, step_started, step_completed, step_failed. When adding a new hook:
- Add a
hooks.Registry[T]field to the component'sHooksstruct (e.g.,internal/runners/hooks.go) - Add exported
On*registration method and unexportedemit*method - Wire it in
cmd/server/events.goRegisterConfiguredHooksswitch - For new components, see
internal/hooks/README.mdfor the full implementation pattern
Adding a new listener type
- Create
internal/listener/<type>.goimplementing theListenerinterface (Name,Start,Stop) - Add a factory function
new<Type>FromConfigincmd/server/listeners.go - Register it in the
listenerFactoriesmap in the same file - See
file_watcher.goandwebhook.gofor reference implementations
REST API and WebSocket
- The REST API is enabled when
api_addris set in YAML config. Endpoints are registered incmd/server/api.go - WebSocket hub (
cmd/server/ws.go) implements RFC 6455 using only the stdlib (no external WS library). Hook events are broadcast to all connected clients aswsEventJSON - The dashboard (
cmd/server/dashboard.html) is embedded via//go:embedand served at/
Pipeline hot-reload
Pipeline directories are polled every 2 seconds (server.go:watchPipelineDirectories). Changed files trigger ReloadPipelinesFromDirectories which atomically replaces all pipeline definitions in the store.
Pipeline chaining
Pipelines can trigger other pipelines via runners.Event sent to Pool.EventSink. The server's handlePipelineEvents loop dispatches these events the same way as listener events.
YAML configuration
Config structs use yaml:"snake_case" tags. The server-level Configuration struct in cmd/server/configuration.go owns the YAML shape; internal/runners stays YAML-agnostic (plain Go types). Top-level keys: api_addr, runners, pipelines, listeners.