@lgrammel/prototype-2
v2.0.3
Published
A minimal hosted coding agent for one GitHub repository.
Readme
@lgrammel/prototype-2
Workbench is a minimal hosted coding agent for one configured GitHub repository per deployment.
The package includes an offline, version-matched coding-agent guide and configuration reference for initialized projects.
Initialize
From the root of the private GitHub repository that Workbench should manage, run:
npx @lgrammel/prototype-2@latest init
# Or select another curated harness and optional native model:
npx @lgrammel/prototype-2@latest init --harness pi --model anthropic/claude-sonnet-4.6The initializer creates an independent workbench/ project, installs this package as a normal
dependency, and scaffolds the required repository-owned default agent as a Codex harness by default,
without creating optional tools/, automations/, or tasks/ directories. Pass
--harness claude-code or --harness pi to select a different curated harness, and optionally pass
--model; without flags, setup uses Codex with gpt-5.6-sol as the default plus gpt-5.6-terra and
gpt-5.6-luna choices. It also creates
.agents/skills/workbench-configuration/SKILL.md at the host repository root so coding agents can
locate and safely improve the Workbench configuration.
Commit and push the generated files. You do not need to create a local .env file or run Workbench,
its migrations, checks, or build locally.
Deploy to Vercel
- Create a fine-grained GitHub personal access token scoped to the target repository with Contents: Read and write and Pull requests: Read and write permissions. Add Issues: Read and write when a task automation manages issue labels.
- Import the repository into Vercel as a new project. Set Root
Directory to
workbench, then deploy. The first deployment may report missing configuration; finish the following steps and redeploy it. - Add the Neon integration to the Vercel project and confirm
that it created a pooled
DATABASE_URLin the Production environment. - In Project Settings → Environment Variables, add these Production variables:
GITHUB_REPOSITORY: the private target inowner/repositoryform; its default branch must bemainGITHUB_TOKEN: the fine-grained token from step 1; mark it sensitiveGIT_AUTHOR_NAMEandGIT_AUTHOR_EMAIL: the identity for commits published by WorkbenchGITHUB_WEBHOOK_SECRET: optional secret of at least 16 characters for GitHub automationsCRON_SECRET: optional secret of at least 16 characters for scheduled automations
- In Project Settings → Security, enable Secure Backend Access with OIDC Federation. In Project Settings → Deployment Protection, enable Vercel Authentication with a scope that protects All Deployments. Workbench relies on this as its access-control boundary. An external webhook or scheduler also needs Vercel's Protection Bypass for Automation so its request reaches Workbench's independently authenticated endpoint.
- Redeploy Production, open the protected URL, create a chat, and ask Workbench to inspect the repository without changing it.
No Sandbox image setting is required; Workbench uses Vercel's managed default. To use the optional
Prototype-2 Playwright image with preinstalled browser tooling, set WORKBENCH_SANDBOX_IMAGE to its
complete immutable @sha256:<digest> reference after the publisher shares that VCR repository with
the project. Workbench rejects mutable references; changing the setting affects new chats only.
The generated Vercel build applies pending database migrations before every build. Trusted workflow
steps retrieve the request-scoped OIDC token automatically and broker it without exposing it to the
model sandbox, so do not add AI_GATEWAY_API_KEY.
Agents
List the agent templates bundled with the installed runtime or create another agent:
cd workbench
npx workbench agent templates
npx workbench agent create bugfix --template bugfix
npx workbench agent create codex-reviewer --template bugfix --harness codex --model gpt-5.6-solTemplates select agent behavior; --harness independently selects codex, claude-code, or
pi execution. Omit --model to use that harness's generator defaults. ACP definitions stay
explicit in agent.ts because their package, executable, and Gateway environment mapping are
implementation-specific.
When --harness is omitted, the generated definition uses the flexible WorkflowAgent execution
mode with top-level model, optional reasoningEffort, and required tools fields. With
--harness, generation uses the selected HarnessAgent adapter and its native tools without adding a
top-level tools object. The generic harness can rely on its native prompt:
import { defineAgent, harness } from "@lgrammel/prototype-2";
export default defineAgent({
label: "Codex harness",
description: "Codex Harness agent",
execute: harness.codex,
model: ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"],
reasoningEffort: ["high", "medium", "low"],
publishMode: "manual",
});The first configured model and effort are the defaults. When an agent exposes multiple values, a
human chooses once while creating a chat and Workbench keeps that choice for the chat. A
behavior-specific agent can explicitly add instructions: loadAgentFile("instructions.md"); there
is no implicit prompt-file discovery. harness.claudeCode and harness.pi expose their native
effort controls. harness.acp accepts an
exact-versioned sandbox-installed ACP implementation and declarative AI Gateway environment
sources, covering runtimes without a dedicated Workbench driver. Workbench tools are optional for
a harness definition; adding a top-level tools object supplements the native tools. Matching
configuration resumes bounded opaque state with the current user turn, while a new or changed
configuration starts a fresh native session from the full persisted transcript.
Both execution modes use the same persistent chat sandbox and publication flow. All model-controlled commands and native harness processes run as unprivileged UID 65534 without Git credentials or access to protected Git metadata; only trusted Workbench code can commit or publish.
Human chats default to main; the user can name another repository branch when creating a chat.
Workbench pins the selected ref and SHA before provisioning its sandbox. A non-main chat can
inspect and modify its isolated filesystem, but cannot commit or publish code and therefore must use
an agent whose publishMode is manual.
Upgrade the application, inspect tool-template changes, and safely update pristine tools:
cd workbench
npm install @lgrammel/prototype-2@latest
npx workbench tool diff
npx workbench tool updateModified or intentionally deleted tools are preserved. Commit and push the reviewed source, manifest, and lockfile changes to deploy the upgrade through Vercel.
Shell image outputs
Repository-owned tools can ask the Workbench shell to import images created by a command:
const result = await shell.run(
'playwright screenshot http://127.0.0.1:3000 "$WORKBENCH_OUTPUT_DIR/home.png"',
{
outputImages: [{ path: "home.png", description: "Rendered home page" }],
},
);outputImages accepts up to four relative paths beneath the per-command
WORKBENCH_OUTPUT_DIR. The structured result contains output, exitCode, truncated, and
validated artifacts. Each artifact includes immutable metadata and a ready-to-copy Markdown image
reference for the agent's final response. PNG, JPEG, and WebP are supported. The Playwright command
in this example requires browser tooling from the target project or the optional custom Sandbox
image; the default managed image does not guarantee it.
Automations
Automations are optional reviewed TypeScript definitions under
workbench/automations/<id>/automation.ts. They are configured in code and bundled at deployment;
the application provides a read-only viewer with recent execution attempts, manual Run now,
retry for failed, skipped, or unsuccessful dispatched attempts, links to agent chats, and inline
task and effect evidence. It does not provide an automation or task editor.
import { defineAutomation } from "@lgrammel/prototype-2";
import { z } from "zod";
const selectionSchema = z.object({
repository: z.string(),
task: z.string(),
});
export default defineAutomation({
label: "Repository health check",
description: "Implement one small, high-confidence maintenance improvement.",
trigger: { type: "manual" },
source: {
description: "Configured repository and maintenance task",
schema: selectionSchema,
select: ({ repository }) => ({
repository: repository.name,
task: "Find, implement, and verify one small maintenance improvement.",
}),
},
execution: {
type: "agent",
agentId: "default",
messageTemplate: `Work on {{repository}}.\n\nTask:\n{{task}}`,
},
sourceUrlTemplate: "https://github.com/{{repository}}",
maximumConcurrentExecutions: 1,
});The source selector runs in trusted server/workflow code. Its result must match schema and contain
only JSON data. Every {{path.to.value}} placeholder must resolve; a missing value fails the
execution instead of producing a partial prompt. Selected external text remains untrusted model
input. Each accepted agent execution creates a fresh chat using the selected agent's unchanged
tools, applicability, publication mode, and publication policy.
An agent execution can also select a branch or GitHub pull-request checkout from that validated
source. A pull-request review may declare the narrow github.pull-request-comment effect, whose
trusted target callback must resolve to the checked-out pull request and whose body callback can use
only validated source data and the bounded final response. Workbench applies the effect only after
the agent succeeds, revalidates the open pull request at its pinned head, and records an idempotent
request and receipt. Non-main and pull-request checkouts cannot publish code. See the packaged
configuration reference for a complete example.
Automations choose one explicit execution:
execution:
| { type: "agent"; agentId: string; messageTemplate: string; checkout?; effect? }
| { type: "task"; task: ImportedTask; input: ({ source }) => TaskInput; effect?: ManagedEffect }Existing agent definitions with top-level agentId and messageTemplate remain valid and normalize
to agent execution. A task is a separate default export under workbench/tasks/<id>/task.ts:
import { defineTask } from "@lgrammel/prototype-2";
import { z } from "zod";
export default defineTask({
label: "Classify issue",
description: "Choose one configured issue-type label.",
inputSchema: z.object({ title: z.string(), body: z.string() }),
execution: {
type: "structured-model",
model: "openai/gpt-5.4",
prompt: ({ input }) => `Classify this untrusted issue:\n${JSON.stringify(input)}`,
outputSchema: z.object({ label: z.string(), rationale: z.string() }),
},
});The MVP supports only this pure, bounded structured-model task. It creates no chat or sandbox and
receives no tools or GitHub credentials. An automation imports the task, maps validated source to
its typed input, and may own the narrow github.issue-managed-label effect. Workbench persists and
validates the decision before the effect, restricts the selected label to the configured managed
set, preserves unrelated labels, and applies retries idempotently. See the packaged
configuration reference for the complete task binding and effect. Keep a
write-capable automation disabled until its managed labels and token permission are configured,
then explicitly enable and redeploy it.
Agent-backed definitions can also hand work to a different deployed agent without an external coordinator; task-backed automations cannot be handoff sources or targets:
{ type: "handoff", from: { type: "automation", automationId: "implement-change" } }
{ type: "handoff", from: { type: "human", agentId: "default" } }An automation-source handoff dispatches automatically only after the source agent succeeds and its automatic publication, if any, succeeds or is skipped and its configured agent effect succeeds. A human-source handoff appears as a follow-up suggestion after the latest eligible successful human-started run and requires a click. The result is a linked child chat whose separate persistent sandbox is an exact point-in-time fork of the source workspace. The target gets a fresh execution session and retains its own deployed instructions, tools, applicability, publication mode, and publication policy. Bounded textual and metadata context accompanies the fork, but managed artifact binaries do not.
Source and target agents must differ. Definition loading rejects missing references, automatic cycles, chains deeper than eight, and more than ten automatic handoffs from one source. Stable source-run/target-automation idempotency prevents duplicate children, and source/child lineage links remain visible. Failed or cancelled agent/publication work and failed effects do not hand off. This native coordination needs no external API call, webhook, scheduler, or GitHub event and is not a general workflow/DAG editor.
Trigger endpoints
- Manual/API:
POST /api/automations/<id>/triggerwith an optional JSON body such as{ "idempotencyKey": "caller-event-123", "payload": {} }. The viewer uses this endpoint only for definitions withtrigger: { type: "manual" }. - Schedule:
GET /api/automations/schedulewithAuthorization: Bearer <CRON_SECRET>. A scheduler tick evaluates all enabled schedule definitions; theirintervalMinutesvalues determine stable, deduplicated time buckets. A Vercel Cron can call the endpoint and automatically sendsCRON_SECRETin that header. Choose a tick frequency supported by the deployment's Vercel plan; the configuration reference includes thevercel.jsonconfiguration. - GitHub:
POST /api/automations/github. Workbench verifiesX-Hub-Signature-256againstGITHUB_WEBHOOK_SECRET, requiresX-GitHub-EventandX-GitHub-Delivery, checks the configured repository, and dispatches matchingeventand optionalactionsdefinitions.
Deployment Protection runs before these handlers. Give automated callers a dedicated Vercel
automation-bypass secret. Prefer the x-vercel-protection-bypass header; for GitHub webhooks, which
cannot set arbitrary headers, append the same parameter to the webhook URL. The bypass token only
gets the request through Vercel and does not replace Workbench's webhook signature or cron-secret
checks. Keep all three secrets separate and out of automation source and rendered messages.
Changing, adding, or removing a definition requires review and redeployment. Updating the npm
package refreshes only the package-owned runtime and never creates, rewrites, or deletes the
repository's automations/ or tasks/ tree.
