Osy#betaa language · its runtime Osyrin · a hosted platform
Why Osy#Built for agentsAgents as declarationsWorkflows that waitRuns exactly onceSecure by defaultNothing to mockThe editor is the compilerUI in the languageDocuments are dataOne program

Reference / Local

Running a function

osy run <function> [path] [--arg NAME=VALUE] [--args-json <json>] [--as <login>] [--password <pw>] [--json]

Runs one of your app's own functions from the command line — to put the app in a known state, backfill a column, or kick off a job. It runs as a real user (or as nobody), so your security rules apply exactly as they do in the browser.

stable1 example compiled by CIlocalcliauthoring

Summary#

Runs one function your app declares, with arguments, against the local platform (Running a local platform). It is the third thing you can do to an app from outside it: osy compile ships its code, osy import loads its rows, and osy run makes it do something.

The run happens as somebody. With no --as it runs anonymous — the same thing a visitor with no session gets. Name a user and it runs as that person, with their roles and row filters live.

Signature#

osy run <function> [path] [--arg NAME=VALUE] [--args-json <json>] [--as <login>] [--password <pw>] [--json]

Description#

What you can run#

Any top-level function your app declares. Methods on a class need an instance, constructors are not verbs, tests belong to osy test, and a workflow's entry points are driven by the workflow engine — each of those is refused by name, telling you which it is. Misspell a function and the answer lists the ones you can run.

osy run RecalculateTotals
osy run SendDigest --arg since=2026-08-01 --arg dryRun=true

How do I pass arguments?#

--arg NAME=VALUE binds one parameter and repeats. true, false and numbers are read as such; everything else is text, including identifiers and dates, which are converted to the parameter's declared type on arrival. Only the first = splits, so --arg filter=status=open passes status=open.

When a parameter takes a class, pass the whole argument object as JSON:

osy run PlaceOrder --args-json '{"order":{"reference":"A-1","total":42}}'

Passing an entity#

A parameter typed as one of your entities takes a row, and what you pass decides which row:

you passit binds
an existing row's id — --arg customer=8050d9b7-…, or {"customer": "8050d9b7-…"}, or {"customer": {"id": "8050d9b7-…"}}that row, loaded as the principal the run happens as. An id that names no row — or a row that principal may not read — is refused as not found, naming the entity and the id. Nothing is constructed.
an object of fields with no id — {"customer": {"name": "Acme", "code": "acme"}}a new row in the run's unit of work, exactly as new Customer { … } in the body would be; it is written when the function commits.
an id and fieldsrefused. An id binds a row as it is; to change its fields, do so in the function.
entity Customer {
  [MaxLength(200)] string Name;
  [Unique, MaxLength(100)] string Code;
  security {
    allow read, create when IsAuthenticated || IsAnonymous;
  }
}

entity Invoice {
  [Required] Customer Customer;
  decimal Amount;
  security {
    allow read, create when IsAuthenticated || IsAnonymous;
  }
}

Customer AddCustomer(string name, string code) {
  var c = new Customer { Name = name, Code = code };
  UnitOfWork.Commit();
  return c;
}

Invoice RaiseInvoice(Customer customer, decimal amount) {
  var i = new Invoice { Customer = customer, Amount = amount };
  UnitOfWork.Commit();
  return i;
}
osy run AddCustomer --arg name=Acme --arg code=acme --json         # answers the row, `id` included
osy run RaiseInvoice --arg customer=8050d9b7-… --arg amount=42      # binds THAT Customer
osy run RaiseInvoice --args-json '{"customer": {"id": "8050d9b7-…"}, "amount": 42}'   # the same
osy run RaiseInvoice --args-json '{"customer": {"name": "New Co", "code": "new"}, "amount": 42}'  # a new Customer, written with the Invoice

The id is the one --json gave you when the row was returned (below), or what osy query shows.

Who it runs as#

This is the part worth reading twice, because it decides what the run is allowed to do.

  • No --as — the function runs anonymous. If your app denies anonymous writes, the run is refused, and that refusal is correct: it is what a stranger hitting the same code would get.
  • --as <login> --password <pw> — the function runs as that user. <login> is whatever your app.Auth binds as its login field, usually an email. Their roles apply and row filters bind to them, exactly as under runas in a test. It is a real login: --as takes that user's own password, because naming a principal must never be enough to become one. Omit --password and you are prompted.

There is no switch that turns security off. A run no user could perform tells you nothing about whether your app works, and a privileged job is served by naming a user who genuinely holds that authority — add one with Adding an account, roles and all, if the app has none yet.

osy run ArchiveOldOrders --as [email protected] --password 's3cret'

A login or password that does not check out refuses the run — as one answer, "invalid login or password", the same thing your app's own login page says. It never quietly falls back to anonymous, because a run with less authority than you asked for looks exactly like a successful one until it doesn't.

What comes back#

A function that returns a value prints it. --json gives you the whole result — whether it succeeded, what it returned, and which principal it ran as — for a script to read.

A returned entity is its row: id first, then every field you declared, then createdAt and modifiedAt. A reference member (Invoice.Customer) is the referenced row's id. A returned list is one such object per row. The id is the one value every row is guaranteed to have, and it is what the next call takes — you never need a function of your own to learn it.

{
  "success": true,
  "output": {
    "id": "8050d9b7-ab3b-46b1-9037-ee9631e52064",
    "name": "Acme",
    "code": "acme",
    "createdAt": "2026-09-05T21:45:26.8817050Z",
    "modifiedAt": "2026-09-05T21:45:26.8817050Z"
  },
  "error": null,
  "ranAs": "anonymous"
}

A function that refused (a validation error, a denial, a throw of your own) is reported with its own message and a non-zero exit code. That is an answer about your app, not a failure to reach it.

Against a deployed app#

osyrin app run <function> is the same command against a platform you are logged in to, with the same --as rule — which matters more there, not less.

Examples#

osy run SeedCatalogue                                  # anonymous — fine if the app allows it
osy run SeedCatalogue --as [email protected] --password 's3cret'   # as a real user, with their rules
osy run Backfill --arg batch=500 --arg dryRun=true --json

See also#

Importing data — load the app's rows, the other half of putting it in a known state.

Compiling your app — ship the code the function comes from.

runas — the same principal idea inside a test.

The inner loop — where this sits in the build-run-look loop.

Related

Importing data

Loads your app's own data — the JSON files that live beside its source — into the app on the local platform. Rows are…

Compiling your app

Compiles your app's source into the app on the local platform — the inner-loop compile-and-apply. It applies additive…

Reading your data

Evaluates one Osy# expression against your app and prints what it answered — the rows, the count, the projection. The…

The inner loop

The local develop-and-debug loop for an Osy# app — for the person in the editor and the coding agent at the CLI alike…

runas

Runs a block as a given principal, so security rules apply exactly as they would for that user. It is how you test that…