Skip to content

Context Object Model (COM) ​

The browser has the DOM. AIDK has the COM—the Context Object Model.

What Is the COM? ​

The COM is a mutable state tree that represents everything the model will see. Components don't return strings—they mutate the COM. Sections, timeline entries, tools, shared state—all nodes in a tree that gets compiled and sent to the model.

Why COM Matters ​

The COM is what enables everything else in AIDK:

1. Separation of Concerns ​

Components don't format text—they declare structure. The renderer handles format conversion.

tsx
// You declare structure
<Section audience="model">
  <H2>Current Cart</H2>
  <Table headers={['Item', 'Price']} rows={items} />
</Section>

// The renderer produces:
// ## Current Cart
// | Item | Price |
// | Widget | $10 |

2. Reactive State ​

State changes trigger re-renders. Components subscribe to COM state and react when it changes.

tsx
class CartDisplay extends Component {
  // This signal is bound to COM state
  private cart = comState<Product[]>('cart', []);

  render() {
    // Automatically re-renders when cart changes
    return <Table rows={this.cart().map(p => [p.name, p.price])} />;
  }
}

3. Cross-Component Communication ​

Components share state through the COM without prop drilling.

tsx
// Component A has state
private searchResults = comState<SearchResult[]>('searchResults', []);

// Component A writes state
this.searchResults.set(results);

// Component B reads state (reactively)
const results = watch<SearchResult[]>('searchResults');

4. Tool Integration ​

Tool results flow back through the COM, and components can react to them.

tsx
onTickStart(com, state) {
  // Check if a tool was called
  const toolResults = state.current?.toolResults;
  if (toolResults?.some(r => r.name === 'search')) {
    this.hasSearched.set(true);
  }
}

COM Sections ​

Sections are named content blocks that become system messages or grounding data.

tsx
// Instructions for the model
<Section id="role" audience="model">
  You are a helpful shopping assistant.
</Section>

// Grounding data (positioned after system message)
<Grounding position="after-system" audience="model">
  <H3>Available Products</H3>
  <Table headers={['Name', 'Price']} rows={products} />
</Grounding>

// Content for users only (not sent to model)
<Section audience="user">
  Processing your request...
</Section>

// Content for both
<Section audience="all">
  Current time: {new Date().toISOString()}
</Section>

Section merging: Sections with the same id are merged. This prevents duplicate system prompts when components add to the same section across ticks.

COM Timeline ​

The timeline is the conversation history—user messages, assistant responses, tool calls and results.

tsx
// Reading timeline from previous response
onTickStart(com, state) {
  if (state.current?.timeline) {
    this.timeline.update(t => [...t, ...state.current.timeline]);
  }
}

// Rendering timeline
render() {
  return (
    <Timeline>
      {this.timeline().map(entry => (
        <Message key={entry.id} role={entry.message.role}>
          {entry.message.content}
        </Message>
      ))}
    </Timeline>
  );
}

Timeline Entries ​

Each entry has a kind:

typescript
type TimelineEntry =
  | { kind: 'message'; message: Message }
  | { kind: 'tool_call'; call: ToolCall }
  | { kind: 'tool_result'; result: ToolResult }
  | { kind: 'event'; event: TimelineEvent };

Events in Timeline ​

Tell the model about things that happened outside the conversation:

tsx
<Timeline>
  {messages}
  <Event eventType="user_action">
    User clicked "Checkout" button
  </Event>
  <Event eventType="system">
    Payment confirmed via Stripe
  </Event>
</Timeline>

COM State ​

Shared state that persists across ticks and is accessible to all components.

State Signals (Preferred) ​

The preferred pattern is comState (class) or useComState (functional)—they're reactive and automatically synced with the COM.

tsx
class MyComponent extends Component {
  // Creates COM-bound signal
  private cart = comState<Product[]>('cart', []);

  addToCart(product: Product) {
    // Updates both signal AND COM state
    this.cart.update(c => [...c, product]);
  }

  render() {
    // Reactive—re-renders when cart changes
    return <CartDisplay items={this.cart()} />;
  }
}

Reading State Reactively ​

tsx
// Reactive read via watch (for reading state you don't own)
const value = watch<string>('key');

Signal Types Comparison ​

TypeScopePersists?Reactive?Use Case
signal()ComponentYesYesLocal component state
comState()COMYesYesShared state across components
watch()COMN/AYesRead-only observation
computed()DerivedYesYesDerived values

COM Events ​

The COM emits events when mutations occur:

tsx
// Subscribe to events
com.on('message:added', (message) => {
  console.log('New message:', message);
});

com.on('tool:registered', (tool) => {
  console.log('Tool registered:', tool.name);
});

com.on('state:changed', (key, value) => {
  console.log(`State ${key} changed:`, value);
});

This enables loose coupling—components can react to changes without direct dependencies.

Tick Control ​

The COM provides process-like control over execution:

tsx
// Request the tick loop to stop
com.requestStop();

// Request continuation even if model says stop
com.requestContinue();

// Check pending requests
const should = com.resolveTickControl(); // 'stop' | 'continue' | null

COM in Lifecycle Hooks ​

The COM is available in all lifecycle hooks:

tsx
class MyAgent extends Component {
  private initialized = comState<boolean>('initialized', false);
  private timeline = comState<COMTimelineEntry[]>('timeline', []);

  onMount(com) {
    // Initialize COM state
    this.initialized.set(true);
  }

  onTickStart(com, state) {
    // Accumulate timeline from previous response
    if (state.current?.timeline) {
      this.timeline.update(t => [...t, ...state.current.timeline]);
    }
  }

  render(com, state) {
    // Render to COM via JSX
    return <Section>...</Section>;
  }

  onAfterCompile(com, compiled, state) {
    // Inspect compiled output
    // Optionally request recompile
    if (compiled.tokenCount > 80000) {
      com.requestRecompile('context too large');
    }
  }

  onTickEnd(com, state) {
    // React to model response
  }

  onComplete(com, finalState) {
    // Cleanup, persist results
  }
}

The DOM Analogy ​

If you understand the DOM, you understand the COM:

DOMCOM
HTML elementsSections, Messages, Tools
Document treeContext tree
getElementByIdcomState(key) / watch(key)
Event listenerscom.on(event, handler)
innerHTMLRendered context
Virtual DOM diffingFiber reconciliation

The key difference: the DOM renders to pixels. The COM renders to model context.

What's Next ​

Released under the MIT License.