,-,------,
_ \(\(_,--'
<`--'\>/(/(__
/. . `'` ' \
(`') , @
`-._, /
)-)_/--( >
'''' ''''
pig.
This repository is the canonical public toolchain for Promptless Instruction Hub repositories. It bundles the Python compiler and exposes reusable GitHub workflows, a GitLab CI template, and a composite GitHub Action for validating, building, and publishing generated hub artifacts.
Use the GitHub workflows or GitLab template to keep toolchain setup in this repository. For custom GitHub jobs, see direct action usage.
Add these two caller files to your hub repository. They use GitHub's
reusable workflow syntax,
with uses on the job. The shared workflows handle checkout and toolchain setup.
.github/workflows/instruction-hub-check.yml:
name: Check Instruction Hub
on:
pull_request:
workflow_dispatch:
permissions:
contents: read
jobs:
instruction-hub:
uses: Promptless/pig-toolchain/.github/workflows/pr-check.yml@main.github/workflows/instruction-hub-publish.yml:
name: Publish Instruction Hub
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: write
concurrency:
group: instruction-hub-release
cancel-in-progress: false
jobs:
instruction-hub:
if: github.ref == 'refs/heads/main'
uses: Promptless/pig-toolchain/.github/workflows/publish.yml@main
with:
source-branch: mainThe check workflow validates and builds the hub
without committing generated files. The publish workflow
publishes generated artifacts to release/stable and updates marketplace
pointers on the source branch for the configured Claude, Codex, and Cursor targets.
The caller above runs publication on pushes to main or manual runs on main,
and its concurrency group prevents overlapping publish runs.
Publishing uses the caller repository's automatic GITHUB_TOKEN; no additional
secret is required. Grant contents: write as shown and ensure repository rules
allow that token to push to both the source and release branches.
All workflow inputs are optional. Set them under the calling job's with:
| Input | Workflow | Default | Purpose |
|---|---|---|---|
hub-root |
Both | . |
Hub directory within the checked-out repository. |
mode |
Check | build |
Use check only when generated artifacts are committed alongside source. |
source-branch |
Publish | main |
Branch allowed to publish and receive marketplace pointer updates. |
release-branch |
Publish | release/stable |
Branch that receives generated artifacts; must differ from source-branch. |
If your source branch is not main, update push.branches, the job's if, and
source-branch together. For a hub in a subdirectory, set hub-root in both
callers.
Use @main in both workflow references to follow the latest merged toolchain.
Each reusable workflow checks out the compiler using the ref in its
caller's uses; there is no separate toolchain-ref input on GitHub.
For a hub at the repository root, add this to .gitlab-ci.yml:
include:
- remote: https://raw.githubusercontent.com/Promptless/pig-toolchain/main/templates/gitlab/instruction-hub.ymlThe template validates and builds hub
changes in merge requests. Default-branch pushes that change hub source or CI
configuration publish to release/stable and update the Claude, Codex, and Cursor
marketplace pointers. Manually started pipelines validate on every branch and
also publish on the default branch. Publishing is serialized, skips superseded
hub source, and includes pointer commits from earlier jobs before pushing.
Use a Linux runner that supports image:. Enable Settings > CI/CD > Job token
permissions > Allow Git push requests to the repository in the consuming
project. The user starting the pipeline must be allowed to push to the default
and release branches. Publishing uses CI_JOB_TOKEN; its pushes do not trigger
another pipeline. No GitHub token is needed.
The template scopes its image and variables to its own jobs. All inputs are
optional and go under the remote include's inputs:
| Input | Default | Purpose |
|---|---|---|
toolchain-ref |
main |
Leave as main to use the latest merged compiler. |
release-branch |
release/stable |
Branch that receives generated artifacts; must differ from the default branch. |
check-stage |
test |
Existing pipeline stage for validation. |
publish-stage |
deploy |
Existing pipeline stage for publishing. |
For a pipeline with custom stages, use include:inputs:
stages: [verify, publish]
include:
- remote: https://raw.githubusercontent.com/Promptless/pig-toolchain/main/templates/gitlab/instruction-hub.yml
inputs:
check-stage: verify
publish-stage: publish
release-branch: release/stableThe compiler defaults to main: each job fetches the latest merged toolchain
when it starts and logs the resolved commit for diagnostics. Keep both the
remote template URL and toolchain-ref on main. The template runs the same scripts/run.sh entrypoint as the
GitHub Action, with GitLab workspace, repository, identity, and branch checks
supplied by the template.
Use the composite action in a custom GitHub job when you need its additional inputs, such as generated paths or marketplace pointer controls. This publish job belongs in a workflow triggered from your source branch:
jobs:
instruction-hub:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- uses: Promptless/pig-toolchain@main
with:
mode: publish
source-branch: main
github-token: ${{ github.token }}Keep fetch-depth: 0 for publication and pass the token as shown when checkout
credentials are not persisted. Use @main for the latest merged action and
compiler.
The action runs the bundled compiler directly:
uv run --project "$GITHUB_ACTION_PATH" promptless-instruction-hub <command>Before publishing a source change, run the full non-mutating compilation:
pig verify --hub .pig verify validates every stable asset and renders every stable plugin for
every configured target in an ephemeral directory. It does not create, update,
or compare generated files in the source worktree, whether verification
succeeds or fails. Use pig build --check instead when the repository
intentionally commits generated artifacts and must prove they are current.
Run the full test suite in parallel:
uv run pytest tests -n auto --dist workstealOmit the parallel flags when running a single test file or a -k selection.
hub.yaml declares the marketplace's literal ID and display name:
org: Promptless
marketplace:
id: promptless-instruction-hub
name: Promptless Instruction Hub
version: 0.1.0
stable_plugins: [pig, dev]
targets: [claude, codex, gemini, cursor]Each file in plugins/*.yaml defines one plugin, using its own literal ID:
# plugins/dev.yaml
id: dev
name: Promptless Dev
includes:
- skill:review-docsThe skill in this example must exist in assets/skills/review-docs/. Keep the
required plugins/pig.yaml created by pig init in stable_plugins.
The compiler uses marketplace.id as the marketplace name and each plugin's
id as its native name. It adds no prefix or suffix. IDs use lowercase letters,
digits, and hyphens, with a letter or digit at each end. Plugin display names
come from name where the target supports them. Authored plugin output goes to
dist/{target}/{plugin.id}/. Gemini receives extensions with the same plugin
IDs; it has no generated marketplace manifest.
pig init --org Acme defaults to marketplace ID acme-instruction-hub and
display name Acme Instruction Hub. Override those with --marketplace-id and
--marketplace-name. version is the hub release version shared by all compiled
plugins. Publication compares generated output at the previous release version:
changed output advances the patch version, while unchanged output keeps it.
Set a higher version in hub.yaml to request a specific release, such as a
minor or major bump. Publication never lowers the version.
The publisher writes the resolved version back to hub.yaml and commits it
with the source marketplace pointers. It pushes the source and release commits
in one atomic Git transaction with explicit leases on both branch revisions.
If either branch changes during the build, or either update is rejected, neither
publish update lands. Rerun from the latest source branch after a race. The Git
server must support atomic pushes, and the publisher needs write access to both
branches. Publish requires committed source files and a clean index.
An unchanged rerun creates no commits. If only one branch needs a content change, the other receives an empty recording commit so Git checks both leases. Merely writing the resolved version back does not cause another version bump.
A Hub can distribute upstream plugins for Claude, Codex, and Cursor. Set
source.ref to a full 40-character commit SHA for a fixed pin, or "latest"
to follow the upstream default branch when the Hub publishes.
# plugins/doc-detective.yaml
kind: external
id: doc-detective
name: Doc Detective
source:
type: git
url: https://github.com/doc-detective/agent-tools.git
ref: "latest"
targets:
claude:
path: plugins/doc-detective
codex:
path: plugins/doc-detective
cursor:
path: plugins/doc-detectiveAdd the ID to hub.yaml's stable_plugins. It must match the upstream manifest's
name. Declare only targets the upstream supports; paths are relative to its
repository, with . for a plugin at the root.
Verify the upstream plugin and local build:
pig resolve-external --hub .
pig verify --hub .For "latest", commit the generated hub.external-plugins.lock.json alongside
the definition. Offline builds use this lock; CI build and publish modes refresh
it. Catalog definitions use ref; locks, release provenance, and marketplaces
use the resolved sha. Consumers update installed plugins through their host.
The generated PIG plugin includes an
add-external-plugin skill
with instructions for verification, updates, rollback, private repositories,
and host compatibility.
When an authored plugin replaces an external Claude plugin, the resolved Hub
release version must differ from the previous upstream version. If the automatic
bump collides, choose a higher version with
pig set-version --hub . --version <new-version> before publishing.
This is a breaking source configuration and installed plugin identity change.
Coordinate the toolchain upgrade with the hub migration. Validation rejects
legacy fields and the old packages/ directory with migration guidance.
- Replace root
plugin_idandplugin_namewithmarketplace.idandmarketplace.name. To keep an existing marketplace registration, set the new ID to its previous generated name: the oldplugin_idplus-marketplace. The compiler now uses that value verbatim. - Move
packages/toplugins/and renamestable_packagestostable_pluginsinhub.yaml. Keep each definition'sid,name, andincludes. Update custom CI path filters and scripts that reference the old directory orpig init --plugin-id/--plugin-nameflags. - Rename
plugin_versiontoversioninhub.yamland use--versioninstead of--plugin-versionin scripts. Version 2 release manifests use top-levelversion,marketplace,stable_plugins, andversion_basis.plugins. Version 1 manifests are rejected. Coordinate a one-time rebuild of existing release artifacts with the source migration before resuming publication. - Run
pig verify --hub ., then publish using the upgraded toolchain. - Refresh the marketplace and replace installed plugins using their new IDs.
For example,
promptless-instruction-hub-devbecomesdev. Remove the old installation so its skills and hooks are not loaded alongside the new one. An updater cannot infer that these different plugin IDs are replacements.
To distinguish plugins from multiple hubs in a host that uses plugin names as
skill namespaces, choose explicit IDs such as acme-dev for customer plugins.
The compiler never adds that prefix automatically. The managed PIG plugin
continues to require the ID pig.
hub.release.json and hub.stable.json share a schema version (2 for authored-only
releases, 3 when external plugins are selected) and the same top-level version.
Runtime enrollment metadata still uses plugin_version
for the installed plugin's version and package_id for the source plugin ID.
Runtime plugin_id matches the literal ID in the native plugin manifest.
build: validate the hub and run a build without committing generated files.check: validate the hub and fail if committed generated output is stale.publish: build generated output fromsource-branchand atomically update the release branch together with the source version and marketplace pointers.
Customer hubs should usually use build for pull requests and publish after
changes merge to the default branch. Use check only for repositories that
intentionally commit generated artifacts on the same branch as source assets.
Cursor source-branch pointers use the existing type: github descriptor for
github.com. Other hosts, including GitLab, use a git-subdir source with the
repository URL, plugin path, and release branch. This preserves GitLab subgroup
paths and supports hubs inside a repository subdirectory. Release-branch Cursor
marketplaces continue to use relative plugin paths.
GitLab CI adapters should map GITHUB_SERVER_URL to CI_SERVER_URL and
GITHUB_REPOSITORY to CI_PROJECT_PATH. INPUT_UPDATE_CURSOR_POINTER enables
the GitLab pointer writer. Keep INPUT_GITHUB_TOKEN unset and use GitLab checkout
credentials for publishing.
Cursor advertises GitLab imports through its team marketplaces. Manifest generation does not verify that import path: Cursor needs its own repository access, and some desktop importers still reject GitLab URLs. Verify installation and refresh through the team's GitLab-capable importer before relying on Cursor delivery.
Every hub must keep the canonical pig plugin in stable_plugins. pig init
scaffolds that plugin as the home for scanned shared instructions and the
optional Promptless-managed lifecycle integration. Other customer instruction plugins
do not receive managed hooks or runtime files.
Instruction Hub source config lives at hub.yaml in the hub root. Build-generated
release metadata is also flat at the hub root:
hub.release.json: current release manifest.hub.stable.json: stable channel pointer.
Scan-generated metadata is committed as a root file:
hub.repo-context.json: scanned repository-context inventory.
Native Cursor rules can be authored as assets/rules/<id>.mdc with a matching
<id>.asset.yaml sidecar declaring support.cursor.mode: native. The compiler
preserves existing rule frontmatter, including description, globs, and
alwaysApply. Rules without frontmatter receive the generated description and
alwaysApply: false defaults.
Every generated plugin embeds local metadata as root files inside each plugin:
hub.release.json: plugin-local release/status manifest.hub.managed-runtimes.json: Promptless-managed runtime metadata for plugins that include managed-runtime artifacts.
Keep an explicitly invoked workflow as command:<id> in a plugin's includes.
Author it in assets/commands/<id>.md:
---
description: Rebase the current PR onto main and report the result.
---
Fetch the latest main, rebase the current PR branch, and resolve conflicts.The compiler preserves the asset ID and the complete procedure. It chooses the target's format and registers exactly one entry:
| Target | Generated file | Invocation control |
|---|---|---|
| Codex | skills/<id>/SKILL.md |
agents/openai.yaml sets policy.allow_implicit_invocation: false |
| Claude | skills/<id>/SKILL.md |
Frontmatter sets disable-model-invocation: true |
| Cursor | skills/<id>/SKILL.md |
Frontmatter sets disable-model-invocation: true |
| Gemini | commands/<id>.toml |
Native slash command |
For example, Codex gets rebase-pr, with its normal plugin namespace, instead of
the source-command-rebase-pr name produced by Codex's legacy command importer.
No additional Markdown command is emitted alongside a converted skill.
Release metadata still records command:rebase-pr as the source asset.
Commands without a support table default to native delivery on all hub targets.
An explicit table is an allowlist: omitted targets remain unsupported. Existing
mode: native command declarations now select conversion; explicit
mode: unsupported exclusions remain effective. For example:
# assets/commands/rebase-pr.asset.yaml
support:
claude:
mode: native
codex:
mode: native
cursor:
mode: native
gemini:
mode: unsupported
reason: Not enabled for this workflow.Portable command frontmatter requires a nonempty description of at most 1,024
characters without angle-bracket invocation syntax. Optional name must match
the asset ID. Names are limited to 64 characters; Codex's combined
plugin-id:command-id must also fit that limit. Optional
disable-model-invocation and user-invocable must be true: commands always
require explicit invocation. Use a skill asset for automatic selection.
Claude conversion also preserves argument-hint, $ARGUMENTS, $ARGUMENTS[N],
and $N. Other targets reject these fields and substitutions. Portable workflows
can instead describe how to use the context supplied with the invocation. The
compiler rejects unsupported frontmatter, including allowed-tools, model,
and context, and host-specific dynamic context syntax such as shell injection,
Gemini {{args}} / @{...}, and Claude path variables. It does not silently drop
permissions, tool restrictions, subagent behavior, or template expansion.
For a command that needs host-specific features, set that target to
mode: verbatim. This copies a single native .md file for Claude/Cursor or a
native .toml file for Gemini, including its metadata and substitutions. Gemini
TOML must contain a nonempty prompt string. Codex has no native command format
in this contract; author a Codex skill asset for features outside conversion.
Directory command bundles are not supported by conversion or verbatim delivery.
The existing projected mode remains an inert Markdown projection, not a
registered, executable command.
Validation rejects command/skill invocation collisions within each plugin,
including converted agents and compiler-managed skills. Before adopting this
compiler, remove redundant command wrappers whose names already belong to a
skill, or give the workflows distinct names. Native files that previously used
mode: native and require unsupported syntax must switch to mode: verbatim.
After publishing, refresh the installed plugin and reload the harness to replace
cached command imports.
These adapters follow the documented invocation controls for Codex skills, Claude skills, Cursor skills, and Gemini custom commands. Artifact checks validate file formats, names, registration, and invocation settings; installation, command-picker behavior, and refresh still need testing in each supported harness version.
An agent can ship as a native Claude subagent and a Codex skill from the same
Markdown source. Set its adjacent <id>.asset.yaml file to:
support:
claude:
mode: native
codex:
mode: agent-skillKeep the agent:<id> reference in the plugin's includes list. The compiler
writes skills/<id>/SKILL.md into the Codex plugin and registers it as a skill.
Release metadata retains the source agent identity. Conversion is opt-in,
Codex-only, and accepts .md files; directory-based agents are not converted.
The generated skill contains the full authored procedure and instructions to delegate it to one child. An assigned specialist executes the procedure directly without delegating the same role again. The parent relays clarification questions and answers, then returns the result. If subagent tools are unavailable, the instructions require stopping. The model must follow these instructions; Codex has no documented skill frontmatter that enforces subagent execution. See the Codex skill documentation.
Source frontmatter must have a nonempty description of at most 1,024 characters.
Use a shared portable description and put invocation examples in the body. The
generated skill name is the asset ID. Optional name, model, and color fields
are accepted; the generated skill omits model and color settings and tells the
parent to launch the child without model or reasoning overrides.
tools and disallowedTools accept comma-separated strings or YAML lists.
They become advisory instructions with their original tool names; the compiler
does not map names between hosts. An empty tools list means the specialist
must not use tools. pig validate, pig verify, and pig build print one warning
per included converted agent that declares either field. Codex does not enforce
these restrictions through skill metadata. Write required behavior in the shared
procedure without claiming that a host enforces it.
Conversion rejects other frontmatter fields, invalid descriptions, and skill destination collisions, including names reserved for compiler-managed skills. The compiler does not truncate descriptions or rewrite procedures.
The old .promptless/instruction-hub.yaml and generated .promptless/...
layout is not read or migrated by this toolchain. Existing hubs must rename
their config to hub.yaml and regenerate output with pig build.
Hubs follow the latest merged toolchain on main. GitHub callers use @main;
GitLab callers use the /main/ template URL and the default toolchain-ref: main.
Resolved commit hashes in CI logs identify the compiler used for a build.
Releases containing external plugins use manifest schema 3 and record their
provenance in version_basis.plugins; authored-only releases use schema 2.
The publisher accepts both. Upgrade older toolchains before consuming schema 3
releases.
The publisher stores verified upstream versions in release-side
hub.external.json, bound to the release hash and exact source declarations.
Version comparisons use this record even if the old repository is unavailable.
Releases without this record require fetching the old pin.
Register hook:<id> in a plugin's includes. Keep its Python script and one
asset.yaml declaration in assets/hooks/<id>/. For example:
support:
claude: {mode: native}
codex: {mode: native}
cursor: {mode: native}
hook:
entrypoint: run.py
timeout: 75
status_message: Checking credentials
bindings:
claude:
- {event: PostToolUseFailure, matcher: Bash}
codex:
- {event: PostToolUse, matcher: Bash}
cursor:
- {event: postToolUseFailure, matcher: Shell}The compiler generates the native JSON, plugin-root paths, Python 3.9+ prerequisite
check, and a shared input/output adapter. No installed toolchain package is needed
on the user's machine. Generated launchers use a POSIX shell. Entrypoints must be existing .py files inside the bundle;
unknown fields, invalid bindings, and conflicting definitions fail validation.
Changing the declaration changes the asset's content hash and release identity.
Bindings deliberately name native events: the compiler does not guess equivalent
triggers across harnesses. The portable adapter supports Claude's SessionStart,
PostToolUse, and PostToolUseFailure; Codex's SessionStart and PostToolUse;
and Cursor's sessionStart, postToolUse, and postToolUseFailure.
Bindings must cover exactly the targets marked native for this asset.
Scripts read one JSON object on stdin:
{
"event": "tool_result",
"cwd": "/workspace/repo",
"shell": {
"command": "python helper.py",
"output": "command output",
"exit_code": 1,
"status": "failed"
}
}event is session_start or tool_result. shell is null for startup and
non-shell tools. Its status is success, failed, unknown, running,
cancelled, timeout, or permission_denied. A missing exit code stays null;
in particular, Codex can emit raw output with an unknown status. Hooks must not
assume that unknown means failed. Cursor uses its reported cwd, falling back to
its first workspace root. A missing working directory remains null.
Emit {"context": "Message for the agent"} on stdout, or leave stdout empty to
stay quiet. Use stderr for diagnostics. The adapter wraps context in the native
response envelope and preserves a nonzero script exit. Scripts execute in the
adapter's process, so cancellation signals reach their handlers directly. The
adapter does not retry commands, start background workers, or infer recovery
policy; those decisions belong to the hook script.
For other events, runtimes, or host features, keep hooks.<target>.json and shared
scripts in the bundle instead of declaring hook: in asset.yaml. A shared
hooks.json is used when the target-specific file is absent. Mixing a portable
declaration with native JSON in one bundle is rejected; separate bundles can mix
freely within a plugin. Existing single-file JSON hooks and other legacy native
files remain supported.
The compiler copies bundles to hooks/<id>/ and combines selected configurations
at hooks/hooks.json, appending handlers in asset-reference order, joining
descriptions, and rejecting conflicting top-level metadata. Native JSON must
contain a hooks object whose event values are arrays of handler objects. PIG's
managed lifecycle hooks are appended afterward when ingestion is enabled; other
plugins receive only their own hooks.
The shared PR workflow supports an optional test job. Configure it in the hub's
existing workflow alongside hub-root; keep the tests with their source assets:
with:
hub-root: .
test-command: python -m unittest discover -s tests -v
test-runs-on: macos-latest
test-python-version: "3.9"With no test-command, no test job runs. The default runner is ubuntu-latest
and the default Python version is 3.9. The toolchain owns checkout, interpreter
setup, execution, and failure propagation. The hub owns the command and test
requirements; add its test paths to the workflow's PR filters. Test commands are
trusted repository code, run without persisted checkout credentials.
The toolchain injects the harness-specific update-instruction-hub
skill into the canonical pig plugin for Codex and Claude. Each generated copy
is scoped to its hub's generated marketplace name, so customer-specific names,
repository URLs, and checkout locations are not hardcoded. Each hub's generated
PIG plugin receives its own scoped copy. Codex uses its marketplace upgrade and
skill refresh host operations, stopping if those current-session actions are
unavailable; Claude updates each installed plugin at its original scope and uses
/reload-plugins to apply the changes without restarting.
Instruction Hubs can publish and install instructions without an ingestion worker.
New hubs created by pig init explicitly disable managed trace ingestion in hub.yaml:
trace_ingestion:
enabled: falseWith ingestion disabled, the toolchain emits no managed enrollment hooks, runtime bundle, or managed-runtime metadata. Authored skills, agents, rules, commands, hooks, MCP configuration, and the Claude/Codex update skill remain available. Verification and publishing do not require worker credentials or worker access.
Omitting trace_ingestion or enabled also disables ingestion. Existing hubs
that use ingestion must explicitly set enabled: true. Enabling ingestion
bundles the existing Claude/Codex host runtime; it does not
provision a worker. Cursor and Gemini do not receive that managed runtime.
After changing this setting, publish the hub and refresh its installed plugins. Disabling it removes managed hooks from the new release; an older installed plugin keeps its hooks until refreshed. It does not delete previously ingested data or change a worker deployment.
When trace_ingestion.enabled is true, the toolchain owns Promptless-managed runtime artifacts that are injected into
the canonical pig plugin, including the host runtime used by Codex and
Claude lifecycle hooks. Other generated plugins receive no toolchain-managed
runtime or lifecycle hooks. During dogfood, generated Codex hooks wrap the bundled
stdlib-only Python runtime with POSIX shell checks. The stable executable in
runtime/ delegates to private sibling modules that separate CLI dispatch,
enrollment, trace collection, host configuration, persistence, and output.
Generated Claude hooks use Claude Code's exec-form hook so Windows installs do
not need a POSIX shell; Node must be available to start the inline launcher.
Every SessionStart launcher starts one detached supervisor that inherits the
hook input and redirects background output away from the agent transcript. The
Claude supervisor collects both Claude Code and any detected Claude Desktop
sources.
Startup launchers emit schema-safe diagnostics when the host cannot resolve the
plugin root, a readable managed runtime bundle (the launcher plus its sibling
package and CLI entry module), or Python 3.9+. Terminal lifecycle launchers stay
quiet: they resolve a complete runtime bundle under the plugin root, fall back
to a complete sibling installed version with the same runtime-bundle layout for
the same plugin id when the recorded root is stale or incomplete, and exit 0
with no output when no usable bundle exists.
sh -c 'root=${PLUGIN_ROOT:-}; ...; find python3/python/py; run promptless-host-runtime session-start --host codex --detach'
sh -c 'root=${PLUGIN_ROOT:-}; ...; find same-plugin sibling runtime if needed; run promptless-host-runtime collect --host codex --lifecycle stop --detach --quiet'
sh -c 'root=${PLUGIN_ROOT:-}; ...; find same-plugin sibling runtime if needed; run promptless-host-runtime collect --host codex --lifecycle session_end --detach --quiet'
node -e '... resolve ${CLAUDE_PLUGIN_ROOT}; find Python 3.9+; run promptless-host-runtime session-start --host claude --detach' '${CLAUDE_PLUGIN_ROOT}'
node -e '... resolve ${CLAUDE_PLUGIN_ROOT}; find same-plugin sibling runtime if needed; run promptless-host-runtime collect --host claude --lifecycle session_end --detach --quiet' '${CLAUDE_PLUGIN_ROOT}'The dogfood host runtime uses PROMPTLESS_WORKER_BASE_URL or the default
production worker. It reads the worker's public /healthz identity, opens the
hosted Promptless dashboard start URL, and listens on a loopback callback with a
per-attempt state token for the approved session proof. It then polls the hosted
runtime for a one-time per-host credential, caches that credential, and uses the
host credential to fetch /v0/host-enrollment/policy?target=... and post
/v0/host-enrollment/check-ins.
SessionStart never waits for browser approval, worker requests, trace discovery,
or the upload ledger. It launches one detached supervisor, emits and claims any
already-pending plugin-update, first-enrollment, and internal-user notices using
local state only, and returns. The supervisor runs enrollment and reconciliation
before collecting Claude Code and Claude Desktop sequentially for Claude, or the
single native source family for other hosts. On Linux, enrollment does not invoke a browser
when DISPLAY, WAYLAND_DISPLAY, MIR_SOCKET, and WSL_INTEROP are all
absent; set PROMPTLESS_HOST_ENROLLMENT_OPEN_BROWSER=1 to force a browser
attempt or 0 to disable one explicitly. Detached enrollment outcomes remain
available in ~/.promptless/instruction-hub/last-bootstrap-status.json and the
bounded host-runtime-diagnostics.jsonl log.
The worker's per-source watermark is authoritative after an ambiguous upload. When a committed response is lost and the local file grows before retry, the worker returns its watermark with a digest for the committed source range. The runtime verifies that digest against the current local bytes, advances only to an interior worker watermark, and rebuilds the remaining upload in the same hook run. The local ledger also records a digest for every acknowledged prefix so replacement or rotation at the same path cannot silently mix two source generations. It does not reconcile gaps, rewinds, changed source identities, or conflicts for another range. Upload requests contain one source chunk because the worker commits one chunk per transaction; this keeps the request-level acknowledgement at the same atomic boundary.
The runtime uploads native host transcript JSONL ranges to
/v0/traces/batches?target=.... Claude Code, Codex, and Claude Desktop share one
uploader and forward-only ledger. The ledger lives at
~/.promptless/instruction-hub/host-runtime-ledger.json or
PROMPTLESS_HOST_RUNTIME_LEDGER when set. Uploads use the host credential and
are gated by the enabled_hosts policy. Codex idle discovery scans only
CODEX_HOME/sessions/**/*.jsonl and
CODEX_HOME/archived_sessions/**/*.jsonl. Hook-provided current transcript
paths remain eligible outside those roots.
SessionStart hooks launch one quiet ensure-then-collection supervisor. They
include active files so pre-existing history is uploaded from byte zero when a
source has no acknowledged offset. Terminal lifecycle hooks (Stop,
SessionEnd, and SubagentStop) run collection only. Hook input accepts
snake_case, camelCase, and nested
session/transcript/agent transcript references from Codex- and
Claude-style hooks. Claude Desktop has no hook-provided current transcript and
starts with idle catch-up.
Hook timeouts cover the launcher, while collection runs in a detached process.
Terminal hooks use a shared 3-second launcher budget on every host, which also
fits Codex's SessionEnd maximum. Startup hooks use 30 seconds.
A collection follows this order:
upload at most one pending current-transcript request
-> start a fresh 25-second catch-up deadline
-> upload remaining current-transcript ranges
-> scan and upload idle transcripts
The first pending current-transcript request receives its own fixed 25-second
deadline before the catch-up clock starts. Contention or exhausting that budget
reports trace_upload_partial for a later hook to resume. Remaining current-
transcript work, idle discovery, and idle uploads share the fresh catch-up
deadline, configurable with
PROMPTLESS_HOST_RUNTIME_COLLECT_DEADLINE_SECONDS.
Each request is one ledger transaction:
lock -> reload ledger -> select request -> post -> validate acknowledgement
-> persist acknowledged offsets -> unlock
Policy reads and transcript-root scans run without the ledger lock. The ledger
advances only after the worker acknowledges the exact source ranges and content
hashes. Releasing and reloading the ledger between requests preserves progress
from other collectors. When the catch-up deadline expires, collection
reports trace_upload_partial and resumes from the acknowledged offsets on a
later hook.
Source ranges target 4 MiB and end on complete-record boundaries. Serialized
requests target 6 MiB and never exceed 10 MiB; sizing includes chunks and request
metadata. Each batch carries the currently installed plugin_version, which is
treated as the version associated with every byte in that batch.
An unseen source starts at byte zero. A known source resumes at its last worker-acknowledged offset, including offsets written by earlier runtime versions. Plugin updates immediately use the new collection code, but do not rewind those existing offsets; intentionally skipped prefixes therefore remain grandfathered unless the ledger is reset. Obsolete baseline and release-marker fields are discarded when an older ledger is next rewritten.
Collection runs detached from the hook process group. Quiet collection writes
no status JSON to hook stdout. A source that vanishes or loses read permission
mid-collect is recorded as drift and surfaced through unreadable_source_count;
it does not block later sources. Support diagnostics are bounded, redacted JSONL
at ~/.promptless/instruction-hub/host-runtime-diagnostics.jsonl with 0600
permissions and no transcript content, tool inputs, or credentials. Detached
launch and nonzero-exit failures are also recorded in the structured
last-bootstrap-status.json support status.
Host enrollment is per host, not per installed pig version. The credential
and pending approval are cached at a single host-global path
(~/.promptless/instruction-hub/) and keyed only on the worker deployment and
agent host (claude/codex). A non-blocking, per-credential enrollment-leader lock
ensures that overlapping host starts or plugin upgrades drive at most one browser
approval while the others reuse the result or defer to a later session. The
per-plugin CLAUDE_PLUGIN_DATA/PLUGIN_DATA directories are intentionally not
used for this state.
Native JSONL ledgers are the only telemetry source: the runtime writes no OTel
exporter config for either host. Hosts configured by earlier managed bootstraps
have that config removed on the next ensure run — the managed [otel] block
in Codex config.toml and the marker-owned OTEL_*/telemetry env keys in
Claude settings.json are deleted (with a timestamped backup), while unmanaged
user config is never touched. The hosted policy's legacy collector section is
ignored.
The host runtime has one executable entrypoint with subcommands. session-start
detaches one ensure-then-collection supervisor. ensure enrolls when needed,
removes legacy managed telemetry config, and posts a check-in. collect is the
native JSONL upload path; hooks pass --detach so the runtime supervises
collection outside the hook process group. Pass --include-active for a
user-initiated sweep that includes files still inside the idle grace period.
enroll acquires only
the host credential. status prints local JSON without network,
browser, config writes, or check-ins. reset --yes clears cached host
credentials and pending enrollments while preserving the stable host id,
last-seen plugin versions, and one internal welcome marker per installed
marketplace version. version reports runtime metadata.
Before the customer-grade release, replace the dogfood Python implementation
with a static native binary built and versioned by Promptless, then bundled into
the toolchain release. Customer Instruction Hub repositories should not need
Python, Node, uv, Go, Rust, curl, jq, or other runtime/build dependencies installed
for the hook to run. Customer builds should only consume the already-built
Promptless artifact bundle that the toolchain copies into plugin runtime/.
The dogfood runtime trusts the authenticated TLS worker response and validates only the hosted policy shape. The customer-grade static binary must verify an asymmetric hosted-policy signature with a pinned Promptless public key before it edits local host config.