c2j
c2j is the local job-oriented CLI for submitting and running recipe jobs through an SWF runtime.
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.
Command Summary
c2j self
c2j cells
c2j init
c2j submit
c2j run
c2j run one
c2j run any
c2j run loop
c2j ready
c2j list
c2j test
Use go run ./cmd/c2j --help or c2j --help to see the full command tree.
Quick Start
1. Check current-cell resolution
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 self
List allowed dependent cells:
c2j cells
Generate a starter config if needed:
c2j init --stdout
If the current directory does not resolve as a cell, either:
- create
.c2j/config.yaml, or
- pass
--cell <repo-or-path> explicitly to submit or list
2. Submit and run a local recipe file
For local authoring, this is the default loop:
c2j submit \
--recipe-file ./recipes/my-recipe.yaml \
--run \
--embed
That does all of the following:
- loads the local YAML file
- embeds that recipe into the submitted job
- starts an embedded SWF runtime
- submits the job
- immediately executes it
3. Continue or inspect a job later
Find recent jobs for the current cell:
c2j list --self --embed
Continue a submitted job:
c2j run --job-id <job-id> --embed
Current Cell Commands
c2j self
Shows how c2j resolves the current cell from .c2j/config.yaml or supported auto-detection.
c2j self
c2j self --json
Fields include:
short_name
repo
ref
root_repo
root_ref
pattern
c2j cells
Lists dependent cells allowed by the current config.
c2j cells
c2j cells --json
This is mainly useful when you want to target another cell by short name and need to verify how config expands it.
c2j init
Writes a commented .c2j/config.yaml template.
c2j init
c2j init --stdout
c2j init --force
The generated template can derive values from the Go module in the current repo when base: go is appropriate.
Submitting Jobs
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 --embed
Submit and run immediately:
c2j submit --recipe-file ./recipes/my-recipe.yaml --run --embed
By 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 \
--embed
Inputs file in JSON or YAML:
c2j submit \
--recipe-file ./recipes/my-recipe.yaml \
--inputs-file ./recipes/test-inputs.yaml \
--run \
--embed
Positional prompt shortcut:
c2j submit "Summarize the repo" --recipe my-prompt-recipe --embed
The positional argument is merged as inputs.prompt.
Rules:
--inputs-json and --inputs-file are mutually exclusive
- the positional prompt cannot also be provided as
inputs.prompt
Attaching files
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"'
Choosing the target cell
Use the current cell:
c2j submit --recipe-file ./recipes/my-recipe.yaml --self --embed
Use 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.yaml defines a pattern
Rules:
--self and --cell are mutually exclusive
- if no
--cell is given, c2j behaves as if you targeted --self
Getting machine-readable output
If you only want the submitted job identity:
c2j submit \
--recipe-file ./recipes/my-recipe.yaml \
--json \
--embed
This emits:
{
"tenant_id": "0",
"job_id": "job-...",
"recipe": "my_recipe_id"
}
Note:
--json and --run are mutually exclusive
Testing Recipes
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.json
Run a local suite:
c2j test run \
--recipe-file ./recipes/my-recipe.yaml \
--file ./recipes/my-recipe.test.yaml \
--artifact-mode inline
Run one case:
c2j test case run \
--recipe-file ./recipes/my-recipe.yaml \
--file ./recipes/my-recipe.test.yaml \
--case-id smoke
Useful 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 root automatically; use
--runtime-root and --keep-runtime only for debugging
Running Jobs
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> --embed
Common 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 --embed
Important 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:
prompt
Prompt on stdin/stdout and submit the response
ops
Emit machine-readable input_required JSON and exit non-zero
fail
Exit immediately when input is required
--ci enables machine-readable input-required behavior without prompting.
Not-ready handling
--on-not-ready controls how run reacts when a job is not runnable yet:
wait
fail
fail-on-lease
fail-on-pending-jobs
fail-on-future
fail-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.
Exit codes
c2j run uses distinct exit codes:
1: general failure or job failure
2: wait timeout
3: input required
4: job not runnable under the selected policy
5: invalid job identity or invalid run arguments
Worker Modes
c2j run loop runs a long-lived worker for one tenant. It leases available jobs
from an external SWF runtime and executes up to the configured local concurrency.
Basic usage:
c2j run loop --tenant-id <tenant-id> --swf-url http://localhost:9047 --concurrency 4
Important behavior:
--concurrency <n> controls how many jobs this process can run at once
--tenant-id defaults the same way as submit, run, and list
--swf-url must be an external http:// or https:// runtime
--embed is not available for run loop
--swf-url embed:/// is rejected for run loop
run loop is non-interactive; use run or an ops surface for jobs that need input
Loose scheduling with ready and run any
c2j ready prints the number of currently ready recipe jobs for one tenant:
c2j ready --tenant-id <tenant-id> --swf-url http://localhost:9047
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 --tenant-id <tenant-id> --swf-url http://localhost:9047
These can be composed by an external scheduler:
count=$(c2j ready --tenant-id <tenant-id> --swf-url http://localhost:9047)
if [ "$count" -gt 0 ]; then
for _ in $(seq 1 "$count"); do
c2j run any --tenant-id <tenant-id> --swf-url http://localhost:9047 &
done
wait
fi
ready is only a non-mutating snapshot and can become stale under competing
workers. run any uses SWF polling so finding available work and acquiring the
lease happen in one runtime operation.
Listing Jobs
List jobs for the current cell:
c2j list --self --embed
List as JSON:
c2j list --self --json --embed
Filter by status:
c2j list --self --status pending_jobs --status active --embed
List jobs for another cell:
c2j list --cell github.com/colony-2/root --embed
Useful filters:
--job-id
--job-type
--status
--waiting-for
--created-after
--created-before
--page-size
--page-token
--all
Embedded Runtime
--embed is shorthand for:
--swf-url embed:///
Use it when you want a local self-contained runtime instead of an external SWF server.
Behavior:
- starts embedded Postgres and Strata as needed
- uses a persistent runtime root on disk
- works well for local recipe authoring and debugging
Defaults:
- runtime URL:
embed:///
- runtime root:
~/.c2j/embed/default
You can override the root with:
export C2J_EMBED_ROOT=/absolute/path/to/embed-root
Notes:
C2J_EMBED_ROOT must be an absolute path
- only one
c2j process can own a given embedded runtime root at a time
- if you need parallel embedded runtimes, give each process a different
C2J_EMBED_ROOT
Runtime and Tenant Defaults
c2j reads these environment variables:
C2J_SWF_URL
C2J_TENANT_ID
C2J_EMBED_ROOT
Built-in defaults:
C2J_SWF_URL: http://localhost:9047
C2J_TENANT_ID: 0
Examples:
export C2J_SWF_URL=http://localhost:9047
export C2J_TENANT_ID=123
When --embed is present, it overrides C2J_SWF_URL for that command.
c2j run loop is the exception: it does not support --embed and rejects
C2J_SWF_URL=embed:///.
Common Workflows
Local recipe authoring loop
c2j self
c2j submit --recipe-file ./recipes/my-recipe.yaml --run --embed
Detached submit, then later run
c2j submit --recipe-file ./recipes/my-recipe.yaml --json --embed
c2j run --job-id <job-id> --embed
Run against a remote runtime instead of embed
c2j submit \
--recipe-file ./recipes/my-recipe.yaml \
--swf-url http://localhost:9047 \
--run
Target another cell explicitly
c2j submit \
--recipe-file ./recipes/my-recipe.yaml \
--cell github.com/colony-2/root \
--run \
--embed
Gotchas
--recipe and --recipe-file are mutually exclusive
--json and --run are mutually exclusive on submit
--inputs-json and --inputs-file are mutually exclusive
--self and --cell are mutually exclusive
--artifact names must be unique relative paths; directories are not supported yet
self, 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-file is clearer than passing a local file path through --recipe