Skip to docs content
Browse documentation
On this page

Package lifecycle: reuse, execute, or create

Choose between invoking existing behavior, temporary execute exploration, deferred workflows, and creating a durable saved package; test package-owned job wrappers safely.

Use this guide to decide whether to reuse existing behavior, explore with execute, or create durable package code. Use it before scheduling new package behavior.

Choose the smallest durable surface

Invoke an existing package or capability

Search first. If a built-in capability or saved package already does the work, invoke it instead of creating another implementation.

  • Call discovered built-in capabilities through minion in execute.
  • For a saved package export, use a static minion:@your-org/package/export import. It is the default for package reuse from execute and from other packages. Ad hoc execute bundles per call, so static imports from execute always see the current runnable version.
  • Work that must run exactly once uses workflows .

This is the default for an established operation whose behavior should stay owned by its existing capability or package.

Use execute for temporary one-off exploration

Use execute when the code is disposable: inspect an API response, test an assumption, transform a small result, or run a one-time operation after any required confirmation.

Keep the module focused and return structured evidence. Do not treat the ephemeral module as the durable source for behavior that must be maintained, reused, or evolved.

Ad hoc execute runs with the signed-in member's authority: it can call the capabilities that member may call through minion. A saved package is the declared-authority unit: its manifest, minion.dependencies, secret host approvals and integration allowed hosts are the request, and agents cannot widen those. Outbound emissions (email, paid APIs, third-party writes) are not invertible; use a package dryRun and fresh user confirmation before a live mutation.

Reuse a package from your organization first

Any saved package in your organization is visible to search and minion.packages.list(). Read one with minion.packages.get({ name }) before writing something similar. If one is close, change it with a repo session instead of starting over. There is no public package catalog and no cross-organization sharing yet; see Sharing packages .

Create a saved package

Create or extend a saved package when behavior is reusable, expected to evolve, or needs a named package-owned schedule that evolves with its implementation, and no existing saved package covers it. If the implementation needs a library that does not fit a Worker isolate, keep this package as the orchestrator and offload the heavy process; see Offload work that does not fit a Worker isolate . The files rooted at package.json are the durable source of truth. Package exports form the callable surface, while jobs, subscriptions, webhooks, and apps remain package-owned behavior.

Recurring schedules belong on a saved package under package.json#minion.jobs. Deferred one-shot work uses workflows.create({ runAt }) from execute or package runtime. See Workflows .

Two built-in guides cover the details: guide:package_with_daily_job for package shape, jobs and the required README.md, and guide:edit_a_saved_package for changing a package through a repo session. Open them with search. See Package authoring for the full checklist and guide:serve_an_app when the package serves a web app.

When the OAuth token is coarser than the intended export (Gmail can send whenever it can create a draft), publish a thin package that only performs the allowed call, then lock it. See Gmail drafts without send for that pattern.

Signals to escalate from execute to a package

Move the behavior into a package when one or more of these become true:

  • the user will run it again or other package code should reuse it
  • it needs a named package-owned schedule that should evolve with the implementation, or another durable surface such as a subscription, webhook, app, workflow entrypoint, or package-owned storage
  • the logic needs multiple files, dependencies, review, or version history
  • inputs, output, error handling, or integration behavior will evolve
  • a one-off script has already been copied, repaired, or rerun
  • you are calling a third-party product API with raw integration auth helpers (createAuthenticatedFetch or equivalent) beyond a cheap smoke test. Integrations are auth; packages are how agents should call the product. Search for an existing wrapper package first, then create a thin helpers package

Do not create a package merely to wrap one clear call to an existing capability or package export. One-shot reminders and deferred work belong in workflows, not a new package.

Choose how to write the source

New package or full replacement

Send the complete UTF-8 text file set to minion.packages.save (package.json, README.md and your source files). Pass publish: true if you are an owner or admin and want it to go live once the checks pass.

Changing an existing package

  1. Open a session with minion.repo.openSession({ package }) and read the files you need with repo.readFile and repo.search. packages.get returns only the README, exports, jobs and versions.
  2. Edit with repo.editFiles or repo.applyPatch, then look at repo.diff.
  3. Run repo.runChecks and fix what it reports.
  4. An owner or admin publishes with repo.publish. A member hands over the sessionId.

See Editing package source for the details, including rebasing when the package moved under you. Packages hold text files only. Binary assets and git remotes aren't supported.

Test the scheduled wrapper before enabling its schedule

Scheduled behavior should not be its first real execution. Structure the package with:

  • a shared implementation that accepts explicit input
  • an optional callable export that passes representative input to that implementation
  • a scheduled wrapper export that loads its own current configuration and calls the same implementation (a job can also pass fixed params from its manifest entry)

Make the wrapper callable through package.json exports and point the job's export at it. A job declared in minion.jobs starts disabled: Minion won't enable it until one run of it has succeeded, and changing a job's schedule or export disables it again.

After package checks and publishing succeed:

  1. Inspect the published package and export contracts with minion.packages.get.
  2. Call the scheduled wrapper from authenticated execute with a static import and the same arguments the job will pass. This verifies the same contract the scheduler uses.
  3. Run the job itself once with minion.jobs.runNow({ id }) and read the result with minion.runs.get. A successful run is what allows it to be enabled.
  4. Optionally invoke the underlying callable export with representative input: realistic field shapes, boundary values, and the same configuration references the wrapper will load. Never put plaintext secrets in params.
  5. Validate the structured results and the intended durable or external effects. Exercise an expected failure path when the operation is risky.
  6. Only then enable the job with minion.jobs.update({ id, changes: { enabled: true } }) and verify the job detail (minion.jobs.get, or /app/jobs) shows the schedule and next run.

Example test call:

import scheduledReport from 'minion:@your-org/daily-report/scheduled-report'

export default async function main() {
	return await scheduledReport()
}

For exports that send messages, write remote records, charge accounts, or make other external mutations, prefer a package-specific dryRun input. Implement it so the export performs validation and returns a preview while skipping the mutation. dryRun is a package contract, not an automatic Minion runtime flag; test the dry-run path first.

Immediately before any live external mutation, obtain explicit user confirmation that identifies the target and scope of the mutation. Do not infer confirmation from an earlier request, a successful dry run, or the agent's own assessment. After confirmation, perform only the confirmed call, then report the result. Require fresh confirmation if the target or scope changes.

Keep the schedule disabled when representative testing is inconclusive, credentials or host approval are missing, the result is unexpected, or the live mutation has not been explicitly confirmed by the user.

Delete a package

minion.packages.delete permanently removes a saved package the organization owns. Do not call it because a package is unused, failing, or over quota unless the owner explicitly asked to delete that package.

  1. Load the package with packages.get or packages.list.
  2. Show the owner the package name and that delete stops its jobs and clears its storage and package-scoped secrets. This cannot be undone.
  3. Wait for the owner to type the package name.
  4. Call minion.packages.delete({ name, confirmName }) with confirmName matching that name exactly. Owners and admins only.

Hiding a package (minion.packages.update({ name, changes: { hidden: true } })) is not deletion. The package stays saved and runnable; it just drops out of ordinary search results.

Evolve the durable behavior

For later changes, read the current files in a repo session, preserve the package's intent, update the README with the implementation, and repeat the disabled-schedule test when the scheduled path or its inputs change. Publish only checked versions; every publish is a new numbered version, so a bad one can be rolled back by locking an earlier version.