Skip to content

feat: let a task wait on an outside event, and hold a task whose effect is not settled - #81

Draft
sstriker wants to merge 4 commits into
mainfrom
feat/task-record-rest
Draft

sstriker wants to merge 4 commits into
mainfrom
feat/task-record-rest

Conversation

@sstriker

Copy link
Copy Markdown
Contributor

This is a further part of package 2 of PLAN.md: the task record and its claims. #73 and #77 landed the first parts.

The paper says that waiting is a ledger state, and that an agent does not wait in a session for an outside event. It also says that an external effect with an unknown outcome blocks reassignment. The code had neither rule. A task record also had none of six fields that this change adds.

What changes

  • Waiting. The holder gives a task up until an outside event comes: wait_for, or amem task wait <id> <event>. The task is then blocked, and its waiting_on field names the event. No session can claim it. A person or a tap reports the event with amem task arrived <event>, and each task of the domain that waited on it is open again. amem refuses an agent that reports an event. The memory server has no tool that reports an event.
  • External actions. A holder records an external action before it makes the action: effect_begun, or amem task effect-begin <id> <action>. The effects field lists the actions that are not settled. While the list is not empty, no session can claim the task, also after the lease expires, and the holder cannot complete the task.
  • Settlement. effect_settled, or amem task effect-settle <id> <action> applied|not_applied, records what the destination holds. The holder settles its action while its claim is in force. After that, only a person or a tap settles it. The ledger refuses a different outcome for a settled action.
  • The same work under a different name. The ledger files no retry, no continuation and no subagent task of a task with an unsettled action. No session can claim a task that continues or retries such a task. amem does not invalidate such a task, and fan_out does not close a continuation that has such an action. When the last action is settled and a continuation is already done, the ledger closes the task with the handoff of that continuation.
  • Words of a session. amem refuses wait_for, effect_begun and the reason of an invalidation from a session whose label is above the classification of the task.
  • Record of a refusal. A refused claim of a held task goes on the record with the event or the action as its reason.
  • Workers. amem work and amem gofannon work do not stop on a task that has an unsettled action. They release the task with a handoff.
  • Fields. A task has parent, scores, implements, reversibility, artifacts_touched and policy_context. amem task create and create_task take them, but not policy_context, and create_task does not take scores. amem sets policy_context to the ids of the verified policy entries. amem refuses a parent that the session cannot read.
  • Both backends. The SQLite backend and the PostgreSQL backend each project the new events through one function, and the ledger tests run on both.
  • Demo. Step 4.4 of act 4 (scripts/demo/4-fabric.sh) shows a task that waits on check:fixtures-42, the event that opens it again, an action push-fixtures-1 that holds the task after its lease expires, and the settlement that frees it. docs/demo-narrative.md, local/README.md, docs/terms.md, README.md and PLAN.md say the same.

The command line and the memory server call the same functions of the service: wait_for, effect_begun, effect_settled and create_task.

Not in this change

README.md ("The task record, in part"), local/README.md and row 2 of PLAN.md list what is open:

  • The other operations have no attempt key.
  • No connector makes external actions in sequence and settles them, so the holder must record each action.
  • The taps do not report events: a person does, or a script that runs amem task arrived.
  • No rule reads scores, implements, reversibility or artifacts_touched.
  • The hold of an unsettled action follows the origin of a task, so a new task with the same text and no such origin is not held.
  • amem does not raise the classification of a task, so a session that read above its task records no action and no wait on it.
  • A task under a lease in force stays with its holder when its continuation is done, and it is on offer again if that lease expires.
  • The test for amem work is at the function that decides (completes_with_report), not through a full run of a harness.
  • The generation does not prove who the caller is.

Checks

The pre-commit hook ran make check for 37601fa. The log of that run shows 1031 tests passed and 6 skipped, and the commit has exit status 0. The last commit of the branch changes only PLAN.md, and the hook does not run make check for such a change.

Act 4 ran on 37601fa, on a new scratch record with the debug binary, with no gateway key and no harness. Step 4.4 printed each line, and the script has exit status 0. The steps of act 4 that need a harness did not run: the subagents on the board, the work of the board, and the results that come back.

main has new commits since the merge commit 9d4fc6a. git merge-tree reports no conflict between this branch and main. No check ran on that merge.

Each new assertion failed with its guard removed, one mutation for each run. The ledger tests ran on SQLite and PostgreSQL. For 37601fa there were 22 mutations, and each test run that held one of them failed:

Guard removed Test that failed
The check in create, the task: origin, the walk up the chain no_work_is_filed_from_a_task_whose_effect_is_not_settled, a_task_with_an_unsettled_effect_is_not_retried_fanned_out_or_closed
The check of a resumed task in claim_next and claim, for each backend a_task_that_resumes_one_with_an_unsettled_effect_is_nobody_s
The effect check in invalidate a_task_with_an_unsettled_effect_is_not_retried_fanned_out_or_closed
The effect check before a fold a_continuation_with_an_unsettled_effect_is_not_folded_into_a_new_one
The close after a settlement a_task_closes_with_its_done_continuation_when_its_last_effect_is_settled
The label check in effect_begun, wait_for and invalidate a_session_that_read_above_a_task_writes_no_words_of_its_own_on_it
The check of a named parent a_filing_names_no_parent_that_the_session_may_not_read
The content of policy_context, and its stamp a_filing_names_the_verified_policy_of_the_record
The waiting clause in admit, and the tasks that arrived reads a_task_sent_back_to_triage_while_it_waits_still_waits
The refusal of a different outcome a_settled_effect_gives_the_task_back_and_fences_who_began_it
The reason from the hold a_task_that_waits_on_an_event_is_nobody_s_until_the_event_comes, an_effect_not_settled_holds_the_task_past_its_lease, a_task_that_resumes_one_with_an_unsettled_effect_is_nobody_s
A report that does not complete a_report_does_not_complete_a_task_with_an_action_that_nobody_settled
A gofannon run that does not complete a_run_that_left_an_action_not_settled_gives_the_task_back_with_its_result

The message of 37601fa gives what each test reported. The message of 7cfd32b names the mutations for the first commit: the check in claimable, the completion with an unsettled action, the refusal of an agent that reports an event, and the settlement by a second agent. The pass below showed that the two other tests of 7cfd32b, for policy_context and for parent, did not fail without their rule. 37601fa replaces them.

Adversarial pass

A fresh agent ran the pass on 9d4fc6a, with the diff of the branch as its scope. It found nine items, and 37601fa answers each of them:

  1. High. An unsettled effect held only the claims of its own task. A different agent got the work with retry_task or fan_out, or closed the task with invalidate. Now the ledger files no such task, no session claims a task that continues or retries it, and the service does not invalidate it.
  2. Medium. A done continuation did not close a task with an unsettled effect, and a later settlement put the work on offer again. Now the settlement of the last effect closes the task.
  3. Medium. wait_for, effect_begun and the reason of invalidate put the words of a session on a task below its label. Now the service refuses them. This also changes invalidate, which is older than this branch.
  4. Medium. The check of a named parent and the policy_context stamp had no test that failed without them. Each has one now.
  5. Medium. A waiting task that goes back to triage had no test. It has one now, on both backends.
  6. Low. A second settlement with a different outcome gave no error. Now the ledger refuses it.
  7. Low. A refused claim of a held task had a wrong reason on the record. Now the reason names the event or the effect.
  8. Low, and plausible only. amem work and amem gofannon work stopped on the refusal to complete a task with an unsettled effect. The gofannon test showed the defect with the old code. Now they release the task with a handoff.
  9. Low. The doc comment of project was above project_hold in the PostgreSQL backend. Each function has its own comment now.

The pass did not break these: a claim of a held task by its own id, also after the lease expires and after a rebuild, on both backends; the refusal of a stale holder; the projection of the new events; the refusal of an agent that reports an event; the checks of an event name and an action id.

Three items stay open in part, and the list above names them: the hold follows the origin only (item 1), a task under a lease in force is left to its holder (item 2), and the classification of a task does not go up (item 3).

No pass ran on 37601fa.

Posted by Claude on behalf of @sstriker

🤖 Generated with Claude Code

https://claude.ai/code/session_01D5DyJRpu5ned5y3d92U7Ts

sstriker and others added 4 commits November 14, 2026 22:38
…ct is not settled

A session does not wait for an outside event. The holder gives the task up
with `wait_for`, and the task is blocked until a tap or a person reports the
event. An agent cannot report an event.

A holder records an external action before it makes the action. While an
action is not settled, no session can claim the task, also after the lease
expires, and the holder cannot complete the task. The holder settles its
action while its claim is in force. After that, only a person or a tap
settles it.

A task also has the fields `parent`, `scores`, `implements`, `reversibility`,
`artifacts_touched` and `policy_context`. The service sets `policy_context`,
and it refuses a `parent` that the session cannot read.

Both ledger backends project the new events through one function. The memory
server has the tools `wait_for`, `effect_begun` and `effect_settled`. The
command line has `amem task wait`, `arrived`, `effect-begin` and
`effect-settle`. Act 4 of the demo shows both mechanisms.

Each new guard has a test that fails without the guard: the check in
`claimable`, the completion with an action that is not settled, the refusal
of an agent that reports an event, the settlement by a second agent, the
`policy_context` stamp and the `parent` check.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D5DyJRpu5ned5y3d92U7Ts
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D5DyJRpu5ned5y3d92U7Ts
The adversarial pass found nine defects. This commit fixes each of them.

1. An unsettled effect held only the claims of its own task. A different
   agent got the work with `retry_task` or `fan_out`, or closed the task with
   `invalidate`. Now the ledger files no retry, no continuation and no
   subagent task of such a task. No session can claim a task that continues
   or retries it, on SQLite and on PostgreSQL. The service does not invalidate
   it, and `fan_out` does not close a continuation that has such an effect.
2. A done continuation did not close a task with an unsettled effect, and a
   later settlement put the work on offer again. Now the settlement of the
   last effect closes the task with the handoff of that continuation.
3. `wait_for`, `effect_begun` and the reason of `invalidate` put the words of
   a session on a task below its session label. The service now refuses them.
4. The check of a named `parent` and the `policy_context` stamp had no test
   that failed without them. Each has one now.
5. A waiting task that goes back to triage had no test. It has one now, on
   both backends.
6. A second settlement with a different outcome gave no error. The ledger now
   refuses it, and the record keeps the first outcome.
7. A refused claim of a held task had a wrong reason on the record. The
   reason now names the event or the effect.
8. `amem work` and `amem gofannon work` stopped on the refusal to complete a
   task with an unsettled effect. They now release the task with a handoff.
   The gofannon test showed the defect before the fix.
9. The doc comment of `project` was above `project_hold` in the PostgreSQL
   backend. Each function has its own comment now.

Each assertion failed with its guard removed, one mutation for each run.
The ledger tests ran on SQLite and PostgreSQL:

- no check in `create`, no `task:` in the origins, no walk up the chain:
  `no_work_is_filed_from_a_task_whose_effect_is_not_settled` reported
  "retries:<id> gave Ok(Created ...)". The service test
  `a_task_with_an_unsettled_effect_is_not_retried_fanned_out_or_closed`
  failed too.
- no `resumes_unsettled` in `claim_next` or in `claim`, for each backend:
  `a_task_that_resumes_one_with_an_unsettled_effect_is_nobody_s` reported
  "assertion failed: next(...).is_none()" or "is_wrong_state(&claim(...))".
- no effect check in `invalidate`:
  `a_task_with_an_unsettled_effect_is_not_retried_fanned_out_or_closed`.
- no effect check before a fold:
  `a_continuation_with_an_unsettled_effect_is_not_folded_into_a_new_one`.
- no close after a settlement:
  `a_task_closes_with_its_done_continuation_when_its_last_effect_is_settled`
  reported left Claimed, right Done.
- no label check in `effect_begun`, `wait_for` or `invalidate`:
  `a_session_that_read_above_a_task_writes_no_words_of_its_own_on_it`
  failed at the assertion for that call.
- no `check_task` on the named parent:
  `a_filing_names_no_parent_that_the_session_may_not_read`.
- an empty `policy_context`, and no stamp:
  `a_filing_names_the_verified_policy_of_the_record` reported left [] and
  left ["pol-made-up"].
- no waiting clause in `admit`, and `arrived` that reads only blocked tasks:
  `a_task_sent_back_to_triage_while_it_waits_still_waits` reported left
  Open, right Blocked, and one woken task where two were expected.
- a different outcome accepted:
  `a_settled_effect_gives_the_task_back_and_fences_who_began_it`.
- no reason from the hold:
  `a_task_that_waits_on_an_event_is_nobody_s_until_the_event_comes`,
  `an_effect_not_settled_holds_the_task_past_its_lease` and the test of the
  resumed task reported the old reason.
- a report that completes:
  `a_report_does_not_complete_a_task_with_an_action_that_nobody_settled`.
- a gofannon run that completes:
  `a_run_that_left_an_action_not_settled_gives_the_task_back_with_its_result`
  reported "cannot complete while an effect of it is not settled".

These limits are open, and `local/README.md` and `PLAN.md` name them. The
hold follows the `origin` of a task, so a new task with the same text and no
origin is not held. The classification of a task does not go up, so a session
that read above its task records no effect and no wait on it. A task under a
lease in force stays with its holder when its continuation is done.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D5DyJRpu5ned5y3d92U7Ts
The row of package 2 in "Landed so far" said "this pull request". It now
names #81.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D5DyJRpu5ned5y3d92U7Ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant