Skip to content

Commit 635ca87

Browse files
Add required skills and progress output polish
1 parent 0cdfd06 commit 635ca87

9 files changed

Lines changed: 802 additions & 41 deletions

File tree

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Program frontmatter:
2121

2222
- `id` (optional)
2323
- `model` (optional)
24+
- `skills` (optional list of required provider-native skills)
2425

2526
## How `apply` Works
2627

@@ -98,6 +99,16 @@ The confirmation preview includes a variables summary, for example: `variables:
9899

99100
Program variables are defined in frontmatter under `variables` (`NAME: {}` for required, `NAME: { default: "..." }` for optional defaults) and referenced as `${{ var.NAME }}`.
100101

102+
Required provider-native skills can be declared in frontmatter with `skills`, for example:
103+
104+
```yaml
105+
---
106+
skills: [dstack]
107+
---
108+
```
109+
110+
If a required skill is unavailable, the run fails early. Implementation details and provider-specific behavior are documented in `contrib/ARCHITECTURE.md` and `contrib/DEVELOPMENT.md`.
111+
101112
Example output (will vary by session/model):
102113

103114
```text
@@ -106,7 +117,7 @@ Last session: 019d5843-eb2d-70b1-b49a-343033117944 (success, 43m ago)
106117
program: examples/smoke.md unchanged
107118
changes: 0 files
108119
Proceed? [y/N] y
109-
🧵 019d586b-aa65-78b2-8a0d-27b5543c59bb | workspace
120+
🧵 019d586b-aa65-78b2-8a0d-27b5543c59bb | workspace | claude:sonnet
110121
✔ cat examples/smoke.md | 1ms | out
111122
💬 Verified `example-data/output-smoke.txt:1` already contains the required `SMOKE_OK` line with trailing newline. | msg
112123
turn 1 | tokens: in=117k out=1.6k cached=107k

contrib/ARCHITECTURE.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Clawform runs agent work from markdown files instead of chat windows.
99

1010
A **program** is one markdown file (`*.md`) representing one task.
1111

12-
- frontmatter is tool-owned and strict (`id`, `model`, `variables`)
12+
- frontmatter is tool-owned and strict (`id`, `model`, `skills`, `variables`)
1313
- markdown body is agent-facing and free-form
1414

1515
## 2) Implemented v0 Scope
@@ -63,6 +63,7 @@ Frontmatter (strict):
6363

6464
- `id` (optional)
6565
- `model` (optional override)
66+
- `skills` (optional list of required provider-native skill names)
6667
- `variables` (optional map)
6768
- key: variable name (`[A-Za-z_][A-Za-z0-9_]*`)
6869
- value:
@@ -83,6 +84,16 @@ Variable rules:
8384
3. apply-time `--var NAME=VALUE` overrides frontmatter default
8485
4. variables without default are required at apply time
8586

87+
Skill rules:
88+
89+
1. `skills` is a shared frontmatter list, for example `skills: [dstack]`
90+
2. each listed skill name must be a single non-empty token without whitespace
91+
3. Clawform prepends explicit provider-native skill validation/invocation lines to the real session prompt before the normal apply contract
92+
4. skill command syntax is provider-specific (`$skill` for Codex, `/skill` for Claude)
93+
5. the injected validation line is phrased as `Fail if skill is not found.`
94+
6. the injected prelude also instructs the agent to write `.clawform/agent_result.json` with `status: failure` and `reason: program_blocked` before stopping
95+
7. if the provider still surfaces a missing-skill response without writing the result artifact, Clawform uses provider-specific fallback detection and fails the apply run
96+
8697
## 4) Apply Session Flow (Current Behavior)
8798

8899
1. Load program + config and resolve provider + model (`-p/--provider` overrides default provider selection).
@@ -94,7 +105,7 @@ Variable rules:
94105
- variable diff vs last session variable snapshot (if available)
95106
5. Ask for confirmation (interactive default; skipped by `--yes`).
96107
6. Clear prior run protocol files in `.clawform/` and write runtime variables file (`.clawform/agent_variables.json`) when variables are present.
97-
7. Build runtime prompt; in `workspace` and `auto` modes include explicit verdict-gate rules for sandbox-vs-program blocking.
108+
7. Build runtime prompt; if the program declares `skills`, prepend provider-native skill invocation commands first. In `workspace` and `auto` modes include explicit verdict-gate rules for sandbox-vs-program blocking.
98109
8. Run provider in the current workspace (no temp workspace copy).
99110
9. Normalize provider-native events into shared progress categories, stream them to terminal, and during the run write session `commands/*` and `messages/*`. In debug mode, also write session `events.ndjson`.
100111
10. In `auto` mode, allow at most one retry in `full-access` mode only when current-run `.clawform/agent_result.json` reports `status=partial|failure` and `reason=sandbox_blocked` (no stdout/stderr heuristic fallback).
@@ -106,7 +117,7 @@ Variable rules:
106117
## 4.1 Progress Rendering Semantics
107118

108119
- In an interactive TTY, rich progress keeps a spinner plus a live `running` or `running: <activity>` status line.
109-
- The run-start line includes the session id, execution mode, and a compact `provider:model` suffix, for example `🧵 <session> | workspace | codex:gpt-5-codex`.
120+
- The run-start line includes the session id, execution mode, a compact `provider:model` suffix, and when applicable a compact `skills:` suffix, for example `🧵 <session> | workspace | codex:gpt-5-codex | skills:dstack`.
110121
- `running` means the provider run is still alive. It is a liveness indicator, not the same thing as reasoning text.
111122
- The live activity suffix is derived from the highest-priority active provider item, for example `search ...`, `fetch ...`, `use ...`, `edit ...`, or `update plan`.
112123
- Completed progress lines are normalized across Claude and Codex into shared categories such as reasoning (`💭`), assistant text (`💬`), search (`🔎`), fetch (`🌐`), command (``), file change (`✏️`), plan update (`update plan | ...`), generic tool (`🔧`), and unknown provider event (`📦`).

contrib/DEVELOPMENT.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ Interactive progress UI is enabled automatically only when stdin/stdout are atta
108108
Current progress semantics:
109109

110110
- Rich mode keeps a spinner plus a live `running` or `running: <activity>` status line.
111-
- The run-start line includes the session id, execution mode, and a compact `provider:model` suffix such as `🧵 <session> | workspace | claude:sonnet`.
111+
- The run-start line includes the session id, execution mode, a compact `provider:model` suffix, and when applicable a compact `skills:` suffix such as `🧵 <session> | workspace | claude:sonnet | skills:dstack`.
112112
- `running` is a liveness indicator. It does not mean the model is explicitly emitting reasoning.
113113
- Plain mode prints stable progress lines without the interactive spinner/status renderer.
114114
- Completed provider items are normalized across Claude and Codex into categories such as `💭`, `💬`, `🔎`, `🌐`, ``, `✏️`, `update plan | ...`, `🔧`, and `📦`.
@@ -124,6 +124,16 @@ cargo run -p clawform -- apply -f examples/smoke.md --debug
124124

125125
That debug run will also persist `.clawform/programs/<program_id>/sessions/<session_id>/events.ndjson` for postmortem event inspection.
126126

127+
Program frontmatter also supports required provider-native skills:
128+
129+
```yaml
130+
---
131+
skills: [dstack]
132+
---
133+
```
134+
135+
When present, Clawform prepends explicit provider-native skill validation/invocation lines to the real session prompt before the normal apply contract, for example `$dstack Fail if skill is not found.` or `/dstack Fail if skill is not found.`. The injected prelude also tells the agent to stop and write `.clawform/agent_result.json` with `reason: program_blocked` if a required skill is unavailable.
136+
127137
Disable progress rendering entirely:
128138

129139
```bash

crates/clawform-cli/src/lib.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::ffi::OsString;
44
use std::fs;
55
use std::io::{self, IsTerminal, Write};
66
use std::path::{Path, PathBuf};
7+
use std::sync::atomic::{AtomicBool, Ordering};
78

89
use anyhow::{anyhow, Context, Result};
910
use clap::{ArgAction, Parser, Subcommand, ValueEnum};
@@ -20,6 +21,7 @@ const MAX_REPORTED_FILES_DISPLAY: usize = 20;
2021
const AGENT_RESULT_REL: &str = ".clawform/agent_result.json";
2122
const HELP_RENDER_WIDTH: usize = 100;
2223
const HELP_SPEC_WIDTH: usize = 30;
24+
static DETAILED_TERMINAL_VERDICT_PRINTED: AtomicBool = AtomicBool::new(false);
2325
const TOP_LEVEL_HELP_ROWS: &[(&str, &str)] = &[
2426
("-h, --help", "print help"),
2527
("-V, --version", "print version"),
@@ -205,13 +207,18 @@ enum Commands {
205207
}
206208

207209
pub fn main_entry() {
210+
DETAILED_TERMINAL_VERDICT_PRINTED.store(false, Ordering::Relaxed);
208211
if let Err(err) = real_main() {
209212
if is_user_cancelled_error(&err) {
210-
print_canceled(true);
213+
if !DETAILED_TERMINAL_VERDICT_PRINTED.load(Ordering::Relaxed) {
214+
print_canceled(true);
215+
}
211216
std::process::exit(130);
212217
}
213218
if is_blocked_error(&err) {
214-
print_blocked();
219+
if !DETAILED_TERMINAL_VERDICT_PRINTED.load(Ordering::Relaxed) {
220+
print_blocked();
221+
}
215222
std::process::exit(2);
216223
}
217224
eprintln!("error: {:#}", err);
@@ -875,6 +882,7 @@ fn maybe_print_agent_status_from_result_file(workspace_root: &Path) {
875882
line.push_str(&result_suffix);
876883
}
877884
eprintln!("{}", line);
885+
DETAILED_TERMINAL_VERDICT_PRINTED.store(true, Ordering::Relaxed);
878886
}
879887

880888
fn status_file_separator(use_color: bool) -> &'static str {

0 commit comments

Comments
 (0)