Summary
A --tool-cmd subprocess cannot read the memory while its own run is executing — not even a pure RECALL. The embedded backend's file lock is exclusive, so any read opens read-write and is refused:
areev: STO-E001: storage error: Locking error: Failed locking file
'data/accounting-dev.db'. File is locked by another process
On its own that is awkward. Combined with the trigger seam shipped in 1.3.0/1.4.0 it becomes structural: a trigger-started run cannot carry any context from the memory it lives in.
Repro (1.4.0)
A workflow node bound to a host tool whose command is:
#!/bin/sh
cat > /dev/null
areev cal 'RECALL facts WHERE namespace = "accounting.dev.vendors" LIMIT 3' \
--db accounting.db --ns accounting.dev # -> STO-E001, exit 1
echo '{"ok":true}'
areev run start --workflow <WF> --run-id probe --tool-cmd ./probe.sh → the tool runs, the CAL read is refused.
Why the trigger seam turns this into a blocker
Before triggers, the host started the run, so it could assemble context first and pass it in --input. That is exactly what our accounting agent does today: recall the Skill instructions, the Goal targets, the learned vendor-alias facts and the thread history, then run start --input '{…, "context": …}'.
With areev trigger run, Areev starts the run, and every path to the memory is closed at once:
- The host no longer starts the run, so it cannot pre-inject.
- The connector cannot read — the evaluator holds the memory while it spawns the connector.
- The tools cannot read — the driver holds it for the duration of the run.
So a trigger-started agent run is blind to its own memory: no instructions, no goals, no learned facts, no thread history. For a memory engine, an agent that cannot read the memory at the moment it acts is a sharp edge.
This is not hypothetical — it is why our first-party agent (mindgryd/mg#6) has not migrated its launchd job to areev trigger, despite #36 having shipped exactly the seam it asked for.
Precedent: this was already solved once, for blobs
1.2.1 (#27) added blob get with a deliberately lock-free path, and the reasoning in the changelog applies verbatim here:
blob get deliberately does not open the memory: the embedded backend's file lock is exclusive, so while a run holds a memory even a reader is refused, which put an attachment out of reach of the very --tool-cmd subprocess the run spawned to process it.
Attachments got a door. Grains did not.
Proposals (either alone helps; together they close it)
A. A lock-free / read-only open for reads. SQLite WAL already permits concurrent readers; the refusal comes from opening read-write. A --read-only open (or an automatic downgrade when the statement is provably a read, which CAL already classifies by tier) would let a tool run RECALL/ASSEMBLE/RUN "saved_query" mid-run. Consistency is not a concern a reader here needs — it wants the state as of now, and the run's own writes are journaled anyway.
B. Declared context on the Trigger grain. Let a Trigger name a saved query whose result the evaluator assembles (it already holds the memory) and places into the run input:
areev trigger add --type polling --connector gmail \
--workflow <WF> --context-query triage_ctx --because "…"
The run then starts with {trigger, connector, scope, item, context}. This keeps the "tools never touch the store" property intact and makes the context part of the declaration — auditable and replicated, which is the whole point of the trigger grain.
C. Note the backend divergence. The Postgres backend "admits MULTIPLE CONCURRENT WRITERS per memory" (crates/areev-store/src/pg.rs), so this constraint largely disappears at the medium tier. That means the same plan behaves differently on two supported backends — a tool that reads its memory works on Postgres and fails on Turso. Whatever the fix, the divergence deserves to be stated in docs/run.md, because it silently decides whether an agent design is portable across the tiers.
Impact
Any agent whose tools need to consult the memory they are governed by: retrieval-augmented extraction, alias/rule lookups, thread history, policy checks. Today all of it must be hoisted into the host before run start, which the trigger seam removes as an option.
Summary
A
--tool-cmdsubprocess cannot read the memory while its own run is executing — not even a pureRECALL. The embedded backend's file lock is exclusive, so any read opens read-write and is refused:On its own that is awkward. Combined with the trigger seam shipped in 1.3.0/1.4.0 it becomes structural: a trigger-started run cannot carry any context from the memory it lives in.
Repro (1.4.0)
A workflow node bound to a host tool whose command is:
areev run start --workflow <WF> --run-id probe --tool-cmd ./probe.sh→ the tool runs, the CAL read is refused.Why the trigger seam turns this into a blocker
Before triggers, the host started the run, so it could assemble context first and pass it in
--input. That is exactly what our accounting agent does today: recall the Skill instructions, the Goal targets, the learned vendor-alias facts and the thread history, thenrun start --input '{…, "context": …}'.With
areev trigger run, Areev starts the run, and every path to the memory is closed at once:So a trigger-started agent run is blind to its own memory: no instructions, no goals, no learned facts, no thread history. For a memory engine, an agent that cannot read the memory at the moment it acts is a sharp edge.
This is not hypothetical — it is why our first-party agent (mindgryd/mg#6) has not migrated its launchd job to
areev trigger, despite #36 having shipped exactly the seam it asked for.Precedent: this was already solved once, for blobs
1.2.1 (#27) added
blob getwith a deliberately lock-free path, and the reasoning in the changelog applies verbatim here:Attachments got a door. Grains did not.
Proposals (either alone helps; together they close it)
A. A lock-free / read-only open for reads. SQLite WAL already permits concurrent readers; the refusal comes from opening read-write. A
--read-onlyopen (or an automatic downgrade when the statement is provably a read, which CAL already classifies by tier) would let a tool runRECALL/ASSEMBLE/RUN "saved_query"mid-run. Consistency is not a concern a reader here needs — it wants the state as of now, and the run's own writes are journaled anyway.B. Declared context on the Trigger grain. Let a Trigger name a saved query whose result the evaluator assembles (it already holds the memory) and places into the run input:
The run then starts with
{trigger, connector, scope, item, context}. This keeps the "tools never touch the store" property intact and makes the context part of the declaration — auditable and replicated, which is the whole point of the trigger grain.C. Note the backend divergence. The Postgres backend "admits MULTIPLE CONCURRENT WRITERS per memory" (
crates/areev-store/src/pg.rs), so this constraint largely disappears at the medium tier. That means the same plan behaves differently on two supported backends — a tool that reads its memory works on Postgres and fails on Turso. Whatever the fix, the divergence deserves to be stated indocs/run.md, because it silently decides whether an agent design is portable across the tiers.Impact
Any agent whose tools need to consult the memory they are governed by: retrieval-augmented extraction, alias/rule lookups, thread history, policy checks. Today all of it must be hoisted into the host before
run start, which the trigger seam removes as an option.