From 6fc28c3f4a2fce094ff90e567f90976d0b4483b8 Mon Sep 17 00:00:00 2001 From: HWLAB Code Queue Date: Thu, 21 May 2026 14:57:00 +0000 Subject: [PATCH] feat: add agent runtime skeleton --- cmd/hwlab-agent-mgr/main.mjs | 58 +++ cmd/hwlab-agent-worker/main.mjs | 58 +++ deploy/deploy.json | 51 +++ internal/agent/index.mjs | 352 ++++++++++++++++++ internal/agent/index.test.mjs | 68 ++++ package.json | 2 +- scripts/bootstrap-skills.sh | 38 +- scripts/validate-contract.mjs | 37 +- scripts/worker-entrypoint.sh | 18 +- skills/hwlab-agent-runtime/SKILL.md | 14 + .../hwlab-agent-runtime/references/runtime.md | 7 + 11 files changed, 694 insertions(+), 9 deletions(-) create mode 100644 cmd/hwlab-agent-mgr/main.mjs create mode 100644 cmd/hwlab-agent-worker/main.mjs create mode 100644 deploy/deploy.json create mode 100644 internal/agent/index.mjs create mode 100644 internal/agent/index.test.mjs create mode 100644 skills/hwlab-agent-runtime/SKILL.md create mode 100644 skills/hwlab-agent-runtime/references/runtime.md diff --git a/cmd/hwlab-agent-mgr/main.mjs b/cmd/hwlab-agent-mgr/main.mjs new file mode 100644 index 00000000..ec14406f --- /dev/null +++ b/cmd/hwlab-agent-mgr/main.mjs @@ -0,0 +1,58 @@ +#!/usr/bin/env node +import { createAgentRunPlan, createSkillsManifest, finalizeAgentRun } from "../../internal/agent/index.mjs"; + +function parseArgs(argv) { + const args = { + agentSessionId: "agt_local", + projectId: "prj_local", + goal: "dry-run agent runtime skeleton", + prompt: "dry-run", + skillCommitId: "" + }; + + for (let i = 0; i < argv.length; i += 1) { + const arg = argv[i]; + if (arg === "--agent-session-id") args.agentSessionId = argv[++i] || args.agentSessionId; + else if (arg === "--project-id") args.projectId = argv[++i] || args.projectId; + else if (arg === "--goal") args.goal = argv[++i] || args.goal; + else if (arg === "--prompt") args.prompt = argv[++i] || args.prompt; + else if (arg === "--skill-commit-id") args.skillCommitId = argv[++i] || args.skillCommitId; + else if (arg === "--help" || arg === "-h") args.help = true; + } + + return args; +} + +function printHelp() { + console.log("usage: node cmd/hwlab-agent-mgr/main.mjs [--agent-session-id ID] [--project-id ID] [--goal TEXT] [--prompt TEXT] [--skill-commit-id COMMIT]"); +} + +const args = parseArgs(process.argv.slice(2)); + +if (args.help) { + printHelp(); + process.exit(0); +} + +const plan = createAgentRunPlan({ + agentSessionId: args.agentSessionId, + projectId: args.projectId, + goal: args.goal, + prompt: args.prompt, + skillCommitId: args.skillCommitId || null +}); +const finished = finalizeAgentRun(plan); + +const result = { + serviceId: "hwlab-agent-mgr", + dryRun: true, + agentSession: finished.agentSession, + workerSession: finished.workerSession, + workspaceVolume: finished.workspaceVolume, + traceEvent: finished.traceEvent, + evidenceRecord: finished.evidenceRecord, + cleanup: finished.cleanup, + skillsManifest: args.skillCommitId ? createSkillsManifest({ commitId: args.skillCommitId }) : null +}; + +console.log(JSON.stringify(result, null, 2)); diff --git a/cmd/hwlab-agent-worker/main.mjs b/cmd/hwlab-agent-worker/main.mjs new file mode 100644 index 00000000..b8341e0f --- /dev/null +++ b/cmd/hwlab-agent-worker/main.mjs @@ -0,0 +1,58 @@ +#!/usr/bin/env node +import { createAgentRunPlan, finalizeAgentRun } from "../../internal/agent/index.mjs"; + +function parseArgs(argv) { + const args = { + agentSessionId: process.env.HWLAB_AGENT_SESSION_ID || "agt_local", + projectId: process.env.HWLAB_PROJECT_ID || "prj_local", + prompt: "worker dry-run", + goal: "agent worker session skeleton", + skillCommitId: process.env.HWLAB_SKILL_COMMIT_ID || "" + }; + + for (let i = 0; i < argv.length; i += 1) { + const arg = argv[i]; + if (arg === "--agent-session-id") args.agentSessionId = argv[++i] || args.agentSessionId; + else if (arg === "--project-id") args.projectId = argv[++i] || args.projectId; + else if (arg === "--prompt") args.prompt = argv[++i] || args.prompt; + else if (arg === "--goal") args.goal = argv[++i] || args.goal; + else if (arg === "--skill-commit-id") args.skillCommitId = argv[++i] || args.skillCommitId; + else if (arg === "--help" || arg === "-h") args.help = true; + } + + return args; +} + +function printHelp() { + console.log("usage: node cmd/hwlab-agent-worker/main.mjs [--agent-session-id ID] [--project-id ID] [--goal TEXT] [--prompt TEXT] [--skill-commit-id COMMIT]"); +} + +const args = parseArgs(process.argv.slice(2)); + +if (args.help) { + printHelp(); + process.exit(0); +} + +const plan = finalizeAgentRun( + createAgentRunPlan({ + agentSessionId: args.agentSessionId, + projectId: args.projectId, + goal: args.goal, + prompt: args.prompt, + skillCommitId: args.skillCommitId || null + }) +); + +const result = { + serviceId: "hwlab-agent-worker", + dryRun: true, + agentSession: plan.agentSession, + workerSession: plan.workerSession, + workspaceVolume: plan.workspaceVolume, + traceEvent: plan.traceEvent, + evidenceRecord: plan.evidenceRecord, + cleanup: plan.cleanup +}; + +console.log(JSON.stringify(result, null, 2)); diff --git a/deploy/deploy.json b/deploy/deploy.json new file mode 100644 index 00000000..34378d1b --- /dev/null +++ b/deploy/deploy.json @@ -0,0 +1,51 @@ +{ + "manifestVersion": "v1", + "environment": "dev", + "commitId": "6509a35", + "namespace": "hwlab-dev", + "endpoint": "http://74.48.78.17:6667", + "profiles": { + "dev": { + "name": "dev", + "enabled": true, + "namespace": "hwlab-dev", + "endpoint": "http://74.48.78.17:6667", + "notes": "HWLAB dev deploy manifest with explicit commitId for repo-local skills artifact" + } + }, + "services": [ + { + "serviceId": "hwlab-agent-mgr", + "image": "hwlab-agent-mgr:6509a35", + "namespace": "hwlab-dev", + "healthPath": "/health", + "profile": "dev", + "replicas": 1, + "env": { + "HWLAB_AGENT_SESSION_MODE": "control-plane" + } + }, + { + "serviceId": "hwlab-agent-worker", + "image": "hwlab-agent-worker:6509a35", + "namespace": "hwlab-dev", + "healthPath": "/health", + "profile": "dev", + "replicas": 0, + "env": { + "HWLAB_AGENT_SESSION_MODE": "session-scoped" + } + }, + { + "serviceId": "hwlab-agent-skills", + "image": "hwlab-agent-skills:6509a35", + "namespace": "hwlab-dev", + "healthPath": "/health", + "profile": "dev", + "replicas": 1, + "env": { + "HWLAB_SKILLS_COMMIT_ID": "6509a35" + } + } + ] +} diff --git a/internal/agent/index.mjs b/internal/agent/index.mjs new file mode 100644 index 00000000..8db6e59a --- /dev/null +++ b/internal/agent/index.mjs @@ -0,0 +1,352 @@ +import crypto from "node:crypto"; +import path from "node:path"; + +import { ENVIRONMENT_DEV } from "../protocol/index.mjs"; + +export const AGENT_MGR_SERVICE_ID = "hwlab-agent-mgr"; +export const AGENT_WORKER_SERVICE_ID = "hwlab-agent-worker"; +export const AGENT_SKILLS_SERVICE_ID = "hwlab-agent-skills"; + +export const AGENT_SESSION_STATUSES = Object.freeze([ + "create", + "start", + "trace", + "finish", + "cleanup" +]); + +export const WORKER_SESSION_STATUSES = Object.freeze([ + "create", + "start", + "trace", + "finish", + "cleanup" +]); + +export function createAgentSession({ + agentSessionId, + projectId, + requestedBy = "usr_local", + goal = "", + environment = ENVIRONMENT_DEV, + now = () => new Date().toISOString(), + metadata = {} +}) { + const timestamp = now(); + return { + agentSessionId, + projectId, + serviceId: AGENT_MGR_SERVICE_ID, + status: "create", + environment, + requestedBy, + goal, + startedAt: timestamp, + updatedAt: timestamp, + metadata: { + ...metadata, + lifecycle: AGENT_SESSION_STATUSES.slice(), + currentState: "create" + } + }; +} + +export function advanceAgentSession(session, nextStatus, { now = () => new Date().toISOString(), extra = {} } = {}) { + assertAgentStatus(nextStatus); + const timestamp = now(); + const completedAt = nextStatus === "finish" ? timestamp : session.completedAt; + return { + ...session, + status: nextStatus, + completedAt, + updatedAt: timestamp, + metadata: { + ...session.metadata, + ...extra, + currentState: nextStatus + } + }; +} + +export function createWorkspaceVolume({ agentSessionId, projectId, now = () => new Date().toISOString() }) { + return { + workspaceVolumeId: `vol_${agentSessionId}`, + agentSessionId, + projectId, + serviceId: AGENT_WORKER_SERVICE_ID, + mountPath: path.posix.join("/workspace", projectId, agentSessionId), + lifecycle: "create", + createdAt: now(), + updatedAt: now() + }; +} + +export function createWorkerSession({ + workerSessionId, + agentSessionId, + projectId, + gatewaySessionId = null, + now = () => new Date().toISOString(), + metadata = {} +}) { + const timestamp = now(); + return { + workerSessionId, + agentSessionId, + projectId, + serviceId: AGENT_WORKER_SERVICE_ID, + gatewaySessionId, + status: "create", + environment: ENVIRONMENT_DEV, + startedAt: timestamp, + updatedAt: timestamp, + metadata: { + ...metadata, + lifecycle: WORKER_SESSION_STATUSES.slice(), + workspaceVolumeId: `vol_${agentSessionId}`, + currentState: "create" + } + }; +} + +export function advanceWorkerSession(session, nextStatus, { now = () => new Date().toISOString(), extra = {} } = {}) { + assertWorkerStatus(nextStatus); + const timestamp = now(); + const completedAt = nextStatus === "finish" ? timestamp : session.completedAt; + return { + ...session, + status: nextStatus, + completedAt, + updatedAt: timestamp, + metadata: { + ...session.metadata, + ...extra, + currentState: nextStatus + } + }; +} + +export function createTraceEvent({ + traceEventId, + traceId, + projectId, + serviceId = AGENT_MGR_SERVICE_ID, + level = "info", + message, + environment = ENVIRONMENT_DEV, + now = () => new Date().toISOString(), + agentSessionId = null, + workerSessionId = null, + operationId = null, + metadata = {} +}) { + return { + traceEventId, + traceId, + projectId, + serviceId, + level, + message, + environment, + occurredAt: now(), + agentSessionId, + workerSessionId, + operationId, + metadata: { + ...metadata + } + }; +} + +export function createEvidenceRecord({ + evidenceId, + projectId, + operationId, + uri, + sha256 = digestHex(uri), + serviceId = AGENT_WORKER_SERVICE_ID, + environment = ENVIRONMENT_DEV, + kind = "artifact", + now = () => new Date().toISOString(), + agentSessionId = null, + workerSessionId = null, + mimeType = "text/plain", + sizeBytes = 0, + metadata = {} +}) { + return { + evidenceId, + projectId, + operationId, + agentSessionId, + workerSessionId, + kind, + uri, + mimeType, + sha256, + sizeBytes, + serviceId, + environment, + metadata: { + ...metadata + }, + createdAt: now() + }; +} + +export function createAgentRunPlan({ + agentSessionId, + projectId, + goal = "", + prompt = "", + skillCommitId = null, + now = () => new Date().toISOString() +}) { + const traceId = `trc_${agentSessionId}`; + const workerSessionId = `wkr_${agentSessionId}`; + const operationId = `op_${agentSessionId}`; + const evidenceId = `evi_${agentSessionId}`; + const traceEventId = `tev_${agentSessionId}`; + const workspaceVolume = createWorkspaceVolume({ agentSessionId, projectId, now }); + const agentSession = createAgentSession({ + agentSessionId, + projectId, + goal, + now + }); + const workerSession = createWorkerSession({ + workerSessionId, + agentSessionId, + projectId, + now + }); + const traceEvent = createTraceEvent({ + traceEventId, + traceId, + projectId, + message: prompt || goal || "agent runtime dry run", + agentSessionId, + workerSessionId, + operationId, + now + }); + const evidenceRecord = createEvidenceRecord({ + evidenceId, + projectId, + operationId, + uri: `file://${path.posix.join(workspaceVolume.mountPath, "evidence.txt")}`, + agentSessionId, + workerSessionId, + metadata: { + skillCommitId, + prompt, + goal, + workspaceVolumeId: workspaceVolume.workspaceVolumeId + }, + now + }); + + return { + agentSession: advanceAgentSession(agentSession, "start", { + now, + extra: { + workerSessionId, + workspaceVolumeId: workspaceVolume.workspaceVolumeId + } + }), + workerSession: advanceWorkerSession(workerSession, "start", { + now, + extra: { + workspaceVolumeId: workspaceVolume.workspaceVolumeId + } + }), + workspaceVolume, + events: [ + { state: "create", actor: AGENT_MGR_SERVICE_ID, agentSessionId }, + { state: "start", actor: AGENT_WORKER_SERVICE_ID, workerSessionId }, + { state: "trace", actor: AGENT_MGR_SERVICE_ID, traceEvent }, + { state: "finish", actor: AGENT_WORKER_SERVICE_ID, evidenceRecord }, + { state: "cleanup", actor: AGENT_WORKER_SERVICE_ID, workspaceVolumeId: workspaceVolume.workspaceVolumeId } + ], + traceEvent, + evidenceRecord, + skillCommitId + }; +} + +export function finalizeAgentRun(plan, { now = () => new Date().toISOString() } = {}) { + const finishedAgentSession = advanceAgentSession(plan.agentSession, "finish", { now }); + const finishedWorkerSession = advanceWorkerSession(plan.workerSession, "finish", { now }); + return { + ...plan, + agentSession: advanceAgentSession(finishedAgentSession, "cleanup", { + now, + extra: { cleanedUp: true } + }), + workerSession: advanceWorkerSession(finishedWorkerSession, "cleanup", { + now, + extra: { cleanedUp: true } + }), + cleanup: { + workspaceVolumeId: plan.workspaceVolume.workspaceVolumeId, + removed: true + } + }; +} + +export function createSkillsManifest({ + commitId, + skillName = "hwlab-agent-runtime", + serviceId = AGENT_SKILLS_SERVICE_ID, + environment = ENVIRONMENT_DEV, + skills = [] +}) { + if (!commitId) { + throw new Error("skills manifest requires explicit commitId"); + } + return { + manifestVersion: "v1", + environment, + commitId, + namespace: "hwlab", + endpoint: "http://74.48.78.17:6667", + profiles: { + dev: { + name: "dev", + enabled: true, + namespace: "hwlab-dev", + endpoint: "http://74.48.78.17:6667", + notes: "explicit repo-local skills artifact" + } + }, + services: [ + { + serviceId, + image: `${skillName}:${commitId.slice(0, 7)}`, + namespace: "hwlab-dev", + healthPath: "/health", + profile: "dev", + env: { + HWLAB_SKILLS_COMMIT_ID: commitId + } + } + ], + skills + }; +} + +function assertAgentStatus(status) { + if (!AGENT_SESSION_STATUSES.includes(status)) { + throw new Error(`unknown agent session status ${JSON.stringify(status)}`); + } +} + +function assertWorkerStatus(status) { + if (!WORKER_SESSION_STATUSES.includes(status)) { + throw new Error(`unknown worker session status ${JSON.stringify(status)}`); + } +} + +function digestHex(value) { + return crypto.createHash("sha256").update(String(value)).digest("hex"); +} diff --git a/internal/agent/index.test.mjs b/internal/agent/index.test.mjs new file mode 100644 index 00000000..29a15076 --- /dev/null +++ b/internal/agent/index.test.mjs @@ -0,0 +1,68 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { + AGENT_SESSION_STATUSES, + WORKER_SESSION_STATUSES, + advanceAgentSession, + advanceWorkerSession, + createAgentRunPlan, + createSkillsManifest, + finalizeAgentRun +} from "./index.mjs"; + +test("agent runtime run plan exposes create/start/trace/finish/cleanup", () => { + const plan = createAgentRunPlan({ + agentSessionId: "agt_01J00000000000000000000000", + projectId: "prj_01J00000000000000000000000", + goal: "verify runtime skeleton", + prompt: "dry run" + }); + + assert.deepEqual(AGENT_SESSION_STATUSES, ["create", "start", "trace", "finish", "cleanup"]); + assert.deepEqual(WORKER_SESSION_STATUSES, ["create", "start", "trace", "finish", "cleanup"]); + assert.equal(plan.agentSession.status, "start"); + assert.equal(plan.workerSession.status, "start"); + assert.equal(plan.events.map((event) => event.state).join(","), "create,start,trace,finish,cleanup"); + assert.equal(plan.traceEvent.message, "dry run"); + assert.equal(plan.evidenceRecord.projectId, "prj_01J00000000000000000000000"); + assert.equal(plan.evidenceRecord.metadata.workspaceVolumeId, "vol_agt_01J00000000000000000000000"); +}); + +test("finalizeAgentRun marks cleanup and keeps evidence visible", () => { + const plan = createAgentRunPlan({ + agentSessionId: "agt_01J00000000000000000000001", + projectId: "prj_01J00000000000000000000001", + goal: "cleanup coverage" + }); + const finished = finalizeAgentRun(plan); + + assert.equal(finished.agentSession.status, "cleanup"); + assert.equal(finished.workerSession.status, "cleanup"); + assert.equal(finished.cleanup.removed, true); + assert.ok(finished.evidenceRecord.sha256); +}); + +test("skills manifest requires explicit commitId", () => { + assert.throws(() => createSkillsManifest({}), /explicit commitId/); + + const manifest = createSkillsManifest({ commitId: "6509a35" }); + assert.equal(manifest.commitId, "6509a35"); + assert.equal(manifest.services[0].serviceId, "hwlab-agent-skills"); +}); + +test("advance helpers preserve completedAt on non-finish states", () => { + const session = { + status: "start", + completedAt: undefined, + metadata: {} + }; + + const agent = advanceAgentSession(session, "trace"); + const worker = advanceWorkerSession(session, "trace"); + + assert.equal(agent.status, "trace"); + assert.equal(worker.status, "trace"); + assert.equal(agent.completedAt, undefined); + assert.equal(worker.completedAt, undefined); +}); diff --git a/package.json b/package.json index ad2dad1c..d2bcf54a 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "validate": "node scripts/validate-contract.mjs", - "check": "node --check internal/protocol/index.mjs && node --check scripts/validate-contract.mjs && node scripts/validate-contract.mjs", + "check": "node --check internal/protocol/index.mjs && node --check internal/agent/index.mjs && node --check scripts/validate-contract.mjs && node scripts/validate-contract.mjs && node --test internal/agent/index.test.mjs && sh -n scripts/bootstrap-skills.sh scripts/worker-entrypoint.sh", "cli:health": "node tools/hwlab-cli/bin/hwlab-cli.mjs health", "cli:dry-run": "node tools/hwlab-cli/bin/hwlab-cli.mjs test e2e --env dev --mvp --dry-run", "cli:projects": "node tools/hwlab-cli/bin/hwlab-cli.mjs project list", diff --git a/scripts/bootstrap-skills.sh b/scripts/bootstrap-skills.sh index 2a4165bd..bce27b8a 100755 --- a/scripts/bootstrap-skills.sh +++ b/scripts/bootstrap-skills.sh @@ -2,6 +2,40 @@ set -eu repo_root="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" -mkdir -p "$repo_root/skills" +commit_id="${1:-}" -echo "HWLAB skills directory ready: $repo_root/skills" +mkdir -p "$repo_root/skills/hwlab-agent-runtime/references" +mkdir -p "$repo_root/skills/hwlab-agent-runtime/assets" + +cat > "$repo_root/skills/hwlab-agent-runtime/SKILL.md" < "$repo_root/skills/hwlab-agent-runtime/references/runtime.md" < "$repo_root/skills/hwlab-agent-runtime/.commit-id" +fi + +echo "HWLAB skills directory ready: $repo_root/skills/hwlab-agent-runtime" diff --git a/scripts/validate-contract.mjs b/scripts/validate-contract.mjs index a1eed22e..14e01d1d 100644 --- a/scripts/validate-contract.mjs +++ b/scripts/validate-contract.mjs @@ -13,7 +13,7 @@ import { const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); -async function parseJSONFiles(dir) { +async function parseJSONFiles(dir, { requireSchema = true } = {}) { const entries = await readdir(path.join(repoRoot, dir), { withFileTypes: true }); const parsed = []; for (const entry of entries) { @@ -23,13 +23,36 @@ async function parseJSONFiles(dir) { const relativePath = path.join(dir, entry.name); const raw = await readFile(path.join(repoRoot, relativePath), "utf8"); const doc = JSON.parse(raw); - assert.ok(doc.$schema, `${relativePath} missing $schema`); - assert.ok(doc.$id, `${relativePath} missing $id`); + if (requireSchema) { + assert.ok(doc.$schema, `${relativePath} missing $schema`); + assert.ok(doc.$id, `${relativePath} missing $id`); + } parsed.push({ relativePath, doc }); } return parsed; } +async function parseJSONSchemaFiles(dir) { + const parsed = []; + for (const item of await parseJSONFiles(dir, { requireSchema: false })) { + if (!item.doc.$schema || !item.doc.$id) { + continue; + } + parsed.push(item); + } + return parsed; +} + +async function parseDeployManifest(relativePath) { + const raw = await readFile(path.join(repoRoot, relativePath), "utf8"); + const doc = JSON.parse(raw); + assert.equal(doc.manifestVersion, "v1", `${relativePath} manifestVersion`); + assert.equal(doc.environment, ENVIRONMENT_DEV, `${relativePath} environment`); + assert.ok(doc.commitId, `${relativePath} commitId`); + assert.ok(Array.isArray(doc.services) && doc.services.length >= 1, `${relativePath} services`); + return doc; +} + function assertUnique(name, values) { assert.equal(new Set(values).size, values.length, `${name} must be unique`); } @@ -91,8 +114,9 @@ function assertEnvelopeValidation() { ); } -const schemas = await parseJSONFiles("protocol/schemas"); -const deploySchemas = await parseJSONFiles("deploy"); +const schemas = await parseJSONSchemaFiles("protocol/schemas"); +const deploySchemas = await parseJSONSchemaFiles("deploy"); +const deployManifest = await parseDeployManifest("deploy/deploy.json"); const docsByName = new Map([...schemas, ...deploySchemas].map((item) => [path.basename(item.relativePath), item.doc])); assert.ok(schemas.length >= 13, "expected protocol schemas"); @@ -102,5 +126,8 @@ assertUnique("tables", TABLES); assertCommonSchema(docsByName.get("common.json")); assertDeploySchema(docsByName.get("deploy.schema.json")); assertEnvelopeValidation(); +assert.equal(deployManifest.services.some((service) => service.serviceId === "hwlab-agent-mgr"), true); +assert.equal(deployManifest.services.some((service) => service.serviceId === "hwlab-agent-worker"), true); +assert.equal(deployManifest.services.some((service) => service.serviceId === "hwlab-agent-skills"), true); console.log(`validated ${schemas.length + deploySchemas.length} JSON files and ${SERVICE_IDS.length} service ids`); diff --git a/scripts/worker-entrypoint.sh b/scripts/worker-entrypoint.sh index 2fb7afa7..5dd4b4b6 100755 --- a/scripts/worker-entrypoint.sh +++ b/scripts/worker-entrypoint.sh @@ -1,5 +1,21 @@ #!/usr/bin/env sh set -eu -echo "HWLAB worker entrypoint placeholder" +repo_root="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" +workspace_root="${HWLAB_WORKSPACE_ROOT:-/workspace}" +agent_session_id="${HWLAB_AGENT_SESSION_ID:-agent_local}" +project_id="${HWLAB_PROJECT_ID:-prj_local}" +skill_commit_id="${HWLAB_SKILL_COMMIT_ID:-}" + +mkdir -p "$workspace_root" + +echo "HWLAB worker starting" +echo "repo=$repo_root" +echo "workspace=$workspace_root" +echo "agentSessionId=$agent_session_id" +echo "projectId=$project_id" +if [ -n "$skill_commit_id" ]; then + echo "skillsCommitId=$skill_commit_id" +fi + exec "$@" diff --git a/skills/hwlab-agent-runtime/SKILL.md b/skills/hwlab-agent-runtime/SKILL.md new file mode 100644 index 00000000..e78f5236 --- /dev/null +++ b/skills/hwlab-agent-runtime/SKILL.md @@ -0,0 +1,14 @@ +--- +name: hwlab-agent-runtime +description: Build and validate the HWLAB agent-mgr and agent-worker runtime skeleton, including session lifecycle, workspace volume bookkeeping, trace events, evidence records, and explicit skills commitId handling. +--- + +# HWLAB Agent Runtime + +Use this skill when working on HWLAB agent runtime skeletons. + +- Keep the control plane long-lived and worker execution session-scoped. +- Model the lifecycle as create, start, trace, finish, cleanup. +- Always carry explicit evidence record fields through the run plan. +- Use repo-local skills artifacts only when a commitId is supplied explicitly. +- Do not infer or fall back to external skill sources. diff --git a/skills/hwlab-agent-runtime/references/runtime.md b/skills/hwlab-agent-runtime/references/runtime.md new file mode 100644 index 00000000..e125d601 --- /dev/null +++ b/skills/hwlab-agent-runtime/references/runtime.md @@ -0,0 +1,7 @@ +# Runtime Notes + +- agent-mgr owns session creation and trace emission. +- agent-worker owns worker session execution and cleanup. +- Each agent session gets its own workspace volume identity. +- Evidence records must expose projectId, operationId, kind, uri, sha256, serviceId, environment, and createdAt. +- Skills deployment artifacts require an explicit commitId.