Autonomous Agents Agentic Workflow
Open App Editing Session — Autonomous Agents Agentic Workflow
Open a live editing session at the workspace root: orient on the target, submit the ready report, then ride the session (dev server on request)
sidebutton install agents Opens a live editing session on a workspace — the workspace itself, or one checkout inside it when the caller names a project (v3, KAN-64). Claude launches in the WORKSPACE ROOT like every other agent workflow, so the workspace's own setup (AGENTS.md, .mcp.json, .claude/skills, settings, shared/) is in play; the TARGET of the session is told to the agent in the prompt, not cd'd into. The boot turn loads the session's role persona, orients on the target from what is already in context plus one look at the folder (ls, README head, git status -sb — no network), and submits the ready report: a status line, a few sentences of kickoff, a SUGGESTIONS block of one-click chips, and SESSION_READY. That report IS the readiness signal — the boot turn completing is what tells the portal the session is connected (SCRUM-1965) — so the boot takes no screenshot, depends on no chrome binary, and finishes in well under a minute on a warm VM. The first turn is the whole job; the user's chat then continues on the same still-alive Claude session, one turn per message.
Nothing is installed, synced or started at boot. The dev server is an on-request turn — "Boot the project — start the dev server" leads the chips whenever a checkout is the target — and when asked, the agent reads the dev-session module first, uses the project's app contract (dev command, port, env, preview route) when the portal passed one, starts the server detached and bound to the loopback interface so it survives every turn end and is reachable only through the agent daemon's authenticated preview passthrough (SP-B/SP-C), and reuses a server that is still healthy on a reconnect instead of booting a second one. Repo updates are the pull-repos job's business: the boot reports each checkout as it is (branch @ sha, ahead/behind as of the last fetch) and offers "Pull updates" as a chip when it is behind, so nothing moves HEAD inside the Claude turn, where the Stop hook's telemetry would count upstream commits as the session's own work.
Durability is git, uniformly for every repo the session touches: each turn ends with a real commit pushed to the branch the checkout is on, so a reclaimed spot VM costs at most the current turn. A rejected push is rebased once and retried once; if it still will not land, the reply says so and the commit rides into the next turn rather than being forced over someone else's work. That is also why the session rules below insist every working tree stays publishable at all times — nothing secret may ever be written into a checkout, and evidence files belong in the workspace's artifacts folder outside them.
Steps
- 1. Open a terminal
- title
- Agent: Workspace Session
- cwd
- {{entry_path}}
terminal.open - 2. Run a terminal command
- cmd
- |
terminal.run
Workflow definition
schema_version: 1
# Id is a CONTRACT, deliberately without the `agent_` prefix: executePipeline writes this exact
# string into ~/.sidebutton/job-context.json (.workflow_id) on the VM, the portal floor entry keys
# on it, and SP-D's per-turn Stop-hook branch (SCRUM-1937) will read it from there. Renaming it
# later is a fleet-wide coordination job — treat it as frozen.
id: app_edit_session
title: "Open App Editing Session"
description: "Open a live editing session at the workspace root: orient on the target, submit the ready report, then ride the session (dev server on request)"
overview: |
Opens a live editing session on a workspace — the workspace itself, or one checkout inside it when the caller names a project (v3, KAN-64). Claude launches in the WORKSPACE ROOT like every other agent workflow, so the workspace's own setup (AGENTS.md, .mcp.json, .claude/skills, settings, shared/) is in play; the TARGET of the session is told to the agent in the prompt, not cd'd into. The boot turn loads the session's role persona, orients on the target from what is already in context plus one look at the folder (ls, README head, `git status -sb` — no network), and submits the ready report: a status line, a few sentences of kickoff, a SUGGESTIONS block of one-click chips, and SESSION_READY. That report IS the readiness signal — the boot turn completing is what tells the portal the session is connected (SCRUM-1965) — so the boot takes no screenshot, depends on no chrome binary, and finishes in well under a minute on a warm VM. The first turn is the whole job; the user's chat then continues on the same still-alive Claude session, one turn per message.
Nothing is installed, synced or started at boot. The dev server is an on-request turn — "Boot the project — start the dev server" leads the chips whenever a checkout is the target — and when asked, the agent reads the dev-session module first, uses the project's app contract (dev command, port, env, preview route) when the portal passed one, starts the server detached and bound to the loopback interface so it survives every turn end and is reachable only through the agent daemon's authenticated preview passthrough (SP-B/SP-C), and reuses a server that is still healthy on a reconnect instead of booting a second one. Repo updates are the pull-repos job's business: the boot reports each checkout as it is (branch @ sha, ahead/behind as of the last fetch) and offers "Pull updates" as a chip when it is behind, so nothing moves HEAD inside the Claude turn, where the Stop hook's telemetry would count upstream commits as the session's own work.
Durability is git, uniformly for every repo the session touches: each turn ends with a real commit pushed to the branch the checkout is on, so a reclaimed spot VM costs at most the current turn. A rejected push is rebased once and retried once; if it still will not land, the reply says so and the commit rides into the next turn rather than being forced over someone else's work. That is also why the session rules below insist every working tree stays publishable at all times — nothing secret may ever be written into a checkout, and evidence files belong in the workspace's artifacts folder outside them.
category:
level: pipeline
domain: engineering
metadata:
agent: true
role: se
# Gate-verdict vocabulary (PLAYBOOKS.md §5). Both tokens are boot-turn outcomes: the boot report
# ends with SESSION_READY once the agent has oriented on the target and posted the report, or
# BOOT_FAILED when the target folder does not exist — the only boot failure since v3. Later chat
# turns (the dev-server turn included) emit neither — they are conversation, not a gated step result.
verdicts: [SESSION_READY, BOOT_FAILED]
params:
agentic_app:
type: string
default: "cc"
description: "Agent-app slug selecting the per-run env file ~/.agent-env.d/<slug> (AAP-C); 'cc'/subscription clears provider vars"
entry_path:
type: string
default: "~/workspace"
description: "Workspace directory the dispatch targets — the session's cwd; force-injected by executePipeline and used as the workspace_slug match"
# --- App contract (SCRUM-1964 / SP2-B), v3 meaning (KAN-64) ---
# Resolved per project from workspace_projects and passed through by the portal's dispatch. Every
# field keeps its name — older portals keep sending them — and defaults to "" so a project that
# declares no contract carries nothing. project_path and app_path now describe the TARGET: the
# checkout the caller named, or the workspace itself when none was (the portal then sends an empty
# project_path and app_path = the workspace path). dev_cmd/dev_port/dev_env/preview_path are carried
# for the on-request dev-server turn only; the boot never reads them.
# NOTE: an absent param resolves to "" on the VM, NOT to the default below, so any value used as a
# path must be joined portal-side (that is what app_path is) rather than templated from two params.
project_path:
type: string
default: ""
description: "The checkout named as the target — its git root, <workspace>/<subpath>; empty when the target is the workspace itself"
app_dir:
type: string
default: ""
description: "Directory INSIDE the checkout that holds the app; empty means the checkout root"
app_path:
type: string
default: "~/workspace"
description: "The TARGET the agent is told about — <workspace>/<subpath>[/<app_dir>] for a checkout, or the workspace path itself"
dev_cmd:
type: string
default: ""
description: "Command that starts the dev server, run from app_path — used only when the session is asked to start it; empty means read package.json and pick the dev script"
dev_port:
type: string
default: ""
description: "Port the dev server must listen on — used only when the session is asked to start it; empty uses the framework convention (Astro 4321, Vite 5173, Next 3000)"
dev_env:
type: string
default: ""
description: "Space-separated non-secret KEY=VALUE flags to export before the dev command — used only when the session is asked to start it, e.g. SB_DEV_SESSION=1"
preview_path:
type: string
default: ""
description: "Route the preview should open on when / is not the app's entry, e.g. /portal — used only when the session is asked to start the dev server"
hint:
type: string
default: ""
description: "Optional extra instructions for the agent — the user's first message, appended last"
# KAN-47 — the role the session rides on. The portal stores it on app_sessions.role and passes it
# here, so a thread opened as QA answers as QA on every turn, not just the one that said so. Empty
# = the session named no role, and the prompt below is written to skip the persona load on empty:
# per the NOTE above, an absent param resolves to "" on the VM rather than to this default, and
# `skill://agents/.md` is not a resource. Not every registry role ships a pack file either (csm and
# design have none today), which is why the skip is a normal path and not an error.
role:
type: string
default: ""
description: "Account-role slug the session rides on (se/qa/sd/pm/ops/...); empty means no role persona"
steps:
- type: terminal.open
title: "Agent: Workspace Session"
# The WORKSPACE ROOT (KAN-64 / v3), like every other agent workflow — where .mcp.json, AGENTS.md,
# .claude/skills, settings, shared/ and the folder-trust seed live (config-apply writes them per
# entry path). The target (app_path) is told to the agent in the prompt, not cd'd into: a child
# cwd hides the whole workspace setup from Claude Code, and terminal.open on a folder that does
# not exist yet would refuse to open the session at all instead of letting the boot report it.
cwd: "{{entry_path}}"
- type: terminal.run
cmd: |
source ~/.agent-env
# AAP-C (SCRUM-1506) + AAP-17 (SCRUM-1653): clear EVERY provider var an agent-app can deliver so
# none hijacks/poisons a subscription run. A stray global ANTHROPIC_MODEL / ANTHROPIC_SMALL_FAST_MODEL
# needs no CLAUDE_CODE_USE_* flag, so the old ${!CLAUDE_CODE_USE_@} glob never caught it — it survived
# into the run and 404-ed aux/small-fast calls against api.anthropic.com. This explicit list mirrors
# AGENT_APP_ENV_KEYS 1:1 (the-assistant website/src/lib/cloud/agent-app-env.ts — the single source of
# truth; a parity test in each repo guards the two from drifting). Explicit over a glob: the union has
# non-ANTHROPIC_ members (AWS_REGION, AWS_PROFILE, CLOUD_ML_REGION, CLAUDE_CODE_MAX_OUTPUT_TOKENS) and
# a ${!AWS_@} glob would over-clear unrelated creds. Then source the per-run app env by slug when it
# exists; no file => subscription/default. base/19-secrets stages ~/.agent-env.d/<slug>.
unset \
ANTHROPIC_API_KEY ANTHROPIC_BASE_URL ANTHROPIC_AUTH_TOKEN CCR_CONFIG_B64 \
CLAUDE_CODE_USE_BEDROCK AWS_REGION AWS_PROFILE ANTHROPIC_MODEL \
ANTHROPIC_SMALL_FAST_MODEL_AWS_REGION ANTHROPIC_SMALL_FAST_MODEL CLAUDE_CODE_MAX_OUTPUT_TOKENS \
CLAUDE_CODE_USE_VERTEX CLOUD_ML_REGION ANTHROPIC_VERTEX_PROJECT_ID ANTHROPIC_VERTEX_BASE_URL \
CLAUDE_CODE_USE_FOUNDRY ANTHROPIC_FOUNDRY_RESOURCE ANTHROPIC_FOUNDRY_BASE_URL \
ANTHROPIC_DEFAULT_OPUS_MODEL ANTHROPIC_DEFAULT_SONNET_MODEL ANTHROPIC_DEFAULT_HAIKU_MODEL
if [ -f "$HOME/.agent-env.d/{{agentic_app}}" ]; then
source "$HOME/.agent-env.d/{{agentic_app}}"
fi
# The Stop hook's artifacts lane uploads from the FIRST of <cwd>/artifacts, ~/workspace/artifacts,
# ~/artifacts that exists (agent-runners base/14-claude-stop-hook.sh). With the cwd at the workspace
# root, creating the folder before Claude starts makes the workspace's own artifacts/ the drop
# point of every session on it. Read through a quoted heredoc — the same shape the prompt below
# uses — so nothing in a dispatched path is ever evaluated as shell: entry_path arrives on the job
# body (POST /api/jobs) and is only matched against enabled_entry_paths when an account has set
# that list. The leading ~ still has to expand, so expand it here rather than leave the value bare
# for the shell; and skip the mkdir on an empty value, since an absent param resolves to "" on the
# VM (the NOTE above) and bare that would have run `mkdir -p /artifacts` at the filesystem root.
SB_ENTRY_DIR="$(cat <<'SB_ENTRY_PATH'
{{entry_path}}
SB_ENTRY_PATH
)"
if [ -n "$SB_ENTRY_DIR" ]; then mkdir -p "${SB_ENTRY_DIR/#\~/$HOME}/artifacts"; fi
claude --dangerously-skip-permissions "$(cat <<'SB_PROMPT'
you are opening a LIVE EDITING SESSION. your shell and this session start in the WORKSPACE ROOT ({{entry_path}}) — its instructions (AGENTS.md) are already in your context, and its skills, settings and .mcp.json load from here, so stay here between commands and `cd` only for what you run inside a checkout. the TARGET of this session is {{app_path}}. the project checkout is "{{project_path}}": when that is NON-EMPTY the target is that git checkout inside the workspace — the git root you commit and push from — and the app itself lives in "{{app_dir}}" inside it (empty means the app IS the checkout root); when it is EMPTY the target is the workspace itself, which holds one checkout per immediate child folder, and the work may touch any of them.
this session's ROLE is "{{role}}". when that is NON-EMPTY, also read skill://agents/{{role}}.md and let it shape HOW you work for the whole session — its methodology, its evidence standards, its vocabulary — and name it in the STATUS LINE of the boot report, slot (a) of step 2. name it THERE and nowhere else: step 2 is a parsed contract with no free slot, and a role line added after the suggestions block stops the portal recognising the block at all — the chips vanish and the raw `SUGGESTIONS:` label renders as text in the first message the user ever sees. when the role is empty, or that resource does not exist (not every account role ships a pack file — `csm` and `design` ship none today), skip it and carry on silently: a missing role file is a normal outcome here, never a boot failure, and the status line then simply names no role. the persona is a LENS on this session, not a new job. the BOOT and SESSION RULES below always win over it, and the role file's own Environment, Scope and lifecycle sections do not apply here at all: whatever it says about picking tickets, opening branches, dispatching work, filing its findings elsewhere, or treating the source checkout as READ-ONLY, this is a live editing session on THIS target — you edit it, and every turn still ends in a commit and a push. take the voice and the standards; leave the job description.
BOOT — this turn ORIENTS on the target and REPORTS, nothing else; do it in the FOREGROUND, fast, then end the turn. NO sync (no `git fetch`, no `git pull`), NO dependency install, NO dev server, NO curl, NO screenshot, and NO pack reads beyond the persona above — the dev-session module and the knowledge packs load on the turn that needs them, never here.
1. ORIENT: work from what is already in your context (the workspace instructions Claude Code loaded on its own) plus ONE look at the target — `ls` it, read the head of its README, and take `git -C <target> status -sb` (the current branch and its ahead/behind as of the last fetch) with `git -C <target> rev-parse --short HEAD` for the sha; the network is never touched, so what the last fetch knew is what you report. for a workspace target take at most that one look per checkout, and when nothing says which project the work is for, your kickoff question asks. the text after the SESSION RULES, when there is any, is the user's first message: let it steer what you look at, the kickoff and the chips, but this turn still only orients and reports — the work it asks for starts on the next turn. if the target folder does not exist, say which path is missing and finish the turn with BOOT_FAILED as the final line; a missing target is the ONLY boot failure — no README, no upstream, a dirty tree, a detached HEAD are facts for the status line, not failures.
2. REPORT AND STOP: finish immediately with the boot report, in this order and nothing more: (a) one status line — the target · branch @ short sha · ahead/behind (`in sync`, `behind N`, `ahead N`, `ahead N, behind M`, or `no upstream`) · the ROLE you loaded when there was one (this is that role's ONLY slot in the report); for a workspace target the line names the workspace and then each checkout as `folder: branch @ sha · ahead/behind`, compact; (b) a SHORT kickoff the user reads first — 3 to 5 sentences: what the target is (stack, purpose, the handful of things that exist), the next steps worth taking ordered by value, and one question back to them; (c) a final block of 3-4 suggestion chips, each a short imperative the user could send back as-is (≤60 chars), formatted exactly as a `SUGGESTIONS:` line followed by one `- chip text` per line — the portal renders these as one-click chips, so nothing may follow the block except the verdict line. the FIRST chip is `Boot the project — start the dev server` whenever the target is a checkout; add `Pull updates` whenever a checkout is behind its upstream; for a workspace target give one chip per checkout (`Work on <folder>`), the ones the workspace setup or the first message point at first, within the cap; (d) the final line exactly SESSION_READY. keep (b) tight — no headings, no file dumps. this turn's end COMPLETES the job, and that completion is exactly what flips the window to connected; so do not hold the turn open and do not start anything you would have to wait on.
SESSION RULES — every later turn. each user message is one turn: do what it asks, then end the turn promptly so the reply reaches the user.
- TURN END = COMMIT + PUSH in EVERY repo you touched, uniformly. in each checkout you changed, commit with a real message describing the change (never "wip"/"autosave"), then `git push` the branch it is on — for a project session that is main. a commit you did not push exists only on a VM that can be reclaimed. include any commit an earlier turn had to leave local. treat every turn end as a publish: leave each tree building and coherent.
- IF THE PUSH IS REJECTED (someone else advanced the branch): `git pull --rebase` ONCE and push ONCE more. if it is still rejected, STOP retrying — say so plainly in your reply, keep the commit local, and push it again at the end of the next turn. never `git push --force`, never reset to origin: a rejected push means someone else's work is on that branch.
- never destroy commits — no `git reset --hard`, no `git checkout -- .`, no `git clean`, no force-push over the session branch. on a reconnect the local branch may carry commits that exist nowhere else. if you must undo, revert forward with a new commit.
- PULL UPDATES only when asked (the `Pull updates` chip or a message): `git pull --ff-only` in the checkout it names; when it cannot fast-forward, keep the local tree, say so, and carry on. otherwise the checkouts are moved only by the separate pull-repos job, between sessions.
- NEVER PARK CREDENTIALS: nothing secret goes into any worktree — no .env with real values, no tokens in config or committed fixtures. the only credential this VM holds is the sb_token already in ~/.agent-env, and every turn publishes the trees.
- keep evidence (screenshots, reports, datasets) in {{entry_path}}/artifacts — the workspace's artifacts folder, created before this session started and the directory the artifacts lane actually uploads from — never inside a checkout.
- THE DEV SERVER IS AN ON-REQUEST TURN. when asked (the boot chip or any message), read skill://agents/dev-session/_skill.md FIRST — it carries the port conventions, the loopback rule, the HMR-behind-two-proxies config and the readiness rule — then work from the app directory of the target ({{app_path}} for a checkout target; for a workspace target, the checkout the message names). the project may declare a contract — USE IT WHEN IT IS SET, it is the operator telling you what this repo actually needs, and only fall back to reading package.json when a field is empty: the dev command is `{{dev_cmd}}` when that is non-empty, otherwise the package.json dev script; export `{{dev_env}}` first when that is non-empty (space-separated KEY=VALUE flags, non-secret by construction — `export {{dev_env}}` works as written); the port is `{{dev_port}}` when that is set, otherwise the framework convention (Astro 4321, Vite 5173, Next 3000); the preview opens on `{{preview_path}}` when that is non-empty, otherwise `/`, or the actual first page when `/` 404s (check src/pages or the router). install dependencies only when they are missing or the lockfile moved since the last install. FIRST check whether a healthy server already answers on the port — on a reconnect it usually does — and REUSE it rather than boot a second one (a double boot dies on EADDRINUSE or silently lands on a port the preview cannot reach). otherwise start it DETACHED so it outlives the turn and the 60-minute session tidy — `setsid nohup <dev command> >"$HOME/dev-server.log" 2>&1 &` — with the IPv4 host flag EXPLICIT: `--host 127.0.0.1` for Astro and Vite, `-H 127.0.0.1` for Next; never 0.0.0.0 (the VM opens no ports; the preview arrives through the daemon's authenticated passthrough) and never the default, which binds the IPv6 loopback `[::1]` that the passthrough dialing `127.0.0.1` cannot reach. verify with `curl -sS -o /dev/null -w '%{http_code}' http://127.0.0.1:<port>/` until ANY http status answers (000 means nothing is listening; a 404 on `/` is a healthy server), for at most ~90 seconds, and `curl` the entry route the same way before you name it. your reply then states the dev command, the PORT, the ENTRY ROUTE and the local URL — this is a chat turn and carries no verdict token — or, when nothing ever answered, says so with the last ~40 lines of ~/dev-server.log.
- once up, the dev server stays UP across turns: never stop or restart it at the end of a turn. if a change needs a restart, or the server died, restart it exactly as above and say so in your reply.
- report honestly: if a change did not work, say what you saw. the user is watching the live preview and will see it too.
{{hint}}
SB_PROMPT
)"