Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,29 @@ and this project adheres to

## [Unreleased]

### Added

- **In-run progress reporting.** A compile now reports each phase on
stderr as it starts and as its artifact lands with the elapsed time,
streams a compiled phase's runtime status lines live instead of
buffering them until the run ends, and emits a heartbeat so the
terminal is never silent for more than 30 seconds while work is in
flight. `runSlc` takes an optional progress sink; hosts that supply
none keep the previous quiet behavior.
- **An agent-stall watchdog.** An agent call that observes no activity
for `stallTimeout` seconds — the new config-file key, overridden by
`SLC_STALL_TIMEOUT`, defaulting to 600 with `0` disabling — is
aborted and reported as a failed phase naming the inactivity
duration, instead of hanging indefinitely on a stalled session. The
aborted call is never retried and a pinned phase still fails closed
([DR-019](specs/decisions/019-compile-progress-stall-watchdog.md)).

### Changed

- Documented compile and run durations are now measured ranges stated
as agent- and workload-dependent, replacing the "more than ten
minutes" estimate.

## [0.2.0] - 2026-07-27

### Added
Expand Down
32 changes: 22 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,13 @@ slc playbook my-workflow.md
dependency, so it compiles in any directory — no clone, no project
setup. Compilation drives your configured coding agent — the first run
seeds `~/.config/slc/config.yaml` with `agent: claude-code`; set
`SLC_AGENT` (or edit that file) to use another agent CLI — and a full
pipeline can take more than ten minutes. Plain-text input (`.txt`)
works too; it is normalized first. The pipeline's optimization pass,
which rewrites judgment-free steps into plain script, runs by default
`SLC_AGENT` (or edit that file) to use another agent CLI. Expect it to
take a while: duration is agent- and workload-dependent, and measured
compiles of a five-line workflow have run from tens of minutes to more
than two hours, with the first intermediate typically landing within
about five minutes. Plain-text input (`.txt`) works too; it is
normalized first. The pipeline's optimization pass, which rewrites
judgment-free steps into plain script, runs by default
(`--no-optimize` skips it).

Artifacts land in your working directory: `my-workflow.playbook/` holds
Expand All @@ -106,9 +109,16 @@ Intermediates are first-class: edit one and re-run a single phase
(`slc playbook.gears2fsm …`) and it lands in the same place.
`slc --help` shows all invocation forms.

Success prints the written artifact paths and exits 0; a failure prints
diagnostics to stderr — naming the failing phase when one is at fault —
and exits non-zero.
While a compile runs, `slc` reports progress on stderr: each phase as it
starts, each artifact as it lands with the elapsed time, the compiled
runtime's own state transitions, and a heartbeat so the terminal is
never silent for more than 30 seconds. An agent call that goes quiet for
`stallTimeout` seconds (default 600, `0` disables) is aborted and
reported as a failed phase rather than hanging indefinitely.

Success prints the written artifact paths to stdout and exits 0; a
failure prints diagnostics to stderr — naming the failing phase when one
is at fault — and exits non-zero.

## Configuration

Expand All @@ -123,14 +133,16 @@ and `pipelinePath` to the working directory.
# slc.config.yaml
agent: claude-code # claude-code | codex | gemini | opencode
model: claude-opus-4-8 # optional; omit to use the agent CLI's default
stallTimeout: 600 # seconds of agent silence before a stalled call fails
pipelinePath: # search roots for <pipeline> references; defaults to the cwd
- ./pipelines
```

A `slc.config.yaml` in the working directory wins over the user config;
`SLC_AGENT`, `SLC_MODEL`, and `SLC_PIPELINE_PATH` override either per
key. Discovery order, `--config`, and validation rules live in the
[CLI spec](specs/user/cli.md); `slc --help` prints the summary.
`SLC_AGENT`, `SLC_MODEL`, `SLC_STALL_TIMEOUT`, and `SLC_PIPELINE_PATH`
override either per key. Discovery order, `--config`, and validation
rules live in the [CLI spec](specs/user/cli.md); `slc --help` prints the
summary.

## How pipelines work

Expand Down
26 changes: 18 additions & 8 deletions demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,21 @@ What each step does:
**unrelated packages** that happen to share the names.
2. **`npx slc playbook workflow.txt`** compiles the one-paragraph
description into a runnable playbook. The compile calls your
configured agent and **can take more than ten minutes**; it succeeds
when `./workflow.ts` appears. In a hurry? Skip it — a precompiled
copy ships in [`reference/`](reference/), runnable as
configured agent, and **its duration is agent- and
workload-dependent: measured compiles of this five-line workflow have
run from tens of minutes to more than two hours**. It reports each
phase on stderr as it goes and succeeds when `./workflow.ts` appears.
Want to skip the wait? A precompiled copy ships in
[`reference/`](reference/), runnable as
`npx playbook run ./reference/workflow.ts "<task>"` with the same
task string as the next step.
task string as the next step — though note the run itself is real
agent work too (see below).
3. **`npx playbook run …`** hands the buggy [`sample.c`](sample.c) to
the two agents. They commit, review, and debate inside a Git
repository created here; every round is real agent work, so expect
this to take a while too. The run exits `0` when a review comes back
clean.
repository created here; every round is real agent work, so expect a
run on the order of an hour — one run from the precompiled artifacts
measured about 51 minutes, varying with agent, model, and task. The
run exits `0` when a review comes back clean.
4. **`git log`** shows what the loop produced: the reviewed commits
(`git show` displays the final fix).

Expand Down Expand Up @@ -94,7 +99,12 @@ runtime, and by default runs the compile optimization that reduces LLM
calls.
The agent driving the compilation is set in `~/.config/slc/config.yaml`
(created on the first run; defaults to Claude Code).
Compiling may take more than ten minutes.
Compile time is agent- and workload-dependent: the first intermediate
has measured about 4 minutes in and the next about a minute later,
while full compiles of this workflow have ranged from tens of minutes
to more than two hours — the later phases dominate. `slc` prints
each phase, each artifact with its elapsed time, and a heartbeat while
work is in flight, so you can tell progress from a stall.

Artifacts land in the current directory: `./workflow.playbook/` (the
compile intermediates) and `./workflow.ts` (the runnable entry).
Expand Down
7 changes: 4 additions & 3 deletions demo/README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ git log --oneline
每一步做什么:

1. **`npm install`** 安装 `@sublang/slc`(编译器)与 `@sublang/playbook`(提供 `playbook` 命令,以及生成文件从 `./node_modules` 导入的运行时引擎)。必须先于各条 `npx` 命令执行——若什么都没装,`npx` 会提示从 npm 仓库下载名为 `slc`、`playbook` 的**无关同名包**。
2. **`npx slc playbook workflow.zh.txt`** 把这一段描述编译成可运行的 playbook。编译会调用你配置的 agent,**可能耗时十分钟以上**;`./workflow.zh.ts` 出现即编译成功。赶时间?可以跳过——[`reference/`](reference/) 内附带预编译制品,直接运行 `npx playbook run ./reference/workflow.zh.ts "<task>"`(任务文本同下一步)。
3. **`npx playbook run …`** 把带 bug 的 [`sample.c`](sample.c) 交给两个 agent。它们在此处新建的 Git 仓库里提交、评审、争论;每一轮都是真实的 agent 工作,同样需要一些时间。当某轮评审不再有问题时,运行以 `0` 退出。
2. **`npx slc playbook workflow.zh.txt`** 把这一段描述编译成可运行的 playbook。编译会调用你配置的 agent,**耗时取决于所用 agent 与工作流规模:这段五行工作流的实测编译从数十分钟到两小时以上不等**;编译过程会在 stderr 上逐阶段报告进度,`./workflow.zh.ts` 出现即编译成功。想跳过等待?[`reference/`](reference/) 内附带预编译制品,直接运行 `npx playbook run ./reference/workflow.zh.ts "<task>"`(任务文本同下一步)——但请注意运行本身同样是真实的 agent 工作(见下一条)。
3. **`npx playbook run …`** 把带 bug 的 [`sample.c`](sample.c) 交给两个 agent。它们在此处新建的 Git 仓库里提交、评审、争论;每一轮都是真实的 agent 工作,通常需要约一小时——基于预编译制品的一次运行实测约 51 分钟,具体取决于 agent、模型与任务。当某轮评审不再有问题时,运行以 `0` 退出。
4. **`git log`** 查看循环产出:经过评审的提交(`git show` 显示最终修复)。

## 详细说明
Expand All @@ -58,7 +58,8 @@ npx slc playbook workflow.zh.txt

`slc` 会先将输入文本按 playbook 要求规范化,最终链接到已安装的 `@sublang/playbook` 运行时,并默认执行减少 LLM 调用的编译优化。
编译所用的 agent 由 `~/.config/slc/config.yaml` 指定(首次运行会自动生成,默认为 Claude Code)。
编译耗时可能超过十分钟。
编译耗时取决于 agent 与工作流规模:首个中间产物实测约 4 分钟落盘、下一个再约 1 分钟,而整条流水线实测从数十分钟到两小时以上不等——耗时主要集中在后段阶段。
`slc` 会逐阶段打印进度、每个制品的落盘耗时,以及工作进行中的心跳,因此正在推进还是已经卡死一望便知。

制品输出在当前目录下,包括:`./workflow.zh.playbook/`(编译中间产物)与 `./workflow.zh.ts`(可运行的入口)。
我们提供参考制品供预览或对比校验:中文流程位于 [`reference/workflow.zh.playbook/`](reference/workflow.zh.playbook/),英文流程位于 [`reference/workflow.playbook/`](reference/workflow.playbook/)。
Expand Down
74 changes: 74 additions & 0 deletions specs/decisions/019-compile-progress-stall-watchdog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<!-- SPDX-License-Identifier: Apache-2.0 -->
<!-- SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai> -->

# DR-019: Compile Progress Reporting and Agent-Stall Watchdog

## Status

Accepted

## Context

A compile prints one line and then goes silent for as long as it runs ([#4][1]).
Measured on a five-line workflow: the first intermediate landed at t+4 min, the second at t+5 min, and `gears2fsm` then ran 123 minutes with no output at 0.2% CPU — a live agent session waiting on the network — before the run was abandoned.
The user cannot distinguish "working hard" from "hung", and the documented "more than ten minutes" estimate is materially optimistic against measured runs (a precompiled demo run took 51 minutes; the abandoned compile exceeded two hours).

The silence and the hang have distinct causes, both host-side:

- Progress exists but is discarded.
A compiled phase's runtime emits the same human status lines `playbook run` prints as state transitions happen, but the SLC port adapter buffers them as drainable diagnostics ([PHEXEC-25](../dev/phase-execution.md#phexec-25)) that the bin prints only after the whole run returns.
The generic step loop knows each phase, target, and timing and reports none of it while running.
- No layer bounds an agent call in time.
Cligent exposes `maxTurns` and `maxBudgetUsd` but no time-based option; the transport's event loop awaits the next adapter event indefinitely; the bin sets no deadline.
A network-stalled agent session therefore parks the pipeline forever, and the only recourse is Ctrl-C.

Structurally, a compiled phase also pays three strictly serialized cold agent sessions per phase — a judge classification of the seeded Boss turn, the transformation-performing Captain session (dominant, with a fixed link-time prompt independent of workload size), and a judge adjudication of the Captain's reply — all with `resume: false`.
That cost is owned by the pinned artifacts and `@sublang/playbook`, is the same regardless of how small the user's workflow is, and is out of scope here; this decision makes the spend visible and bounds the failure mode, so waiting is informed rather than blind.

Every Cligent adapter emits events incrementally — at least at message and tool boundaries — so adapter events are a usable activity signal, but a single long tool execution or model turn is legitimately silent on all adapters.
An absolute per-phase deadline would misfire on legitimately long phases, whose duration varies from minutes to hours with agent, model, and workload.

## Decision

### Stderr-only progress reporting

- The generic step loop reports each phase start, finish, and failure — with the phase name, target artifact, and elapsed time — through a progress sink the host injects beside the resolver and executors.
- The bin renders these events as human-readable lines on standard error.
Standard output remains reserved for the success report ([CLI-3](../user/cli.md#cli-3)); a failed run still writes nothing to standard output ([CLI-16](../test/cli.md#cli-16)).
The exact line format is host-owned presentation and is not specified.

### Live status streaming

- When the host configures a status sink, a compiled phase's human status and non-trace operational telemetry stream to it as they occur instead of being collected for the end; streamed lines are not duplicated into the run's diagnostics.
- Without a sink, the drainable-diagnostics behavior is unchanged, so embedders of the library API see today's contract.
- Exact `playbook.trace` payloads stay out of the streamed lines exactly as they stay out of ordinary diagnostics ([DR-010](010-playbook-runtime-contract-evolution.md#port-policy-and-diagnostic-privacy)).

### Silence-bounded heartbeat

- While a phase is in flight, when no progress line has been written for the 30-second silence bound, the bin writes an elapsed-time heartbeat, so the terminal is never silent longer than the bound while work is running.

### Inactivity watchdog, not a per-phase deadline

- The stall signal is agent inactivity: an in-flight agent call whose transport observes no adapter event for the stall timeout is aborted through the existing cancellation plumbing and reported as a failed call carrying the inactivity duration.
Cligent's abort drain guarantees the event loop terminates promptly with a terminal event, so a watchdog abort is safe at the transport seam.
That same drain can deliver a genuine success for a call that completed just as the window expired; the observed outcome then wins over the stall verdict, because discarding a finished phase is the expensive false negative this watchdog exists to prevent, and a real hang drains to an interrupted outcome rather than a successful one.
- The configured window must stay representable as a timer delay, so a value the runtime would silently clamp is refused at both configuration sources rather than inverting the watchdog into an immediate abort.
- The watchdog applies to every agent call on both transports: the single interpreted invocation and each compiled player, Captain, and judge call.
- A tripped watchdog surfaces through the unchanged phase protocols: a failure report naming the phase and target, no retry of the call ([PHEXEC-12](../dev/phase-execution.md#phexec-12), [PHEXEC-23](../dev/phase-execution.md#phexec-23)), and fail-closed handling for a pinned phase — never a silent interpreted fallback ([PHEXEC-27](../dev/phase-execution.md#phexec-27)).
- The timeout is configuration: the `stallTimeout` config-file key in seconds, overridden by a non-blank `SLC_STALL_TIMEOUT`, defaulting to 600 seconds, with `0` disabling the watchdog.
The default is deliberately generous because one long tool execution or model turn is legitimately event-silent on every adapter; the aim is to convert an indefinite hang into a loud, attributed failure within minutes, not to police normal phase length.

### Measured time estimates

- User documentation states measured ranges — tens of minutes to more than two hours for a full compile of a five-line workflow; about 51 minutes for a run from the precompiled demo artifacts — and states that duration is agent- and workload-dependent, instead of the "more than ten minutes" phrasing.

## Consequences

- A compile is never silent longer than the heartbeat bound: phases announce themselves, targets land with elapsed times, compiled-runtime transitions stream as they happen, and a stalled agent call fails loudly within the stall timeout instead of hanging for hours.
- `SlcDeps` gains an optional progress sink; hosts that do not supply one keep today's quiet behavior, and the sink addition is not a breaking API change.
- [PHEXEC-25](../dev/phase-execution.md#phexec-25) is amended for streaming; new items [CLI-32](../user/cli.md#cli-32)–[CLI-37](../test/cli.md#cli-37) and [PHEXEC-36](../dev/phase-execution.md#phexec-36)–[PHEXEC-38](../test/phase-execution.md#phexec-38) specify the progress, heartbeat, and watchdog behavior.
- The structural three-cold-sessions-per-phase cost and the fixed-size Captain prompt remain; reducing them needs artifact and `@sublang/playbook` changes under a later decision.

## References

[1]: https://github.com/sublang-ai/slc/issues/4 "No progress output during compile: silent for 10+ minutes (measured: 2h)"
7 changes: 6 additions & 1 deletion specs/dev/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ core to a concrete host: resolving a pipeline reference to a directory,
selecting and constructing the coding agent from configuration with credentials
from the environment, injecting an interpreted executor for unpinned phases and
the compiled-execution factory that pinned phases select, passing a cancellation
signal, short-circuiting `--version`/`--help`, and mapping the run result to
signal and the progress reporter that renders in-run events on stderr,
short-circuiting `--version`/`--help`, and mapping the run result to
process streams and an exit code.
The user-facing surface (streams, exit status, conveniences) is in the `cli`
user package; generic mechanics and the execution boundary are in the `pipeline`
Expand Down Expand Up @@ -51,6 +52,10 @@ When discovery finds neither the working-directory `slc.config.yaml` nor the use

When the slc executable runs a pipeline, phase, or link, the executable shall inject into `runSlc` an interpreted executor built on the agent transport — the execution for every unpinned phase — and a compiled-execution factory that runs a current pinned phase's compiled `playbook` artifact, resolved against its pipeline directory, with the runtime's player ports backed by one configured agent transport per player id, its Captain and judge ports backed by one shared configured transport, and the selected model applied as the default per-player model ([DR-004](../decisions/004-slc-interpreted-phase-execution.md), [DR-005](../decisions/005-slc-self-hosting-meta-pipeline.md#strategy-selection), [PHEXEC-25](phase-execution.md#phexec-25), [PHEXEC-27](phase-execution.md#phexec-27)).

### CLI-35

When the slc executable builds run dependencies for a documented invocation form, the executable shall construct a progress reporter that renders phase, streamed-status, and heartbeat events as lines on standard error, inject it into `runSlc` as the run's progress sink, thread it into the compiled-execution factory so streamed runtime status reaches the same reporter, and resolve the stall timeout — a non-blank `SLC_STALL_TIMEOUT` environment variable, otherwise the config file's `stallTimeout` field, otherwise 600 seconds, `0` disabling — into every agent transport it constructs ([DR-019](../decisions/019-compile-progress-stall-watchdog.md), [CLI-8](#cli-8), [CLI-32](../user/cli.md#cli-32), [CLI-34](../user/cli.md#cli-34), [PHEXEC-36](phase-execution.md#phexec-36)).

## Process control

### CLI-9
Expand Down
Loading