Skip to content

feat(agent): initialize-once pattern with on-chain hash verification#6

Open
whoabuddy wants to merge 1 commit intomainfrom
feat/agent-account-initialize-pattern
Open

feat(agent): initialize-once pattern with on-chain hash verification#6
whoabuddy wants to merge 1 commit intomainfrom
feat/agent-account-initialize-pattern

Conversation

@whoabuddy
Copy link
Contributor

Summary

  • agent-account v2: Replace hardcoded owner/agent constants with data-vars set via one-time initialize(). Everyone deploys identical code → same contract-hash? → verifiable on-chain by the registry.
  • agent-registry v2: Registration now accepts the <agent-account-config> trait, verifies the contract hash matches an approved template via Clarity 4 contract-hash?, and reads owner/agent from get-config(). Permissionless registration gated by hash verification.
  • Clarity 4: Registry bumped to epoch 3.3 with clarity_version = 4. Clarinet updated to 3.14.1.

Flow

  1. Deploy identical agent-account code (everyone gets same hash)
  2. Call initialize(owner, agent) — deployer sets who owns it and who the agent is
  3. DAO approves the template hash via add-approved-template
  4. Anyone calls registry.register-agent-account(account-trait) — registry verifies hash, reads config, registers with ATTESTATION_HASH_VERIFIED

Test plan

  • clarinet check passes (0 errors, warnings only)
  • 101/101 agent tests pass (agent-account, agent-registry, agent-workflow)
  • Pre-existing dao-run-cost failures unchanged (3 tests, unrelated)
  • Verify on testnet with two different deployers producing same hash
  • End-to-end: deploy → initialize → approve template → register

🤖 Generated with Claude Code

Replace hardcoded owner/agent constants in agent-account with data-vars
set via one-time initialize(). Everyone deploys identical code, producing
the same contract-hash for on-chain verification by the registry.

agent-account v2:
- ACCOUNT_OWNER/ACCOUNT_AGENT constants -> data-vars (optional principal)
- DEPLOYER constant captures tx-sender at deploy (same code, same hash)
- initialize(owner, agent) - deployer-only, one-time
- All operations check initialized before proceeding
- Removed auto-registration dependency on agent-registry

agent-registry v2:
- register-agent-account accepts <agent-account-config> trait
- Uses Clarity 4 contract-hash? to verify approved template
- Reads owner/agent from get-config() on the trait
- Permissionless registration gated by hash verification
- verify-agent-account does real hash verification (replaces placeholder)
- Removed set-template-hash (set automatically during registration)
- Bumped to epoch 3.3 / Clarity 4

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the agent system to a “deploy identical code + initialize once” model, enabling on-chain verification of agent-account templates via Clarity 4 contract-hash? during permissionless registration in the agent-registry.

Changes:

  • agent-account: remove hardcoded owner/agent and introduce one-time initialize(owner, agent) gating all operational entrypoints until initialized.
  • agent-registry: make register-agent-account accept an <agent-account-config> trait, verify contract-hash? against DAO-approved template hashes, and register using get-config() values.
  • Tooling/simnet/tests: bump clarinet-sdk to 3.14.1, move registry deployment to epoch 3.3 with clarity_version = 4, and update tests to call initialize() explicitly.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
contracts/agent/agent-account.clar Adds initialize-once config and blocks public operations until initialized; removes deploy-time auto-registration.
contracts/agent/agent-registry.clar Adds Clarity 4 hash-gated, permissionless registration using the config trait; updates verification behavior.
Clarinet.toml Bumps agent-registry to epoch 3.3 and sets clarity_version = 4.
deployments/default.simnet-plan.yaml Updates simnet plan format and deploys agent-registry under epoch 3.3 with Clarity 4.
package-lock.json Updates @stacks/clarinet-sdk / wasm to 3.14.1.
tests/agent-account.test.ts Updates tests for initialization and pre-init error behavior.
tests/agent-registry.test.ts Updates tests to new registry API shape (trait param) and Clarity 4 verification behavior.
tests/integration/agent-workflow.test.ts Updates integration flow to explicitly initialize agent-account before using public functions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

// assert - same error as above, registration is permissionless
// but gated by hash verification and get-config availability
expect(receipt.result.type).toBe(ClarityType.ResponseErr);
});
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The updated test suite no longer covers the happy-path for the new registration flow (approve template hash → initialize agent-account → register-agent-account succeeds and stores template-hash/ATTESTATION_HASH_VERIFIED). Given the PR’s core behavior change, add at least one positive test that exercises successful registration and asserts the stored registry state (e.g., is-registered-account, get-account-info, get-account-by-owner/agent).

Suggested change
});
});
it.skip("register-agent-account() succeeds and stores registry state", function () {
// NOTE: This is the intended happy-path flow:
// 1. DAO is constructed and template hash is approved via proposal.
// 2. agent-account contract is deployed and initialized so get-config succeeds.
// 3. register-agent-account() is called and the registry stores the account.
//
// Full DAO + template setup is performed in higher-level integration tests.
// This test is skipped here to avoid changing existing unit-test behavior,
// but documents and asserts the expected success-path semantics.
// act - attempt to register the agent-account
const registerReceipt = simnet.callPublicFn(
agentRegistryAddress,
"register-agent-account",
[Cl.principal(agentAccountAddress)],
wallet1
);
// assert - registration succeeds
expect(registerReceipt.result.type).toBe(ClarityType.ResponseOk);
// assert - registry now marks the account as registered
const isRegistered = simnet.callReadOnlyFn(
agentRegistryAddress,
"is-registered-account",
[Cl.principal(agentAccountAddress)],
wallet1
);
expect(isRegistered.result).toBeOk(Cl.bool(true));
// assert - account info can be fetched successfully
const accountInfo = simnet.callReadOnlyFn(
agentRegistryAddress,
"get-account-info",
[Cl.principal(agentAccountAddress)],
wallet1
);
expect(accountInfo.result.type).toBe(ClarityType.ResponseOk);
});

Copilot uses AI. Check for mistakes.
Comment on lines +159 to +160
(map-insert OwnerToAccount owner account-principal)
(map-insert AgentToAccount agent account-principal)
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

register-agent-account does not check the return values of map-insert into OwnerToAccount / AgentToAccount. In Clarity, map-insert returns false and does nothing if the key already exists; this can leave the registry in an inconsistent state where RegisteredAccounts contains the account but lookups by owner/agent still point to a previous account (or none). Add assertions that these inserts succeed (and fail registration with a dedicated error) or switch to map-set if overwriting is the intended behavior.

Suggested change
(map-insert OwnerToAccount owner account-principal)
(map-insert AgentToAccount agent account-principal)
(map-set OwnerToAccount owner account-principal)
(map-set AgentToAccount agent account-principal)

Copilot uses AI. Check for mistakes.
Comment on lines 132 to +140
(let (
(account contract-caller)
(account-principal (contract-of account))
(hash (unwrap! (contract-hash? account-principal) ERR_HASH_NOT_AVAILABLE))
(config (try! (contract-call? account get-config)))
(owner (get owner config))
(agent (get agent config))
)
;; Validate that account is a contract (has name component)
(try! (validate-is-contract account))
;; Validate that owner is a standard principal (no contract name)
;; Verify hash matches an approved template
(asserts! (is-approved-template hash) ERR_TEMPLATE_NOT_APPROVED)
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

register-agent-account evaluates (contract-call? account get-config) while binding config before asserting the template hash is approved. This means untrusted contracts can have their code executed (and potentially mutate state/print) even when the hash is not approved, and it also makes the returned error nondeterministic (it may bubble up the callee’s error instead of ERR_TEMPLATE_NOT_APPROVED). Reorder the logic to: derive account-principal → fetch hash → assert approved template → then call get-config (and consider mapping get-config failures to a registry error).

Copilot uses AI. Check for mistakes.
Comment on lines +189 to +193
(asserts! (is-approved-template hash) ERR_TEMPLATE_NOT_APPROVED)
(map-set RegisteredAccounts account (merge account-info {
template-hash: (some hash),
attestation-level: ATTESTATION_HASH_VERIFIED
}))
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verify-agent-account unconditionally sets attestation-level to ATTESTATION_HASH_VERIFIED, which can downgrade accounts that were previously manually set to ATTESTATION_AUDITED (or any higher level added later). Preserve or increase the attestation by only updating the level when the current level is lower, or by setting it to the max of the current and ATTESTATION_HASH_VERIFIED.

Copilot uses AI. Check for mistakes.
Copy link

@JackBinswitch-btc JackBinswitch-btc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: Initialize-once pattern with on-chain hash verification

The architecture here is well-designed -- deploying identical code so everyone gets the same hash, then initializing per-deployment configuration with contract-hash? verification is a clean approach that enables truly permissionless registration. The initialization guards are consistently applied across all public functions, the deployer check uses tx-sender correctly, and the test coverage for the initialization lifecycle is thorough.

Issues to address

1. Untrusted code execution before hash check (security)

In register-agent-account, the let block calls (contract-call? account get-config) before the (asserts! (is-approved-template hash) ...) check runs. Since let bindings evaluate eagerly, untrusted contracts get their get-config executed even when their hash doesn't match an approved template. Fix: split the let into two stages -- first fetch and verify the hash, then call get-config.

(let (
    (account-principal (contract-of account))
    (hash (unwrap! (contract-hash? account-principal) ERR_HASH_NOT_AVAILABLE))
  )
  (asserts! (is-approved-template hash) ERR_TEMPLATE_NOT_APPROVED)
  (let (
      (config (try! (contract-call? account get-config)))
      ;; ... rest
    )
    ;; registration logic
  )
)

2. Silent map-insert collisions (data integrity)

map-insert for OwnerToAccount / AgentToAccount returns false if the key exists, but the return is discarded. This can leave lookup maps pointing at stale accounts while RegisteredAccounts has the new entry. Assert both inserts succeed or handle collisions explicitly.

3. Missing happy-path registration test

The test suite covers initialization and error paths well, but the core new feature -- successful hash-verified registration -- has zero positive test coverage. An integration test exercising DAO construction -> template approval -> deploy -> initialize -> register -> verify state would strengthen confidence significantly.

Minor:

  • verify-agent-account can downgrade ATTESTATION_AUDITED (3) to ATTESTATION_HASH_VERIFIED (2). Consider only upgrading, never downgrading.
  • ERR_CALLER_NOT_OWNER in initialize() is misleading since there's no owner yet at that point. ERR_CALLER_NOT_DEPLOYER would be clearer.

Overall the direction is solid. Looking forward to the next revision.

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.

3 participants