Skip to content

feat: slash commands and goal halt-gate - #30

Merged
arjunkmrm merged 2 commits into
mainfrom
feat/slash-commands-and-goal
May 28, 2026
Merged

feat: slash commands and goal halt-gate#30
arjunkmrm merged 2 commits into
mainfrom
feat/slash-commands-and-goal

Conversation

@arjunkmrm

@arjunkmrm arjunkmrm commented May 28, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • New JSX emit kind: emitCommand({name, description?, handler}). Collected by the render walker into Rendered.commands alongside fragments and tools.

  • Slash-command router in agent.run: a string input matching /<name>(\s+args)? is routed to the matching command's handler instead of inference. Unknown slash inputs fall through to inference with a warning.

    // tree: <Agent><Goal /><Workspace root="./" /><Messages /></Agent>
    
    await agent.run("/goal tests are green")
    //   → matches the "goal" command; handler registers a halt predicate
    //   → no user.message appended, no inference triggered
    //   → returns once handler resolves
    
    await agent.run("/goal clear")
    //   → same command, args = "clear"; handler unregisters the predicate
    
    await agent.run("fix the failing test")
    //   → no slash prefix → normal path: user.message appended, inference fires
    
    await agent.run("/typo something")
    //   → no "typo" command registered → warns
    //     "[agentjsx] received slash input \"/typo\" but no command with
    //      that name is registered; passing through as user.message"
    //   → falls through to inference with the literal "/typo something"
  • Halt-predicate registry on AgentCtx: registerHaltPredicate, clearHaltPredicate, getHaltPredicates. Stored as a SubscriptionRef<Map<string, HaltPredicate>> so future consumers can subscribe.

  • runHaltGate(infer) supervisor: forked scoped fiber that watches the event log for "about to return control to the user" states — both explicit assistant.halted and natural terminals (last event = assistant.message with no pending tool calls). Runs all registered predicates; on any ok: false, appends a synthetic user.message with the failing reasons so the inference loop reprompts the model.

  • <Goal /> component: ships the /goal <condition> (and /goal clear) slash command. The handler registers a HaltPredicate that judges the transcript against the condition via a separate infer call, returning {ok, reason} JSON.

  • <Skills> reflects commands: frontmatter into emitCommand entries — each skill's commands become slash invocations whose handlers append the skill's prompt as a user message.

Test plan

  • pnpm check-types — clean.
  • pnpm test — 45 files, 166 tests pass.
  • test/agentctx/core/agent-run-contract.test.ts (5 tests): non-slash input goes to inference; unknown slash falls through with warning; registered command's handler runs and the slash input is not appended as user.message; assistant.halted is terminal without predicates; halt-gate reprompts when a predicate returns ok=false.
  • test/agentctx/core/goal-component.test.ts: <Goal> emits one fragment and one command with name "goal".
  • Live e2e against sonnet-4-6 with a temporary <Goal />-based example: set /goal "greeted in pirate speak", agent says "Hello!" → halt-gate runs judge → ok=false, reason "no pirate speak" → reprompt appended → agent re-infers "Ahoy, matey!" → judge ok=true → halt stands.

@arjunkmrm
arjunkmrm merged commit 689bdb4 into main May 28, 2026
1 check passed
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