Scheduling Jobs
Jobs, FastAPI releases, and CodeRepository images are created from repository
workflow declarations. Put a direct .yaml or .yml child under
.mainsequence/workflows/, validate it with the CodeRepositoryBranch workflow
endpoint, commit it, and push the exact source commit. Django resolves the
resource and image identities and creates the deployment run. The SDK does not
create these objects by posting selected UIDs to collection endpoints.
Create a Job from repository source
Retrieve the current template from
GET /api/v1/code-repository-branches/{uid}/workflow-template/. Declare a
kind: job resource with its repository-relative execution_path, compute
settings, and optional schedule. Validate the proposed repository path and
content with
POST /api/v1/code-repository-branches/{uid}/validate-workflow/.
The backend owns the accepted version, fields, defaults, permissions, and
application semantics; use the current template rather than copying an old
example as a schema.
Commit the workflow file and push it with Git as usual:
git add .mainsequence/workflows/jobs.yaml
git commit -m "Update job workflow"
git push
Inspect the repository-event result and the Job or deployment history after the push. A successful Git push does not itself prove deployment success.
A workflow declaration owns its Job. On the next push of the branch head, these delete the Job:
- removing the declaration or its file;
- a
scopethat excludes the branch's Environment.
Renaming the file or the key deletes the Job and creates a new one. An invalid
file deletes nothing. The repository-event result lists deleted targets under
deletions.
Deploy on every push or on release tags
The platform deploys from Git pushes according to the repository's
.mainsequence/workflows/*.yaml. A declaration's tag_regex decides which
pushes deploy:
tag_regexomitted, ornull: every push to the branch deploys.tag_regexset to a regular expression: a tag that matches it deploys the commit it points at, whether that is the branch's latest commit or an older commit on the branch. Every-push targets ignore tags.
Automatic deployment and its tag_regex are set in the workflow file only.
Use the current workflow template for where they go in a declaration. A
ResourceRelease or Job update does not accept them, and the SDK models
expose automatic_deployment and automatic_redeployment_policy as read-only
fields.
The Main Sequence platform does not create or name tags. Versions and release
tags belong to the repository: raise the version in pyproject.toml (for
example with uv version --bump patch) in the commit you want to release, and
let the repository's own CI create the tag. The following GitHub Actions
workflow is an example of such repository code, not a platform requirement. On
every push to main it runs the tests and then tags the version that
pyproject.toml declares, once:
# Example: .github/workflows/release.yml in your repository
name: release
on:
push:
branches: [main]
concurrency:
group: release-main
cancel-in-progress: false
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: astral-sh/setup-uv@v6
- run: uv sync --locked
- run: uv run pytest
- name: Tag the version declared in pyproject.toml
run: |
TAG="v$(uv version --short)"
if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
echo "$TAG already exists; nothing to release"
exit 0
fi
git tag "$TAG" "$GITHUB_SHA"
git push origin "refs/tags/$TAG"
With this example, a tag_regex such as ^v[0-9]+\.[0-9]+\.[0-9]+$ deploys
the commits that CI tagged, and a push that does not raise the version deploys
nothing.
After a dependency change, run mainsequence code-repository sync to refresh
uv.lock, the local environment and requirements.txt, then commit and push
those files with the change. The command makes no Git commit, tag or push. A
Job runs the repository's files from /workspace with the dependencies that
the committed requirements.txt lists; see
Dependencies and the deployed image.
Schedules and arguments
Use an interval schedule for a regular period such as every hour, or a
five-field crontab expression for calendar timing. Define the schedule in the
repository workflow. scheduled_command_args is an ordered list of argv
entries copied into each future scheduler-created JobRun. Updating the saved
list changes only future runs; existing run snapshots stay unchanged.
Manual runs use only their own command_args and never inherit scheduled
arguments. Pass each argument separately:
mainsequence code-repository jobs run <JOB_UID> \
--arg=--start-date \
--arg=2026-09-08T16:43:00Z
mainsequence code-repository jobs run <JOB_UID> -- --family jobs
For an existing Job, jobs update <JOB_UID> --scheduled-arg=<VALUE> replaces
the saved list; --clear-scheduled-args clears it. The repository workflow may
reapply its declared value on a later event.
Inspect and run an existing Job
mainsequence code-repository jobs list
mainsequence code-repository jobs run <JOB_UID>
mainsequence code-repository jobs runs list <JOB_UID>
mainsequence code-repository jobs runs logs <JOB_RUN_UID> --max-wait-seconds 900
mainsequence code-repository images list
The Job always refers to an exact persisted image. Each JobRun freezes the image UID, digest, and commit selected at run creation, even if a later repository event promotes a new image for the Job.
The Python client supports the same existing-Job operations:
from mainsequence.client import Job, JobRun
jobs = Job.filter()
job = jobs[0]
run_payload = job.run_job(command_args=["--family", "jobs"])
job_runs = JobRun.filter(job__uid=job.uid)
logs = job_runs[0].get_logs()
usage = job_runs[0].get_resource_usage()
logs is an OwnerLogPage with opaque cursor pagination. usage contains
aggregate CPU, memory, and disk samples. Application code does not supply an
Organization Environment UID to read the run's application logs.
Caller context in a run
A run created by a request that carried caller context starts with two environment variables, in W3C header format:
BAGGAGE: the request'smainsequence.<application>.<name>keys;TRACEPARENT: the trace the run belongs to.
A scheduled run, or a run the platform starts on its own, has neither.
Importing mainsequence restores them as the process's OpenTelemetry context,
so Job code reads the context with OpenTelemetry's API:
import mainsequence # restores the run's TRACEPARENT and BAGGAGE
from opentelemetry import baggage
person = baggage.get_baggage("mainsequence.advisor.person")
if person is None:
... # no external user: do only what needs none
- The context is restored on the thread that imports the SDK. A thread you
start yourself begins without it, as with any OpenTelemetry context; run its
work with
contextvars.copy_context().runto keep it. - Calls the run makes through the SDK pass the context on, so the runs and
Agent Tasks it creates inherit it. A call the run makes with its run
credentials and no
baggageheader is recorded with the run's own context; a header, when sent, wins. - Log lines written through the SDK's logger carry the
mainsequence.keys asbaggage. job.run_job()called while a FastAPI release handles a request gives the run that request's context.
Missing context means no external user: a run without BAGGAGE fails closed
for anything that needs one.
Who can use a Job
A Job's access is decided by its policies:
viewer: see the Job, its schedule, runs, logs and outputs, including the Artifacts its runs published;runner: also run the Job and stop its runs;editor: also change it (name, description, resources, schedule, arguments, image), post a run's status and outputs by hand, delete it, manage what its workload can reach, and see who has access;owner: also decide who has access.
Each run uses compute and credits and acts with the Job's own workload access,
which can be broader than yours, and run logs and outputs can contain the data
the Job processed. job.run_job() and jobs run need runner or more, and
jobs update needs editor or owner. The Job's creator holds owner;
repository workflows create Jobs as the Organization's automation identity,
which holds no policy on them. Organization admins hold every action.
Whoever may run a Job may also set the
caller context of the runs they start: a run
carries the context of the request that created it, whatever that context
says. A Job whose code makes rules from BAGGAGE gives the policies that run
it, runner, editor and owner, including the ones it takes from its
branch, only to service users it trusts to vouch for that context.
A standalone Job also takes the policies of its branch: reader on the branch
gives viewer, and editor or owner gives editor, whether the branch
policy is held on the branch or passed down from its repository or a Project.
A release's backing Job takes its release's policies instead: viewer gives
viewer, editor and owner give editor, and user gives nothing.
get_access() shows each of those as a computed row with source
inherited. A backing Job is changed, run and deleted with its release, so on
it an update raises BadRequestError and a run or a delete ConflictError,
whatever you hold. Every policy on a Job gives viewer on the Artifacts its
runs published; see Who can use an Artifact.
Read and change the policies with the access methods on Job:
from mainsequence.client import Job
job = Job.filter(name__contains="nightly-prices")[0]
job.attach_policy("runner", principal_type="team", principal_uid=team_uid)
answer = job.explain_access("jobs.run")
print(answer.allowed, answer.blocked_by_rule, answer.granting_policies)
The access methods address the Job by its UID alone and take no Environment;
with a workload's runtime credentials they reach only the Jobs of that
workload's Environment. A refused change raises PermissionDeniedError, whose
payload names the required_action and the granting_policies. A repository
workflow file's access entry for a Job gives its workload the policy it
names, such as policy: runner. mainsequence access policies --type jobs
lists the policies with their actions, and
Who Has Access to an Object describes every method and
model.
From the command line:
mainsequence code-repository jobs access list <JOB_UID>
mainsequence code-repository jobs access grant <JOB_UID> runner --user <USER_UID>
mainsequence code-repository jobs access revoke <JOB_UID> <BINDING_UID>
mainsequence code-repository jobs access explain <JOB_UID> jobs.run
See Jobs in the CLI reference.
Operational checks
Use repository-relative, forward-slash execution paths such as
scripts/simulated_prices_launcher.py. Validate the workflow before pushing.
If a scheduled run does not appear, inspect the applied Job, schedule, and
repository-event result. If a run starts but fails, inspect its frozen image
and logs. The CLI log command can wait longer with --max-wait-seconds.