On this page

Review agent proposals with bind hooks

Prerequisite: bind and runtime hooks. Expert 37 shows an application installing its own registration policy on a live normal Conduit, then passing it object references supplied by an agent workflow. Use the existing codegen lessons when the workflow must first produce those references.

Check the actual supplied reference

The example registers two ordered pre strategies:

  1. Require an existing ReportTool object.

  2. Require its schema_version to be 1.

The second check sees the same object as the first, including its current instance state. An unrelated object and a tool with an unsupported schema both fail in pre_bind, before a Spell is created or registered.

conduit.add_bind_hooks(
    pre=[review.require_report_tool, review.require_supported_schema],
    activation=[review.configure_spell],
    post=[review.record_registration],
)

supplied = ReportTool()
spell_id = conduit.bind(
    spell=supplied,
    existence="unique",
    submitted_by="report-agent",
)
assert conduit.meld("ReportTool") is supplied

The unique-only lifetime is Melder's existing rule for supplied objects. The hook pipeline checks and annotates that registration; resolution returns the same supplied reference. The example's type/schema checks are application policy, not new built-in restrictions on every Melder binding.

Modify the Spell at the right stage

Pre-bind receives only the target reference. Bind keyword metadata becomes available on the new Spell in activation. The example checks its submitted_by label there, then sets spell.metadata["review_policy"] and appends an agent-reviewed tag. The label is application-provided metadata; trusted caller identity must come from the application's own authenticated context if that distinction is required.

Post-bind receives the same Spell after registration. The example verifies that book.find_spell_by_spell_id(spell.spell_id) returns it, sets spell.metadata["post_checked"] = True, and records its ID in the application's audit. The caller then inspects those changes through the public lookup.

These are live metadata changes. They do not replace source code, create a new version, recompute a Spell ID or automatically republish a recorded snapshot. Native identity is established before bind activation; identity-bearing changes need the normal binding/version workflow. Recording and Nexus publication can precede post-bind, so a post-hook metadata edit is not a promise that an earlier published record changed.

This example uses active bind registrations. bind_inactive also runs the lifecycle, but its post callback receives a parked candidate. A callback for that path should not assume ordinary selected-version lookup returns the parked Spell.

Handle failure according to its phase

Failure phase

Demonstrated result

pre_bind

An unsuitable reference is refused; activation and post do not run

bind_activation

An unsupported submitter label is refused; the unpublished Spell is retired and the caller's object remains usable

post_bind

The notification fails after registration; the binding and earlier post metadata changes remain

Inspect HookExecutionError.original_exception for the application's cause. Raising stops the remaining callbacks in that stage; return values are ignored.

Expert 37 appends a simulated failing audit callback after its normal post callback. It catches the resulting post_bind error and proves meld("BackupReport") still returns the registered object. Inspect existing state before retrying such a bind; a failed notification does not mean a second registration is needed.

Keep policy updates on the Book's existing lifecycle

add_bind_hooks appends and clear_bind_hooks clears all stages for future binds. Each bind retains its captured callbacks, including when callbacks change the registry. Use the Book or its normal-Conduit facade for updates. Configuration seeds are captured when a Book is constructed; changing another Book's runtime registry is separate.

The example's audit is sequential and in memory. Concurrent callbacks must coordinate any application state they share, and external notifications keep their own failure and retry policy. The existing transaction admission and versioning workflow continue to govern structural changes.

The workflow in code

These are the core steps from the saved example. Use its complete linked script for all class definitions and setup.

 1def main() -> None:
 2    """Bind accepted agent proposals and assert the precise refusal boundaries.
 3
 4    Contract:
 5        Uses ordinary public binding against a live normal Conduit. The
 6        proposal references stand for objects supplied by an agent workflow;
 7        acquiring or generating those references is a separate concern.
 8    """
 9    book = md.Spellbook(configuration=md.SpellbookConfiguration().with_defaults())
10    conduit = None
11    try:
12        conduit = book.conjure(dynamic=True, name="agent-bind-review")
13        review = BindingReview(book)
14        conduit.add_bind_hooks(
15            pre=[review.require_report_tool, review.require_supported_schema],
16            activation=[review.configure_spell],
17            post=[review.record_registration],
18        )
19
20        # A reference check rejects by raising, not by returning False.
21        for proposal, cause in ((object(), TypeError), (ReportTool(2), ValueError)):
22            try:
23                conduit.bind(spell=proposal, existence="unique", submitted_by="report-agent")
24            except md.HookExecutionError as error:
25                assert error.phase == "pre_bind"
26                assert isinstance(error.original_exception, cause)
27                print("reference refused:", error.phase, str(error.original_exception))
28            else:
29                raise AssertionError("The unsuitable proposal was registered.")
30        assert book.describe_spells_in_spellbook() == []
31        assert review.published_ids == []
32
33        supplied = ReportTool()
34        try:
35            conduit.bind(spell=supplied, existence="unique", submitted_by="unknown-agent")
36        except md.HookExecutionError as error:
37            assert error.phase == "bind_activation"
38            assert isinstance(error.original_exception, PermissionError)
39            print("agent metadata refused:", error.phase)
40        else:
41            raise AssertionError("The unapproved agent was admitted.")
42        assert book.describe_spells_in_spellbook() == []
43        assert supplied.render("still-owned-by-caller") == "report:still-owned-by-caller"
44
45        spell_id = conduit.bind(
46            spell=supplied, existence="unique", submitted_by="report-agent",
47        )
48        registered = book.find_spell_by_spell_id(spell_id)
49        assert registered is not None
50        assert registered.metadata == {
51            "submitted_by": "report-agent",
52            "review_policy": "report-schema-v1",
53            "post_checked": True,
54        }
55        assert "agent-reviewed" in registered.tags
56        assert conduit.meld("ReportTool") is supplied
57        assert supplied.render("weekly") == "report:weekly"
58        assert review.published_ids == [spell_id]
59        print("accepted existing object:", registered.metadata)
60
61        # add appends: record_registration runs before the later failing callback.
62        conduit.add_bind_hooks(post=[unavailable_audit_sink])
63        backup = BackupReport()
64        try:
65            conduit.bind(spell=backup, existence="unique", submitted_by="report-agent")
66        except md.HookExecutionError as error:
67            assert error.phase == "post_bind"
68            assert isinstance(error.original_exception, RuntimeError)
69            print("post notification failed; inspect the published binding before retrying")
70        else:
71            raise AssertionError("The audit failure was not propagated.")
72        assert len(review.published_ids) == 2
73        published_backup = book.find_spell_by_spell_id(review.published_ids[-1])
74        assert published_backup is not None
75        assert published_backup.spell_name == "BackupReport"
76        assert published_backup.metadata["post_checked"] is True
77        assert conduit.meld("BackupReport") is backup
78        print("binding survived post failure:", published_backup.spell_name)
79
80        conduit.clear_bind_hooks()
81        assert conduit.meld("ReportTool") is supplied
82        assert registered.metadata["post_checked"] is True
83    finally:
84        if conduit is not None:
85            conduit.cleanup()
86        book.cleanup()

Runnable examples

All expert examples · Level contents · Full contents

Canonical page source