LOAD
Reads the config, the process's memory, and today's spend and tick count. If a hard daily cap is reached, the tick doesn't run and the trace says capped.
config v3 memory seen_titles (20) today $0 · 12 ticks · no cap reached
HOW IT WORKS
Caedos splits standing work into the part a model is good at (writing the synth) and the part it isn't (running it faithfully and cheaply, tick after tick).
In each step: a slice of the HN keyword watcher. Illustrative data.
Your AI, in Claude Code or any MCP client, writes the synth, shadows it, deploys it, reads traces and patches from evidence. Then it leaves.
Once deployed, the synth runs as a process. Caedos fires it on its trigger, walks six phases per tick, records everything and repairs what it can.
You, the operator, allow side effects, approve or reject queued actions, read journals and set policy.
notify_webhook alert: New HN stories for your keyword · new_titles: ["Show HN: …"]
Allow is a button in your control room. It isn't one of your AI's tools.
ANATOMY OF A SYNTH
synth: # who it is and when it runs
name: HN watcher
goal: Tell me when new Hacker News stories mention my keyword.
schedule_seconds: 1800
requires_credentials: [WEBHOOK_URL] # declared, stored in the vault
sources: # OBSERVE: where to look
- id: hn
primitive: rest_api
config:
url: "https://hn.algolia.com/api/v1/search_by_date?query=claude&tags=story"
items_path: hits
max_pages: 1
transforms: # OBSERVE: deterministic shaping
- id: titles
primitive: extract
config: { items: $sources.hn.items, field: title }
fast_path: # FILTER: what counts as a change
primitive: fast_path_diff
inputs:
current: $transforms.titles.values
previous: $state.seen_titles
actions: # ACT: what to do about it
- if: $fast_path.triggered
primitive: notify_webhook # side-effecting → leashed
config:
url: $secrets.WEBHOOK_URL
body: { alert: New HN stories for your keyword, new_titles: $fast_path.diff }
- always: true
primitive: write_state # SAVE: memory for next tick
config: { seen_titles: $transforms.titles.values, checked_at: $tick.started_at } This one never calls a model. When you want judgement, add a reasoning block. It only runs when the fast path fires.
THE TICK
Beside each phase: that phase's slice of one quiet tick of the HN keyword watcher. Illustrative data.
Reads the config, the process's memory, and today's spend and tick count. If a hard daily cap is reached, the tick doesn't run and the trace says capped.
config v3 memory seen_titles (20) today $0 · 12 ticks · no cap reached
Runs every source and transform in dependency order: HTTP, REST with pagination, RSS, HTML selectors, MCP tools, webhooks, Relay signals, then count, filter, aggregate, compute, extract, sort, sample, format. A failing source is recorded, not fatal.
sources.hn rest_api → 20 items transforms.titles extract → 20 values source_errors none
The fast path decides if anything happened: a diff against memory, a threshold, a pattern, new items, or always. The very first look at a list is a baseline. It remembers and doesn't alert.
fast_path_diff triggered: false diff []
Only if the gate fired. A model answers into a JSON schema you declare, on the tier you choose (economy, standard, premium), within a daily dollar budget. Out of budget, it skips reasoning and says so.
reasoning not configured model calls 0
Actions run in order, each with an if or always. In leashed mode, anything that touches the outside world is queued for approval; memory writes and internal signals still happen.
notify_webhook skipped: if was false write_state ok
Memory, stats, cost, signals and the trace are written. Secrets never are.
outcome idle origin scheduler mode live cost $0
Outcomes, as the journal shows them: idle · reasoned · acted · budget_exceeded · capped · error
THE AUTHORING LOOP
Your AI gets the handbook the moment it connects. The MCP server briefs it on the rules, so you never paste instructions into chat.
| Syscall | What it does |
|---|---|
caedos_validate | Checks the synth: every reference, secret, connection and expression, before anything runs. |
caedos_shadow | One real tick, actions muted. Evidence, not a grade. |
caedos_deploy | Registers and schedules it. Side-effecting processes come back leashed. |
caedos_run | One tick, now. The leash still holds. |
caedos_traces | What the world did. queued means held, not sent. |
caedos_get → caedos_patch | Change a running process from evidence, not from a wish. |
caedos_ps · caedos_pause · caedos_kill · caedos_devices · caedos_blueprints · caedos_docs | The rest of the table. |
THE LEASH
When a synth includes an action that reaches the outside world (a webhook, an API call, an MCP tool), Caedos deploys
it with approval_mode: required. Each outbound action lands in the process's approval queue with a
one-line summary of what it would send. Approve or reject it. When you've seen enough, press Allow
and it runs on its own. You can put the leash back any time.
THE TRACE
Each trace records two facts that are easy to confuse:
scheduler, signal, webhook, authoring, operator, or honestly unknown).live, queued, shadow).It also records what each source returned, what the gate decided, what the model said and cost, what each action did, what memory changed, which config version ran, and any warnings. A classic one: your fast path is comparing a field this source never produces.
Replay: take any recorded tick, edit the config, and re-run it against that day's observations. Nothing is fetched. Nothing is sent.
THE RELAY
Each synth runs as a process. A process can publish a signal (team.findings) and another can wake up
on it. Split watch,
decide and send into separate processes, each with its own budget and its own leash. The control
room draws the wiring.
Watch
Counts Hacker News mentions every hour. When the count shifts, a model writes a one-line read.
team.findings Decide
Turns the finding into a draft post. Your AI writes this one; it isn't a shipped blueprint.
content.draft WHEN THINGS GO WRONG