c2j is the local job-oriented CLI for submitting and running recipe jobs through JobDB.
Use it when you want to:
- submit a recipe job from a named recipe or a local recipe file
- run or continue an existing job
- count and claim available jobs for external scheduling
- run a tenant worker loop with bounded local concurrency
- use the embedded local runtime for fast iteration
- inspect the current cell configuration used for job targeting
- list jobs for a cell
Examples below assume you are running from the repo root.
c2j self
c2j cells
c2j init
c2j version
c2j submit
c2j run
c2j run one
c2j run any
c2j run loop
c2j ready
c2j list
c2j testUse go run ./cmd/c2j --help or c2j --help to see the full command tree.
Use the version subcommand to identify the exact executable you are running:
c2j versionRelease builds report the release version injected by the release pipeline. Local
go build, go run, and go install builds fall back to Go's embedded VCS
metadata, so untagged builds include the current git revision when it is
available. Dirty suffixes are only included when Go reports that the worktree was
modified at build time.
submit targets the current cell by default. That usually comes from .c2j/config.yaml, but supported project types can also be auto-detected.
Inspect the resolved config:
c2j selfList allowed dependent cells:
c2j cellsGenerate a starter config if needed:
c2j init --stdoutIf the current directory does not resolve as a cell, either:
- create
.c2j/config.yaml, or - pass
--cell <repo-or-path>explicitly tosubmitorlist
For local authoring, this is the default loop:
c2j submit \
--recipe-file ./recipes/my-recipe.yaml \
--run \
--embedThat does all of the following:
- loads the local YAML file
- embeds that recipe into the submitted job
- starts an embedded JobDB runtime
- submits the job
- immediately executes it
Find recent jobs for the current cell:
c2j list --self --embedContinue a submitted job:
c2j run --job-id <job-id> --embedShows how c2j resolves the current cell from .c2j/config.yaml or supported auto-detection.
c2j self
c2j self --jsonFields include:
short_namereporefroot_reporoot_refpattern
Lists dependent cells allowed by the current config.
c2j cells
c2j cells --jsonThis is mainly useful when you want to target another cell by short name and need to verify how config expands it.
Writes a commented .c2j/config.yaml template.
c2j init
c2j init --stdout
c2j init --forceThe generated template can derive values from the Go module in the current repo when base: go is appropriate.
Submit a named recipe:
c2j submit --recipe default --embed--recipe accepts a recipe name or git selector. For local files, prefer --recipe-file.
Submit a local recipe file:
c2j submit --recipe-file ./recipes/my-recipe.yaml --embedSubmit and run immediately:
c2j submit --recipe-file ./recipes/my-recipe.yaml --run --embedBy default, if neither --recipe nor --recipe-file is set, c2j submits the recipe named default.
Inline JSON:
c2j submit \
--recipe-file ./recipes/my-recipe.yaml \
--inputs-json '{"message":"hello"}' \
--run \
--embedInputs file in JSON or YAML:
c2j submit \
--recipe-file ./recipes/my-recipe.yaml \
--inputs-file ./recipes/test-inputs.yaml \
--run \
--embedPositional prompt shortcut:
c2j submit "Summarize the repo" --recipe my-prompt-recipe --embedThe positional argument is merged as inputs.prompt.
Rules:
--inputs-jsonand--inputs-fileare mutually exclusive- the positional prompt cannot also be provided as
inputs.prompt
Attach local files as job artifacts with repeatable --artifact flags:
c2j submit \
--recipe-file ./recipes/review-docs.yaml \
--artifact ./docs/brief.md \
--artifact requirements=./docs/requirements.md \
--run \
--embed--artifact <path> uses the file basename as the artifact name.
--artifact <name>=<path> sets an explicit artifact name.
Recipes bind submitted artifacts into an op inbox explicitly:
sequence:
- id: inspect
op: command_execution
artifacts:
brief.md: '${{ context.artifacts["brief.md"] }}'
inputs:
run: 'cat "${{ context.environment.op.inbox }}/brief.md"'Use the current cell:
c2j submit --recipe-file ./recipes/my-recipe.yaml --self --embedUse another cell explicitly:
c2j submit \
--recipe-file ./recipes/my-recipe.yaml \
--cell github.com/colony-2/root \
--embed--cell accepts:
- a canonical repo string
- a clone URL
- a local repository path
- a configured short name when
.c2j/config.yamldefines a pattern
Rules:
--selfand--cellare mutually exclusive- if no
--cellis given,c2jbehaves as if you targeted--self
If you only want the submitted job identity:
c2j submit \
--recipe-file ./recipes/my-recipe.yaml \
--json \
--embedThis emits:
{
"tenant_id": "0",
"job_id": "job-...",
"recipe": "my_recipe_id"
}Note:
--jsonand--runare mutually exclusive
c2j test compiles, validates, and runs recipe test suites locally. It does not call the old Colony2 API.
Compile a suite to canonical IR:
c2j test compile \
--recipe-file ./recipes/my-recipe.yaml \
--file ./recipes/my-recipe.test.yaml \
--out ./tmp/compiled-test.jsonRun a local suite:
c2j test run \
--recipe-file ./recipes/my-recipe.yaml \
--file ./recipes/my-recipe.test.yaml \
--artifact-mode inlineRun one case:
c2j test case run \
--recipe-file ./recipes/my-recipe.yaml \
--file ./recipes/my-recipe.test.yaml \
--case-id smokeUseful flags:
--recipe <name-or-git-selector>targets a current-cell recipe or explicit git selector--recipe-file <path>uses a local inline recipe file--case <id>filters suite mode to selected cases--parallelism <n>controls local case concurrency--out-dir <dir>defaults to.c2j/test-results/<timestamp>/- passthrough cases use a disposable embedded runtime automatically
c2j run executes or continues one existing job and prints live story progress
to stdout. c2j run one is the explicit form of the same command.
Basic usage:
c2j run --job-id <job-id> --embed
c2j run one --job-id <job-id> --embedCommon variants:
c2j run --job-id <job-id> --wait-timeout 30m --embed
c2j run --job-id <job-id> --input-mode fail --embed
c2j run --job-id <job-id> --ci --embedImportant behavior:
- completed jobs return successfully
- failed jobs return a non-zero exit code
- suspended jobs may wait, prompt, or fail depending on flags
- when input is pending, interactive terminals default to prompting
- in CI or non-terminal mode, input handling defaults to
ops
--input-mode controls what happens when a job is blocked on user input:
promptPrompt on stdin/stdout and submit the responseopsEmit machine-readableinput_requiredJSON and exit non-zerofailExit immediately when input is required
--ci enables machine-readable input-required behavior without prompting.
--on-not-ready controls how run reacts when a job is not runnable yet:
waitfailfail-on-leasefail-on-pending-jobsfail-on-futurefail-on-missing-capability
With the default wait, run will print waiting: ... lines and poll until
the job becomes runnable or the wait timeout is reached.
c2j run uses distinct exit codes:
1: general failure or job failure2: wait timeout3: input required4: job not runnable under the selected policy5: invalid job identity or invalid run arguments
c2j run loop runs a long-lived worker for one tenant. It leases available jobs
from a remote JobDB runtime and executes up to the configured local concurrency.
Basic usage:
c2j run loop --jobdb http://localhost:9047/<tenant-id> --concurrency 4Important behavior:
--concurrency <n>controls how many jobs this process can run at once--jobdbmust be a remotehttp://host/tenantorhttps://host/tenantURI--embedis not available forrun loop--jobdb embed:///is rejected forrun looprun loopis non-interactive; userunor an ops surface for jobs that need input
c2j ready prints the number of currently ready recipe jobs for one tenant:
c2j ready --jobdb http://localhost:9047/<tenant-id>c2j run any atomically polls for one available item of recipe work, leases it,
runs it, and exits. If no lease is available, it exits successfully after
printing no jobs found.
c2j run any --jobdb http://localhost:9047/<tenant-id>These can be composed by an external scheduler:
count=$(c2j ready --jobdb http://localhost:9047/<tenant-id>)
if [ "$count" -gt 0 ]; then
for _ in $(seq 1 "$count"); do
c2j run any --jobdb http://localhost:9047/<tenant-id> &
done
wait
fiready is only a non-mutating snapshot and can become stale under competing
workers. run any uses JobDB polling so finding available work and acquiring the
lease happen in one runtime operation.
List jobs for the current cell:
c2j list --self --embedList as JSON:
c2j list --self --json --embedFilter by status:
c2j list --self --status pending_jobs --status active --embedList jobs for another cell:
c2j list --cell github.com/colony-2/root --embedUseful filters:
--job-id--job-type--status--waiting-for--created-after--created-before--page-size--page-token--all
--embed is shorthand for:
--jobdb embed:///Use it when you want a local self-contained runtime instead of a remote JobDB server.
Behavior:
- starts embedded Postgres and Strata as needed
- uses a persistent runtime root on disk
- always uses tenant
0 - works well for local recipe authoring and debugging
Defaults:
- runtime URL:
embed:/// - runtime root:
~/.c2j/embed/default
Notes:
- only one
c2jprocess can own a given embedded runtime root at a time
c2j reads these environment variables:
C2J_JOBDB
JobDB URI forms:
https://jobdb.example.com/<tenant-id>http://localhost:9047/<tenant-id>embed:///
Examples:
export C2J_JOBDB=http://localhost:9047/my-tenant--jobdb overrides C2J_JOBDB for that command. Project config may also define:
jobdb: https://jobdb.example.com/my-tenantFor local embedded mode in project config:
jobdb: embed:///c2j self
c2j submit --recipe-file ./recipes/my-recipe.yaml --run --embedc2j submit --recipe-file ./recipes/my-recipe.yaml --json --embed
c2j run --job-id <job-id> --embedc2j submit \
--recipe-file ./recipes/my-recipe.yaml \
--jobdb http://localhost:9047/my-tenant \
--runc2j submit \
--recipe-file ./recipes/my-recipe.yaml \
--cell github.com/colony-2/root \
--run \
--embed--recipeand--recipe-fileare mutually exclusive--jsonand--runare mutually exclusive onsubmit--inputs-jsonand--inputs-fileare mutually exclusive--selfand--cellare mutually exclusive--artifactnames must be unique relative paths; directories are not supported yetself,cells, and implicit current-cell submission depend on config or supported auto-detection succeeding- short cell names require a config pattern; without config, use an explicit repo or path
--recipe-fileis clearer than passing a local file path through--recipe
- command entrypoint: main.go
- embedded runtime notes: embed-swf-mode-spec.md
- recipe authoring docs: RECIPE_AUTHORING_GUIDE.md