@lotics/cli 0.315.18 → 0.316.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +37 -24
- package/README.md +42 -37
- package/dist/src/cli.js +27215 -48999
- package/dist/src/client.d.ts +12 -88
- package/dist/src/client.js +10 -74
- package/dist/src/invocation.d.ts +1 -1
- package/docs/agent_surface.md +121 -114
- package/docs/app_architecture.md +39 -0
- package/docs/app_bindings.md +24 -62
- package/docs/building_an_app.md +97 -81
- package/docs/cli_reference.md +18 -19
- package/docs/data_model.md +129 -183
- package/docs/design.md +156 -170
- package/docs/document_templates.md +125 -99
- package/docs/examples.md +1599 -1238
- package/docs/field_values.md +14 -11
- package/docs/filters.md +47 -9
- package/docs/first_build.md +180 -0
- package/docs/getting_started.md +15 -0
- package/docs/knowledge_docs.md +73 -209
- package/docs/migration.md +22 -26
- package/docs/workflow_steps.md +22 -579
- package/docs/workflows.md +407 -208
- package/docs/working_in_a_workspace.md +60 -0
- package/package.json +1 -1
package/docs/app_bindings.md
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
|
-
# App bindings — the queries
|
|
1
|
+
# App bindings — the queries and workflows an app calls
|
|
2
2
|
|
|
3
|
-
A custom-code app reaches workspace data through
|
|
3
|
+
A custom-code app reaches workspace data through two kinds of binding, each declared on the app
|
|
4
4
|
under an alias — a JS identifier (`/^[a-zA-Z_$][a-zA-Z0-9_$]*$/`):
|
|
5
5
|
|
|
6
6
|
| Binding | Declared with | The app calls it with | An agent calls it with |
|
|
7
7
|
|---|---|---|---|
|
|
8
8
|
| Query | `set_app_queries` (CLI, chat) | `useQuery("<alias>", params?)` | `run_app_query` |
|
|
9
9
|
| Workflow | `set_app_workflow` | `useWorkflow("<alias>")({ ...inputs })` | `run_app_workflow` |
|
|
10
|
-
| Agent | `set_app_agent` (CLI, chat) | `useAgentRun("<alias>")({ ...inputs })` | — |
|
|
11
10
|
|
|
12
11
|
Each runs under the app's authority (`{ type: "app", app_id }`), the app owner's reach — never the
|
|
13
12
|
calling member's. Declaring one needs the app's owner or an admin. A write is live: the app's next
|
|
14
|
-
call uses it. `
|
|
13
|
+
call uses it. `get_app` lists an app's aliases with their params and inputs.
|
|
15
14
|
|
|
16
15
|
Each reader returns a fingerprint of what it read — `get_app_workflow`'s `body_sha`, and on the CLI and in chat
|
|
17
|
-
`get_app_query`'s `sha`
|
|
16
|
+
`get_app_query`'s `sha` — and a write built on that read passes it back:
|
|
18
17
|
`set_app_queries`' `expected_shas` (alias → `sha`), `set_app_workflow`'s `expected_body_sha`,
|
|
19
|
-
`
|
|
18
|
+
`remove_app_binding`'s `expected_sha`. The write is
|
|
20
19
|
refused, and writes nothing, when the binding changed since; omit it for an unconditional write.
|
|
21
20
|
|
|
22
21
|
## Queries
|
|
@@ -66,9 +65,15 @@ Filters (`from_table.filter`, `filter.predicate`, an aggregate's `filter`) are t
|
|
|
66
65
|
`filters` reference — over the input's columns, taking `{{params.x}}` as values. `search` matches
|
|
67
66
|
every text-bearing field of the row, accent- and case-insensitive.
|
|
68
67
|
|
|
69
|
-
A
|
|
68
|
+
A query names a table's fields by the alias its document states, wherever a node reads the table directly (a
|
|
69
|
+
`from_table`'s filter and sort, the `project`, `filter`, `group`, `window`, `unnest` or `unpivot` over it) and a select
|
|
70
|
+
condition's values by the option's alias. A column a `project` writes answers under the alias it was written with; every
|
|
71
|
+
name past a `project` or an aggregate is that column's own. A `union` whose arms read one alias from different tables
|
|
72
|
+
projects each arm's columns under one name first. The query is stored by key and read back by alias.
|
|
73
|
+
|
|
74
|
+
A `project` column is a field's alias as a bare string (output name and type come from the field), or
|
|
70
75
|
`{ output?, type?, source, writable_target?, limit? }` to rename, compute or bound one. A computed
|
|
71
|
-
`source` is a field
|
|
76
|
+
`source` is a field's alias, `{ literal }`, `{ eq: [a, b] }`, `{ neq: [a, b] }`, `{ isEmpty }`,
|
|
72
77
|
`{ isNotEmpty }`, `{ coalesce: [...] }`, `{ concat: [...] }`, `{ record_id: true }`,
|
|
73
78
|
`{ link: { source, field } }` (a field of the first linked record),
|
|
74
79
|
`{ link_agg: { source, field, operation } }` (`sum`, `avg`, `min`, `max`, `count`, `string_agg`
|
|
@@ -92,7 +97,7 @@ name → `{ type, required?, description?, …per type }`. An entry is required
|
|
|
92
97
|
|---|---|
|
|
93
98
|
| `text`, `number`, `boolean`, `date`, `datetime`, `email` | — |
|
|
94
99
|
| `record_link` | `table_id`; `multi?`; `max?` (with `multi`, the most ids one call carries) |
|
|
95
|
-
| `select` | exactly one of `options: [{ label, value }]` or `field: "
|
|
100
|
+
| `select` | exactly one of `options: [{ label, value }]` or `field: "<table id>.<alias>"` (a select field whose CURRENT options back it); `multi?` |
|
|
96
101
|
| `date_range` | `include_time?` |
|
|
97
102
|
| `member` | `multi?`; `group?` (a member group the value must belong to) |
|
|
98
103
|
| `file` | `multi?` — an uploaded `fil_…` id, for a files field |
|
|
@@ -113,7 +118,7 @@ name → `{ type, required?, description?, …per type }`, every entry present u
|
|
|
113
118
|
|---|---|
|
|
114
119
|
| `text`, `number`, `boolean`, `date`, `datetime`, `email`, `json` | — |
|
|
115
120
|
| `record_link` | `table_id`, `multi?` |
|
|
116
|
-
| `select` | exactly one of `options: [{ label, value }]` or `field: "
|
|
121
|
+
| `select` | exactly one of `options: [{ label, value }]` or `field: "<table id>.<alias>"`; `multi?` |
|
|
117
122
|
| `object` | `fields` |
|
|
118
123
|
| `array` | `items` |
|
|
119
124
|
|
|
@@ -124,12 +129,13 @@ keeps the type tracking the live field.
|
|
|
124
129
|
## Workflows
|
|
125
130
|
|
|
126
131
|
`set_app_workflow` binds a workflow body to an alias: `{ app_id, alias, source, inputs?, outputs?,
|
|
127
|
-
name?, description? }`. On an app
|
|
128
|
-
`
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
refused as an alias the app does
|
|
132
|
+
name?, description? }`. On an app written as a tree the alias is one the tree names — a Workflow node's
|
|
133
|
+
`key` or an Act's `workflow` — and the node states `inputs`, `outputs` and `description`
|
|
134
|
+
(`model/workflow-node`, whose value shapes differ from the **Inputs** below): a call stating any of
|
|
135
|
+
them for it is refused, and outputs the node leaves out are derived from the body. An alias the tree
|
|
136
|
+
names nowhere is archived by the next `set_app`. A Workflow node runs only for someone who holds the
|
|
137
|
+
app: on an app shared by a public link, a visitor's call of it is refused as an alias the app does
|
|
138
|
+
not have. This page's `inputs` and `outputs` are for a custom-code app.
|
|
133
139
|
|
|
134
140
|
- `source` is the body with no `on({...})` trigger — the app is the trigger. It reads each declared
|
|
135
141
|
input as `trigger.app_workflow.inputs.<name>`; no record is in scope, so it reads each one by id
|
|
@@ -145,50 +151,6 @@ refused as an alias the app does not have.
|
|
|
145
151
|
and writes nothing. A call naming a live alias replaces its body in place, keeping its run history.
|
|
146
152
|
- A call whose payload does not match `inputs` is refused before the body runs.
|
|
147
153
|
|
|
148
|
-
`get_app_workflow` reads the body back; `
|
|
149
|
-
|
|
154
|
+
`get_app_workflow` reads the body back; `set_app_workflow` with `verify_only` and `record_id` or `preview_inputs`
|
|
155
|
+
rehearses a draft before binding; on the CLI and in chat, `remove_app_binding` with
|
|
150
156
|
`kind: "workflow"` unbinds it.
|
|
151
|
-
|
|
152
|
-
## Agents
|
|
153
|
-
|
|
154
|
-
On the CLI and in chat, `set_app_agent` binds a streaming tool-loop agent to an alias. A run streams its work to the app,
|
|
155
|
-
returns a typed result and keeps a run history per session.
|
|
156
|
-
|
|
157
|
-
The declaration:
|
|
158
|
-
|
|
159
|
-
- `instructions` — the task, every run. A new alias needs it.
|
|
160
|
-
- `tool_names` — the tools it may call, from these and no other: `analyze_pdf_template`,
|
|
161
|
-
`code_edit_file`, `code_exec`, `code_read_file`, `code_write_file`, `excel_create_file`,
|
|
162
|
-
`excel_find_cells`, `excel_format_range`, `excel_get_range`, `excel_update_range`,
|
|
163
|
-
`generate_bank_qr_code`, `generate_document`, `generate_image`, `generate_qr_code`, `get_template`,
|
|
164
|
-
`grep_knowledge`, `list_banks`, `list_knowledge`, `lookup_business`, `query_templates`,
|
|
165
|
-
`read_knowledge`, `run_app_query`, `run_app_workflow`, `validate_excel_template`,
|
|
166
|
-
`vietcombank_convert_currency`, `view_files`, `word_create_document`, `word_find_text`,
|
|
167
|
-
`word_get_content`, `word_get_table_data`, `word_insert_conditional`, `word_insert_loop`,
|
|
168
|
-
`word_replace_text`. `[]` is a reasoning-only agent. A few fields of a document read well through
|
|
169
|
-
the `excel_*` / `word_*` tools; a cross-check, reconciliation or transform a decision rests on
|
|
170
|
-
belongs in the code tools.
|
|
171
|
-
- `query_aliases` / `workflow_aliases` — the app's own queries and workflows it may call through
|
|
172
|
-
`run_app_query` / `run_app_workflow`. They are its whole reach over records: no agent tool takes a
|
|
173
|
-
`table_id`, so a read is bounded by the query's tables, rows and columns, and a write goes through
|
|
174
|
-
the app's own workflow.
|
|
175
|
-
- `knowledge_doc_ids` — knowledge docs it may read with `grep_knowledge` / `read_knowledge`
|
|
176
|
-
(declare them in `tool_names`). Each must be one the app owner and you can use. Nothing is
|
|
177
|
-
inlined and size does not matter. The list is the agent's whole reach — `list_knowledge` finds no
|
|
178
|
-
doc outside it — so name a doc's id in `instructions` to point it there. `code_exec` computes
|
|
179
|
-
across a corpus (counting, cross-referencing); reading needs only the knowledge tools.
|
|
180
|
-
- `model_tier` — `haiku`, `sonnet` or `opus`. Omitted, the run follows the platform's default tier
|
|
181
|
-
and moves with new models; pin one only as a tested choice, and never `haiku`, a utility tier.
|
|
182
|
-
- `effort_level` — `low`, `medium`, `high`, `xhigh` or `max`, one the pinned tier supports; it
|
|
183
|
-
needs `model_tier`.
|
|
184
|
-
- `prefix_cache_ttl` — `5m` (the default) or `1h`. `1h` doubles the cache write price and pays only
|
|
185
|
-
when runs land 5 to 60 minutes apart, as a scheduled sweep does.
|
|
186
|
-
- `inputs` — the per-run payload (see **Inputs**); omitted, the payload is untyped.
|
|
187
|
-
- `outputs` — the structured result (see **Outputs**), checked before the run is saved and typed
|
|
188
|
-
as `run.output` in the app; omitted, the result is the final message.
|
|
189
|
-
- `writes` — where the outputs land: `{ table_id, row, fields }`, `row` naming a single
|
|
190
|
-
`record_link` input to `table_id` and `fields` mapping each output name to a field key of that
|
|
191
|
-
table. A result that validates is written to that row under the app's authority.
|
|
192
|
-
|
|
193
|
-
Send only what you change: an omitted field keeps its stored value, `null` clears an optional one.
|
|
194
|
-
On the CLI and in chat, `get_app_agent` reads one back; `remove_app_binding` with `kind: "agent"` deletes it.
|
package/docs/building_an_app.md
CHANGED
|
@@ -1,33 +1,34 @@
|
|
|
1
1
|
# Building an app, end to end
|
|
2
2
|
|
|
3
|
-
The other references here describe **
|
|
3
|
+
The other references here describe **grammars** — what a table document or an app tree may state, what a tool takes. This
|
|
4
4
|
one describes the **sequence**: the order the steps go in, and why. Read it once for the shape, then
|
|
5
5
|
reach for the area doc (`lotics docs`) whenever you need the detail.
|
|
6
6
|
|
|
7
|
-
**The live app is the only edit surface.** Every change to an app — applying a
|
|
8
|
-
build, setting one query or workflow — mints a new version of it
|
|
9
|
-
|
|
7
|
+
**The live app is the only edit surface.** Every change to an app — applying a tree, deploying a
|
|
8
|
+
build, setting one query or workflow — mints a new version of it. To go back, apply the earlier tree
|
|
9
|
+
again (or deploy the earlier source). Nothing about an app lives in a local directory the platform
|
|
10
|
+
reads back.
|
|
10
11
|
|
|
11
|
-
**
|
|
12
|
-
|
|
12
|
+
**Going back restores the app, not the data.** A table change, and every row a workflow wrote while
|
|
13
|
+
you tried it, stay where they are. Try a write on a throwaway record.
|
|
13
14
|
|
|
14
15
|
---
|
|
15
16
|
|
|
16
17
|
## 1 — Two kinds of app
|
|
17
18
|
|
|
18
|
-
- **An app stated
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
reaches them, its
|
|
22
|
-
`model/
|
|
19
|
+
- **An app stated as a tree** — the default, and the right one for almost every job. The tree (a
|
|
20
|
+
JSON file, one per app) is a List of one table, whose rows are recognised by the
|
|
21
|
+
table's own `records`, and the Record its rows open, with the logs that stand beside it (further Lists at the root, which a change may add at any time, read by the organization's own members alone): its columns and filters, its Sections in the order its work
|
|
22
|
+
reaches them, its Acts and the Checks that guard them (`lotics docs model/app-tree`,
|
|
23
|
+
`model/list-node`, `model/section-node`, `model/act-node`, `model/check-node`). The platform
|
|
23
24
|
compiles each app and draws it with the runtime every such app shares, so how each piece looks is
|
|
24
|
-
the platform's, one way per concept, and no
|
|
25
|
-
generated workflow that re-checks on the server what the
|
|
26
|
-
- **A custom-code app** — React you write, for a surface the
|
|
25
|
+
the platform's, one way per concept, and no prop changes it. Every write the app makes is a
|
|
26
|
+
generated workflow that re-checks on the server what the tables state.
|
|
27
|
+
- **A custom-code app** — React you write, for a surface the tree's components cannot state. It
|
|
27
28
|
reads and writes through `@lotics/app-sdk` (queries, workflows, files, AI) and draws with whatever
|
|
28
29
|
React you choose.
|
|
29
30
|
|
|
30
|
-
## 2 — Clarify what is being asked, before
|
|
31
|
+
## 2 — Clarify what is being asked, before designing it
|
|
31
32
|
|
|
32
33
|
**A metric name is not a definition.** "Revenue", "in stock", "active", "overdue" — each is a
|
|
33
34
|
business rule the person asking owns, and the cost of guessing is a screen that is confidently
|
|
@@ -46,76 +47,93 @@ invisible in review: every later artifact is correct with respect to the wrong d
|
|
|
46
47
|
|
|
47
48
|
## 3 — The data model, before any app
|
|
48
49
|
|
|
49
|
-
Get this wrong and nothing above it can be precise. Each
|
|
50
|
+
Get this wrong and nothing above it can be precise. Each kind of thing is its own table with links into
|
|
50
51
|
the spine; attributes and evidence are fields on their owner. A single table with a `type` column
|
|
51
|
-
standing in for three
|
|
52
|
+
standing in for three kinds of thing collapses the distinctions every later query needs.
|
|
52
53
|
|
|
53
54
|
**Verify real VALUES, never just that a field exists.** `lotics run query_records` a sample and
|
|
54
55
|
look at fill rates — a field that is present and empty on 90% of rows will not support the screen
|
|
55
56
|
you are about to design.
|
|
56
57
|
|
|
57
|
-
**That includes imagery.** If the
|
|
58
|
+
**That includes imagery.** If the thing has a likeness — a product, a property, a vehicle, a
|
|
58
59
|
person — its picture is the strongest identifier a register row can carry, and an empty image field
|
|
59
60
|
is a data gap to fill before you design around it: `lotics file upload`, then `update_records`.
|
|
60
61
|
|
|
61
|
-
**
|
|
62
|
-
adding any, because the fact is often already there in another shape: a place written as text
|
|
63
|
-
beside a link to the place record, a status word beside the select that decides it, a total beside
|
|
64
|
-
the formula that computes it. Two columns for one fact never stay equal. Prefer the link, the
|
|
65
|
-
select, or the formula, and compose the text when you READ.
|
|
66
|
-
|
|
67
|
-
**Match on ids and option keys, never on rendered text.** Resolve a name to its `rec_…` or `opt_…`
|
|
62
|
+
**Match on ids and option aliases, never on rendered text.** Resolve a name to its `rec_…` or its option alias
|
|
68
63
|
once, at the boundary, and compare those. Treat an unresolved name as UNKNOWN, never as a wildcard.
|
|
69
64
|
|
|
70
|
-
**How the tables RELATE is `lotics docs data_model`** — read it before designing a
|
|
71
|
-
decisions outlive any one app.
|
|
65
|
+
**How the tables RELATE, and one fact in one column, is `lotics docs data_model`** — read it before designing a
|
|
66
|
+
schema; those decisions outlive any one app.
|
|
72
67
|
|
|
73
|
-
|
|
74
|
-
distinction. Read what a field MEANS before you remove it.
|
|
68
|
+
## 4 — An app stated as a tree
|
|
75
69
|
|
|
76
|
-
|
|
70
|
+
Tables first, then apps. `set_tables` takes a list of table documents, one or many, and writes them all or
|
|
71
|
+
none; `set_app` takes one app per call. Each creates when it is given no id and edits when it is given one.
|
|
77
72
|
|
|
78
73
|
```
|
|
79
|
-
lotics docs model #
|
|
80
|
-
lotics docs design #
|
|
81
|
-
lotics
|
|
82
|
-
lotics
|
|
83
|
-
lotics
|
|
84
|
-
lotics
|
|
74
|
+
lotics docs model # the keys of a table document and the app tree's components, with a worked example
|
|
75
|
+
lotics docs design # the treatment per kind of row
|
|
76
|
+
lotics run set_tables @tables.json # one call, every table: {"tables": [...]}
|
|
77
|
+
lotics run set_app @orders.call.json # one app: {"body": <the App tree>} to create (add "dry_run": true to check)
|
|
78
|
+
lotics app apply orders.app.json --check # the same tool from a bare tree file: what would refuse it, writing nothing
|
|
79
|
+
lotics app apply orders.app.json # no app known: creates it; known: edits it to the file
|
|
80
|
+
lotics app pull <app_id> # the app's tree, as ./<name>.app.json
|
|
85
81
|
```
|
|
86
82
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
`
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
83
|
+
A new workspace is built in one order: one `set_tables` call with the tables, then every `set_app`
|
|
84
|
+
call, then fixes. One call carries at most 50 tables, 200 first rows a table, and 2000 rows and 2000
|
|
85
|
+
documents in all; a workspace of more tables is built in further calls, each naming the tables already made by
|
|
86
|
+
their ids. The example files at `https://lotics.ai/presets/index.json` are read and their tables passed to
|
|
87
|
+
`set_tables`.
|
|
88
|
+
|
|
89
|
+
A table is a document: its `fields` are keyed by alias, and a link field names the table it
|
|
90
|
+
points at by id, or by the `ref` of another table of the same call, and states `multi`. A
|
|
91
|
+
two-way link states `pair` on one table, as `true` or as a free alias, and the partner is made on the other; a many-to-many link states both sides. A table is edited by an RFC 7396 merge patch of its document:
|
|
92
|
+
what the patch leaves alone is left alone and `null` deletes. `dry_run` checks the call and writes
|
|
93
|
+
nothing; a refusal lists everything wrong in it at once.
|
|
94
|
+
|
|
95
|
+
The App node is the app: its `name`, `description`, `icon` and `color` are props of it, and no other input of
|
|
96
|
+
`set_app` states them. It names the tables and templates it uses by their aliases (an entity is a table's alias, a `template` a template's alias);
|
|
97
|
+
`set_app` fills the `tables` and `templates` maps by id from them and `get_app` shows aliases (`lotics docs model/app-node`). A call that creates
|
|
98
|
+
is `{"body": <the App tree>}`; one that changes a part is `{"app_id": "app_…", "nodes": {"<address>": <patch>}}`,
|
|
99
|
+
each address a node's path of keys (`get_app` prints every one), a patch a JSON merge patch of that node's props
|
|
100
|
+
and `null` removing the node; the App's own props change through `body`. A call stating `base_version_id` — the
|
|
101
|
+
version you read — is refused when the app has moved on. The old body grammar (`register`, `record`, `acts`,
|
|
102
|
+
`edits`) is refused with a sentence pointing here.
|
|
103
|
+
|
|
104
|
+
A tree states what it means once: a statement equal to what the runtime draws anyway (a column the row already shows) is kept,
|
|
105
|
+
drawn once and noted, while a statement that conflicts is refused. A register List's `label` is the register's heading in the app's lists; its rows are named by the table's own label and singular.
|
|
106
|
+
`type` may be omitted, and a Section, Side or Page
|
|
107
|
+
without a `key` is keyed by its title's slug, written once and kept.
|
|
108
|
+
|
|
109
|
+
`app apply` with no app known creates one from the file, named by the tree's `name`, and records its id and
|
|
110
|
+
version in `<file>.lotics.json` (`app pull` writes the same file), so the next apply of that file edits that
|
|
111
|
+
app; `--app <app_id>` names it instead, and is refused for a file whose record names another app. A known app
|
|
112
|
+
is read first and only what the file changes is sent, as a JSON merge patch (RFC 7396): a key the file drops
|
|
113
|
+
is removed, a list is replaced whole, and what the file leaves alone is left alone. A file pulled at a
|
|
114
|
+
version the app has since left is refused, naming the version: pull the current tree into another path and
|
|
115
|
+
carry your edits over. Applying a tree the app already has mints nothing.
|
|
116
|
+
`--check` sends the same change as `dry_run`: what would refuse the tree, the notes on it, and the permissions the
|
|
117
|
+
write would grant and remove, writing nothing. Every write's answer states those permissions: the saves, adds,
|
|
118
|
+
Deletes and act writes the new tree grants and removes, from the previous tree to the new one.
|
|
119
|
+
|
|
120
|
+
**Nothing is written into the tree for you.** The answer notes a treatment the rows call for that the tree leaves out —
|
|
121
|
+
Readings over its rows, a record's Sections, an Act per status move, a picture where its rows hold photos
|
|
122
|
+
(`lotics docs design`) — and the tree states the ones the job needs.
|
|
123
|
+
|
|
124
|
+
**The tree changes after the app exists, and applying it again is how it lands.** Pull the tree, edit
|
|
125
|
+
the file, apply it; or send `nodes` for one part. An Act whose write the tree cannot say names its own `workflow`; that workflow's code is
|
|
105
126
|
the live one, and `lotics run set_app_workflow` changes it. A job no press starts — an agent or a
|
|
106
|
-
service handing the app a file or a list of rows — is a
|
|
107
|
-
(`lotics docs model/
|
|
108
|
-
set_app_workflow` sets its
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
family and how it is called. A question the screen never asks is a query the app declares in `queries`
|
|
117
|
-
(`lotics docs model/queries`), beside its `workflows`. Both are part of the design: step 5 of
|
|
118
|
-
`lotics docs design`.
|
|
127
|
+
service handing the app a file or a list of rows — is a Workflow node the app declares
|
|
128
|
+
(`lotics docs model/workflow-node`): the node states what it takes and hands back, and `lotics run
|
|
129
|
+
set_app_workflow` sets its code with `inputs` and `outputs` left out.
|
|
130
|
+
|
|
131
|
+
**What a person reads and does in the app, an agent reads and does through it.** Writing the tree generates a
|
|
132
|
+
query for every List, record, child List and figure the screen reads, and a workflow for every add, edit,
|
|
133
|
+
removal and act — `lotics run get_app` lists them, and `lotics docs agent_surface` names each
|
|
134
|
+
family and how it is called. A question the screen never asks is a Query node the app declares
|
|
135
|
+
(`lotics docs model/query-node`), beside its Workflow nodes. Both are part of the design: `lotics docs
|
|
136
|
+
agent_surface`, § Declaring more.
|
|
119
137
|
|
|
120
138
|
## 5 — A custom-code app
|
|
121
139
|
|
|
@@ -134,7 +152,7 @@ version. `useQuery("<alias>")` and `useWorkflow("<alias>")` call them. A deploy
|
|
|
134
152
|
and carries every binding forward unchanged.
|
|
135
153
|
|
|
136
154
|
**Named queries.** Author them as `kind: "project"` with a `filter`, naming each projected column
|
|
137
|
-
(`{ "source": "
|
|
155
|
+
(`{ "source": "amount", "output": "total" }`), so a row reads as `r.total`. Scope per-user reads with
|
|
138
156
|
`is_current_member` **inside the query** — a member id passed from the client is chosen by the
|
|
139
157
|
caller. Write one `description` per alias: it is the line a chat or MCP caller chooses by.
|
|
140
158
|
|
|
@@ -147,12 +165,12 @@ that caller.
|
|
|
147
165
|
Static checks prove parse, types and names — they evaluate nothing. Rehearse a write first:
|
|
148
166
|
|
|
149
167
|
```
|
|
150
|
-
lotics run
|
|
168
|
+
lotics run set_app_workflow '{"app_id":"app_…","alias":"…","source":"…","verify_only":true,"record_id":"rec_…"}'
|
|
151
169
|
```
|
|
152
170
|
|
|
153
|
-
It walks the
|
|
154
|
-
|
|
155
|
-
|
|
171
|
+
It walks the draft through the same engine an Act's preview uses, under the app's authority: reads are real,
|
|
172
|
+
writes and sends are said and not made, and it returns what the run would answer plus the calls it would make.
|
|
173
|
+
Pass `preview_inputs` for the values of a workflow that takes inputs and no row.
|
|
156
174
|
|
|
157
175
|
Then run it end to end, on a throwaway record:
|
|
158
176
|
|
|
@@ -184,17 +202,15 @@ where a table left columns off at that width, `shed` names them.
|
|
|
184
202
|
|
|
185
203
|
The app is drawn live, as you, read-only: a screen that writes when it opens shows that write
|
|
186
204
|
refused, and `errors` lists what failed on the page. A version that looks wrong is one
|
|
187
|
-
`lotics
|
|
205
|
+
`lotics app apply` away from the one before: apply the earlier tree again.
|
|
188
206
|
|
|
189
|
-
For an app a
|
|
190
|
-
|
|
191
|
-
on it
|
|
192
|
-
leaves it (`draft`). `verdict_unread` says why none was read.
|
|
207
|
+
For an app stated as a tree, `verdict` comes beside the images: what a check of that app finds now,
|
|
208
|
+
beside its rows — what the next write would refuse it for and the notes
|
|
209
|
+
on it. `verdict_unread` says why none was read.
|
|
193
210
|
|
|
194
|
-
The app's `name`, `icon`, `
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
the app, and `lotics model pull` reads them back as the app holds them. The
|
|
211
|
+
The app's `name`, `icon`, `color` and `description` are props of the App node; an app
|
|
212
|
+
stating no `color` gets the platform's grey tile. `lotics run set_app` with the app's id and a `body` patch changes them
|
|
213
|
+
on the app, and `lotics app pull` reads the tree back as the app holds it. The
|
|
198
214
|
`description` heads the capability listing the member's chat agent reads on **every** turn, so a
|
|
199
215
|
standing process the app expects that agent to carry out belongs there and nowhere else.
|
|
200
216
|
|