Skip to content

Actions

Actions are how AI does things through String — calling APIs, running commands, submitting data. An action is declared inside a document, invoked with a single line, and its response comes back as Markdown.


When AI reads a document, actions appear as one-line hints:

# Weather Dashboard
**Search for a city:**
`/act.search_city --name "{City Name}"`
**Set a weather alert:**
`/act.create_alert --condition "{rain|snow|temp}"`

That’s all the AI needs. The name, the flags, the expected values — readable at a glance. The AI doesn’t see the underlying implementation. It doesn’t need to.

If the AI wants more detail, it asks:

/act.search_city --help

String returns the full schema — parameter types, which are required, what the action does. But this is on-demand, not forced into context.


Action definitions live inside the same document, in fenced code blocks with an act. info string:

```act.search_city
GET https://api.weather.com/search
name: string (required) "City name to search"
unit: string (optional) "celsius|fahrenheit"
```

String parses these blocks and registers the actions. The blocks are not shown to the AI when it reads the document — they are metadata, not content. In a standard Markdown viewer, they render as ordinary code blocks. Graceful degradation.


The first line of the definition block declares the HTTP method (or CLI for shell commands).

GET https://api.weather.com/search
name: string (required) "City name"
unit: string (optional) "celsius|fahrenheit"

Parameters are appended as a query string. The AI invokes it as:

/act.search_city --name "Seoul"

String sends GET https://api.weather.com/search?name=Seoul.

POST https://api.weather.com/alerts
city: string (required) "Topic city"
condition: string (required) "rain|snow|temp"
threshold: number (optional) "Trigger value"

Parameters are sent as a JSON body. The AI invokes it as:

/act.create_alert --city "Seoul" --condition "rain"

String sends a POST with {"city": "Seoul", "condition": "rain"}.

PUT https://api.weather.com/alerts/{alert_id}
alert_id: string (required) "Alert ID"
condition: string (required) "New condition"
PATCH https://api.weather.com/users/{user_id}/settings
user_id: string (required) "User ID"
unit: string (optional) "celsius|fahrenheit"

Same as POST — parameters are sent as a JSON body. PUT replaces the entire resource, PATCH updates specific fields. Standard REST semantics.

DELETE https://api.weather.com/alerts/{alert_id}
alert_id: string (required) "Alert ID"

Same as GET — parameters are appended as a query string (or substituted into the URL path).

CLI kubectl apply -f {manifest}
manifest: path (required) "Kubernetes manifest file"

String executes the command with parameters substituted. The AI invokes it as:

/act.deploy --manifest ./app.yaml

String runs kubectl apply -f ./app.yaml.


HTTP actions can declare custom headers using curl-style -H flags on the first line:

GET https://gmail.googleapis.com/v1/users/me/messages -H "Authorization: Bearer {access_token}"
maxResults: number (optional)
q: string (optional) "Gmail search query"
POST https://api.github.com/repos/{repo}/issues -H "Authorization: token $GITHUB_TOKEN" -H "Accept: application/vnd.github.v3+json"
title: string (required)
body: string (optional)

Multiple headers: -H "..." -H "..." on the same line. Both {var} (session) and $var (persistent) are both substituted in header values.

String does not inject default headers for actions — only the declared -H headers are sent. This is standard REST behavior. (Accept: text/markdown is only injected for /open, not /act.)


Actions follow GNU/POSIX CLI conventions. All of the following forms are valid:

/act.search_city --name "Seoul"
/act.search_city --name=Seoul
/act.search_city -n Seoul
/act.search_city Seoul
  • Long flag, separate value: --name value
  • Long flag, attached value: --name=value (GNU style)
  • Short alias: -n value (when the action declares name, -n)
  • Boolean long: --verbose (no value = true)
  • Quoted values: --name "value with spaces"

Bare values (no leading -- or -) bind to the action’s required fields, in declaration order. Optional fields are never bound positionally — pass them by flag.

For an action declared as:

prompt, -p: string (required) "Image description"
filename, -f: string (required) "Output path"
resolution, -r: string "1K, 2K, or 4K" = "1K"

these all produce the same payload:

/act.generate --prompt "a serene japanese garden" --filename out.png
/act.generate "a serene japanese garden" out.png
/act.generate "a serene japanese garden" --filename out.png
/act.generate -p "a serene japanese garden" -f out.png

A positional value never overrides a flag: if a required field has already been set by --name value, it is skipped when binding the remaining positionals. Passing more positionals than there are unfilled required fields is an error.

A bare -- token ends option processing. Everything after it is treated as a positional, even if it starts with -. This is the standard POSIX escape hatch for values that look like flags:

/act.search -- --tricky-query

Here --tricky-query binds to the first required field rather than being parsed as an unknown long flag.

/act and /action are interchangeable; dot-notation and space-separated forms are both valid:

/act.search_city --name "Seoul"
/act search_city --name "Seoul"
/action.search_city --name "Seoul"
/action search_city --name "Seoul"

Dot-notation is preferred in documentation and action hints for readability.


By default, String passes the raw response to the AI as Markdown. This works for many cases — the AI reads the result and decides what to do next.

For structured responses, a response template can format the output and store values for later use. Response templates are defined in a companion code block:

```act.search_city.response
{temp} = {Response.body.temperature}
{city} = {Response.body.city}
## {city}
- Temperature: {temp}°C
- Condition: {Response.body.condition}
- Humidity: {Response.body.humidity}%
```

Assignment lines{var} = {expression}

Lines matching {identifier} = ... are variable assignments. The value is extracted from the response and stored as a session variable. Assignment lines produce no output.

{temp} = {Response.body.temperature}
{city} = {Response.body.city}

Available response references:

ReferenceWhat it returns
{Response.body.field}A specific field from the response body
{Response.body}The entire response body
{Response.status}HTTP status code (e.g. 200)

Output lines — everything else

All other lines are rendered as a Markdown template. Variables (both just-assigned and previously stored) are substituted.

## {city}
- Temperature: {temp}°C

Display only — no assignments, just formatting:

```act.get_status.response
**Status:** {Response.body.status}
**Uptime:** {Response.body.uptime}
```

Store only — assignments with no output lines. The action runs silently, storing values for later use:

```act.get_token.response
{auth_token} = {Response.body.token}
{expires} = {Response.body.expires_at}
```

Both — store values and display formatted output:

```act.search_city.response
{temp} = {Response.body.temperature}
{city} = {Response.body.city}
## Weather in {city}
- Temperature: {temp}°C
- Condition: {Response.body.condition}
```

Session variables persist within the topic. A value stored by one action can be used by subsequent actions or templates in the same session.


Actions and response templates enable two patterns for building String apps.

Everything in one document. Actions are defined inline, response templates format the results in place.

# Weather Dashboard
`/act.search_city --name "{City Name}"`
`/act.get_forecast --city "{City}" --days "7"`
​```act.search_city
GET https://api.weather.com/search
name: string (required)
​```
​```act.search_city.response
{city} = {Response.body.city}
{temp} = {Response.body.temperature}
## {city}
- Temperature: {temp}°C
- Condition: {Response.body.condition}
​```
​```act.get_forecast
GET https://api.weather.com/forecast
city: string (required)
days: number (optional)
​```
​```act.get_forecast.response
## {Response.body.city} — {Response.body.days}-Day Forecast
{Response.body.forecast}
​```

The AI stays on one page. Actions update the view through templates.

Actions and content spread across multiple documents. Each page is a separate .md file with its own actions.

weather-app/
├── string.md # Home — search action
├── forecast.md # 7-day forecast page
├── alerts.md # Alert management
└── nav/
└── main.md # Navigation menu

The AI navigates with /open and acts with /act. Each page declares its own actions. The forecast page has forecast-related actions. The alerts page has alert-related actions.

# string.md — navigates to forecast
/act.search_city --name "Seoul"
→ response includes [7-Day Forecast](./forecast.md)
→ AI: /open ./forecast.md
# forecast.md — has its own actions
/act.get_forecast --city "Seoul" --days "7"

Single-page works for simple, focused tools — a calculator, a status dashboard, a quick search. Multi-page works for larger systems with distinct sections — an email client, a project management tool, a documentation site.

Both patterns use the same primitives: /act for actions, /open for navigation. The difference is in how the documents are organized, not in how the AI interacts with them.


A document can declare a default action in its frontmatter:

---
default: get_weather
---

When the document is opened (/open), String automatically executes act.get_weather. The AI sees the document content and the action result together, separated by a rule:

# Weather Dashboard
Check the weather for any city.
`/act.get_weather --city "{city}"`
`/act.set_alert --condition "{condition}"`
---
## Seoul
- Temperature: 22°C
- Condition: Sunny

Document first, result after. The AI reads the page context (what this app does, what actions are available) and the live data in one turn.

If the AI runs /act.get_weather --city "Tokyo" after opening the page, only the response is returned — the document content is already in the AI’s context from the initial open.

/refresh re-renders the document and re-runs the default action, producing the combined output again.

The default is part of the document’s design, not an AI preference. A weather dashboard should load weather on open. An inbox should load messages. The author declares this intent in frontmatter.

If no default is declared, /open shows the document content only. The AI decides what to do next.


ConceptHow
Invocation/act.name --flag value — also --flag=value, short -f, positional in declaration order, -- to end options
Definition```act.name code block (parsed by String, hidden from AI)
TypesGET, POST, PUT, PATCH, DELETE, CLI — first line of definition
Headers-H "Key: Value" on first line — curl syntax, supports {var} and $var
Response template```act.name.response — optional formatting
Assignment{var} = {Response.body.field} — session variable
OutputAll non-assignment lines — rendered as Markdown
Help/act.name --help — on-demand schema
Default actiondefault: name in frontmatter — runs on /open and /refresh