Skip to content

Add supervised workflow scripts for coordinating pi-web sessions #35

Description

@ashwin-pc

Summary

Add an experimental workflow feature to pi-web: supervised local scripts that can coordinate normal pi-web sessions through a small SDK/API.

The goal is not to build a full workflow engine, DAG runner, or extension framework yet. The goal is to validate a minimal primitive:

Workflows are ordinary local TypeScript/JavaScript scripts that can spawn, message, wait on, and summarize pi-web sessions, while pi-web supervises the run lifecycle.

Motivation

pi-web is already session-oriented and minimal. For larger tasks, it would be useful to let a script coordinate multiple independent sessions, for example:

  • start a security review session
  • start a test-gap review session
  • wait for both to finish
  • start a synthesis session with their findings

This should preserve pi-web's ethos: small, local-first, inspectable, and user-controlled.

Product direction

For the first version, ignore extensions/server-extension architecture.

Build workflows as a small built-in experimental feature, but keep the internal boundary clean enough that it could be extracted into an extension later if pi-web grows a proper server-extension host.

Do not build:

  • a generic workflow engine
  • DAGs/phases/retry semantics
  • a visual workflow builder
  • a new extension host
  • parent/child session hierarchy as a core abstraction

Do build:

  • supervised script runs
  • session spawn/send/wait primitives
  • durable run state
  • append-only audit events
  • stdout/stderr logs
  • human and agent cancellation
  • a small UI to inspect running/completed workflows

Core invariant: always cancellable

Every workflow must be cancellable by both:

  1. the human, from the pi-web UI
  2. an agent/session, through an explicit workflow API

Cancellation should not rely only on workflow script cooperation. pi-web should supervise the process and enforce cancellation.

Suggested cancellation sequence:

  1. mark run as cancellation_requested
  2. append an audit event with actor/reason
  3. broadcast a runtime/state update
  4. signal the workflow process gracefully
  5. after a grace period, hard-kill if still running
  6. mark run as cancelled
  7. preserve logs, events, and any sessions created by the run

Cancelling a workflow should abort active prompts in sessions created by that workflow, but should not delete those sessions. They should remain available for audit/review.

Core invariant: always auditable and viewable

All workflow runs should have durable state and append-only audit logs.

Suggested storage layout:

.pi/web/workflows/runs/<run-id>/run.json
.pi/web/workflows/runs/<run-id>/events.jsonl
.pi/web/workflows/runs/<run-id>/stdout.log
.pi/web/workflows/runs/<run-id>/stderr.log

Example WorkflowRun shape:

type WorkflowRun = {
  id: string;
  name: string;
  scriptPath: string;
  cwd: string;
  status:
    | "starting"
    | "running"
    | "cancellation_requested"
    | "cancelled"
    | "succeeded"
    | "failed";
  startedAt: string;
  updatedAt: string;
  endedAt?: string;
  pid?: number;
  exitCode?: number;
  signal?: string;
  createdBy: {
    actor: "human" | "agent";
    sessionId?: string;
  };
  cancelledBy?: {
    actor: "human" | "agent";
    sessionId?: string;
    reason?: string;
    timestamp: string;
  };
  sessionIds: string[];
  currentStep?: string;
  progress?: {
    completed?: number;
    total?: number;
    label?: string;
  };
  logPath: string;
  eventLogPath: string;
};

Example audit events:

{"type":"workflow_started","runId":"...","actor":"human","timestamp":"..."}
{"type":"step_started","step":"spawn security reviewer","timestamp":"..."}
{"type":"session_spawned","sessionId":"...","name":"security review","timestamp":"..."}
{"type":"session_prompt_sent","sessionId":"...","promptPreview":"Review auth/session risks...","timestamp":"..."}
{"type":"cancellation_requested","actor":"agent","reason":"Branch is stale","timestamp":"..."}
{"type":"process_killed","signal":"SIGTERM","timestamp":"..."}
{"type":"workflow_cancelled","timestamp":"..."}

Minimal server API

Initial endpoints could be:

GET  /api/workflows
GET  /api/workflows/:runId
GET  /api/workflows/:runId/events
GET  /api/workflows/:runId/logs
POST /api/workflows/run
POST /api/workflows/:runId/cancel

Minimal workflow SDK

A workflow script should be able to coordinate normal sessions:

const security = await pi.sessions.spawn({
  name: "security review",
  message: "Review the current branch for security issues only.",
});

const tests = await pi.sessions.spawn({
  name: "test review",
  message: "Review the current branch for missing or weak tests.",
});

await Promise.all([
  pi.sessions.waitForIdle(security.id, { signal: pi.workflow.signal }),
  pi.sessions.waitForIdle(tests.id, { signal: pi.workflow.signal }),
]);

pi.workflow.throwIfCancelled();

await pi.sessions.spawn({
  name: "final synthesis",
  message: "Combine the security and test findings into a concise review.",
});

Suggested SDK surface:

pi.sessions.list();
pi.sessions.spawn({ name, cwd, message });
pi.sessions.send({ sessionId, message, mode });
pi.sessions.waitForIdle(sessionId, { signal });
pi.sessions.abort(sessionId);

pi.workflow.currentRun();
pi.workflow.setStep(label);
pi.workflow.setProgress(progress);
pi.workflow.throwIfCancelled();
pi.workflow.signal;

pi.workflows.list();
pi.workflows.get(runId);
pi.workflows.cancel(runId, { reason });

Minimal UI

Add a small workflow runs panel/drawer:

  • list running and recent workflow runs
  • show status: starting/running/cancellation requested/cancelled/succeeded/failed
  • show current step/progress if provided
  • show sessions created by the run
  • open stdout/stderr logs
  • open event audit log
  • cancel button always visible for active runs

UX principle

Running a generated workflow script should require explicit approval. A workflow script is code and can spend tokens, spawn sessions, and potentially drive tools.

Suggested approval copy:

Pi created workflow script:
.pi/web/workflows/<name>.ts

It will:
- start N sessions
- use cwd <path>
- wait for completion
- create a synthesis session

[Run once] [Open script] [Cancel]

Future considerations

  • This could become a bundled pi-web extension later, but only after pi-web has multiple features that need a server-extension host.
  • Do not introduce server-extension architecture as part of the MVP.
  • Keep the workflow service behind a small internal interface so extraction remains possible later.

Example internal boundary:

type WorkflowService = {
  run(input: WorkflowRunInput): Promise<WorkflowRun>;
  cancel(runId: string, reason?: string): Promise<void>;
  get(runId: string): Promise<WorkflowRun>;
  list(): Promise<WorkflowRun[]>;
  events(runId: string): Promise<WorkflowEvent[]>;
};

Acceptance criteria

  • A user can run a local workflow script from pi-web.
  • A workflow can spawn and message normal pi-web sessions.
  • A workflow can wait for sessions to become idle.
  • Active workflow runs are visible in the UI at all times.
  • Human can cancel any active workflow from the UI.
  • Agent/session can request cancellation through an explicit API.
  • Cancellation is enforced by the server even if the script ignores it.
  • Workflow run state is persisted to disk.
  • Workflow audit events are append-only and inspectable.
  • stdout/stderr logs are preserved and viewable.
  • Cancelling a workflow does not delete sessions it created.
  • The implementation does not introduce a generic workflow engine, DAG runner, or server-extension host.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions