cairn-pack-hello 0.2.0__tar.gz
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.
- cairn_pack_hello-0.2.0/.gitignore +3 -0
- cairn_pack_hello-0.2.0/PKG-INFO +238 -0
- cairn_pack_hello-0.2.0/README.md +227 -0
- cairn_pack_hello-0.2.0/__init__.py +11 -0
- cairn_pack_hello-0.2.0/data/sample.jsonl +20 -0
- cairn_pack_hello-0.2.0/data/train.jsonl +48 -0
- cairn_pack_hello-0.2.0/flows/assistant.md +97 -0
- cairn_pack_hello-0.2.0/flows/engine-check.yaml +23 -0
- cairn_pack_hello-0.2.0/flows/lora.yaml +25 -0
- cairn_pack_hello-0.2.0/flows/nightly-job.md +59 -0
- cairn_pack_hello-0.2.0/flows/summarise.yaml +18 -0
- cairn_pack_hello-0.2.0/hooks.py +51 -0
- cairn_pack_hello-0.2.0/pack.toml +50 -0
- cairn_pack_hello-0.2.0/pipelines/ask-the-docs.yaml +85 -0
- cairn_pack_hello-0.2.0/pipelines/nightly-ops.yaml +108 -0
- cairn_pack_hello-0.2.0/pipelines/profile-and-publish.yaml +193 -0
- cairn_pack_hello-0.2.0/pipelines/tiny-finetune.yaml +150 -0
- cairn_pack_hello-0.2.0/policies/require-review-on-publish.yaml +31 -0
- cairn_pack_hello-0.2.0/profiles/hello-infisical.toml +19 -0
- cairn_pack_hello-0.2.0/profiles/hello.toml +46 -0
- cairn_pack_hello-0.2.0/pyproject.toml +69 -0
- cairn_pack_hello-0.2.0/rag/docs/budgets.txt +7 -0
- cairn_pack_hello-0.2.0/rag/docs/gates.txt +12 -0
- cairn_pack_hello-0.2.0/rag/docs/triggers.txt +8 -0
- cairn_pack_hello-0.2.0/tools/calc_server.py +36 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: cairn-pack-hello
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Example cairn domain pack: a data, an agentic, an ml and an operations pipeline, each behind gates.
|
|
5
|
+
License: Apache-2.0
|
|
6
|
+
Requires-Python: >=3.12
|
|
7
|
+
Requires-Dist: cairndev>=0.1.0
|
|
8
|
+
Provides-Extra: all
|
|
9
|
+
Requires-Dist: cairndev[rag-sqlite-vec,runtime-dbos,ssh]>=0.1.0; extra == 'all'
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# hello-cairn: the example pack
|
|
13
|
+
|
|
14
|
+
`hello-cairn` is a domain pack: data plus a little glue, no operators. It runs on the cairn
|
|
15
|
+
engine and its builtin operator packs alone. Its four pipelines each do one thing a new user
|
|
16
|
+
recognises, and together they exercise what the engine adds around a piece of work: gates
|
|
17
|
+
answered by data or by a person, budgets, triggers, an agentic template, an MCP tool,
|
|
18
|
+
retrieval, a run hook, SSH, secrets, and an audit record.
|
|
19
|
+
|
|
20
|
+
| Pipeline | Kind | What it does |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| `profile-and-publish` | data | Profile a dataset, stop unless the profile passes, redact the summary, run a flow, stage it, ask a person, tag it, publish it, audit it. |
|
|
23
|
+
| `ask-the-docs` | agentic | Retrieve from the pack's docs, ask a model, measure the answer with a tool on the pack's own MCP server, remember it, check its length, ask a person. |
|
|
24
|
+
| `nightly-ops` | operations | Check a secret, run a report on an SSH box, carry it through a flow, propose the follow-up to a person, audit it. Nightly once enabled; a run hook writes receipts. |
|
|
25
|
+
| `tiny-finetune` | ml | Profile training rows, LoRA fine-tune a small model on CPU, gate on its held-out loss, tag it, ask a person, write its card. |
|
|
26
|
+
|
|
27
|
+
## Quickstart
|
|
28
|
+
|
|
29
|
+
Run everything from the repository root: relative paths in the pipelines' defaults resolve
|
|
30
|
+
against the engine's working directory.
|
|
31
|
+
|
|
32
|
+
### 1. Install, and point the engine at the example
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
uv sync --extra dev --extra hello --extra rag # add --extra auth if your engine needs it
|
|
36
|
+
export CAIRN_PROFILE=examples/hello-cairn/profiles/hello.toml
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`--extra rag` is what lets the engine index the docs corpus at boot (LlamaIndex ingest);
|
|
40
|
+
without it ask-the-docs still runs and retrieves nothing. `auth` is optional: `cairn dev`
|
|
41
|
+
signs you in as dev/dev without it.
|
|
42
|
+
|
|
43
|
+
`cairndev[hello]` installs this pack as the wheel `cairn-pack-hello` plus what its pipelines
|
|
44
|
+
reach beyond the base install: the sqlite-vec store (retrieval and memory), asyncssh
|
|
45
|
+
(`remote/exec`) and DBOS (`CAIRN_RUNTIME=dbos`). Without installing it,
|
|
46
|
+
`export CAIRN_PACK_PATH=examples/hello-cairn` loads the same directory as a dev pack; a dev
|
|
47
|
+
pack declares no entry points, so the run-hook listener does not fire.
|
|
48
|
+
|
|
49
|
+
`profiles/hello.toml` is the deployment profile the example runs under. It carries
|
|
50
|
+
`cairn/profiles/local-dev.toml`'s bindings for the builtin operators (memory, storage, rag,
|
|
51
|
+
mcp), but not local-dev's `eval.delta` binding (a platform operator) or its `llm_gateway` and
|
|
52
|
+
`embedder` backend entries: the model and the embedder come from the engine's environment
|
|
53
|
+
(step 3). It adds `secrets.store` → `secrets/env` (nightly-ops' secret check is a capability
|
|
54
|
+
node and compiles only with a binding, which local-dev does not carry) and the `hello-calc`
|
|
55
|
+
MCP server the pack declares. It pins no runtime, so `CAIRN_RUNTIME=dbos` selects DBOS.
|
|
56
|
+
|
|
57
|
+
### 2. See what will happen before anything runs
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
cairn pipeline explain hello-cairn/profile-and-publish -e sandbox
|
|
61
|
+
cairn validate examples/hello-cairn # with CAIRN_PROFILE set as in step 1
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`cairn validate` needs the example's profile: under local-dev the nightly-job template does
|
|
65
|
+
not compile (no `secrets.store` binding).
|
|
66
|
+
|
|
67
|
+
`explain` lists every leg with the operator class or flow file it resolves to, the gate that
|
|
68
|
+
answers it, and the budget, series and approvers.
|
|
69
|
+
|
|
70
|
+
### 3. Start an engine
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
export OPENAI_API_KEY=sk-... # ask-the-docs' model call and embeddings
|
|
74
|
+
export RAG_EMBED_PROVIDER=openai # retrieval and memory embed with OpenAI
|
|
75
|
+
export CAIRN_RAG_AUTOINDEX=1 # index rag/docs at boot (needs the `rag` extra)
|
|
76
|
+
export CAIRN_MCP_SERVERS='{"hello-calc": {"transport": "stdio", "command": "python",
|
|
77
|
+
"args": ["examples/hello-cairn/tools/calc_server.py"]}}'
|
|
78
|
+
cairn dev --engine-only --port 8471 & # sqlite engine, in-process runtime, login dev/dev
|
|
79
|
+
cairn remote login -u dev -p dev -e http://localhost:8471
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Any OpenAI-compatible endpoint works in place of a key: `OPENAI_BASE_URL`, or
|
|
83
|
+
`LLM_GATEWAY=proxy` with `LLM_GATEWAY_URL`. The engine has no built-in mock model, and
|
|
84
|
+
`CAIRN_BACKEND=mock` is the telemetry backend, not a model. `profile-and-publish` and
|
|
85
|
+
`nightly-ops` call no model.
|
|
86
|
+
|
|
87
|
+
### 4. Run each pipeline
|
|
88
|
+
|
|
89
|
+
**profile-and-publish**
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
cairn remote launch hello-cairn/profile-and-publish --environment sandbox --watch
|
|
93
|
+
cairn remote advance <pipeline_id> review # the person gate
|
|
94
|
+
cairn remote advance <pipeline_id> promote # the operator's own gate
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The pipeline stops a second time at `promote`: `artifact/promote` declares
|
|
98
|
+
`requires_approval`, and a pipeline can add a gate but never remove one an operator declares.
|
|
99
|
+
`cairn remote advance` approves either kind and records the signed-in user as the approver.
|
|
100
|
+
|
|
101
|
+
The record ends `done`: `data-ok` has verdict `data`, `review` `approved`, `promote` the
|
|
102
|
+
tag `hello-sample@published` on the flow leg's registered artifact, and `audit` a
|
|
103
|
+
hash-chained row in `CAIRN_AUDIT_LOG` (default `./audit.jsonl`). `storage/put` writes under
|
|
104
|
+
`CAIRN_ARTIFACTS_DIR` (default `~/.cairn/artifacts`). With `-i min_rows=1000` the data gate
|
|
105
|
+
stops the pipeline (`gate_blocked`) before anything is written. Launch with
|
|
106
|
+
`--environment hello-prod` and the pack's policy
|
|
107
|
+
(`policies/require-review-on-publish.yaml`) stops `stage` for an approval before it writes.
|
|
108
|
+
|
|
109
|
+
**ask-the-docs**
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
cairn remote launch hello-cairn/ask-the-docs --environment sandbox --watch
|
|
113
|
+
cairn remote advance <pipeline_id> review
|
|
114
|
+
cairn remote findings <pipeline_id> # hits, answer, answer_stats, remembered
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The `assistant` leg (`flows/assistant.md`) searches the `docs` corpus (`rag/docs`, declared
|
|
118
|
+
in `pack.toml` as `[requires.rag.docs]`), re-ranks, asks the model, calls `word_count` on
|
|
119
|
+
`tools/calc_server.py` through `mcp/call`, and writes the exchange to the `hello_answers`
|
|
120
|
+
memory store. `length` flags answers longer than `max_words`. An un-indexed corpus returns no
|
|
121
|
+
hits and the model says the docs do not cover the question. Retrieval uses sqlite-vec unless
|
|
122
|
+
`RAG_VECTOR_STORE=pgvector`.
|
|
123
|
+
|
|
124
|
+
**nightly-ops**
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
export HELLO_REPORT_TOKEN=... # the secret the job checks for
|
|
128
|
+
export HELLO_SSH_KEY="$(cat ~/.ssh/id_ed25519)" # in the ENGINE's environment
|
|
129
|
+
CAIRN_STORAGE=sqlite:///$HOME/.cairn/dev.db \
|
|
130
|
+
cairn connect add ssh --name hello-ssh --set host=<box> --set user=<user> \
|
|
131
|
+
--set known_hosts=$HOME/.ssh/known_hosts --secret PRIVATE_KEY=HELLO_SSH_KEY
|
|
132
|
+
cairn remote launch hello-cairn/nightly-ops --environment sandbox --watch
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The connection goes into the engine's store, hence `CAIRN_STORAGE`; its secret is an env var
|
|
136
|
+
NAME the engine reads. The box needs `sh`, an SFTP subsystem, and `setsid` (Linux) or
|
|
137
|
+
`perl` (macOS): `remote/exec` detaches the job into its own session through whichever is
|
|
138
|
+
present (#2606). `job` (`flows/nightly-job.md`) checks the secret through the profile's
|
|
139
|
+
`secrets.store` binding, then runs `command` on the box and reads back `metrics.json`.
|
|
140
|
+
`propose` waits for a person (`monitor/propose` declares `requires_approval`;
|
|
141
|
+
`cairn remote advance <pipeline_id> propose`). With the wheel installed, every settled leg
|
|
142
|
+
appends a line to `$CAIRN_HOME/hello-cairn/receipts.jsonl` (default `~/.cairn`).
|
|
143
|
+
|
|
144
|
+
For secrets in Infisical, run the engine with
|
|
145
|
+
`CAIRN_PROFILE=examples/hello-cairn/profiles/hello-infisical.toml`, `uv sync --extra
|
|
146
|
+
infisical`, and `INFISICAL_CLIENT_ID`, `INFISICAL_CLIENT_SECRET`, `INFISICAL_PROJECT_ID`
|
|
147
|
+
(and `INFISICAL_HOST` for a self-hosted server). Nothing in the pipeline changes.
|
|
148
|
+
|
|
149
|
+
**tiny-finetune**
|
|
150
|
+
|
|
151
|
+
Needs the builtin train-local pack (inside `cairndev`) and its training stack on the engine's
|
|
152
|
+
box: `cairndev[train]` (torch, transformers, peft, datasets; CPU is enough;
|
|
153
|
+
there is no torch wheel for Intel macOS).
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
cairn remote launch hello-cairn/tiny-finetune --environment sandbox --watch
|
|
157
|
+
cairn remote launch hello-cairn/tiny-finetune -i base_model=Qwen/Qwen2.5-0.5B --watch
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`base_model` defaults to the stand-in `sshleifer/tiny-gpt2` (seconds); Qwen2.5-0.5B trains
|
|
161
|
+
in minutes. `loss-ok` stops the pipeline when the held-out loss is above `target_loss`;
|
|
162
|
+
`promote` and `review` each wait for a person.
|
|
163
|
+
|
|
164
|
+
Until the train-local pack was merged (hello-cairn WP3), tiny-finetune was pending in two
|
|
165
|
+
strict xfails and one strict list entry. WP3 removed all three in the change that added the
|
|
166
|
+
pack: `test_tiny_finetune_is_runnable_on_the_public_core`
|
|
167
|
+
(`tests/core/test_example_pipeline_governance.py`) and `test_the_example_pack_validates_clean`
|
|
168
|
+
(`tests/cli/test_validate_example_pack.py`, under `profiles/hello.toml`) are ordinary tests,
|
|
169
|
+
and `tests/core/test_validate_author_gate.py` admits tiny-finetune like every other shipped
|
|
170
|
+
pipeline.
|
|
171
|
+
|
|
172
|
+
### Triggers
|
|
173
|
+
|
|
174
|
+
The engine arms every declared trigger when it starts: it installs each discovered pipeline
|
|
175
|
+
into the `default` workspace, and each trigger becomes a row that starts its own pipeline
|
|
176
|
+
(ADR-0131, #2631). A trigger fires only when its declaration says `enabled: true`; to turn one
|
|
177
|
+
on, set that in the pipeline file and restart the engine. Out of the box only nightly-ops' cron
|
|
178
|
+
is on. profile-and-publish's dataset `watch` (`on_change: start`), weekly `schedule` and custom
|
|
179
|
+
`webhook` (`POST /triggers/hello-sample`) are `enabled: false`. Turned on, the watch makes each
|
|
180
|
+
tick check whether `hello-sample` gained a registered version, and starts the pipeline only
|
|
181
|
+
when it did. A version is registered by the `engine-check` leg, which records the checked file's content as a version of `hello-sample` (#2632): a changed sample is a new version, and the same sample adds none. With the watch on, a run that registers a new version is followed by one watch-started run on that same content; moving the registration to the job that lands the data is tracked separately. nightly-ops' `table` watch is declared but not armed: a `table` (or `s3`) watch
|
|
182
|
+
needs cairn-platform's governed import, so core names it unarmed. Every armed row is pinned to
|
|
183
|
+
start a `hello-cairn` pipeline.
|
|
184
|
+
|
|
185
|
+
### Events
|
|
186
|
+
|
|
187
|
+
`profile-and-publish`'s `publish` leg declares `emits: dataset.ready`: when it completes,
|
|
188
|
+
the bus carries that topic with the leg's output as the payload. A leg declared
|
|
189
|
+
`trigger: on:dataset.ready` (in any pipeline) waits for it; `trigger:` also takes `manual`
|
|
190
|
+
and `schedule:<cron>` (#2605). On the Postgres bus the event is durable and replayed to a
|
|
191
|
+
leg that starts waiting later; on the in-process bus only a leg already waiting sees it.
|
|
192
|
+
|
|
193
|
+
A whole pipeline can start on a topic too: `ask-the-docs` declares a `kind: event` trigger on
|
|
194
|
+
`dataset.ready` (`enabled: false`). Turned on, each published `dataset.ready` starts one
|
|
195
|
+
ask-the-docs run (ADR-0131 D4). The listener runs on the engine's leader, so a message
|
|
196
|
+
published while no leader is subscribed starts nothing, on either bus. A pipeline may not
|
|
197
|
+
start on a topic its own legs emit.
|
|
198
|
+
|
|
199
|
+
### How it runs inside the engine
|
|
200
|
+
|
|
201
|
+
[docs/golden-paths/hello.md](../../docs/golden-paths/hello.md) traces each pipeline from the
|
|
202
|
+
start door to its legs, names where governance fires, and lists the proof for each feature.
|
|
203
|
+
|
|
204
|
+
### Seeded demo data
|
|
205
|
+
|
|
206
|
+
`cairn dev --seed` and `cairn demo` seed three records of profile-and-publish
|
|
207
|
+
(`cairn/core/demo_seed.py`). The seeder takes the steps from the loaded pipeline's plan, so
|
|
208
|
+
each record carries every step in plan order (`redact`, `promote` and `audit` included):
|
|
209
|
+
the published record has all of them done, the blocked one stops at `data-ok`, and the
|
|
210
|
+
waiting one stops at `review` with the later steps pending (#2608).
|
|
211
|
+
|
|
212
|
+
## Files
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
pack.toml manifest (kind = domain): the docs corpus, the MCP server
|
|
216
|
+
__init__.py the one-line shim the entry point targets
|
|
217
|
+
pipelines/*.yaml the four pipelines (the runnable surface)
|
|
218
|
+
flows/engine-check.yaml data flow: one noop stage
|
|
219
|
+
flows/summarise.yaml data flow: noop + noop.slow
|
|
220
|
+
flows/lora.yaml ml flow: train.lora
|
|
221
|
+
flows/assistant.md agentic template of ask-the-docs
|
|
222
|
+
flows/nightly-job.md agentic template of nightly-ops (no model)
|
|
223
|
+
rag/docs/*.txt the docs corpus
|
|
224
|
+
data/sample.jsonl 20 invented rows: id, text, label
|
|
225
|
+
data/train.jsonl 48 invented sentences for the fine-tune
|
|
226
|
+
policies/require-review-on-publish.yaml a hello-prod storage policy
|
|
227
|
+
profiles/hello.toml the example's deployment profile
|
|
228
|
+
profiles/hello-infisical.toml the same, with secrets in Infisical
|
|
229
|
+
hooks.py run-hook listener: receipts.jsonl
|
|
230
|
+
tools/calc_server.py stdio MCP server: add, word_count
|
|
231
|
+
pyproject.toml builds the data wheel cairn-pack-hello
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Tests: `tests/examples/test_hello_cairn.py`, `tests/core/test_example_pipeline_governance.py`,
|
|
235
|
+
`tests/cli/test_validate_example_pack.py`. The feature proof (ADR-0127 D1) runs all four pipelines
|
|
236
|
+
on real servers in every storage × runtime × bus cell, plus the crash cells: `tests/e2e/hello`,
|
|
237
|
+
driven by `scripts/oss/feature_proof.sh`, which writes `FEATURE_PROOF.md`. Its model is a keyless
|
|
238
|
+
OpenAI-compatible stub, `tests/e2e/model_stub.py`.
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# hello-cairn: the example pack
|
|
2
|
+
|
|
3
|
+
`hello-cairn` is a domain pack: data plus a little glue, no operators. It runs on the cairn
|
|
4
|
+
engine and its builtin operator packs alone. Its four pipelines each do one thing a new user
|
|
5
|
+
recognises, and together they exercise what the engine adds around a piece of work: gates
|
|
6
|
+
answered by data or by a person, budgets, triggers, an agentic template, an MCP tool,
|
|
7
|
+
retrieval, a run hook, SSH, secrets, and an audit record.
|
|
8
|
+
|
|
9
|
+
| Pipeline | Kind | What it does |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `profile-and-publish` | data | Profile a dataset, stop unless the profile passes, redact the summary, run a flow, stage it, ask a person, tag it, publish it, audit it. |
|
|
12
|
+
| `ask-the-docs` | agentic | Retrieve from the pack's docs, ask a model, measure the answer with a tool on the pack's own MCP server, remember it, check its length, ask a person. |
|
|
13
|
+
| `nightly-ops` | operations | Check a secret, run a report on an SSH box, carry it through a flow, propose the follow-up to a person, audit it. Nightly once enabled; a run hook writes receipts. |
|
|
14
|
+
| `tiny-finetune` | ml | Profile training rows, LoRA fine-tune a small model on CPU, gate on its held-out loss, tag it, ask a person, write its card. |
|
|
15
|
+
|
|
16
|
+
## Quickstart
|
|
17
|
+
|
|
18
|
+
Run everything from the repository root: relative paths in the pipelines' defaults resolve
|
|
19
|
+
against the engine's working directory.
|
|
20
|
+
|
|
21
|
+
### 1. Install, and point the engine at the example
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
uv sync --extra dev --extra hello --extra rag # add --extra auth if your engine needs it
|
|
25
|
+
export CAIRN_PROFILE=examples/hello-cairn/profiles/hello.toml
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`--extra rag` is what lets the engine index the docs corpus at boot (LlamaIndex ingest);
|
|
29
|
+
without it ask-the-docs still runs and retrieves nothing. `auth` is optional: `cairn dev`
|
|
30
|
+
signs you in as dev/dev without it.
|
|
31
|
+
|
|
32
|
+
`cairndev[hello]` installs this pack as the wheel `cairn-pack-hello` plus what its pipelines
|
|
33
|
+
reach beyond the base install: the sqlite-vec store (retrieval and memory), asyncssh
|
|
34
|
+
(`remote/exec`) and DBOS (`CAIRN_RUNTIME=dbos`). Without installing it,
|
|
35
|
+
`export CAIRN_PACK_PATH=examples/hello-cairn` loads the same directory as a dev pack; a dev
|
|
36
|
+
pack declares no entry points, so the run-hook listener does not fire.
|
|
37
|
+
|
|
38
|
+
`profiles/hello.toml` is the deployment profile the example runs under. It carries
|
|
39
|
+
`cairn/profiles/local-dev.toml`'s bindings for the builtin operators (memory, storage, rag,
|
|
40
|
+
mcp), but not local-dev's `eval.delta` binding (a platform operator) or its `llm_gateway` and
|
|
41
|
+
`embedder` backend entries: the model and the embedder come from the engine's environment
|
|
42
|
+
(step 3). It adds `secrets.store` → `secrets/env` (nightly-ops' secret check is a capability
|
|
43
|
+
node and compiles only with a binding, which local-dev does not carry) and the `hello-calc`
|
|
44
|
+
MCP server the pack declares. It pins no runtime, so `CAIRN_RUNTIME=dbos` selects DBOS.
|
|
45
|
+
|
|
46
|
+
### 2. See what will happen before anything runs
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
cairn pipeline explain hello-cairn/profile-and-publish -e sandbox
|
|
50
|
+
cairn validate examples/hello-cairn # with CAIRN_PROFILE set as in step 1
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`cairn validate` needs the example's profile: under local-dev the nightly-job template does
|
|
54
|
+
not compile (no `secrets.store` binding).
|
|
55
|
+
|
|
56
|
+
`explain` lists every leg with the operator class or flow file it resolves to, the gate that
|
|
57
|
+
answers it, and the budget, series and approvers.
|
|
58
|
+
|
|
59
|
+
### 3. Start an engine
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
export OPENAI_API_KEY=sk-... # ask-the-docs' model call and embeddings
|
|
63
|
+
export RAG_EMBED_PROVIDER=openai # retrieval and memory embed with OpenAI
|
|
64
|
+
export CAIRN_RAG_AUTOINDEX=1 # index rag/docs at boot (needs the `rag` extra)
|
|
65
|
+
export CAIRN_MCP_SERVERS='{"hello-calc": {"transport": "stdio", "command": "python",
|
|
66
|
+
"args": ["examples/hello-cairn/tools/calc_server.py"]}}'
|
|
67
|
+
cairn dev --engine-only --port 8471 & # sqlite engine, in-process runtime, login dev/dev
|
|
68
|
+
cairn remote login -u dev -p dev -e http://localhost:8471
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Any OpenAI-compatible endpoint works in place of a key: `OPENAI_BASE_URL`, or
|
|
72
|
+
`LLM_GATEWAY=proxy` with `LLM_GATEWAY_URL`. The engine has no built-in mock model, and
|
|
73
|
+
`CAIRN_BACKEND=mock` is the telemetry backend, not a model. `profile-and-publish` and
|
|
74
|
+
`nightly-ops` call no model.
|
|
75
|
+
|
|
76
|
+
### 4. Run each pipeline
|
|
77
|
+
|
|
78
|
+
**profile-and-publish**
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
cairn remote launch hello-cairn/profile-and-publish --environment sandbox --watch
|
|
82
|
+
cairn remote advance <pipeline_id> review # the person gate
|
|
83
|
+
cairn remote advance <pipeline_id> promote # the operator's own gate
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The pipeline stops a second time at `promote`: `artifact/promote` declares
|
|
87
|
+
`requires_approval`, and a pipeline can add a gate but never remove one an operator declares.
|
|
88
|
+
`cairn remote advance` approves either kind and records the signed-in user as the approver.
|
|
89
|
+
|
|
90
|
+
The record ends `done`: `data-ok` has verdict `data`, `review` `approved`, `promote` the
|
|
91
|
+
tag `hello-sample@published` on the flow leg's registered artifact, and `audit` a
|
|
92
|
+
hash-chained row in `CAIRN_AUDIT_LOG` (default `./audit.jsonl`). `storage/put` writes under
|
|
93
|
+
`CAIRN_ARTIFACTS_DIR` (default `~/.cairn/artifacts`). With `-i min_rows=1000` the data gate
|
|
94
|
+
stops the pipeline (`gate_blocked`) before anything is written. Launch with
|
|
95
|
+
`--environment hello-prod` and the pack's policy
|
|
96
|
+
(`policies/require-review-on-publish.yaml`) stops `stage` for an approval before it writes.
|
|
97
|
+
|
|
98
|
+
**ask-the-docs**
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
cairn remote launch hello-cairn/ask-the-docs --environment sandbox --watch
|
|
102
|
+
cairn remote advance <pipeline_id> review
|
|
103
|
+
cairn remote findings <pipeline_id> # hits, answer, answer_stats, remembered
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The `assistant` leg (`flows/assistant.md`) searches the `docs` corpus (`rag/docs`, declared
|
|
107
|
+
in `pack.toml` as `[requires.rag.docs]`), re-ranks, asks the model, calls `word_count` on
|
|
108
|
+
`tools/calc_server.py` through `mcp/call`, and writes the exchange to the `hello_answers`
|
|
109
|
+
memory store. `length` flags answers longer than `max_words`. An un-indexed corpus returns no
|
|
110
|
+
hits and the model says the docs do not cover the question. Retrieval uses sqlite-vec unless
|
|
111
|
+
`RAG_VECTOR_STORE=pgvector`.
|
|
112
|
+
|
|
113
|
+
**nightly-ops**
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
export HELLO_REPORT_TOKEN=... # the secret the job checks for
|
|
117
|
+
export HELLO_SSH_KEY="$(cat ~/.ssh/id_ed25519)" # in the ENGINE's environment
|
|
118
|
+
CAIRN_STORAGE=sqlite:///$HOME/.cairn/dev.db \
|
|
119
|
+
cairn connect add ssh --name hello-ssh --set host=<box> --set user=<user> \
|
|
120
|
+
--set known_hosts=$HOME/.ssh/known_hosts --secret PRIVATE_KEY=HELLO_SSH_KEY
|
|
121
|
+
cairn remote launch hello-cairn/nightly-ops --environment sandbox --watch
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The connection goes into the engine's store, hence `CAIRN_STORAGE`; its secret is an env var
|
|
125
|
+
NAME the engine reads. The box needs `sh`, an SFTP subsystem, and `setsid` (Linux) or
|
|
126
|
+
`perl` (macOS): `remote/exec` detaches the job into its own session through whichever is
|
|
127
|
+
present (#2606). `job` (`flows/nightly-job.md`) checks the secret through the profile's
|
|
128
|
+
`secrets.store` binding, then runs `command` on the box and reads back `metrics.json`.
|
|
129
|
+
`propose` waits for a person (`monitor/propose` declares `requires_approval`;
|
|
130
|
+
`cairn remote advance <pipeline_id> propose`). With the wheel installed, every settled leg
|
|
131
|
+
appends a line to `$CAIRN_HOME/hello-cairn/receipts.jsonl` (default `~/.cairn`).
|
|
132
|
+
|
|
133
|
+
For secrets in Infisical, run the engine with
|
|
134
|
+
`CAIRN_PROFILE=examples/hello-cairn/profiles/hello-infisical.toml`, `uv sync --extra
|
|
135
|
+
infisical`, and `INFISICAL_CLIENT_ID`, `INFISICAL_CLIENT_SECRET`, `INFISICAL_PROJECT_ID`
|
|
136
|
+
(and `INFISICAL_HOST` for a self-hosted server). Nothing in the pipeline changes.
|
|
137
|
+
|
|
138
|
+
**tiny-finetune**
|
|
139
|
+
|
|
140
|
+
Needs the builtin train-local pack (inside `cairndev`) and its training stack on the engine's
|
|
141
|
+
box: `cairndev[train]` (torch, transformers, peft, datasets; CPU is enough;
|
|
142
|
+
there is no torch wheel for Intel macOS).
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
cairn remote launch hello-cairn/tiny-finetune --environment sandbox --watch
|
|
146
|
+
cairn remote launch hello-cairn/tiny-finetune -i base_model=Qwen/Qwen2.5-0.5B --watch
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`base_model` defaults to the stand-in `sshleifer/tiny-gpt2` (seconds); Qwen2.5-0.5B trains
|
|
150
|
+
in minutes. `loss-ok` stops the pipeline when the held-out loss is above `target_loss`;
|
|
151
|
+
`promote` and `review` each wait for a person.
|
|
152
|
+
|
|
153
|
+
Until the train-local pack was merged (hello-cairn WP3), tiny-finetune was pending in two
|
|
154
|
+
strict xfails and one strict list entry. WP3 removed all three in the change that added the
|
|
155
|
+
pack: `test_tiny_finetune_is_runnable_on_the_public_core`
|
|
156
|
+
(`tests/core/test_example_pipeline_governance.py`) and `test_the_example_pack_validates_clean`
|
|
157
|
+
(`tests/cli/test_validate_example_pack.py`, under `profiles/hello.toml`) are ordinary tests,
|
|
158
|
+
and `tests/core/test_validate_author_gate.py` admits tiny-finetune like every other shipped
|
|
159
|
+
pipeline.
|
|
160
|
+
|
|
161
|
+
### Triggers
|
|
162
|
+
|
|
163
|
+
The engine arms every declared trigger when it starts: it installs each discovered pipeline
|
|
164
|
+
into the `default` workspace, and each trigger becomes a row that starts its own pipeline
|
|
165
|
+
(ADR-0131, #2631). A trigger fires only when its declaration says `enabled: true`; to turn one
|
|
166
|
+
on, set that in the pipeline file and restart the engine. Out of the box only nightly-ops' cron
|
|
167
|
+
is on. profile-and-publish's dataset `watch` (`on_change: start`), weekly `schedule` and custom
|
|
168
|
+
`webhook` (`POST /triggers/hello-sample`) are `enabled: false`. Turned on, the watch makes each
|
|
169
|
+
tick check whether `hello-sample` gained a registered version, and starts the pipeline only
|
|
170
|
+
when it did. A version is registered by the `engine-check` leg, which records the checked file's content as a version of `hello-sample` (#2632): a changed sample is a new version, and the same sample adds none. With the watch on, a run that registers a new version is followed by one watch-started run on that same content; moving the registration to the job that lands the data is tracked separately. nightly-ops' `table` watch is declared but not armed: a `table` (or `s3`) watch
|
|
171
|
+
needs cairn-platform's governed import, so core names it unarmed. Every armed row is pinned to
|
|
172
|
+
start a `hello-cairn` pipeline.
|
|
173
|
+
|
|
174
|
+
### Events
|
|
175
|
+
|
|
176
|
+
`profile-and-publish`'s `publish` leg declares `emits: dataset.ready`: when it completes,
|
|
177
|
+
the bus carries that topic with the leg's output as the payload. A leg declared
|
|
178
|
+
`trigger: on:dataset.ready` (in any pipeline) waits for it; `trigger:` also takes `manual`
|
|
179
|
+
and `schedule:<cron>` (#2605). On the Postgres bus the event is durable and replayed to a
|
|
180
|
+
leg that starts waiting later; on the in-process bus only a leg already waiting sees it.
|
|
181
|
+
|
|
182
|
+
A whole pipeline can start on a topic too: `ask-the-docs` declares a `kind: event` trigger on
|
|
183
|
+
`dataset.ready` (`enabled: false`). Turned on, each published `dataset.ready` starts one
|
|
184
|
+
ask-the-docs run (ADR-0131 D4). The listener runs on the engine's leader, so a message
|
|
185
|
+
published while no leader is subscribed starts nothing, on either bus. A pipeline may not
|
|
186
|
+
start on a topic its own legs emit.
|
|
187
|
+
|
|
188
|
+
### How it runs inside the engine
|
|
189
|
+
|
|
190
|
+
[docs/golden-paths/hello.md](../../docs/golden-paths/hello.md) traces each pipeline from the
|
|
191
|
+
start door to its legs, names where governance fires, and lists the proof for each feature.
|
|
192
|
+
|
|
193
|
+
### Seeded demo data
|
|
194
|
+
|
|
195
|
+
`cairn dev --seed` and `cairn demo` seed three records of profile-and-publish
|
|
196
|
+
(`cairn/core/demo_seed.py`). The seeder takes the steps from the loaded pipeline's plan, so
|
|
197
|
+
each record carries every step in plan order (`redact`, `promote` and `audit` included):
|
|
198
|
+
the published record has all of them done, the blocked one stops at `data-ok`, and the
|
|
199
|
+
waiting one stops at `review` with the later steps pending (#2608).
|
|
200
|
+
|
|
201
|
+
## Files
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
pack.toml manifest (kind = domain): the docs corpus, the MCP server
|
|
205
|
+
__init__.py the one-line shim the entry point targets
|
|
206
|
+
pipelines/*.yaml the four pipelines (the runnable surface)
|
|
207
|
+
flows/engine-check.yaml data flow: one noop stage
|
|
208
|
+
flows/summarise.yaml data flow: noop + noop.slow
|
|
209
|
+
flows/lora.yaml ml flow: train.lora
|
|
210
|
+
flows/assistant.md agentic template of ask-the-docs
|
|
211
|
+
flows/nightly-job.md agentic template of nightly-ops (no model)
|
|
212
|
+
rag/docs/*.txt the docs corpus
|
|
213
|
+
data/sample.jsonl 20 invented rows: id, text, label
|
|
214
|
+
data/train.jsonl 48 invented sentences for the fine-tune
|
|
215
|
+
policies/require-review-on-publish.yaml a hello-prod storage policy
|
|
216
|
+
profiles/hello.toml the example's deployment profile
|
|
217
|
+
profiles/hello-infisical.toml the same, with secrets in Infisical
|
|
218
|
+
hooks.py run-hook listener: receipts.jsonl
|
|
219
|
+
tools/calc_server.py stdio MCP server: add, word_count
|
|
220
|
+
pyproject.toml builds the data wheel cairn-pack-hello
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Tests: `tests/examples/test_hello_cairn.py`, `tests/core/test_example_pipeline_governance.py`,
|
|
224
|
+
`tests/cli/test_validate_example_pack.py`. The feature proof (ADR-0127 D1) runs all four pipelines
|
|
225
|
+
on real servers in every storage × runtime × bus cell, plus the crash cells: `tests/e2e/hello`,
|
|
226
|
+
driven by `scripts/oss/feature_proof.sh`, which writes `FEATURE_PROOF.md`. Its model is a keyless
|
|
227
|
+
OpenAI-compatible stub, `tests/e2e/model_stub.py`.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""hello-cairn — the example domain pack (#2423, ADR-0127 D2). Data plus glue; this
|
|
2
|
+
shim builds the Pack from the ``pack.toml`` beside it. The glue (``hooks.py``,
|
|
3
|
+
``tools/calc_server.py``) is reached through an entry point and a profile, never
|
|
4
|
+
imported here."""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from cairn.core.pack import Pack
|
|
8
|
+
|
|
9
|
+
pack = Pack.from_manifest(__file__)
|
|
10
|
+
|
|
11
|
+
__all__ = ["pack"]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{"id": 1, "text": "The soup arrived hot and the bread was fresh.", "label": "positive"}
|
|
2
|
+
{"id": 2, "text": "Delivery took two hours longer than promised.", "label": "negative"}
|
|
3
|
+
{"id": 3, "text": "The packaging was fine, nothing special.", "label": "neutral"}
|
|
4
|
+
{"id": 4, "text": "Best noodles I have had in months.", "label": "positive"}
|
|
5
|
+
{"id": 5, "text": "The order was missing the side salad.", "label": "negative"}
|
|
6
|
+
{"id": 6, "text": "Portion size was about what I expected.", "label": "neutral"}
|
|
7
|
+
{"id": 7, "text": "The courier was polite and quick.", "label": "positive"}
|
|
8
|
+
{"id": 8, "text": "My drink had spilled inside the bag.", "label": "negative"}
|
|
9
|
+
{"id": 9, "text": "I ordered the same dish as last week.", "label": "neutral"}
|
|
10
|
+
{"id": 10, "text": "The dessert was a lovely surprise.", "label": "positive"}
|
|
11
|
+
{"id": 11, "text": "The curry was far too salty to finish.", "label": "negative"}
|
|
12
|
+
{"id": 12, "text": "Paid with the app, it worked as usual.", "label": "neutral"}
|
|
13
|
+
{"id": 13, "text": "Everything was still warm when it came.", "label": "positive"}
|
|
14
|
+
{"id": 14, "text": "The tracking map never updated.", "label": "negative"}
|
|
15
|
+
{"id": 15, "text": "The menu has not changed since spring.", "label": "neutral"}
|
|
16
|
+
{"id": 16, "text": "Great value for a weeknight dinner.", "label": "positive"}
|
|
17
|
+
{"id": 17, "text": "The rice was undercooked again.", "label": "negative"}
|
|
18
|
+
{"id": 18, "text": "Arrived at the time the app estimated.", "label": "neutral"}
|
|
19
|
+
{"id": 19, "text": "The new vegetarian option is excellent.", "label": "positive"}
|
|
20
|
+
{"id": 20, "text": "Support never answered my refund request.", "label": "negative"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{"text": "The tea works well on cold mornings."}
|
|
2
|
+
{"text": "A paper map needed a second try."}
|
|
3
|
+
{"text": "The bicycle bell fits in a coat pocket."}
|
|
4
|
+
{"text": "This notebook looks better in person."}
|
|
5
|
+
{"text": "The garden hose is lighter than it looks."}
|
|
6
|
+
{"text": "A wool scarf smells faintly of cedar."}
|
|
7
|
+
{"text": "The desk lamp made less noise than expected."}
|
|
8
|
+
{"text": "That umbrella arrived a day early."}
|
|
9
|
+
{"text": "The bread knife lasted the whole season."}
|
|
10
|
+
{"text": "A clay mug was easy to set up."}
|
|
11
|
+
{"text": "The tea works well on cold mornings."}
|
|
12
|
+
{"text": "A paper map needed a second try."}
|
|
13
|
+
{"text": "The bicycle bell fits in a coat pocket."}
|
|
14
|
+
{"text": "This notebook looks better in person."}
|
|
15
|
+
{"text": "The garden hose is lighter than it looks."}
|
|
16
|
+
{"text": "A wool scarf smells faintly of cedar."}
|
|
17
|
+
{"text": "The desk lamp made less noise than expected."}
|
|
18
|
+
{"text": "That umbrella arrived a day early."}
|
|
19
|
+
{"text": "The bread knife lasted the whole season."}
|
|
20
|
+
{"text": "A clay mug was easy to set up."}
|
|
21
|
+
{"text": "The tea works well on cold mornings."}
|
|
22
|
+
{"text": "A paper map needed a second try."}
|
|
23
|
+
{"text": "The bicycle bell fits in a coat pocket."}
|
|
24
|
+
{"text": "This notebook looks better in person."}
|
|
25
|
+
{"text": "The garden hose is lighter than it looks."}
|
|
26
|
+
{"text": "A wool scarf smells faintly of cedar."}
|
|
27
|
+
{"text": "The desk lamp made less noise than expected."}
|
|
28
|
+
{"text": "That umbrella arrived a day early."}
|
|
29
|
+
{"text": "The bread knife lasted the whole season."}
|
|
30
|
+
{"text": "A clay mug was easy to set up."}
|
|
31
|
+
{"text": "The tea works well on cold mornings."}
|
|
32
|
+
{"text": "A paper map needed a second try."}
|
|
33
|
+
{"text": "The bicycle bell fits in a coat pocket."}
|
|
34
|
+
{"text": "This notebook looks better in person."}
|
|
35
|
+
{"text": "The garden hose is lighter than it looks."}
|
|
36
|
+
{"text": "A wool scarf smells faintly of cedar."}
|
|
37
|
+
{"text": "The desk lamp made less noise than expected."}
|
|
38
|
+
{"text": "That umbrella arrived a day early."}
|
|
39
|
+
{"text": "The bread knife lasted the whole season."}
|
|
40
|
+
{"text": "A clay mug was easy to set up."}
|
|
41
|
+
{"text": "The tea works well on cold mornings."}
|
|
42
|
+
{"text": "A paper map needed a second try."}
|
|
43
|
+
{"text": "The bicycle bell fits in a coat pocket."}
|
|
44
|
+
{"text": "This notebook looks better in person."}
|
|
45
|
+
{"text": "The garden hose is lighter than it looks."}
|
|
46
|
+
{"text": "A wool scarf smells faintly of cedar."}
|
|
47
|
+
{"text": "The desk lamp made less noise than expected."}
|
|
48
|
+
{"text": "That umbrella arrived a day early."}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: assistant
|
|
3
|
+
version: 0.1.0
|
|
4
|
+
module: ask
|
|
5
|
+
tags:
|
|
6
|
+
- flow:state_machine
|
|
7
|
+
- domain:example
|
|
8
|
+
description: |
|
|
9
|
+
Answer a question from the pack's own docs: retrieve from the `docs` corpus,
|
|
10
|
+
re-rank, ask a model, measure the answer with a tool on the example's MCP server,
|
|
11
|
+
and remember the exchange. The agentic leg of `ask-the-docs`.
|
|
12
|
+
|
|
13
|
+
flow:
|
|
14
|
+
type: state_machine
|
|
15
|
+
source: behavior_tree
|
|
16
|
+
root:
|
|
17
|
+
type: sequence
|
|
18
|
+
children:
|
|
19
|
+
# Retrieval: the builtin rag/search over the corpus pack.toml declares as
|
|
20
|
+
# [requires.rag.docs] (sqlite-vec by default; pgvector when RAG_VECTOR_STORE
|
|
21
|
+
# says so). Read-only.
|
|
22
|
+
- type: operation
|
|
23
|
+
operator: rag/search
|
|
24
|
+
id: search
|
|
25
|
+
input:
|
|
26
|
+
corpus: docs
|
|
27
|
+
query: $input.question
|
|
28
|
+
top_k: 5
|
|
29
|
+
output:
|
|
30
|
+
hits: $state.findings.hits
|
|
31
|
+
ok: $state.findings.search_ok
|
|
32
|
+
error: $state.findings.search_error
|
|
33
|
+
# Re-rank the hits (identity unless a cross-encoder or Cohere is installed).
|
|
34
|
+
- type: operation
|
|
35
|
+
operator: rag/rerank
|
|
36
|
+
id: rerank
|
|
37
|
+
input:
|
|
38
|
+
query: $input.question
|
|
39
|
+
hits: $default($state.findings.hits, [])
|
|
40
|
+
top_k: 3
|
|
41
|
+
output:
|
|
42
|
+
hits: $state.findings.context
|
|
43
|
+
reranker_used: $state.findings.reranker
|
|
44
|
+
# The model call. The compiler builds it through core's build_chat_model, so
|
|
45
|
+
# the call is redacted (when a redact.text binding is active) and costed.
|
|
46
|
+
- type: action
|
|
47
|
+
ref: answer
|
|
48
|
+
# A tool call on the example's own stdio MCP server (tools/calc_server.py),
|
|
49
|
+
# through the builtin mcp/call operator: governed and audited like any tool call.
|
|
50
|
+
- type: operation
|
|
51
|
+
operator: mcp/call
|
|
52
|
+
id: measure
|
|
53
|
+
input:
|
|
54
|
+
server: hello-calc
|
|
55
|
+
tool: word_count
|
|
56
|
+
args:
|
|
57
|
+
text: $state.findings.answer
|
|
58
|
+
output:
|
|
59
|
+
result: $state.findings.answer_stats
|
|
60
|
+
ok: $state.findings.measure_ok
|
|
61
|
+
error: $state.findings.measure_error
|
|
62
|
+
# Remember the exchange in a local memory store (memory/write, sqlite-vec). A
|
|
63
|
+
# store name is a collection name: letters, digits and underscores only.
|
|
64
|
+
- type: operation
|
|
65
|
+
operator: memory/write
|
|
66
|
+
id: remember
|
|
67
|
+
input:
|
|
68
|
+
store: hello_answers
|
|
69
|
+
record:
|
|
70
|
+
question: $input.question
|
|
71
|
+
answer: $state.findings.answer
|
|
72
|
+
embed_text: $input.question
|
|
73
|
+
output:
|
|
74
|
+
ok: $state.findings.remembered
|
|
75
|
+
id: $state.findings.memory_id
|
|
76
|
+
error: $state.findings.memory_error
|
|
77
|
+
|
|
78
|
+
nodes:
|
|
79
|
+
answer:
|
|
80
|
+
type: prompt
|
|
81
|
+
model: gpt-4o-mini
|
|
82
|
+
capabilities: []
|
|
83
|
+
prompt_section: answer
|
|
84
|
+
|
|
85
|
+
tools:
|
|
86
|
+
required: []
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
# Ask the docs
|
|
90
|
+
|
|
91
|
+
## answer
|
|
92
|
+
|
|
93
|
+
You answer a question about the hello-cairn example pack. The retrieved passages
|
|
94
|
+
are in `state.findings.context` (each hit's `payload.content` is the text, and
|
|
95
|
+
`payload.source` names the file). Answer in at most three sentences, using only
|
|
96
|
+
what the passages say, and name the file you used. When the passages do not cover
|
|
97
|
+
the question, say that the docs do not cover it. Do not invent features.
|