Office Hours

community

Office Hours

Definition

Office Hours is a structured, recurring engagement format in which platform maintainers, core contributors, or domain experts make themselves available for synchronous interaction with the broader community. In the context of open-source software development and platform engineering, office hours serve as a bridge between asynchronous collaboration (issues, pull requests, documentation) and fully synchronous collaboration (pair programming, real-time design sessions). Participants can bring questions about architecture, request guidance on contribution workflows, discuss design decisions, debug issues collaboratively, or simply observe conversations between experienced practitioners.

Unlike ad-hoc meetings or scheduled design reviews, office hours are characterized by their regularity (weekly, biweekly), open invitation (anyone can attend without pre-registration), low formality (no fixed agenda, participant-driven), and knowledge dissemination focus (the goal is learning, not decision-making). In the Prismatic Platform ecosystem, office hours represent a commitment to the principles of Open Source Advocacy -- that knowledge sharing and community interaction are not peripheral activities but core platform operations.

Overview

The office hours concept originates from academic practice, where professors designate regular times for students to seek guidance outside of lectures. The format translates naturally to open-source software development, where the analogous challenge is enabling asynchronous contributors to access the tacit knowledge that core maintainers carry -- knowledge about design rationale, architectural constraints, historical context, and unwritten conventions that cannot be fully captured in documentation.

In the open-source ecosystem, office hours have become an established practice among successful projects. Kubernetes holds weekly community meetings, Rust has working group sync meetings, and many CNCF projects maintain regular office hours for users and contributors. The practice addresses a fundamental asymmetry in open-source development: maintainers possess deep contextual knowledge, while new contributors need that knowledge to participate effectively. Documentation can bridge this gap partially, but synchronous interaction enables the kind of back-and-forth clarification, contextual exploration, and serendipitous knowledge transfer that static text cannot replicate.

For platform engineering organizations, office hours serve an additional purpose: they create a structured forum for cross-team knowledge sharing. In a large platform with 115 umbrella applications and 530+ agents, no single person understands the entire system. Office hours enable engineers working on different subsystems to learn about each other's domains, identify integration opportunities, and surface cross-cutting concerns that might otherwise remain invisible.

The effectiveness of office hours depends on several factors: consistent scheduling (building habit and expectation), appropriate scope (broad enough to be useful, focused enough to be productive), skilled facilitation (managing diverse participants and topics), accessible tooling (low friction to join and participate), and follow-up mechanisms (capturing decisions and action items from sessions).

Technical Details

Scheduling and Session Management

A robust office hours system requires programmatic session management to handle scheduling, notifications, recording, and follow-up. The following Elixir implementation demonstrates a session lifecycle manager.

defmodule Prismatic.Community.OfficeHours do
  @moduledoc """
  Office Hours session management system. Handles scheduling,
  participant registration, topic queuing, and session lifecycle
  for recurring community engagement sessions.
  """

  use GenServer

  @type session_status :: :scheduled | :in_progress | :completed | :cancelled
  @type topic_priority :: :high | :medium | :low

  @type session :: %{
    id: String.t(),
    scheduled_at: DateTime.t(),
    duration_minutes: pos_integer(),
    status: session_status(),
    host: String.t(),
    participants: [String.t()],
    topics: [topic()],
    notes: String.t(),
    recording_url: String.t() | nil
  }

  @type topic :: %{
    title: String.t(),
    submitted_by: String.t(),
    priority: topic_priority(),
    description: String.t(),
    votes: non_neg_integer()
  }

  defstruct [
    :sessions,
    :recurring_schedule,
    :topic_queue,
    :config
  ]

  def start_link(opts \\ []) do
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  end

  @impl true
  def init(opts) do
    config = %{
      default_duration: Keyword.get(opts, :duration, 60),
      timezone: Keyword.get(opts, :timezone, "Europe/Prague"),
      max_topics_per_session: Keyword.get(opts, :max_topics, 5),
      notification_lead_time: Keyword.get(opts, :lead_time, 24 * 60)
    }

    state = %__MODULE__{
      sessions: %{},
      recurring_schedule: build_recurring_schedule(opts),
      topic_queue: [],
      config: config
    }

    schedule_next_notification(state)
    {:ok, state}
  end

  @spec schedule_session(DateTime.t(), String.t(), keyword()) ::
          {:ok, session()} | {:error, term()}
  def schedule_session(datetime, host, opts \\ []) do
    GenServer.call(__MODULE__, {:schedule, datetime, host, opts})
  end

  @spec submit_topic(String.t(), String.t(), String.t(), keyword()) ::
          {:ok, topic()} | {:error, term()}
  def submit_topic(session_id, title, submitted_by, opts \\ []) do
    GenServer.call(
      __MODULE__,
      {:submit_topic, session_id, title, submitted_by, opts}
    )
  end

  @spec start_session(String.t()) :: {:ok, session()} | {:error, term()}
  def start_session(session_id) do
    GenServer.call(__MODULE__, {:start_session, session_id})
  end

  @spec complete_session(String.t(), keyword()) ::
          {:ok, session()} | {:error, term()}
  def complete_session(session_id, opts \\ []) do
    GenServer.call(__MODULE__, {:complete_session, session_id, opts})
  end

  @spec list_upcoming() :: [session()]
  def list_upcoming do
    GenServer.call(__MODULE__, :list_upcoming)
  end

  @impl true
  def handle_call({:schedule, datetime, host, opts}, _from, state) do
    session = %{
      id: generate_id(),
      scheduled_at: datetime,
      duration_minutes:
        Keyword.get(opts, :duration, state.config.default_duration),
      status: :scheduled,
      host: host,
      participants: [],
      topics: [],
      notes: "",
      recording_url: nil
    }

    updated_sessions = Map.put(state.sessions, session.id, session)
    {:reply, {:ok, session}, %{state | sessions: updated_sessions}}
  end

  @impl true
  def handle_call(
        {:submit_topic, session_id, title, submitted_by, opts},
        _from,
        state
      ) do
    case Map.get(state.sessions, session_id) do
      nil ->
        {:reply, {:error, :session_not_found}, state}

      session when session.status == :scheduled ->
        topic = %{
          title: title,
          submitted_by: submitted_by,
          priority: Keyword.get(opts, :priority, :medium),
          description: Keyword.get(opts, :description, ""),
          votes: 0
        }

        updated_session = %{session | topics: session.topics ++ [topic]}
        updated = Map.put(state.sessions, session_id, updated_session)
        {:reply, {:ok, topic}, %{state | sessions: updated}}

      _session ->
        {:reply, {:error, :session_not_accepting_topics}, state}
    end
  end

  @impl true
  def handle_call({:start_session, session_id}, _from, state) do
    case Map.get(state.sessions, session_id) do
      nil ->
        {:reply, {:error, :session_not_found}, state}

      %{status: :scheduled} = session ->
        updated = %{session | status: :in_progress}
        updated_sessions = Map.put(state.sessions, session_id, updated)

        :telemetry.execute(
          [:prismatic, :community, :office_hours, :session_started],
          %{
            topic_count: length(updated.topics),
            participant_count: length(updated.participants)
          },
          %{session_id: session_id, host: updated.host}
        )

        {:reply, {:ok, updated}, %{state | sessions: updated_sessions}}

      _ ->
        {:reply, {:error, :invalid_session_state}, state}
    end
  end

  @impl true
  def handle_call({:complete_session, session_id, opts}, _from, state) do
    case Map.get(state.sessions, session_id) do
      nil ->
        {:reply, {:error, :session_not_found}, state}

      %{status: :in_progress} = session ->
        updated = %{
          session
          | status: :completed,
            notes: Keyword.get(opts, :notes, session.notes),
            recording_url: Keyword.get(opts, :recording_url)
        }

        updated_sessions = Map.put(state.sessions, session_id, updated)
        {:reply, {:ok, updated}, %{state | sessions: updated_sessions}}

      _ ->
        {:reply, {:error, :invalid_session_state}, state}
    end
  end

  @impl true
  def handle_call(:list_upcoming, _from, state) do
    now = DateTime.utc_now()

    upcoming =
      state.sessions
      |> Map.values()
      |> Enum.filter(fn s ->
        s.status == :scheduled and DateTime.after?(s.scheduled_at, now)
      end)
      |> Enum.sort_by(& &1.scheduled_at, DateTime)

    {:reply, upcoming, state}
  end

  defp build_recurring_schedule(opts) do
    %{
      day_of_week: Keyword.get(opts, :day, :wednesday),
      hour: Keyword.get(opts, :hour, 14),
      minute: Keyword.get(opts, :minute, 0),
      frequency: Keyword.get(opts, :frequency, :weekly)
    }
  end

  defp schedule_next_notification(_state) do
    Process.send_after(self(), :check_notifications, :timer.minutes(15))
  end

  defp generate_id do
    :crypto.strong_rand_bytes(8) |> Base.url_encode64(padding: false)
  end
end

LiveView Dashboard for Office Hours

The Prismatic Platform uses Phoenix LiveView for real-time session interfaces, enabling participants to join, submit topics, vote, and interact during live sessions.

defmodule PrismaticWeb.OfficeHoursLive do
  @moduledoc """
  LiveView interface for office hours sessions.
  Provides real-time topic submission, voting, and
  session state updates via PubSub.
  """

  use PrismaticWeb, :live_view

  @impl true
  def mount(_params, _session, socket) do
    if connected?(socket) do
      Phoenix.PubSub.subscribe(
        Prismatic.PubSub,
        "office_hours:updates"
      )
    end

    upcoming = Prismatic.Community.OfficeHours.list_upcoming()

    {:ok,
     assign(socket,
       upcoming_sessions: upcoming,
       active_session: nil,
       topic_form:
         to_form(%{
           "title" => "",
           "description" => "",
           "priority" => "medium"
         })
     )}
  end

  @impl true
  def handle_event("submit_topic", params, socket) do
    %{"title" => title, "description" => desc, "priority" => priority} =
      params

    case socket.assigns.active_session do
      nil ->
        {:noreply, put_flash(socket, :error, "No active session")}

      session ->
        case Prismatic.Community.OfficeHours.submit_topic(
               session.id,
               title,
               socket.assigns.current_user,
               priority: String.to_atom(priority),
               description: desc
             ) do
          {:ok, _topic} ->
            {:noreply, put_flash(socket, :info, "Topic submitted")}

          {:error, reason} ->
            {:noreply,
             put_flash(socket, :error, "Failed: #{inspect(reason)}")}
        end
    end
  end

  @impl true
  def handle_info({:session_update, session}, socket) do
    {:noreply, assign(socket, active_session: session)}
  end
end

Knowledge Capture Pipeline

The most valuable output of office hours is the tacit knowledge that surfaces during discussion. A knowledge capture pipeline extracts, structures, and stores this knowledge for future reference.

defmodule Prismatic.Community.KnowledgeCapture do
  @moduledoc """
  Captures and structures knowledge from office hours sessions.
  Extracts decisions, action items, architectural insights,
  and FAQ entries from session notes and transcripts.
  """

  @type knowledge_entry :: %{
    type: :decision | :action_item | :insight | :faq | :pattern,
    content: String.t(),
    context: String.t(),
    session_id: String.t(),
    timestamp: DateTime.t(),
    tags: [String.t()]
  }

  @spec capture_from_session(map()) ::
          {:ok, [knowledge_entry()]} | {:error, term()}
  def capture_from_session(%{id: session_id, notes: notes, topics: topics}) do
    entries =
      extract_decisions(notes, session_id) ++
        extract_action_items(notes, session_id) ++
        extract_insights(topics, session_id) ++
        extract_faq_entries(notes, session_id)

    Enum.each(entries, &store_entry/1)
    {:ok, entries}
  end

  defp extract_decisions(notes, session_id) do
    ~r/DECISION:\s*(.+?)(?:\n|$)/i
    |> Regex.scan(notes)
    |> Enum.map(fn [_, content] ->
      %{
        type: :decision,
        content: String.trim(content),
        context: "Office Hours Session #{session_id}",
        session_id: session_id,
        timestamp: DateTime.utc_now(),
        tags: ["decision", "office-hours"]
      }
    end)
  end

  defp extract_action_items(notes, session_id) do
    ~r/(?:TODO|ACTION):\s*(.+?)(?:\n|$)/i
    |> Regex.scan(notes)
    |> Enum.map(fn [_, content] ->
      %{
        type: :action_item,
        content: String.trim(content),
        context: "Office Hours Session #{session_id}",
        session_id: session_id,
        timestamp: DateTime.utc_now(),
        tags: ["action-item", "office-hours"]
      }
    end)
  end

  defp extract_insights(topics, session_id) do
    topics
    |> Enum.filter(fn t -> String.length(t.description) > 100 end)
    |> Enum.map(fn topic ->
      tag =
        topic.title
        |> String.downcase()
        |> String.replace(~r/\s+/, "-")

      %{
        type: :insight,
        content: topic.description,
        context: "Topic: #{topic.title} (Session #{session_id})",
        session_id: session_id,
        timestamp: DateTime.utc_now(),
        tags: ["insight", "office-hours", tag]
      }
    end)
  end

  defp extract_faq_entries(notes, session_id) do
    ~r/Q:\s*(.+?)\nA:\s*(.+?)(?:\n\n|$)/is
    |> Regex.scan(notes)
    |> Enum.map(fn [_, question, answer] ->
      %{
        type: :faq,
        content:
          "Q: #{String.trim(question)}\nA: #{String.trim(answer)}",
        context: "Office Hours Session #{session_id}",
        session_id: session_id,
        timestamp: DateTime.utc_now(),
        tags: ["faq", "office-hours"]
      }
    end)
  end

  defp store_entry(entry) do
    :ets.insert(:knowledge_base, {generate_key(entry), entry})
  end

  defp generate_key(entry) do
    :crypto.hash(
      :sha256,
      "#{entry.session_id}:#{entry.type}:#{entry.content}"
    )
    |> Base.url_encode64(padding: false)
    |> binary_part(0, 16)
  end
end

Implementation in the Prismatic Platform

Community Engagement Model

The Prismatic Platform's community engagement strategy positions office hours as the primary synchronous interaction channel within a broader engagement pyramid. At the base, asynchronous channels (GitHub Issues, GitLab Issues, documentation, glossary) serve the largest audience with the lowest barrier to entry. In the middle tier, office hours provide regular synchronous access for active contributors and users. At the top tier, dedicated mentorship and pair programming sessions serve committed contributors working on complex features.

This tiered model ensures that community investment scales with community engagement. Casual users can find answers in documentation. Active users can attend office hours. Dedicated contributors receive direct mentorship. The Prismatic Platform's 530+ agent system, 115 umbrella apps, and 2.8M lines of code create significant knowledge barriers for new contributors. Office hours lower these barriers by providing a forum where questions that are difficult to answer through documentation alone can be addressed through interactive dialogue.

Architectural Decision Records

Office hours frequently surface architectural questions that lead to design decisions. The platform captures these decisions through Architectural Decision Records (ADRs) that are linked back to the office hours session where the discussion occurred. This creates a traceable provenance chain from question to discussion to decision to implementation, supporting the Nabla Infinity axiom of Provenance Mandatory.

Contributor Onboarding Pathway

New contributors follow a structured onboarding pathway that includes attending at least two office hours sessions before submitting their first significant pull request. This ensures they have exposure to the platform's architectural philosophy, coding conventions, quality standards, and testing requirements before investing effort in implementation. The onboarding office hours cover: platform architecture overview, development environment setup, quality gate system, AIAD agent and command authoring, and the NO MERCY NO DOUBTS doctrine.

Comparison with Alternative Engagement Formats

Scheduled Meetings

Fixed-agenda meetings with specific invitees are appropriate for decision-making and status reporting but poor for knowledge sharing and community building. Office hours differ in their open-door policy, participant-driven agenda, and learning-first orientation.

Chat and Forum Support

Asynchronous channels (Discord, Slack, Discourse) provide 24/7 availability but lack the depth of synchronous interaction. Complex architectural questions often require multiple rounds of clarification that are tedious in chat but natural in voice conversation. Office hours complement async channels by handling the questions that async channels struggle with.

Conference Talks

Conference presentations reach large audiences with polished content but provide limited interaction. The knowledge transfer is one-directional (speaker to audience) with minimal feedback. Office hours are bidirectional: the host learns from participants' questions and use cases as much as participants learn from the host's expertise.

Pair Programming

Pair programming provides the deepest knowledge transfer but requires significant time investment and scales to only one participant. Office hours occupy a middle ground: deeper than chat, broader than pairing, more interactive than talks.

Documentation Sprints

Focused documentation efforts capture knowledge in permanent form but require significant coordination and produce artifacts that may become stale. Office hours generate knowledge continuously in small increments, and the knowledge capture pipeline converts session outputs into documentation updates.

Best Practices

Maintain consistent scheduling. The value of office hours compounds over time as community members build the habit of attending. Changing times or skipping sessions erodes this habit. Schedule sessions at a fixed time and maintain them even when attendance is low.

Rotate hosts across domains. No single person understands the entire platform. Rotating hosts across different domain experts ensures broad topic coverage and prevents knowledge concentration. The Prismatic Platform rotates through architecture, security, OSINT, quality, and infrastructure experts.

Capture and publish session notes. Synchronous knowledge is ephemeral unless captured. Publish session notes, decisions, and action items within 24 hours of each session. The knowledge capture pipeline automates extraction of structured entries from session notes.

Welcome silence. Not every moment needs to be filled with discussion. Participants may need time to formulate questions. Comfortable silences signal a psychologically safe environment.

Start with a brief update. Begin each session with a 5-minute update on recent platform changes, upcoming releases, or noteworthy developments. This provides context and often sparks questions.

Follow up on action items. Track action items from session to session. Completing follow-ups demonstrates that office hours produce tangible outcomes, encouraging continued participation.

Record sessions with consent. Recordings extend the reach of office hours to those who cannot attend live. Always obtain participant consent before recording, and publish recordings in an accessible location.

Common Pitfalls

Irregular scheduling. The most common failure mode is inconsistency. Skipping sessions, changing times, or cancelling when attendance is low sends the message that office hours are not a priority.

Host monologue. When the host dominates conversation, office hours become a lecture. Effective facilitation means asking questions, directing discussion, and creating space for participant contributions.

Scope creep into decision-making. Office hours are for knowledge sharing, not governance. Design decisions that emerge during office hours should be captured as proposals and ratified through the project's normal decision-making process, not treated as final simply because they were discussed in office hours.

Excluding time zones. A single office hours time inherently excludes contributors in distant time zones. Mitigate this by rotating times, offering async follow-up, or hosting multiple sessions at different times.

No follow-through. Questions raised in office hours that are not answered should be tracked and followed up on. Unanswered questions accumulate into a perception that office hours are not useful.

Over-formalization. Adding too much structure (mandatory agendas, formal presentations, registration requirements) transforms office hours into meetings and loses the casual, approachable character that makes them effective.

Use Cases

Contributor onboarding. New contributors attend office hours to learn about platform architecture, development workflow, quality standards, and contribution guidelines through interactive Q&A rather than documentation alone.

Architectural design review. Engineers working on significant features present their design approach in office hours for feedback from experienced contributors before investing heavily in implementation.

Bug triage. Complex bugs that span multiple subsystems are discussed in office hours where contributors from different domains can collaborate on diagnosis and resolution strategies.

Roadmap discussion. Upcoming features and strategic direction are previewed in office hours, allowing community members to ask questions, raise concerns, and suggest alternatives before plans are finalized.

Cross-team knowledge sharing. Engineers from different platform subsystems present their domain's architecture, current challenges, and integration points, enabling the cross-pollination of ideas and identification of shared concerns.

Quality improvement workshops. Focused sessions on specific quality topics -- test writing, Credo compliance, Dialyzer annotation, performance optimization -- provide hands-on guidance that complements documentation.

Office hours connect to several community and development practices in the Prismatic Platform:

  • Community Engagement -- the broader strategy of building and sustaining an active contributor community
  • Mentorship -- one-on-one knowledge transfer relationships that office hours can initiate
  • Community Building -- the organizational effort to create and grow a healthy open-source community
  • Developer Experience -- the overall quality of the contributor's interaction with the platform
  • Collaborative Development -- working together on shared codebases, which office hours facilitate
  • Workshop Facilitation -- structured teaching sessions that complement the informal learning of office hours
  • Conference Speaking -- public presentations that reach broader audiences than office hours
  • Documentation -- the permanent knowledge artifacts that office hours help create and improve
  • Developer Community -- the people who participate in and benefit from office hours
  • Open Source Advocacy -- the principles of openness and sharing that office hours embody

See Also

  • Knowledge Graph -- structured knowledge representation that can store insights captured from office hours sessions
  • Learning Resource -- educational materials that office hours sessions generate
  • Community Contributions -- the work products that result from contributor engagement through office hours
  • Certification Programs -- formal learning pathways that office hours complement informally
  • Share Openly -- the philosophical commitment to knowledge sharing that motivates office hours

Connect and Contribute: Created by Tomas Korcak (korczis) | Open Source under GHL License | GitHub | GitLab | LinkedIn | Contact