State
State is what turns a document into an application.
A static document has content. An application has content that changes based on what happened before. In String, the difference between the two is state — variables that flow between actions and shape what the AI sees next.
No database. No backend session store. Just variables.
How state flows
Section titled “How state flows”Every action produces a result. That result is available as Response
until the next action runs.
/act.login --provider github→ Response.body.access_token = "ghp_abc123..."→ Response.body.agent = "agent01"A response template stores parts of it as session variables:
```act.login.response{token} = {Response.body.access_token}{agent} = {Response.body.agent}```Now subsequent actions can use those values:
/act.get_repos --token {token}/act.create_issue --token {token} --title "Bug report"This is the loop that makes state work:
act → Response → response template stores {var} → act (using {var}) → ...Each turn builds on the last. The AI doesn’t need to remember the
access token in its context — it’s in the session. The AI just
references {token} and String substitutes the value.
Variable types
Section titled “Variable types”String has two kinds of variables with different lifecycles and purposes.
{variable} — Session variables
Section titled “{variable} — Session variables”Ephemeral variables that live in a session. Created by response templates when actions run, consumed by subsequent actions.
{city} = {Response.body.city}{temp} = {Response.body.temperature}- Set by: response templates (primary), AI
/set(possible) - Scoped to: the topic session — each topic has its own set
- Lifetime: the session — lost when the session closes
- Storage: in-memory only
- Used in: action URI path params, flag values, response output
Session variables are the working memory of a running app. A weather
app calls /act.search, the response template stores {city} and
{temp}, and subsequent actions reference them. When the session
ends, they’re gone.
$variable — Persistent variables
Section titled “$variable — Persistent variables”Durable variables stored on disk. Set by the human (via config file)
or by the AI (via /set). Persist across sessions and restarts.
$API_KEY$DEFAULT_REGION$USER_EMAIL- Set by: human (config file) or AI (
/set $VAR = "value") - Scoped to: app or app config
- Lifetime: persistent until explicitly changed or deleted
- Storage: file-backed (EnvStore)
- Used in: action definitions (URI, headers, CLI commands)
- Declared as requirements:
requires:in app frontmatter
Persistent variables are the configuration layer. An API key set for one app is available to that app’s actions, but not to other apps. An app can declare what it requires, and String tells the AI what’s missing.
Two lifecycles
Section titled “Two lifecycles”The two variable types serve different moments in the workflow.
{var} — runtime data flow:
The document developer designs the data pipeline. Response templates
extract values from API responses and store them as {var}. Actions
use those values. The AI doesn’t need to know the plumbing — it
calls /act.search, and {city} is populated by the template.
Document developer writes: AI at runtime: response template /act.search --name "Seoul" {city} = {Response.body.city} → {city} is now "Seoul" {temp} = {Response.body.temp} → {temp} is now "24" /act.forecast --city {city}$var — configuration and credentials:
Before the app runs, $var values need to be in place. The human
sets API keys in the config. The AI sets app-specific parameters
when required.
Human sets once: AI uses in actions: $API_KEY = "sk-abc..." act.search → GET ...?key=$API_KEY $DEFAULT_REGION = "KR" act.weather → ...®ion=$DEFAULT_REGIONWhat goes where:
{var} | $var | |
|---|---|---|
| API response data | Yes | No |
| API keys, credentials | No | Yes |
| Agent preferences | No | Yes |
| Intermediate computation | Yes | No |
| Cross-session config | No | Yes |
$var storage and isolation
Section titled “$var storage and isolation”Persistent variables are app-scoped. Each app gets its own env file; nothing is shared across apps, and the OS shell’s environment never leaks in.
~/.string/agents/{agent}/ apps/ weather/ env.json ← app:weather scope seoul/ env.json ← app:weather:seoul scope (config) moltbook/ env.json ← app:moltbook scopeApp scope — apps/{app}/env.json holds variables for an app:
{ "MOLTBOOK_API_KEY": "moltbook_sk_..."}Config scope — apps/{app}/{config}/env.json holds variables
for a specific configuration (app:weather:seoul). Overrides app
scope for keys that appear in both.
Resolution — config → app, most specific wins:
config env → app env(highest) (lowest)When an action references $API_KEY, String checks the config scope,
then the app scope. No global fallback. No process.env fallback.
A reference that doesn’t resolve in the current app’s scope returns
INVALID_PAYLOAD with a “set it from this app session” hint.
Why no global. A shared env would let any installed app read keys
set by another. The leak is closed by design: an app’s $X is
unreachable from any other app, and the daemon’s shell environment is
opaque to action substitution. The cost — a key needed by N apps must
be set N times — is the price of isolation.
System vars — daemon-defined context vars are still available in
every action: $HOME (the String per-agent.home, not the OS HOME),
$CWD, $CURRENT_FILE, $CURRENT_URI, $CURRENT_TARGET,
$CURRENT_BLOCK, $ARGS. These are supplied fresh per call and live
above the app’s own env in the resolution chain.
Setting a var — /set $X = "..." only works from an app session
(app:NAME or app:NAME:CONFIG). In main, bash:*, or hub topics
it returns INVALID_TARGET — there’s no app to scope to.
Requirements — requires: in frontmatter
Section titled “Requirements — requires: in frontmatter”Apps declare which $var values they need with requires: in frontmatter:
---requires: - API_KEY - DEFAULT_REGION---When the app is opened, String checks whether the required variables are set.
If $API_KEY is missing, the rendered page starts with a setup hint:
[!] Missing required env: $API_KEY Set: /set $API_KEY = "..."The AI then knows to ask the agent for the value and set it:
/set $API_KEY = "sk-abc123"Only $var can be declared as requirements. {var} is runtime
data — it doesn’t make sense to require it before the app runs.
Setting variables
Section titled “Setting variables”/set $VAR — persistent variables
Section titled “/set $VAR — persistent variables”AI or human sets persistent variables with /set:
<𝒞=string:app:weather:korea>/set $API_KEY = "sk-abc123"</𝒞>The scope is determined by the current topic:
| Topic | Stores in |
|---|---|
main | Not allowed for $VAR; use an app topic |
app:weather | App scope (apps/weather/env.json) |
app:weather:korea | Config scope (apps/weather/korea/env.json) |
Multiple variables at once:
<𝒞=string:app:weather>/set$API_KEY = "sk-abc123"$DEFAULT_REGION = "KR"</𝒞>/set {var} — session variables
Section titled “/set {var} — session variables”AI can also set session variables directly, though the recommended path is through response templates:
/set {city} = "Seoul"For multiline values, use a fenced code block with the variable name:
<𝒞=string:app:search>/set```{prompt}You are a research assistant.Summarize the results in bullet points.Keep it under 200 words.</𝒞>
Single and double quotes both work for inline values:/set {region} = ‘TH’ /set $REGION = “TH”
### Listing variables
`/set` with no arguments shows all variables in the current scope:/set → {city} = “Seoul” → {temp} = “24” → $API_KEY = “sk-abc123” → $DEFAULT_REGION = “KR”
---
## Substitution
Both variable types are substituted by the String runtime atexecution time.
**In action definitions (document-level):**
````markdown```act.searchGET https://api.weather.com/search?key=$API_KEY name: string (required)Here `$API_KEY` is resolved from the EnvStore when the action runs.
**In AI commands (flag values):**
```/act.search --name {city}```
Here `{city}` is resolved from the session. The AI references thevariable; String substitutes.
**In response templates:**
````markdown```act.search.response{city} = {Response.body.name}Current weather in {city}: {Response.body.description}```Only {var} can be assigned in response templates. $var cannot
be assigned here — it’s configuration, not runtime data.
Substitution summary:
| Where | {var} | $var |
|---|---|---|
| Action definition (URI, headers) | Substituted | Substituted |
| AI command flag values | Substituted | Substituted |
| Response template assignment | Yes ({var} = {Response...}) | No |
| Response template output | Substituted | Not applicable |
State as application
Section titled “State as application”Traditional applications separate code from state and manage state through databases, session stores, and caches. String collapses this into two layers.
An SFMD document defines actions (code). Variables hold state —
{var} for runtime data, $var for configuration. Together, they
form an application:
Document (actions + templates) +Variables ({var} session + $var persistent) =ApplicationConsider an email client:
/open app:gmail:work
Session state (from actions): {token} = "ya29.abc..." ← from act.login.response {inbox_count} = "12" ← from act.refresh.response {current_folder} = "inbox" ← from act.switch.response
Persistent config (from /set or config file): $GMAIL_CLIENT_ID = "..." ← set once, used by act.login $GMAIL_CLIENT_SECRET = "..." ← set once, used by act.loginThe document defines what actions exist (login, refresh, compose,
search). Session {var} tracks where the agent is (inbox, 12
messages, authenticated). Persistent $var holds credentials set
once and reused across sessions.
The AI navigates it the same way it navigates any String document —
/open to see, /act to do. The difference is that state
accumulates across turns, making each action aware of what came
before.
Summary
Section titled “Summary”| Concept | Rule |
|---|---|
{variable} | Session-scoped, set by response templates, ephemeral |
$variable | Persistent, set by human or AI, file-backed |
| Response | Last action result, overwritten on next action |
| Response template | Can assign {var} only — not $var |
requires: | Declares required app $var — not {var} |
$var cascade | config scope > app scope |
/set | Sets {var} (session) or $var (persistent) |
| Core insight | Document + variables = application |