fcode-python
Write Python 3.13 for Factorial Code processes and modules — the main() entry point, fcode.context.parameters, fcode.import_module(), datastore/storage/env helpers, PEP 8 / snake_case, auto-installed dependencies, and return-value formats. Use when creating or editing .py process or module code for Factorial Code (fcode).
How do I install this agent skill?
npx skills add https://github.com/factorialco/factorial-code-skills --skill fcode-pythonIs this agent skill safe to install?
- Gen Agent Trust Hubpass
The skill provides guidelines for developing Python 3.13 processes on the Factorial Code platform. It includes security best practices like secret encryption and environment variable usage. A minor attack surface exists for indirect prompt injection because the skill describes processing external data (parameters, files, and webhooks) without specifying sanitization protocols.
- Socketpass
No alerts
- Snykpass
Risk: LOW · No issues
What does this agent skill do?
Factorial Code — Python
Guidelines for writing Python that runs on Factorial Code. Runtime is
Python 3.13. For the platform model (processes, modules, datastore) see
fcode-core-concepts.
| Aspect | Guideline |
|---|---|
| Runtime | Python 3.13 |
| Process entry file | main.py |
| Entry point | def main() |
| Parameters | fcode.context.parameters |
| Variables | os.getenv("X") or fcode.env.X |
| Import a module | fcode.import_module("module-slug") |
Gotchas
- Define
main()as the entry point, but never call it yourself — Factorial Code invokes it. fcode.import_module()names must be hardcoded string literals, never variables:fcode.import_module("shopify-client")✅,fcode.import_module(name)❌.- Never alias
fcode.i18n— call it literally (fcode.i18n("key")✅,t = fcode.i18n❌): an aliased call throws "i18n is disabled" at runtime. Seefcode-i18n. - Datastore stores only strings/numbers —
json.dumpsobjects beforeset,json.loadsafterget. - Encrypt secrets you keep in the datastore — pass
Trueas the third argument ofset(fcode.datastore.set(key, token, True));getdecrypts transparently. Plainsetstores the value in clear. - Use snake_case, not camelCase; follow PEP 8; add type hints where helpful.
- Wrap the main flow in
try/except, log the caught error with context viafcode-logs(see Logging), and raise actionable errors. Don't rely on global variables for state — pass it through parameters or return values. - Never hardcode or log secrets — read them from
os.getenv.
Process template
def main():
parameters = fcode.context.parameters
# Your code here
return { "message": "Success!" }
Helpers
import os
# Execution / process / schedule metadata
execution_id = fcode.execution.id
process_id = fcode.execution.process.id
schedule_id = fcode.execution.schedule.id # when run from a schedule
timezone = fcode.execution.timezone
# Workspace (team) metadata
team_slug = fcode.team.slug
# Environment variables (secrets/config)
api_key = os.getenv("API_KEY") # or fcode.env.API_KEY
# Import a Factorial Code module (hardcoded name only)
client = fcode.import_module("module-name")
client_v1 = fcode.import_module("module-name", "v1.0.0") # pinned version tag or alias
# Run another process
fcode.processes.run("process-identifier", options)
# Translations (workspace locales — see fcode-i18n)
greeting = fcode.i18n("greetings.hello", {"name": "Ada"}) # %{name} filled in
fcode.i18n("greetings.hello", {"name": "Ada"}, {"locale": "es"}) # another locale, this lookup only (value may be dynamic)
locale = fcode.i18n.locale # the execution's locale
Logging
Log through the shared fcode-logs module — level-gated logging inherited by
every workspace. It reads the LOG_LEVEL team variable
(debug | info | warn | error, default info) and forwards to print (stdout
for debug/info, stderr for warn/error), so call sites read like a plain print:
log = fcode.import_module("fcode-logs") # import once; reuse `log` everywhere, including `except`
log.info("sync started", process_slug) # stdout when LOG_LEVEL ≤ info
log.debug(request_payload) # stdout only when LOG_LEVEL=debug
log.warn("token missing — skipping") # stderr when LOG_LEVEL ≤ warn
log.error("sync failed", str(err)) # stderr — always emitted
Be verbose — the logging policy (start/end, external calls and decisions at
info; payloads at debug; always log inside except with context before
re-raising) is in fcode-core-concepts §General rules. Set LOG_LEVEL=debug
in a local or dev workspace to trace a full run; production stays at info.
Never log secrets.
Dependencies
External pip packages install automatically — just import them. When the
package name differs from the import name, declare it with @add-package:
# @add-package requests
import requests
Packages a parent workspace provides are already installed here — don't
redeclare them (see fcode-core-concepts).
Datastore & storage
import json, os
# Datastore (strings/numbers only)
fcode.datastore.set("key", "value")
fcode.datastore.set("key", json.dumps({ "name": "John", "age": 30 }))
value = fcode.datastore.get("key")
fcode.datastore.delete("key")
# Encrypted at rest with a key owned by the workspace — for tokens, credentials,
# personal data. The third argument must be a bool; get() is transparent.
# Keys (entry names) are never encrypted.
fcode.datastore.set("oauth.token", access_token, True)
token = fcode.datastore.get("oauth.token")
# Storage (files)
local_path = os.path.join(os.environ.get("TMP_DATA_DIR"), "localfile.txt")
with open(local_path, "rb") as f:
obj = fcode.storage.upload("path/myfile.txt", f)
objects = fcode.storage.list()
content = fcode.storage.download("path/myfile.txt")
with open(local_path, "wb") as f:
f.write(content)
# Form file params arrive as "fcode.storage://…" references — strip the
# prefix before download; see fcode-forms.
# Signed download URL — { "url", "expiresAt" }. A real HTTPS link in the
# cloud, a file:// URL locally (same shape, no special-casing).
signed = fcode.storage.create_signed_url("path/myfile.txt")
fcode.storage.delete("path/myfile.txt")
Local disk: write temp files under os.environ.get("TMP_DATA_DIR").
Variables, schedules & OAuth
Read/write team variables and manage process schedules at runtime — scoped to your own team, no API token needed (like datastore/storage):
# Team variables (config/secrets)
# Default is SENSITIVE: fcode.variables.set(key, value) creates a sensitive
# (masked, immutable-sensitivity) variable. Pass sensitive=False for plain
# config values.
fcode.variables.set("API_KEY", "secret") # sensitive by default
fcode.variables.set("BASE_URL", "https://api.acme.com", sensitive=False) # required for non-secret config
v = fcode.variables.get("API_KEY") # TeamVariable (has resolving_team_slug) or None
all_vars = fcode.variables.list() # includes variables inherited from parents
fcode.variables.delete("API_KEY") # no-op on an inherited variable
# Schedules (cron or one-off date_time) for a process
schedule = fcode.schedule.create(
"my-process",
cron="0 0 6 * * SUN", # or: date_time="2026-04-24T12:30:00.000"
parameters={"foo": "bar"},
allow_concurrent_executions=False, # optional
)
schedules = fcode.schedule.list(process_id=fcode.execution.process.id)
current = fcode.schedule.get(schedule["id"])
fcode.schedule.update(schedule["id"], cron="0 0 7 * * SUN")
fcode.schedule.pause(schedule["id"])
fcode.schedule.resume(schedule["id"])
fcode.schedule.delete(schedule["id"])
# delete every schedule for a process (pass the process UUID)
fcode.schedule.delete_for_process(fcode.execution.process.id)
# Start a third-party OAuth authorization. The platform holds the state, the PKCE
# verifier and the one redirect_uri registered with the provider, and invokes
# `on_complete` when the provider redirects back -- so never build an authorization
# URL or a state by hand, and never expose a callback webhook.
flow = fcode.oauth.start(
authorize_url="https://login.example.com/authorize",
client_id=fcode.env.PROVIDER_CLIENT_ID,
scope=["openid", "offline_access"],
on_complete="oauth-callback", # the process that receives the code
data={"companyId": company_id}, # carried back untouched; never a secret
extra_params={"nonce": nonce}, # optional, provider-specific
pkce=True, # default
)
flow.authorization_url # hand this to the form's oauth widget
on_complete is invoked with code, codeVerifier, redirectUri, data and
state in fcode.context.parameters (or error / errorDescription when the
provider refused). Replay redirectUri in the token exchange — providers compare
it byte for byte. See fcode-examples references/oauth-connect.md.
fcode.variables.set/delete only persist server-side; they are not reflected in
fcode.env within the same run (fcode.env is a snapshot taken at start).
Inherited variables
list()/get() include variables inherited from parent workspaces (model in
fcode-core-concepts); an inherited one carries resolving_team_slug naming
its owner. set() on an inherited key creates an override in this
workspace — the only way to change the value from here — and delete() on one
is a silent no-op, so an uninstall process never removes a parent's
credential (check resolving_team_slug if it must report what it actually
removed). fcode.env.set_env_var / del_env_var behave the same way.
Sending email
Send email with the built-in fcode.send_mail — no SMTP setup required (model
in fcode-core-concepts):
info = fcode.send_mail(
to="user@example.com", # string or list[str]
subject="Report ready",
text="Plain-text body", # provide text, html, or both
html="<b>HTML body</b>",
)
# info => { "messageId", "accepted", "rejected" }
- The
Fromaddress is fixed by the platform; afromyou pass is ignored. - Each execution can send up to 3 emails by default; once the limit is reached, further calls throw.
- Locally (
fcode run) there is no manager, so the email is logged, not sent. - Acceptable use — who may be a recipient, what the body may carry — is a
platform rule that blocks a release when broken; see
fcode-core-concepts§Sending email.
Return values
# Standard
return { "message": "Success!" }
# Custom HTTP status (webhooks)
return { "status": 404, "body": { "message": "Not found" }, "headers": { "Content-Type": "application/json" } }
# Transient (not persisted in execution results)
return { "transient": True, "data": sensitive_data }
# Synchronous UI trigger button inside Factorial (see fcode-ui-triggers)
return { "data": { "synced": 42 } }
return { "errors": [{ "code": "missing_mapping", "message": "Map the Bonus concept first." }] }
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/factorialco/factorial-code-skills/fcode-python">View fcode-python on skillZs</a>