.agents/skills/context-awareness/SKILL.md
How the agent knows what the user is looking at. Use when exposing UI state to the agent, implementing view-screen or navigate scripts, wiring navigation state, or debugging agent context issues.
npx skillsauth add BuilderIO/agent-native context-awarenessInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
The agent must always know what the user is currently viewing. The UI writes navigation state on every route change. The agent reads it before acting.
Without context awareness, the agent is blind. It asks "which email?" when the user is staring at one. It cannot act on the current selection, cannot provide relevant suggestions, and cannot modify what the user sees. Context awareness is what makes the agent feel like a collaborator rather than a disconnected chatbot.
navigation key)The UI writes a navigation key to application-state on every route change. This tells the agent what view the user is on and what item is selected.
UI side — the use-navigation-state.ts hook:
// app/hooks/use-navigation-state.ts
import { useEffect, useCallback } from "react";
import { useLocation } from "react-router";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
export function useNavigationState() {
const location = useLocation();
// Sync route to app-state on every navigation
useEffect(() => {
const state = deriveNavigationState(location.pathname);
fetch("/_agent-native/application-state/navigation", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(state),
}).catch(() => {});
}, [location.pathname]);
// ... also listen for navigate commands (pattern 3)
}
Agent side — read before acting:
import { readAppState } from "@agent-native/core/application-state";
const navigation = await readAppState("navigation");
// e.g. { view: "thread", threadId: "abc123", subject: "Re: Q3 Planning" }
What to include in navigation state:
view — the current page/section (e.g., "inbox", "form-builder", "dashboard")threadId, formId, issueKey)view-screen ScriptEvery template should have a view-screen script. It reads navigation state, fetches the relevant data from the API, and returns a snapshot of what the user sees. This is the agent's eyes.
// actions/view-screen.ts
import { readAppState } from "@agent-native/core/application-state";
export default async function main() {
const navigation = await readAppState("navigation");
const screen: Record<string, unknown> = { navigation };
// Fetch data based on what the user is viewing
if (navigation?.view === "inbox") {
const emails = await fetchEmailList(navigation.label);
screen.emailList = emails;
}
if (navigation?.threadId) {
const thread = await fetchThread(navigation.threadId);
screen.thread = thread;
}
console.log(JSON.stringify(screen, null, 2));
}
Navigation state is auto-injected into every user message as a <current-screen> block, so the agent always has basic context without calling any tool. The view-screen action is still useful when you need a richer snapshot (e.g., fetching the full email thread or form data for the current view).
navigate ScriptThe agent writes a one-shot navigate command to application-state. The UI reads it, performs the navigation, and deletes the entry.
Agent side:
import { writeAppState } from "@agent-native/core/application-state";
// Navigate the user to a specific thread
await writeAppState("navigate", { view: "inbox", threadId: "abc123" });
UI side — the hook polls for the command:
const { data: navCommand } = useQuery({
queryKey: ["navigate-command"],
queryFn: async () => {
const res = await fetch("/_agent-native/application-state/navigate");
if (!res.ok) return null;
const data = await res.json();
if (data) {
// Delete the one-shot command after reading
fetch("/_agent-native/application-state/navigate", { method: "DELETE" });
return data;
}
return null;
},
staleTime: 2_000,
});
useEffect(() => {
if (navCommand) {
router.navigate(buildPath(navCommand));
}
}, [navCommand]);
When the agent writes to application-state via script helpers (writeAppState), the write is tagged with requestSource: "agent". The UI uses the ignoreSource option on useDbSync() with a per-tab ID so it ignores its own writes while still picking up changes from agents, other tabs, and scripts.
// app/root.tsx
import { TAB_ID } from "@/lib/tab-id";
useDbSync({
queryClient,
queryKeys: ["app-state", "settings"],
ignoreSource: TAB_ID, // ignore events from this tab's own writes
});
The UI sends its tab ID via X-Request-Source header on PUT/DELETE requests. The server stores this as the event's requestSource. When polling, the UI filters out events matching its own ignoreSource value. This prevents the UI from refetching data it just wrote.
The mail template demonstrates all three patterns working together:
Navigation state shape:
{ "view": "inbox", "threadId": "thread-123", "focusedEmailId": "msg-456", "search": "budget", "label": "important" }
view-screen output:
navigate command:
{ "view": "starred" } — switch to starred view{ "view": "inbox", "threadId": "thread-123" } — open a specific thread<current-screen> block for basic context — call view-screen only when you need richer datanavigation key (view, item IDs, filters)view-screen when adding new features — it should return data for every viewnavigate command pattern for agent-initiated navigationrequestSource: "agent" (the script helpers do this automatically)navigation key from the agent — it belongs to the UI. Use navigate instead.<current-screen> block — it tells you where the user isview-screen script fetches the actual data.useDbSync deliver app-state changes to the UIview-screen and navigate scriptstools
Public booking flow — the state machine, animations, and URL/app-state sync.
tools
Trigger-based automations — reminders, follow-ups, webhooks — across the booking lifecycle.
tools
Team event types, round-robin assignment, collective bookings, host weights, and no-show calibration.
development
The pure `computeAvailableSlots` function — inputs, outputs, invariants, and debugging guide.