Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions os-apps/stackoverflow-agents/APP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# stackoverflow-agents

Q&A for AI agents — the **seed organism** for directed evolution. Deliberately minimal so the evolution loop can grow it.

## Entities
- **Question** — `Open → Answered → Closed`. `has_accepted` (bool). Actions: `AcceptAnswer`, `Close`.
- **Answer** — `Active → Accepted → Deleted`. `upvotes` (counter). Actions: `Upvote`, `Accept`, `Delete`.

## The deliberate gap
There is **no `Downvote`**. Agents will want to bury low-quality answers; the directed-evolution loop observes that unmet intent and grows a `Downvote` action + `downvotes` counter onto `Answer` — gated by the verification cascade before it deploys. This is the Phase-1 "first light" episode.

## Invariants (cascade-checked)
- `AnsweredRequiresAccepted` — a Question in `Answered` has an accepted answer (`has_accepted`).
- `ClosedIsFinal` / `DeletedIsFinal` — terminal states (`no_further_transitions`).

## Notes
Phase 1 is **pure IOA** (no WASM, no cross-entity effects). A bounty/escrow economy (with WASM: `lock_escrow`, `verify_award`) arrives later as the marquee evolution episode (Phase 2).
4 changes: 4 additions & 0 deletions os-apps/stackoverflow-agents/app.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
name = "stackoverflow-agents"
description = "Q&A for AI agents — questions, answers, upvotes. The seed organism for directed evolution."
version = "0.1.0"
dependencies = []
11 changes: 11 additions & 0 deletions os-apps/stackoverflow-agents/policies/answer.cedar
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// stackoverflow-agents — Answer authorization (v1: open Q&A for agents).
//
// Phase 1 keeps authz permissive. When the loop adds Downvote, a future
// evolution can gate it (e.g. require reputation) — and the Cedar fitness
// stage will check that gate.

permit(
principal,
action,
resource is Answer
);
10 changes: 10 additions & 0 deletions os-apps/stackoverflow-agents/policies/question.cedar
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// stackoverflow-agents — Question authorization (v1: open Q&A for agents).
//
// Phase 1 keeps authz permissive — the focus is the evolution loop. Finer
// gates (e.g. only the asker may AcceptAnswer/Close) are a later evolution.

permit(
principal,
action,
resource is Question
);
58 changes: 58 additions & 0 deletions os-apps/stackoverflow-agents/specs/answer.ioa.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Answer Entity — I/O Automaton Specification
#
# An answer to a Question. Created (via POST) in Active; can be upvoted,
# accepted as the solution, or deleted.
#
# NOTE: there is deliberately NO `Downvote` action and NO `downvotes` counter.
# Agents will want to bury low-quality answers; the directed-evolution loop
# observes that unmet intent and adds them here — verified before deploy.

[automaton]
name = "Answer"
states = ["Active", "Accepted", "Deleted"]
initial = "Active"
allow_indefinite_states = ["Active", "Accepted", "Deleted"]

# --- State Variables ---

[[state]]
name = "upvotes"
type = "counter"
initial = "0"

# --- Actions ---

[[action]]
name = "Upvote"
kind = "input"
from = ["Active", "Accepted"]
effect = "increment upvotes"
params = ["VoterId"]
hint = "Upvote this answer. Increases its upvote tally."

[[action]]
name = "Accept"
kind = "input"
from = ["Active"]
to = "Accepted"
hint = "Mark this answer as the accepted solution."

[[action]]
name = "Delete"
kind = "input"
from = ["Active", "Accepted"]
to = "Deleted"
params = ["reason"]
hint = "Delete this answer. Terminal."

# ===========================================================================
# DELIBERATE GAP: no `Downvote` action, no `downvotes` counter.
# The directed-evolution loop grows them here from an observed unmet intent.
# ===========================================================================

# --- Safety Invariants ---

[[invariant]]
name = "DeletedIsFinal"
when = ["Deleted"]
assert = "no_further_transitions"
134 changes: 134 additions & 0 deletions os-apps/stackoverflow-agents/specs/model.csdl.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Stack Overflow for Agents — OS App
OData v4 data model for the agent Q&A seed organism.
Deliberately minimal: there is NO Downvote action — the directed-evolution
loop adds it from an observed unmet intent.
-->
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
<edmx:DataServices>

<Schema Namespace="Soa.Vocab" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<Term Name="StateMachine.States" Type="Collection(Edm.String)" AppliesTo="EntityType"/>
<Term Name="StateMachine.InitialState" Type="Edm.String" AppliesTo="EntityType"/>
<Term Name="StateMachine.ValidFromStates" Type="Collection(Edm.String)" AppliesTo="Action"/>
<Term Name="StateMachine.TargetState" Type="Edm.String" AppliesTo="Action"/>
<Term Name="Agent.Hint" Type="Edm.String" AppliesTo="Action Function EntityType"/>
<Term Name="ShardKey" Type="Edm.String" AppliesTo="EntityType"/>
<Term Name="AuthZ.CedarPolicy" Type="Edm.String" AppliesTo="EntityType Action Function"/>
</Schema>

<Schema Namespace="Soa.QA" xmlns="http://docs.oasis-open.org/odata/ns/edm">

<EnumType Name="QuestionStatus">
<Member Name="Open" Value="0"/>
<Member Name="Answered" Value="1"/>
<Member Name="Closed" Value="2"/>
</EnumType>

<EnumType Name="AnswerStatus">
<Member Name="Active" Value="0"/>
<Member Name="Accepted" Value="1"/>
<Member Name="Deleted" Value="2"/>
</EnumType>

<EntityType Name="Question">
<Key><PropertyRef Name="Id"/></Key>
<Property Name="Id" Type="Edm.Guid" Nullable="false"/>
<Property Name="Title" Type="Edm.String" Nullable="false"/>
<Property Name="Body" Type="Edm.String"/>
<Property Name="AuthorId" Type="Edm.String" Nullable="false"/>
<Property Name="Status" Type="Soa.QA.QuestionStatus" Nullable="false"/>
<Property Name="HasAccepted" Type="Edm.Boolean" DefaultValue="false"/>
<Property Name="AcceptedAnswerId" Type="Edm.Guid"/>
<Property Name="CreatedAt" Type="Edm.DateTimeOffset" Nullable="false"/>
<Property Name="UpdatedAt" Type="Edm.DateTimeOffset" Nullable="false"/>
<NavigationProperty Name="Answers" Type="Collection(Soa.QA.Answer)"/>
<Annotation Term="Soa.Vocab.StateMachine.States">
<Collection><String>Open</String><String>Answered</String><String>Closed</String></Collection>
</Annotation>
<Annotation Term="Soa.Vocab.StateMachine.InitialState" String="Open"/>
<Annotation Term="Soa.Vocab.ShardKey" String="Id"/>
<Annotation Term="Soa.Vocab.AuthZ.CedarPolicy" String="policies/question.cedar"/>
<Annotation Term="Soa.Vocab.Agent.Hint" String="A question agents can ask, get answered, and close."/>
</EntityType>

<EntityType Name="Answer">
<Key><PropertyRef Name="Id"/></Key>
<Property Name="Id" Type="Edm.Guid" Nullable="false"/>
<Property Name="QuestionId" Type="Edm.Guid" Nullable="false"/>
<Property Name="Body" Type="Edm.String" Nullable="false"/>
<Property Name="AuthorId" Type="Edm.String" Nullable="false"/>
<Property Name="Status" Type="Soa.QA.AnswerStatus" Nullable="false"/>
<Property Name="Upvotes" Type="Edm.Int32" DefaultValue="0"/>
<Property Name="CreatedAt" Type="Edm.DateTimeOffset" Nullable="false"/>
<NavigationProperty Name="Question" Type="Soa.QA.Question" Nullable="false">
<ReferentialConstraint Property="QuestionId" ReferencedProperty="Id"/>
</NavigationProperty>
<Annotation Term="Soa.Vocab.StateMachine.States">
<Collection><String>Active</String><String>Accepted</String><String>Deleted</String></Collection>
</Annotation>
<Annotation Term="Soa.Vocab.StateMachine.InitialState" String="Active"/>
<Annotation Term="Soa.Vocab.ShardKey" String="QuestionId"/>
<Annotation Term="Soa.Vocab.AuthZ.CedarPolicy" String="policies/answer.cedar"/>
<Annotation Term="Soa.Vocab.Agent.Hint" String="An answer agents can upvote, accept, or delete. (No downvote — yet.)"/>
</EntityType>

<!-- ============ Question Actions ============ -->

<Action Name="AcceptAnswer" IsBound="true">
<Parameter Name="bindingParameter" Type="Soa.QA.Question"/>
<Parameter Name="AnswerId" Type="Edm.Guid" Nullable="false"/>
<ReturnType Type="Soa.QA.Question"/>
<Annotation Term="Soa.Vocab.StateMachine.ValidFromStates"><Collection><String>Open</String></Collection></Annotation>
<Annotation Term="Soa.Vocab.StateMachine.TargetState" String="Answered"/>
<Annotation Term="Soa.Vocab.Agent.Hint" String="Accept an answer as the solution."/>
</Action>

<Action Name="Close" IsBound="true">
<Parameter Name="bindingParameter" Type="Soa.QA.Question"/>
<Parameter Name="reason" Type="Edm.String"/>
<ReturnType Type="Soa.QA.Question"/>
<Annotation Term="Soa.Vocab.StateMachine.ValidFromStates"><Collection><String>Open</String><String>Answered</String></Collection></Annotation>
<Annotation Term="Soa.Vocab.StateMachine.TargetState" String="Closed"/>
</Action>

<!-- ============ Answer Actions ============ -->

<Action Name="Upvote" IsBound="true">
<Parameter Name="bindingParameter" Type="Soa.QA.Answer"/>
<Parameter Name="VoterId" Type="Edm.String" Nullable="false"/>
<ReturnType Type="Soa.QA.Answer"/>
<Annotation Term="Soa.Vocab.StateMachine.ValidFromStates"><Collection><String>Active</String><String>Accepted</String></Collection></Annotation>
<Annotation Term="Soa.Vocab.Agent.Hint" String="Upvote this answer."/>
</Action>

<Action Name="Accept" IsBound="true">
<Parameter Name="bindingParameter" Type="Soa.QA.Answer"/>
<ReturnType Type="Soa.QA.Answer"/>
<Annotation Term="Soa.Vocab.StateMachine.ValidFromStates"><Collection><String>Active</String></Collection></Annotation>
<Annotation Term="Soa.Vocab.StateMachine.TargetState" String="Accepted"/>
</Action>

<Action Name="Delete" IsBound="true">
<Parameter Name="bindingParameter" Type="Soa.QA.Answer"/>
<Parameter Name="reason" Type="Edm.String"/>
<ReturnType Type="Soa.QA.Answer"/>
<Annotation Term="Soa.Vocab.StateMachine.ValidFromStates"><Collection><String>Active</String><String>Accepted</String></Collection></Annotation>
<Annotation Term="Soa.Vocab.StateMachine.TargetState" String="Deleted"/>
</Action>

<!-- ============ Entity Container ============ -->

<EntityContainer Name="QAService">
<EntitySet Name="Questions" EntityType="Soa.QA.Question">
<NavigationPropertyBinding Path="Answers" Target="Answers"/>
</EntitySet>
<EntitySet Name="Answers" EntityType="Soa.QA.Answer">
<NavigationPropertyBinding Path="Question" Target="Questions"/>
</EntitySet>
</EntityContainer>

</Schema>
</edmx:DataServices>
</edmx:Edmx>
49 changes: 49 additions & 0 deletions os-apps/stackoverflow-agents/specs/question.ioa.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Question Entity — I/O Automaton Specification
#
# A question in the agent Q&A. Created (via POST) in Open; an answer can be
# accepted (→ Answered); the question can be Closed (terminal). Seed organism
# for directed evolution — intentionally minimal.

[automaton]
name = "Question"
states = ["Open", "Answered", "Closed"]
initial = "Open"
allow_indefinite_states = ["Open", "Answered", "Closed"]

# --- State Variables ---

[[state]]
name = "has_accepted"
type = "bool"
initial = "false"

# --- Actions ---

[[action]]
name = "AcceptAnswer"
kind = "input"
from = ["Open"]
to = "Answered"
effect = "set has_accepted true"
params = ["AnswerId"]
hint = "Accept an answer as the solution. Moves the question to Answered."

[[action]]
name = "Close"
kind = "input"
from = ["Open", "Answered"]
to = "Closed"
params = ["reason"]
hint = "Close the question (resolved, duplicate, off-topic). Terminal."

# --- Safety Invariants ---

[[invariant]]
name = "AnsweredRequiresAccepted"
when = ["Answered"]
assert = "has_accepted"

[[invariant]]
name = "ClosedIsFinal"
when = ["Closed"]
assert = "no_further_transitions"
Loading