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
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
# Changelog

## [0.7.0] - 2026-03-22

### Added
- **Artifact Pipeline**: Prompt steps can now capture and pass artifacts between
workflow stages. New `artifact_glob` and `artifact_name` fields on prompt steps
enable glob-based artifact discovery after a prompt step completes, with pre-step
snapshot to prevent race conditions with concurrent runs.
- **inject_files template expansion**: `inject_files` now supports
`{{output.artifact_name.path}}` template references, allowing artifacts from prior
steps to be automatically injected into downstream agents' worktrees.
Comment thread
nutt-adam marked this conversation as resolved.
- **Dashboard artifact labels**: Flow connectors on the factory floor show
artifact names flowing between pipeline stages during active workflow runs.
- **Dry-run artifact validation**: `tt run --dry-run` validates gstack-slug
availability when artifact_glob uses `{slug}` interpolation, catching
configuration errors before workflow execution.
- **Variable interpolation in globs**: `{slug}`, `{workspace}`, `{agent}`, and
`~` are expanded in artifact_glob patterns at runtime.

## Unreleased

### Added
Expand Down
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "tutti"
version = "0.6.0"
version = "0.7.0"
edition = "2024" # intentional: codebase uses Rust 2024 let-chain syntax
description = "Multi-agent orchestration CLI — your agents, all together"
license = "MIT"
Expand Down Expand Up @@ -33,6 +33,7 @@ cron = "0.12"
tiny_http = "0.12"
getrandom = "0.4.2"
rust-embed = "8"
glob = "0.3"

[dev-dependencies]
serial_test = "3"
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ weekly_hours = 45.0
`tt permissions` is opt-in and reads `[permissions]` from `~/.config/tutti/config.toml`.
With default launch mode (`auto`), constrained non-interactive runs require `[permissions]` allow rules.
For prompt steps that need workspace artifacts, use `inject_files = ["relative/path.json"]` to copy files into the target agent's working tree before the prompt is sent.
Prompt steps can capture artifacts with `artifact_glob` and `artifact_name` — after the prompt step completes, tutti globs for new files and registers them as step outputs. Downstream steps reference artifacts via `inject_files = ["{{output.artifact_name.path}}"]` or `{{output.artifact_name.path}}` in prompt text. Glob patterns support `~`, `{slug}`, `{workspace}`, and `{agent}` interpolation.
For command steps that should run under a workspace subpath, use `subdir = "relative/path"` instead of shell `cd ... &&`.
Use `depends_on = [<step-number>, ...]` on workflow steps to unlock dependency-aware execution; independent `ensure_running`/`review`/`land` steps run in parallel waves.
Budget guardrails are API-only: when `[budget]` is configured and the workspace profile has `plan = "api"`, Tutti checks budget caps before `up/send/run/verify`, emits `budget.threshold` / `budget.blocked` control events, and either warns or blocks based on `budget.mode`.
Expand Down
29 changes: 28 additions & 1 deletion dashboard/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ function processWorkflowEvent(evt) {
status: "running",
startedAt: evt.timestamp,
durationMs: null,
message: null
message: null,
artifactName: (evt.data && evt.data.artifact_name) || null
};
}
} else if (evt.event === "workflow.step.completed") {
Expand Down Expand Up @@ -126,6 +127,26 @@ function runsAtStage(stage) {
return result;
}

// Find artifact name flowing between two pipeline stages (from active runs)
function artifactBetweenStages(fromStage, toStage) {
var ids = Object.keys(appState.runs);
for (var i = 0; i < ids.length; i++) {
var run = appState.runs[ids[i]];
if (!run.steps || run.status !== "running") continue;
for (var j = 0; j < run.steps.length; j++) {
var step = run.steps[j];
if (step && step.artifactName && step.stage === fromStage && step.status === "completed") {
// Check if the next step targets the toStage
var nextStep = run.steps[j + 1];
if (nextStep && nextStep.stage === toStage) {
return step.artifactName;
}
}
}
}
return null;
}
Comment thread
nutt-adam marked this conversation as resolved.

// Build a composite key for workspace-scoped agent storage
function agentKey(workspace, agent) {
return (workspace || "_") + ":" + agent;
Expand Down Expand Up @@ -242,6 +263,12 @@ function renderPipeline() {
if (runsAtStage(prevStage).length > 0 || runsAtStage(stage).length > 0) {
conn.classList.add("flowing");
}
// Show artifact labels on connectors
var artifactLabel = artifactBetweenStages(prevStage, stage);
if (artifactLabel) {
var label = el("span", "artifact-label", artifactLabel);
conn.appendChild(label);
}
$pipeline.appendChild(conn);
}

Expand Down
13 changes: 13 additions & 0 deletions dashboard/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,19 @@ main#factory {
border: 4px solid transparent;
border-left-color: inherit;
}
.artifact-label {
position: absolute;
top: -18px;
left: 50%;
transform: translateX(-50%);
font-size: 10px;
color: var(--flow-active);
background: var(--bg);
padding: 0 4px;
white-space: nowrap;
font-family: var(--font-mono);
opacity: 0.85;
}
Comment thread
nutt-adam marked this conversation as resolved.

/* stage card — the "machine" on the floor */
.stage {
Expand Down
Loading
Loading