Skip to content

feat: agent-native MVP public surface (WS1 + WS2 days 1–3)#44

Merged
Skanislav merged 6 commits into
devfrom
feat/agent-native-mvp-ws2
Apr 14, 2026
Merged

feat: agent-native MVP public surface (WS1 + WS2 days 1–3)#44
Skanislav merged 6 commits into
devfrom
feat/agent-native-mvp-ws2

Conversation

@Skanislav

@Skanislav Skanislav commented Apr 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Day 2 of the agent-native MVP sprint (docs/sprint-agent-native-mvp.md). Closes the agent read↔write loop end-to-end.

  • Runtime manifest: apps/hollab-indexing/src/api/index.ts now serves /agents/index.json and /agents/:orgId.json assembled per request from the Ponder DB via Drizzle (asc/eq re-exported from "ponder" — no extra dep). Per-org route returns org, contracts, circles, roles, members with stable ordering, joining meetingFactory + actionVoting from meetingComponentSet. BigInts serialized as decimal strings via a custom JSON.stringify replacer.
  • Agent example: packages/agent-sdk/examples/propose-tension.ts — fetches the manifest, builds a HollabAgent against viem/chains.foundry, submits createRole through the per-org MeetingFactory, polls the manifest until the role appears, and prints the SPA permalink. Default AGENT_PRIVATE_KEY is anvil account feat: add spec 08 for decentralized frontend deployment via IPFS + ENS #9.
  • Agent chips: apps/hola-modern/src/config/agents.ts is the MVP allowlist (anvil account feat: add spec 08 for decentralized frontend deployment via IPFS + ENS #9). PublicOrgView now chips members and role leads when their address matches, plus a new Roles section with name / purpose / leads.
  • Docs: packages/agent-sdk/docs/agent-manifest-v1.md (full v1 schema with rationale + known limitations) and packages/agent-sdk/README.md (30-line quickstart).

Day 2 finding — openProposals is empty by design

The Ponder schema has a proposal table, but no contract currently emits the events that populate it. MeetingFactory.executeGovernance applies role/policy changes directly to RoleRegistry without going through a tension/proposal lifecycle. Implications:

  1. WS1 "proposals list + adopted timeline" was blocked at the contract layer and replaced with a Roles list — same purpose, real data.
  2. The agent example creates a role, not a proposal. Filename kept (propose-tension.ts) to match the sprint spec; the script header documents the gap.
  3. Manifest openProposals: [] always — additive future-compat, populates without a version bump when the lifecycle ships.

Logged inline in docs/sprint-agent-native-mvp.md "Day 2 progress log".

Test plan

  • pnpm check-types — clean across all 4 affected packages
  • pnpm lint — 0 errors (4 pre-existing warnings, none mine)
  • pnpm test — 11 passing (4 agent-sdk encoding + 7 useHashRouter)
  • Hono route ordering verified — /agents/index.json registered before /agents/:orgId.json, no Ponder reserved-route collision
  • Live smoke test./scripts/dev-local.sh + pnpm --filter @hollab-io/agent-sdk tsx examples/propose-tension.ts <orgId> → role appears on #/o/<orgId> with 🤖 chip. Held until the seed-orgs script lands so a fresh-clone test exercises the whole flow in one pass.

🤖 Generated with Claude Code

Skanislav and others added 6 commits April 14, 2026 11:12
…WS2 day 2)

Closes the agent loop end-to-end: the indexer now assembles agent.json
per request from its own DB, an agent-sdk example fetches the manifest
and submits a CreateRole through the per-org MeetingFactory, and the
public org surface chips known agent addresses on members and role leads.
"openProposals" stays empty and is documented as such — the proposal
lifecycle isn't on-chain yet (Day 2 finding logged in the sprint doc),
so the demo creates a role and the public view shows a Roles list
instead of a proposals timeline.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…gent allowlist

Extracts assembleOrgManifest / assembleOrgIndex into a pure manifest.ts
so the assembly logic is testable offline (no ponder:api / ponder:schema
imports). Adds 39 vitest specs covering bigint→decimal-string, the
"most recent componentSet" join, null-not-undefined for absent factory
addresses, JSON shape parity with agent-manifest-v1.md, deterministic
ordering, large-bigint precision, and input immutability. Also adds 7
specs for isAgentAddress (case insensitivity, null/undefined/empty,
known/unknown). Vitest configured on hollab-indexing using the
workspace-hoisted vitest@2.1.9 — no new deps. Test totals across the
workspace: 11 → 64 (manifest 39, agents 7, prior 18).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Day 2 finding (no proposal lifecycle on-chain yet) propagates into
Day 3: PublicProposalView would render against an empty table. Day 3
deep-link surface becomes #/o/:orgId/r/:roleId — same flywheel
mechanics on data that exists today. Proposal permalinks re-enter scope
when GovernanceProcess proposal events ship.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Load-bearing prerequisite for the WS3 smoke test: deploys two wired
demo orgs (paperclip, solarpunk) on top of DeployLocal. Each org gets
its meeting components via MeetingComponentsFactory (wiring
MeetingFactory as the governance process on the cloned RoleRegistry),
the agent allowlist address added as a member, and roles created via
MeetingFactory.executeGovernance(CreateRole). Paperclip elects the
agent as lead on its first role so the public permalink renders a
agent chip end-to-end from a clean seed.

Roles live on circleId=0 because this contract set has no
CircleRegistry — _createRole doesn't validate the circle id. Noted
inline so it doesn't read as a bug next time.

Verified end-to-end against a local anvil: ONCHAIN EXECUTION COMPLETE
& SUCCESSFUL, paperclip=orgId 2, solarpunk=orgId 3.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… bigint query params

Three latent bugs that surfaced during the Day 3 WS3 smoke test, all
gating the public role-permalink flow end-to-end:

1. structural.ts was a stub — every RoleRegistry:Role* handler just
   refreshed the org snapshot and never wrote to schema.role. The
   role table was empty across every org, so listRolesByOrg and the
   /agents/:orgId.json manifest always returned 0 roles. Pre-existing
   — hidden because the authed StructureView reads roles from RPC via
   useWorkspaceSnapshot, not from the indexer. Handlers now re-read
   getRole + getRoleLeads from the clone and upsert, maintain
   organization.roleCount, and handle RoleLeadAssigned /
   RoleLeadUnassigned by updating only the leads array (with a fall-
   back upsert if the role row hasn't been created yet due to replay
   ordering).

2. The per-org manifest route was /agents/:orgId.json, which Hono 4.x
   captures as an empty string — every request fell into the
   BigInt("") → 0n → "org not found" branch. Replaced with
   /agents/:file{[^/]+\.json} and strip the .json suffix manually.

3. LIST_ROLES_BY_ORG and LIST_CIRCLES_BY_ORG declared $orgId as
   String! while the underlying column is bigint — same Day 1
   episode as GET_ORGANIZATION (10824bb). Never surfaced before
   because the role table was empty. Both now use BigInt!.

Added queries.spec.ts regression — asserts that GET_ORGANIZATION,
LIST_ROLES_BY_ORG, LIST_CIRCLES_BY_ORG, and
LIST_MEETING_COMPONENTS_BY_ORG all declare \$orgId: BigInt! as a
guard against future String! regressions.

Verified live: seeded orgs now show 3 roles on the public view with
the agent chip rendering on the Curator lead, and
/agents/2.json returns a complete manifest with roleCount: 3.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Day 3 of the agent-native MVP sprint — the flywheel. Every governance
object is now a wallet-less, shareable permalink an agent or a stranger
can open cold.

New routes on the public tree (all above the auth gate, zero wagmi,
zero useAccount, zero RPC):

- #/explore — global org directory backed by
  indexing-client.listOrganizations (not the manifest endpoint, so it
  stays on the typed GraphQL client the rest of the app already uses).
  Cards show ENS subname label, name, and member/circle/role counts.
  Sorted by updatedAt desc.
- #/o/:orgId/r/:roleId — PublicRoleView renders name, purpose,
  domains, accountabilities, and leads with the agent chip when the
  lead is in the allowlist. Back-link to the parent org.

useHashRouter gains explore and publicRole variants, with
url-encoded roleId round-trip (encodeURIComponent preserves "-",
survives "/" via %2F) and a malformed-fallback to public when /r has
no roleId. 16 parse/stringify tests total (up from 7).

PublicOrgView role cards now link to the permalink and gained a
Join community CTA that navigates to #/join/:orgId — the join flow
falls through the auth gate to Welcome where the existing
WalletAuthControl/RainbowKit lives, keeping the public subtree
literally zero-wagmi.

Base OG + Twitter card metadata added to index.html so shared links
preview as something other than a blank title.

Sprint doc updated: Day 3 checkboxes closed with the call-outs on
why explore fetches via listOrganizations vs. /agents/index.json
(keeps the public tree on the typed client) and why the Join CTA
delegates rather than mounting RainbowKit in-place. SeedDemoOrgs.s.sol
struck from the carried-over list (shipped in the prior commit).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Skanislav Skanislav changed the title feat: runtime agent.json + propose-tension demo (WS2 day 2) feat: agent-native MVP public surface (WS1 + WS2 days 1–3) Apr 14, 2026
@Skanislav Skanislav merged commit eb7e7ab into dev Apr 14, 2026
6 checks passed
@Skanislav Skanislav deleted the feat/agent-native-mvp-ws2 branch April 15, 2026 10:33
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