npx skills add ...
npx skills add factorialco/factorial-code-skills --skill fcode-core-concepts
Factorial Code platform architecture and core concepts — processes, modules, execution context, variables, inheritance from parent workspaces, datastore, file storage, workspace structure, and naming conventions. Use when building, editing, or reasoning about any Factorial Code (fcode) process, module, or workspace; start here before writing process or module code.
npx skills add factorialco/factorial-code-skills --skill fcode-core-concepts
Factorial Code (fcode) is an enterprise integration and automation platform. You write processes (and reusable modules) in JavaScript or Python; the platform handles sandboxing, dependencies, secrets, scheduling, and execution.
This skill is the mental model. For writing actual code, also use
fcode-javascript or fcode-python; for the CLI workflow, fcode-cli.
These defy reasonable assumptions — get them wrong and the process breaks:
JSON.stringify / json.dumps before set, and parse on get.index/main. A module
lives at modules/<slug>/<slug>.js (or .py) — e.g.
modules/shopify-client/shopify-client.js. Never modules/<slug>/index.js
or main.py (those names are reserved for process entry files), and never
put a module file directly under modules/ without its own folder.variables.env. Read it first and append/patch
only the specific variable(s); rewriting the file drops every variable not in
the new content and can break other processes.variables.inherited.env,
i18n/<locale>.inherited.yaml, inherited processes/modules) — they are owned
by parent workspaces and regenerated on every pull. Override a variable or
locale key by defining it in this workspace's own file; edit processes and
modules in the workspace that owns them.| Concept | What it is | Key point |
|---|---|---|
| Process | The unit of execution (business logic) | Defines input parameters, returns structured results |
| Module | Reusable code library | Shared across processes, can be versioned |
| Workspace version | One tag (e.g. v1.0.0) published on every process and module the team owns | Published from the web UI; retrying the same tag is safe |
| Version alias | Movable pointer to a version | stable always exists — pin consumers to it; moving it is rollout/rollback |
| Variables | Configuration & secrets | Env vars; inherited from parent workspaces; never hardcode secrets |
| Datastore | Persistent key-value store | Strings and numbers only; a value can be encrypted at rest on request |
| Storage | File storage | Binary files, documents, large payloads |
| Locales | Per-language translation files (i18n/<locale>.yaml) | Resolved by fcode.i18n; inherited key by key from parents — see fcode-i18n |
| Built-in transactional email | fcode.sendMail / send_mail; no SMTP setup, credentials live in the manager |
The basic unit of execution, in JavaScript (index.js) or Python (main.py).
Processes may declare input parameters via JSON Schema (parametersSchema.json,
see fcode-json-schema) and read them through fcode.context.parameters.
Return structured JSON; for webhook-style responses return
{ status, headers, body }.
Reusable libraries shared across processes — use them to avoid duplication, encapsulate API clients/integrations, keep process code small, and support versioning. See the module-naming gotcha above.
Each process runs isolated, with access to:
fcode.context.parametersprocess.env.* / os.getenv(...) (or fcode.env.*)fcode.execution.*Store base URLs, timeouts, API keys, and tokens as variables (never hardcode).
variables.env holds team variables (KEY=VALUE); variables.local.env holds
local-only overrides. See the overwrite gotcha above.
variables.meta.json marks each variable's isSensitive flag. Sensitive
values never leave the cloud — locally they appear as a ******** placeholder
in variables.env; put real values in variables.local.env. Details in
fcode-cli.
When a secret's real value is needed for local runs, ask the user for it — or
have them put it in variables.local.env themselves if they prefer not to
share it. FACTORIAL_TOKEN comes from the OAuth flow in the Factorial Code app
details page and is only needed locally (auto-populated remotely); procedure in
fcode-cli.
A team variable is also how a webhook is protected: the process names the
variable holding the token it expects, and callers send that value as
Authorization: Bearer <token>. Only the variable name is stored with the
process, so the token stays out of exports and out of committed files.
Read them at runtime via fcode.env.*. To create/update/delete them
programmatically from a process, use the fcode.variables helper
(set/get/list/delete) — scoped to your team, no API token needed. See
fcode-javascript / fcode-python.
A workspace uses the processes, modules, variables, i18n locales, and
dependencies of its parentTeams parents as if they were its own.
Resolution is the same for all of them: the workspace first, then its
direct parents in the order they are configured, capped at 5 — so it is
not transitive (a grandparent's resources don't reach a grandchild).
variables.env, i18n/<locale>.yaml, or
dependencies/package.json / requirements.txt wins over the parent's — key
by key, and package by package for dependencies, where the child's specifier
replaces the parent's because two versions of one package can't be installed
side by side. Deleting the override brings the parent's back. Processes and
modules have no override — change them in the owner.deploy-{installationId} workspace carries only the
values specific to that customer, while shared defaults and credentials stay
in prod-{appId} / base-app. In the web UI inherited variables carry an
inherited badge and offer Override here.null over GraphQL, ****** over REST, ******** in the
CLI's inherited file), so process code is the only place a value is readable —
treat code in a child workspace as trusted with its parents' credentials.
Local runs get the placeholder — put real values in variables.local.env.Each parent link carries an optional version pin: a tag, or an alias such as
stable, of one of that parent's workspace versions. Without one the parent
resolves live — its current code, which is the behaviour above. With one,
that parent contributes the release, not its working copy:
stable and re-pointing
stable in the parent moves every child pinned to it, in one operation. This
is how a base app rolls a fix out to its installations.The pin is set per parent in the web UI (team settings → Details → parent
teams) or in the workspace's settings.json; the field reference is in
fcode-cli. Don't add or change a pin unless explicitly asked — it decides
which release a workspace runs.
On-disk layout, gitignoring, and push/pull behaviour of inherited resources are
in fcode-cli.
Run a process on a cron or one-off date/time. Manage schedules from process code
with the fcode.schedule helper (create/list/get/update/pause/
resume/delete/deleteForProcess) — same out-of-the-box auth as the other
helpers. See fcode-javascript / fcode-python.
true as the
third argument of set; reading it back is transparent. That is where a
secret the process itself must persist between runs (an OAuth token it
obtained) belongs — see fcode-javascript / fcode-python.Send email with the built-in fcode.sendMail (fcode.send_mail in Python) —
pre-authenticated, no SMTP configuration. The mail server and credentials live in
the executor manager, never in your process. Each execution can send up to 3
emails by default. See fcode-javascript / fcode-python for usage.
Every app sends from the same fixed Factorial address, so one abusive app is a
phishing vector for its recipients and a deliverability problem for every other
app. The built-in mailer is reserved for transactional mail to people the
customer's Factorial account already knows; app validation
(fcode-code-validation) blocks a release on anything else.
Mail outside that envelope — customer-facing campaigns, volume, a custom
sender — needs a dedicated email provider called with the app's own
credentials (fcode-agent shows the choice); never work around the cap with
the built-in mailer.
Processes and modules can be versioned individually, and a workspace version
publishes one tag (e.g. v1.0.0) on every process and module the team owns
at once — resources inherited through parentTeams are never touched. Each
entity's outcome (created / skipped / failed) is recorded in the version's
manifest, so re-creating the same tag after a partial failure only publishes
what is still missing.
When a workspace version is published, bare fcode.import("mod") /
fcode.import_module("mod") calls of workspace-owned modules are pinned to the
tag inside the published snapshots only — the working copy is never
modified, and imports that already carry a tag or alias are left untouched.
A workspace version also publishes every owned locale and pins
fcode.i18n calls the same way — calls already naming a version are left
untouched, everything else (bare calls, locale-only options) gets the tag —
in code and in form schemas, so a release ships with its translations frozen —
see fcode-i18n.
A version alias is a movable pointer to a version. The stable alias
always exists and points at the workspace's stable version. Webhooks, forms,
schedules, and module imports accept an alias wherever they accept a tag, so
moving stable to another version re-points the whole workspace in one
operation — rollout and rollback are a single alias change. Always pin
consumers (webhook URLs, form embeds) to stable; how in fcode-cli and
fcode-forms.
Two consequences of that model:
fcode push never affects consumers pinned to stable. Pushing updates
the current (unversioned) code; pinned consumers keep running the released
version until the alias moves.Versions are published and aliases linked from the web UI (team settings →
Versions tab). The CLI equivalents (fcode settings:versions:* /
settings:aliases:*) are documented in fcode-cli — don't create versions or move
stable unless explicitly asked.
A version tag (v1.0.0) is unrelated to metadata.json tags — those are
process labels (used e.g. for MCP-tool exposure, see fcode-agent).
Every workspace runs on the same fixed plan, so the limits below apply
everywhere — there is no plan to pick and no self-service upgrade. Design the
app to fit them. If a real need doesn't fit, ask us to raise that limit for the
workspace (internal Factorial users: Slack #factorial-code-users; others:
see fcode-ama › Escalation) — never work around a limit, e.g. by splitting
one job across many executions or chaining datastore keys to dodge a cap.
| Limit | Value | When exceeded |
|---|---|---|
| Time per execution | 12 hours | The execution is terminated |
| Concurrent executions per workspace | 50 | New executions are queued |
| Queued executions per workspace | 1,000 | New executions are rejected |
| Requests to webhooks | 50 req/s per workspace | HTTP 429 with Retry-After |
| Requests to forms and OAuth endpoints | 50 req/s per process and caller | HTTP 429 with Retry-After |
| Input parameters — text | 500 KB | HTTP 413, execution not created |
| Input parameters — files | 15 MB in total | HTTP 413, execution not created |
| Return value | 500 KB | The execution fails — put large results in storage and return a reference |
| Execution log | 500 lines and 500 KB | Further log entries are dropped |
| Datastore entries | 20,000 per workspace | Writes fail (HTTP 403) |
| Datastore entry size | 50 KB | The write fails (HTTP 413) |
| Storage files | 1,000 files, 50 MB each | The upload fails |
| Emails per execution | 3 (see Sending email) | Further sends throw |
| Processes, forms, dependencies, executions per day/month | Unlimited | — |
Module vs inline code
| Scenario | Recommendation |
|---|---|
| API client used by multiple processes | Create a module |
| Utility helpers used 2+ times | Create a module |
| One-off transformation / single-use logic | Keep inline |
Datastore vs Variables vs Storage
| Need | Use |
|---|---|
| Config that rarely changes; secrets/credentials | Variables |
| State that changes between runs; cached API responses | Datastore |
| A secret the process obtains and must keep between runs (OAuth tokens) | Datastore, set(key, value, true) |
| Binary files / large exports | Storage |
| Resource | Convention | Example |
|---|---|---|
| Process slug | kebab-case | order-sync-shopify |
| Module slug | kebab-case | shopify-client |
| Variables | SCREAMING_SNAKE_CASE | SHOPIFY_API_KEY |
| JavaScript functions | camelCase | fetchOrders() |
| Python functions | snake_case | fetch_orders() |
A local workspace managed by the fcode CLI (see fcode-cli):
A whole development team checks out one level up, one folder per App, with the
workspace above nested inside it — fcode team:clone in fcode-cli:
Processes and modules also carry versions/<tag>/ subfolders (e.g.
versions/v1.0.0/) holding their published version snapshots — see the
versioning section above. dependencies/package.json holds only the inner
dependencies object (e.g. { "axios": "^1.6.0" }).
metadata.json is where a process's webhook trigger and its Factorial
Action are configured — edit it and fcode push. A webhook is public
(authMode: NONE), inherits the workspace webhookAuth from settings.json
(TEAM), or carries its own header and team variable (CUSTOM). The
factorial block says how Factorial reaches the process: a master switch,
whether Factorial waits for the result, the policies a user needs, and one
sub-block per entry point — a button in the Factorial UI (fcode-ui-triggers),
a form (fcode-forms), a Factorial One tool with its contract, a backend job
(fcode-factorial-actions). The older form and uiTrigger keys are legacy
and kept mirrored with it. Four process slugs are reserved for the app
lifecycle — install, settings, uninstall, sync. Full field reference in
fcode-cli.
fcode-logs module — level-gated logging via
the LOG_LEVEL team variable (default info). Log start/end, major decisions,
and external calls at info, and detail (payloads, intermediate state) at
debug (gated off in production). Never log secrets. Usage in
fcode-javascript / fcode-python.acme-payroll/ # the development team
┣ 📂 .fcode/team.json # the team and the Apps cloned into it
┣ 📂 .claude/skills/ # installed once, symlinked into every App
┗ 📂 payroll-sync/ # one folder per App — a future git repo
┣ 📜 settings.json # the App — name, description (mirror, read-only)
┣ 📜 README.md # the App's README (editable, `fcode app:push`)
┣ 📜 MARKETPLACE.md # the marketplace listing (editable, `fcode app:push`)
┣ 📜 CHANGELOG.md # generated from the App's releases (read-only)
┣ 📂 marketplace/logo.* # the App logo (read-only)
┗ 📂 app/ # the workspace laid out above